1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138
|
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe User do
describe '#authenticatable_salt' do
let(:user) { build(:user, encrypted_password: encrypted_password) }
subject(:authenticatable_salt) { user.authenticatable_salt }
context 'when password is stored in BCrypt format' do
let(:encrypted_password) { '$2a$10$AvwDCyF/8HnlAv./UkAZx.vAlKRS89yNElP38FzdgOmVaSaiDL7xm' }
it 'returns the first 30 characters of the encrypted_password' do
expect(authenticatable_salt).to eq(user.encrypted_password[0, 29])
end
end
context 'when password is stored in PBKDF2 format' do
let(:encrypted_password) { '$pbkdf2-sha512$20000$rKbYsScsDdk$iwWBewXmrkD2fFfaG1SDcMIvl9gvEo3fBWUAfiqyVceTlw/DYgKBByHzf45pF5Qn59R4R.NQHsFpvZB4qlsYmw' } # rubocop:disable Layout/LineLength
it 'uses the decoded password salt' do
expect(authenticatable_salt).to eq('aca6d8b1272c0dd9')
end
it 'does not use the first 30 characters of the encrypted_password' do
expect(authenticatable_salt).not_to eq(encrypted_password[0, 29])
end
end
context 'when the encrypted_password is an unknown type' do
let(:encrypted_password) { '$argon2i$v=19$m=512,t=4,p=2$eM+ZMyYkpDRGaI3xXmuNcQ$c5DeJg3eb5dskVt1mDdxfw' }
it 'returns the first 30 characters of the encrypted_password' do
expect(authenticatable_salt).to eq(encrypted_password[0, 29])
end
end
end
describe '#valid_password?' do
subject(:validate_password) { user.valid_password?(password) }
let(:user) { build(:user, encrypted_password: encrypted_password) }
let(:password) { described_class.random_password }
shared_examples 'password validation fails when the password is encrypted using an unsupported method' do
let(:encrypted_password) { '$argon2i$v=19$m=512,t=4,p=2$eM+ZMyYkpDRGaI3xXmuNcQ$c5DeJg3eb5dskVt1mDdxfw' }
it { is_expected.to eq(false) }
end
context 'when the default encryption method is BCrypt' do
it_behaves_like 'password validation fails when the password is encrypted using an unsupported method'
context 'when the user password PBKDF2+SHA512' do
let(:encrypted_password) do
Devise::Pbkdf2Encryptable::Encryptors::Pbkdf2Sha512.digest(
password, 20_000, Devise.friendly_token[0, 16])
end
it { is_expected.to eq(true) }
it 're-encrypts the password as BCrypt' do
expect(user.encrypted_password).to start_with('$pbkdf2-sha512$')
validate_password
expect(user.encrypted_password).to start_with('$2a$')
end
end
end
context 'when the default encryption method is PBKDF2+SHA512 and the user password is BCrypt', :fips_mode do
it_behaves_like 'password validation fails when the password is encrypted using an unsupported method'
context 'when the user password BCrypt' do
let(:encrypted_password) { Devise::Encryptor.digest(described_class, password) }
it { is_expected.to eq(true) }
it 're-encrypts the password as PBKDF2+SHA512' do
expect(user.encrypted_password).to start_with('$2a$')
validate_password
expect(user.reload.encrypted_password).to start_with('$pbkdf2-sha512$')
end
end
end
end
describe '#password=' do
let(:user) { build(:user) }
let(:password) { described_class.random_password }
def compare_bcrypt_password(user, password)
Devise::Encryptor.compare(described_class, user.encrypted_password, password)
end
def compare_pbkdf2_password(user, password)
Devise::Pbkdf2Encryptable::Encryptors::Pbkdf2Sha512.compare(user.encrypted_password, password)
end
context 'when FIPS mode is enabled', :fips_mode do
it 'calls PBKDF2 digest and not the default Devise encryptor' do
expect(Devise::Pbkdf2Encryptable::Encryptors::Pbkdf2Sha512)
.to receive(:digest).at_least(:once).and_call_original
expect(Devise::Encryptor).not_to receive(:digest)
user.password = password
end
it 'saves the password in PBKDF2 format' do
user.password = password
user.save!
expect(compare_pbkdf2_password(user, password)).to eq(true)
expect { compare_bcrypt_password(user, password) }.to raise_error(::BCrypt::Errors::InvalidHash)
end
end
it 'calls default Devise encryptor and not the PBKDF2 encryptor' do
expect(Devise::Encryptor).to receive(:digest).at_least(:once).and_call_original
expect(Devise::Pbkdf2Encryptable::Encryptors::Pbkdf2Sha512).not_to receive(:digest)
user.password = password
end
it 'saves the password in BCrypt format' do
user.password = password
user.save!
expect { compare_pbkdf2_password(user, password) }
.to raise_error Devise::Pbkdf2Encryptable::Encryptors::InvalidHash
expect(compare_bcrypt_password(user, password)).to eq(true)
end
end
end
|