225 lines
8.2 KiB
Ruby
225 lines
8.2 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
describe SecondFactor::AuthManager do
|
|
fab!(:user) { Fabricate(:user) }
|
|
fab!(:guardian) { Guardian.new(user) }
|
|
fab!(:user_totp) { Fabricate(:user_second_factor_totp, user: user) }
|
|
|
|
def create_request(request_method: "GET", path: "/")
|
|
ActionDispatch::TestRequest.create({
|
|
"REQUEST_METHOD" => request_method,
|
|
"PATH_INFO" => path
|
|
})
|
|
end
|
|
|
|
def create_manager(action)
|
|
SecondFactor::AuthManager.new(guardian, action)
|
|
end
|
|
|
|
def create_action
|
|
TestSecondFactorAction.new(guardian)
|
|
end
|
|
|
|
def stage_challenge(successful:)
|
|
action = create_action
|
|
action.expects(:no_second_factors_enabled!).never
|
|
action
|
|
.expects(:second_factor_auth_required!)
|
|
.with({ random_param: 'hello' })
|
|
.returns({ callback_params: { call_me_back: 4314 } })
|
|
.once
|
|
manager = create_manager(action)
|
|
request = create_request(
|
|
request_method: "POST",
|
|
path: "/abc/xyz"
|
|
)
|
|
secure_session = {}
|
|
begin
|
|
manager.run!(request, { random_param: 'hello' }, secure_session)
|
|
rescue SecondFactor::AuthManager::SecondFactorRequired
|
|
# expected
|
|
end
|
|
|
|
challenge = JSON
|
|
.parse(secure_session["current_second_factor_auth_challenge"])
|
|
.deep_symbolize_keys
|
|
|
|
challenge[:successful] = successful
|
|
secure_session["current_second_factor_auth_challenge"] = challenge.to_json
|
|
[challenge[:nonce], secure_session]
|
|
end
|
|
|
|
describe '#allow_backup_codes!' do
|
|
it 'adds the backup codes method to the allowed methods set' do
|
|
manager = create_manager(create_action)
|
|
expect(manager.allowed_methods).not_to include(
|
|
UserSecondFactor.methods[:backup_codes]
|
|
)
|
|
manager.allow_backup_codes!
|
|
expect(manager.allowed_methods).to include(
|
|
UserSecondFactor.methods[:backup_codes]
|
|
)
|
|
end
|
|
end
|
|
|
|
describe '#run!' do
|
|
context 'when the user does not have a suitable 2FA method' do
|
|
before do
|
|
user_totp.destroy!
|
|
end
|
|
|
|
it 'calls the no_second_factors_enabled! method of the action' do
|
|
action = create_action
|
|
action.expects(:no_second_factors_enabled!).with({ hello_world: 331 }).once
|
|
action.expects(:second_factor_auth_required!).never
|
|
action.expects(:second_factor_auth_completed!).never
|
|
manager = create_manager(action)
|
|
manager.run!(create_request, { hello_world: 331 }, {})
|
|
end
|
|
|
|
it 'calls the no_second_factors_enabled! method of the action even if a nonce is present in the params' do
|
|
action = create_action
|
|
params = { second_factor_nonce: SecureRandom.hex }
|
|
action.expects(:no_second_factors_enabled!).with(params).once
|
|
action.expects(:second_factor_auth_required!).never
|
|
action.expects(:second_factor_auth_completed!).never
|
|
manager = create_manager(action)
|
|
manager.run!(create_request, params, {})
|
|
end
|
|
end
|
|
|
|
it "initiates the 2FA process and stages a challenge in secure session when there is no nonce in params" do
|
|
action = create_action
|
|
action.expects(:no_second_factors_enabled!).never
|
|
action
|
|
.expects(:second_factor_auth_required!)
|
|
.with({ expect_me: 131 })
|
|
.returns({ callback_params: { call_me_back: 4314 }, redirect_path: "/gg", description: "hello world!" })
|
|
.once
|
|
action.expects(:second_factor_auth_completed!).never
|
|
manager = create_manager(action)
|
|
request = create_request(
|
|
request_method: "POST",
|
|
path: "/abc/xyz"
|
|
)
|
|
secure_session = {}
|
|
expect {
|
|
manager.run!(request, { expect_me: 131 }, secure_session)
|
|
}.to raise_error(SecondFactor::AuthManager::SecondFactorRequired)
|
|
json = secure_session["current_second_factor_auth_challenge"]
|
|
challenge = JSON.parse(json).deep_symbolize_keys
|
|
expect(challenge[:nonce]).to be_present
|
|
expect(challenge[:callback_method]).to eq("POST")
|
|
expect(challenge[:callback_path]).to eq("/abc/xyz")
|
|
expect(challenge[:redirect_path]).to eq("/gg")
|
|
expect(challenge[:allowed_methods]).to eq(manager.allowed_methods.to_a)
|
|
expect(challenge[:callback_params]).to eq({ call_me_back: 4314 })
|
|
expect(challenge[:description]).to eq("hello world!")
|
|
end
|
|
|
|
it "sets the redirect_path to the root path if second_factor_auth_required! doesn't specify a redirect_path" do
|
|
action = create_action
|
|
action.expects(:no_second_factors_enabled!).never
|
|
action
|
|
.expects(:second_factor_auth_required!)
|
|
.with({ expect_me: 131 })
|
|
.returns({ callback_params: { call_me_back: 4314 } })
|
|
.once
|
|
action.expects(:second_factor_auth_completed!).never
|
|
manager = create_manager(action)
|
|
request = create_request(
|
|
request_method: "POST",
|
|
path: "/abc/xyz"
|
|
)
|
|
secure_session = {}
|
|
expect {
|
|
manager.run!(request, { expect_me: 131 }, secure_session)
|
|
}.to raise_error(SecondFactor::AuthManager::SecondFactorRequired)
|
|
json = secure_session["current_second_factor_auth_challenge"]
|
|
challenge = JSON.parse(json).deep_symbolize_keys
|
|
expect(challenge[:redirect_path]).to eq("/")
|
|
|
|
set_subfolder("/community")
|
|
action = create_action
|
|
action.expects(:no_second_factors_enabled!).never
|
|
action
|
|
.expects(:second_factor_auth_required!)
|
|
.with({ expect_me: 131 })
|
|
.returns({ callback_params: { call_me_back: 4314 } })
|
|
.once
|
|
action.expects(:second_factor_auth_completed!).never
|
|
manager = create_manager(action)
|
|
request = create_request(
|
|
request_method: "POST",
|
|
path: "/abc/xyz"
|
|
)
|
|
secure_session = {}
|
|
expect {
|
|
manager.run!(request, { expect_me: 131 }, secure_session)
|
|
}.to raise_error(SecondFactor::AuthManager::SecondFactorRequired)
|
|
json = secure_session["current_second_factor_auth_challenge"]
|
|
challenge = JSON.parse(json).deep_symbolize_keys
|
|
expect(challenge[:redirect_path]).to eq("/community")
|
|
end
|
|
|
|
it "calls the second_factor_auth_completed! method of the action if the challenge is successful and not expired" do
|
|
nonce, secure_session = stage_challenge(successful: true)
|
|
|
|
action = create_action
|
|
|
|
action.expects(:no_second_factors_enabled!).never
|
|
action.expects(:second_factor_auth_required!).never
|
|
action
|
|
.expects(:second_factor_auth_completed!)
|
|
.with({ call_me_back: 4314 })
|
|
.once
|
|
manager = create_manager(action)
|
|
request = create_request(
|
|
request_method: "POST",
|
|
path: "/abc/xyz"
|
|
)
|
|
manager.run!(request, { second_factor_nonce: nonce }, secure_session)
|
|
end
|
|
|
|
it "does not call the second_factor_auth_completed! method of the action if the challenge is not marked successful" do
|
|
nonce, secure_session = stage_challenge(successful: false)
|
|
|
|
action = create_action
|
|
action.expects(:no_second_factors_enabled!).never
|
|
action.expects(:second_factor_auth_required!).never
|
|
action.expects(:second_factor_auth_completed!).never
|
|
manager = create_manager(action)
|
|
request = create_request(
|
|
request_method: "POST",
|
|
path: "/abc/xyz"
|
|
)
|
|
expect {
|
|
manager.run!(request, { second_factor_nonce: nonce }, secure_session)
|
|
}.to raise_error(SecondFactor::BadChallenge) do |ex|
|
|
expect(ex.error_translation_key).to eq("second_factor_auth.challenge_not_completed")
|
|
end
|
|
end
|
|
|
|
it "does not call the second_factor_auth_completed! method of the action if the challenge is expired" do
|
|
nonce, secure_session = stage_challenge(successful: true)
|
|
|
|
action = create_action
|
|
action.expects(:no_second_factors_enabled!).never
|
|
action.expects(:second_factor_auth_required!).never
|
|
action.expects(:second_factor_auth_completed!).never
|
|
manager = create_manager(action)
|
|
request = create_request(
|
|
request_method: "POST",
|
|
path: "/abc/xyz"
|
|
)
|
|
|
|
freeze_time (SecondFactor::AuthManager::MAX_CHALLENGE_AGE + 1.minute).from_now
|
|
expect {
|
|
manager.run!(request, { second_factor_nonce: nonce }, secure_session)
|
|
}.to raise_error(SecondFactor::BadChallenge) do |ex|
|
|
expect(ex.error_translation_key).to eq("second_factor_auth.challenge_expired")
|
|
end
|
|
end
|
|
end
|
|
end
|