157 lines
5.1 KiB
Ruby
157 lines
5.1 KiB
Ruby
|
require 'spec_helper_min'
|
||
|
|
||
|
describe Carto::UserMultifactorAuth do
|
||
|
|
||
|
def totp_code(mfa)
|
||
|
ROTP::TOTP.new(mfa.shared_secret).now
|
||
|
end
|
||
|
|
||
|
before :all do
|
||
|
@valid_type = 'totp'
|
||
|
@carto_user = FactoryGirl.create(:carto_user)
|
||
|
end
|
||
|
|
||
|
before :each do
|
||
|
Cartodb::Central.stubs(:sync_data_with_cartodb_central?).returns(false)
|
||
|
@carto_user.reload.user_multifactor_auths.each(&:destroy!)
|
||
|
end
|
||
|
|
||
|
after :all do
|
||
|
@carto_user.destroy!
|
||
|
end
|
||
|
|
||
|
describe '#create' do
|
||
|
it 'does not validate an unsupported multi-factor authentication type' do
|
||
|
mfa = Carto::UserMultifactorAuth.new(user_id: @carto_user.id, type: 'wadus')
|
||
|
mfa.valid?.should eq(false)
|
||
|
end
|
||
|
|
||
|
it 'validates supported multi-factor authentication types' do
|
||
|
mfa = Carto::UserMultifactorAuth.new(user_id: @carto_user.id, type: @valid_type)
|
||
|
expect { mfa.valid? }.to be_true
|
||
|
end
|
||
|
|
||
|
it 'creates a new multifactor auth for a user' do
|
||
|
expect {
|
||
|
Carto::UserMultifactorAuth.create!(user_id: @carto_user.id, type: @valid_type)
|
||
|
}.to_not raise_error
|
||
|
end
|
||
|
|
||
|
it 'populates shared_secret' do
|
||
|
mfa = Carto::UserMultifactorAuth.create!(user_id: @carto_user.id, type: @valid_type)
|
||
|
expect { mfa.shared_secret }.to_not be_nil
|
||
|
end
|
||
|
|
||
|
it 'is disabled by default' do
|
||
|
mfa = Carto::UserMultifactorAuth.create!(user_id: @carto_user.id, type: @valid_type)
|
||
|
expect { mfa.disabled? }.to be_true
|
||
|
end
|
||
|
|
||
|
it 'syncs to central' do
|
||
|
Cartodb::Central.stubs(:sync_data_with_cartodb_central?).returns(true)
|
||
|
Cartodb::Central
|
||
|
.any_instance
|
||
|
.expects(:update_user)
|
||
|
.with(@carto_user.username,
|
||
|
has_entries(multifactor_authentication_status: User::MULTIFACTOR_AUTHENTICATION_NEEDS_SETUP))
|
||
|
.once
|
||
|
Carto::UserMultifactorAuth.create!(user_id: @carto_user.id, type: @valid_type)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
describe '#update' do
|
||
|
it 'syncs enabled to central' do
|
||
|
mfa = Carto::UserMultifactorAuth.create!(user_id: @carto_user.id, type: @valid_type)
|
||
|
Cartodb::Central.stubs(:sync_data_with_cartodb_central?).returns(true)
|
||
|
|
||
|
Cartodb::Central
|
||
|
.any_instance
|
||
|
.expects(:update_user)
|
||
|
.with(@carto_user.username,
|
||
|
has_entries(multifactor_authentication_status: User::MULTIFACTOR_AUTHENTICATION_ENABLED))
|
||
|
.once
|
||
|
|
||
|
mfa.enabled = true
|
||
|
mfa.save!
|
||
|
end
|
||
|
|
||
|
it 'syncs needs setup to central' do
|
||
|
mfa = Carto::UserMultifactorAuth.create!(user_id: @carto_user.id, type: @valid_type, enabled: true)
|
||
|
Cartodb::Central.stubs(:sync_data_with_cartodb_central?).returns(true)
|
||
|
|
||
|
Cartodb::Central
|
||
|
.any_instance
|
||
|
.expects(:update_user)
|
||
|
.with(@carto_user.username,
|
||
|
has_entries(multifactor_authentication_status: User::MULTIFACTOR_AUTHENTICATION_NEEDS_SETUP))
|
||
|
.once
|
||
|
|
||
|
mfa.enabled = false
|
||
|
mfa.save!
|
||
|
end
|
||
|
|
||
|
it 'syncs disabled to central' do
|
||
|
mfa = Carto::UserMultifactorAuth.create!(user_id: @carto_user.id, type: @valid_type)
|
||
|
Cartodb::Central.stubs(:sync_data_with_cartodb_central?).returns(true)
|
||
|
|
||
|
Cartodb::Central
|
||
|
.any_instance
|
||
|
.expects(:update_user)
|
||
|
.with(@carto_user.username,
|
||
|
has_entries(multifactor_authentication_status: User::MULTIFACTOR_AUTHENTICATION_DISABLED))
|
||
|
.once
|
||
|
|
||
|
mfa.destroy!
|
||
|
end
|
||
|
end
|
||
|
|
||
|
describe '#verify!' do
|
||
|
it 'verifies a valid code' do
|
||
|
mfa = Carto::UserMultifactorAuth.create!(user_id: @carto_user.id, type: @valid_type)
|
||
|
code = totp_code(mfa)
|
||
|
mfa.verify!(code)
|
||
|
expect { mfa.enabled }.to be_true
|
||
|
end
|
||
|
|
||
|
it 'does not allow reuse of a valid code' do
|
||
|
mfa = Carto::UserMultifactorAuth.create!(user_id: @carto_user.id, type: @valid_type)
|
||
|
code = totp_code(mfa)
|
||
|
mfa.verify!(code)
|
||
|
expect { mfa.verify!(code) }.to raise_error("The code is not valid")
|
||
|
end
|
||
|
|
||
|
it 'does not verify an invalid code' do
|
||
|
mfa = Carto::UserMultifactorAuth.create!(user_id: @carto_user.id, type: @valid_type)
|
||
|
totp = ROTP::TOTP.new(ROTP::Base32.random_base32)
|
||
|
expect { mfa.verify!(totp.now) }.to raise_error("The code is not valid")
|
||
|
end
|
||
|
|
||
|
it 'does not allow use of future/past tokens' do
|
||
|
mfa = Carto::UserMultifactorAuth.create!(user_id: @carto_user.id, type: @valid_type)
|
||
|
totp = ROTP::TOTP.new(mfa.shared_secret)
|
||
|
expect { mfa.verify!(totp.at(Time.now - 90.seconds)) }.to raise_error("The code is not valid")
|
||
|
end
|
||
|
|
||
|
it 'verifies a code taking into account 30 seconds drift' do
|
||
|
mfa = Carto::UserMultifactorAuth.create!(user_id: @carto_user.id, type: @valid_type)
|
||
|
totp = ROTP::TOTP.new(mfa.shared_secret)
|
||
|
expect { mfa.verify!(totp.at(Time.now - 30.seconds)) }.to be_true
|
||
|
end
|
||
|
end
|
||
|
|
||
|
describe '#provisioning_uri' do
|
||
|
before :each do
|
||
|
@multifactor_auth = FactoryGirl.create(:totp, :active, user: @carto_user)
|
||
|
end
|
||
|
|
||
|
after :each do
|
||
|
@multifactor_auth.destroy!
|
||
|
end
|
||
|
|
||
|
it 'provides a provisioning_uri' do
|
||
|
uri = "otpauth://totp/CARTO:#{@carto_user.username}?secret=#{@multifactor_auth.shared_secret}&issuer=CARTO"
|
||
|
@multifactor_auth.provisioning_uri.should eq uri
|
||
|
end
|
||
|
end
|
||
|
end
|