From: Tom Hughes <tom@compton.nu>
Date: Tue, 13 Aug 2013 23:13:49 +0000 (+0100)
Subject: Upgrade passwords to the latest hashing scheme on login
X-Git-Tag: live~6023
X-Git-Url: https://git.openstreetmap.org/rails.git/commitdiff_plain/b9daf066842c9e15489d0ccd387d694021975222

Upgrade passwords to the latest hashing scheme on login
---

diff --git a/app/models/user.rb b/app/models/user.rb
index 4c51089e6..b8ec9aebc 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -70,7 +70,14 @@ class User < ActiveRecord::Base
         end
       end
 
-      user = nil if user and not PasswordHash.check(user.pass_crypt, user.pass_salt, options[:password])
+      if user and PasswordHash.check(user.pass_crypt, user.pass_salt, options[:password])
+        if PasswordHash.upgrade?(user.pass_crypt, user.pass_salt)
+          user.pass_crypt, user.pass_salt = PasswordHash.create(options[:password])
+          user.save
+        end
+      else
+        user = nil
+      end
     elsif options[:token]
       token = UserToken.find_by_token(options[:token])
       user = token.user if token
diff --git a/lib/password_hash.rb b/lib/password_hash.rb
index 1bd80291a..5adfc7a34 100644
--- a/lib/password_hash.rb
+++ b/lib/password_hash.rb
@@ -29,6 +29,22 @@ module PasswordHash
     return hash == candidate
   end
 
+  def self.upgrade?(hash, salt)
+    if salt.nil?
+      return true
+    elsif salt =~ /!/
+      algorithm, iterations, salt = salt.split("!")
+      return true if Base64.strict_decode64(salt).length != SALT_BYTE_SIZE
+      return true if Base64.strict_decode64(hash).length != HASH_BYTE_SIZE
+      return true if iterations.to_i != PBKDF2_ITERATIONS
+      return true if algorithm != DIGEST_ALGORITHM
+    else
+      return true
+    end
+
+    return false
+  end
+
 private
 
   def self.hash(password, salt, iterations, size, algorithm)
diff --git a/test/unit/password_hash_test.rb b/test/unit/password_hash_test.rb
index 61d3d4921..825942afa 100644
--- a/test/unit/password_hash_test.rb
+++ b/test/unit/password_hash_test.rb
@@ -4,12 +4,14 @@ class PasswordHashTest < ActiveSupport::TestCase
   def test_md5_without_salt
     assert_equal true, PasswordHash.check("5f4dcc3b5aa765d61d8327deb882cf99", nil, "password")
     assert_equal false, PasswordHash.check("5f4dcc3b5aa765d61d8327deb882cf99", nil, "wrong")
+    assert_equal true, PasswordHash.upgrade?("5f4dcc3b5aa765d61d8327deb882cf99", nil)
   end
 
   def test_md5_with_salt
     assert_equal true, PasswordHash.check("67a1e09bb1f83f5007dc119c14d663aa", "salt", "password")
     assert_equal false, PasswordHash.check("67a1e09bb1f83f5007dc119c14d663aa", "salt", "wrong")
     assert_equal false, PasswordHash.check("67a1e09bb1f83f5007dc119c14d663aa", "wrong", "password")
+    assert_equal true, PasswordHash.upgrade?("67a1e09bb1f83f5007dc119c14d663aa", "salt")
   end
 
   def test_default
@@ -21,5 +23,7 @@ class PasswordHashTest < ActiveSupport::TestCase
     assert_equal false, PasswordHash.check(hash1, salt1, "wrong")
     assert_equal true, PasswordHash.check(hash2, salt2, "password")
     assert_equal false, PasswordHash.check(hash2, salt2, "wrong")
+    assert_equal false, PasswordHash.upgrade?(hash1, salt1)
+    assert_equal false, PasswordHash.upgrade?(hash2, salt2)
   end
 end