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:
parent
e46ff8adbd
commit
b8a92582a1
5 changed files with 64 additions and 20 deletions
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
describe '::hashed_sha2?' do
|
||||||
|
|
||||||
|
it 'detects sha2 hashes' do
|
||||||
|
expect(described_class.hashed_sha2?(zammad_sha2)).to be true
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in a new issue