Fixed bug: Legacy hashed passwords (like sha2) will get rehashed when set.

This happens when a user password should get set without transfering it in plain like e.g. the REST API, an import or the auto_wizard.json file. Therefore Zammad should accept legacy passwords when set but should convert them on first login to the internal format (as already implemented).

Additionally there should be no exclusion for the import mode when setting/ensuring a password since Zammand couldn't handle it later.
This commit is contained in:
Thorsten Eckel 2017-05-08 12:04:54 +02:00
parent e46ff8adbd
commit b8a92582a1
5 changed files with 64 additions and 20 deletions

View file

@ -980,7 +980,6 @@ raise 'Minimum one user need to have admin permissions'
def ensure_password def ensure_password
return if password_empty? return if password_empty?
return if Setting.get('import_mode')
return if PasswordHash.crypted?(password) return if PasswordHash.crypted?(password)
self.password = PasswordHash.crypt(password) self.password = PasswordHash.crypt(password)
end end

View file

@ -17,24 +17,37 @@ module PasswordHash
end end
def crypted?(pw_hash) def crypted?(pw_hash)
return if !pw_hash return false if !pw_hash
# taken from: https://github.com/technion/ruby-argon2/blob/7e1f4a2634316e370ab84150e4f5fd91d9263713/lib/argon2.rb#L33 return true if hashed_argon2?(pw_hash)
return if pw_hash !~ /^\$argon2i\$.{,112}/ return true if hashed_sha2?(pw_hash)
true false
end end
def legacy?(pw_hash, password) def legacy?(pw_hash, password)
return if pw_hash.blank? return false if pw_hash.blank?
return if !password return false if !password
legacy_sha2?(pw_hash, password) sha2?(pw_hash, password)
end
def hashed_sha2?(pw_hash)
pw_hash.start_with?('{sha2}')
end
def hashed_argon2?(pw_hash)
# taken from: https://github.com/technion/ruby-argon2/blob/7e1f4a2634316e370ab84150e4f5fd91d9263713/lib/argon2.rb#L33
pw_hash =~ /^\$argon2i\$.{,112}/
end
def sha2(password)
crypted = Digest::SHA2.hexdigest(password)
"{sha2}#{crypted}"
end end
private private
def legacy_sha2?(pw_hash, password) def sha2?(pw_hash, password)
return if !pw_hash.start_with?('{sha2}') return false if !hashed_sha2?(pw_hash)
crypted = Digest::SHA2.hexdigest(password) pw_hash == sha2(password)
pw_hash == "{sha2}#{crypted}"
end end
def argon2 def argon2

View file

@ -25,9 +25,4 @@ FactoryGirl.define do
factory :user_login_failed, parent: :user do factory :user_login_failed, parent: :user do
login_failed { (Setting.get('password_max_login_failed').to_i || 10) + 1 } login_failed { (Setting.get('password_max_login_failed').to_i || 10) + 1 }
end end
factory :user_legacy_password_sha2, parent: :user do
after(:build) { |user| user.class.skip_callback(:validation, :before, :ensure_password, if: -> { password && password.start_with?('{sha2}') }) }
password '{sha2}dd9c764fa7ea18cd992c8600006d3dc3ac983d1ba22e9ba2d71f6207456be0ba' # zammad
end
end end

View file

@ -20,13 +20,18 @@ RSpec.describe Auth::Internal do
end end
it 'converts legacy sha2 passwords' do it 'converts legacy sha2 passwords' do
user = create(:user_legacy_password_sha2)
expect(PasswordHash.crypted?(user.password)).to be_falsy pw_plain = 'zammad'
sha2_pw = PasswordHash.sha2(pw_plain)
user = create(:user, password: sha2_pw)
result = instance.valid?(user, 'zammad') expect(PasswordHash.crypted?(user.password)).to be true
expect(PasswordHash.legacy?(user.password, pw_plain)).to be true
result = instance.valid?(user, pw_plain)
expect(result).to be true expect(result).to be true
expect(PasswordHash.legacy?(user.password, pw_plain)).to be false
expect(PasswordHash.crypted?(user.password)).to be true expect(PasswordHash.crypted?(user.password)).to be true
end end
end end

View file

@ -20,6 +20,18 @@ RSpec.describe PasswordHash do
it 'responds to legacy?' do it 'responds to legacy?' do
expect(described_class).to respond_to(:legacy?) expect(described_class).to respond_to(:legacy?)
end end
it 'responds to sha2' do
expect(described_class).to respond_to(:sha2)
end
it 'responds to hashed_sha2?' do
expect(described_class).to respond_to(:hashed_sha2?)
end
it 'responds to hashed_argon2?' do
expect(described_class).to respond_to(:hashed_argon2?)
end
end end
context 'encryption' do context 'encryption' do
@ -56,5 +68,25 @@ RSpec.describe PasswordHash do
it 'detects sha2 hashes' do it 'detects sha2 hashes' do
expect(described_class.legacy?(zammad_sha2, pw_plain)).to be true expect(described_class.legacy?(zammad_sha2, pw_plain)).to be true
end end
it 'detects crypted passwords' do
expect(described_class.crypted?(zammad_sha2)).to be true
end
describe '::sha2' do
it 'creates sha2 hashes' do
hashed = described_class.sha2(pw_plain)
expect(hashed).to eq zammad_sha2
end end
end end
describe '::hashed_sha2?' do
it 'detects sha2 hashes' do
expect(described_class.hashed_sha2?(zammad_sha2)).to be true
end
end
end
end