-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
474f09f
commit fbac42b
Showing
15 changed files
with
527 additions
and
22 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
class OneLoginController < ApplicationController | ||
before_action :one_login_enabled | ||
before_action :set_sentry_context, only: :callback | ||
|
||
def callback | ||
auth = request.env['omniauth.auth'] | ||
session[:onelogin_id_token] = auth&.credentials&.id_token | ||
candidate = OneLoginUser.authentificate(auth) | ||
|
||
sign_in(candidate, scope: :candidate) | ||
candidate.update!(last_signed_in_at: Time.zone.now) | ||
|
||
redirect_to candidate_interface_interstitial_path | ||
rescue OneLoginUser::Error => e | ||
session[:one_login_error] = e.message | ||
redirect_to auth_onelogin_sign_out_path | ||
end | ||
|
||
def sign_out | ||
id_token = session[:onelogin_id_token] | ||
one_login_error = session[:one_login_error] | ||
reset_session | ||
|
||
session[:one_login_error] = one_login_error | ||
# Go back to one login to sign out the user on their end as well | ||
redirect_to logout_onelogin_path(id_token_hint: id_token) | ||
end | ||
|
||
def sign_out_complete | ||
if session[:one_login_error].present? | ||
Sentry.capture_message(session[:one_login_error], level: :error) | ||
redirect_to internal_server_error_path | ||
else | ||
redirect_to candidate_interface_create_account_or_sign_in_path | ||
end | ||
end | ||
|
||
def failure | ||
session[:one_login_error] = "One login failure with #{params[:message]} " \ | ||
"for onelogin_id_token: #{session[:onelogin_id_token]}" | ||
|
||
redirect_to auth_onelogin_sign_out_path | ||
end | ||
|
||
private | ||
|
||
def one_login_enabled | ||
return if FeatureFlag.active?(:one_login_candidate_sign_in) | ||
|
||
redirect_to root_path | ||
end | ||
|
||
def set_sentry_context | ||
Sentry.set_extras( | ||
omniauth_hash: request.env['omniauth.auth'].to_h, | ||
) | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
class OneLoginUser | ||
class Error < StandardError; end | ||
attr_reader :email_address, :token | ||
|
||
def initialize(omniauth_object) | ||
@email_address = omniauth_object.info.email | ||
@token = omniauth_object.uid | ||
end | ||
|
||
def self.authentificate(omniauth_auth) | ||
new(omniauth_auth).authentificate | ||
end | ||
|
||
def authentificate | ||
one_login_auth = OneLoginAuth.find_by(token:) | ||
existing_candidate = Candidate.find_by(email_address:) | ||
|
||
return candidate_with_one_login(one_login_auth) if one_login_auth | ||
return existing_candidate_without_one_login(existing_candidate) if existing_candidate | ||
|
||
created_candidate | ||
end | ||
|
||
private | ||
|
||
def candidate_with_one_login(one_login_auth) | ||
one_login_auth.update!(email_address:) | ||
one_login_auth.candidate | ||
end | ||
|
||
def existing_candidate_without_one_login(existing_candidate) | ||
if existing_candidate.one_login_auth.present? && existing_candidate.one_login_auth.token != token | ||
raise( | ||
Error, | ||
"Candidate #{existing_candidate.id} has a different one login " \ | ||
"token than the user trying to login. Token used to auth #{token}", | ||
) | ||
end | ||
|
||
existing_candidate.create_one_login_auth!(token:, email_address:) | ||
existing_candidate | ||
end | ||
|
||
def created_candidate | ||
candidate = Candidate.create!(email_address:) | ||
candidate.create_one_login_auth!(token:, email_address:) | ||
|
||
candidate | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
module OneloginSetup | ||
def self.configure(builder) | ||
client_id = ENV.fetch('GOVUK_ONE_LOGIN_CLIENT_ID', '') | ||
onelogin_issuer_uri = URI(ENV.fetch('GOVUK_ONE_LOGIN_ISSUER_URL', '')) | ||
private_key_pem = ENV.fetch('GOVUK_ONE_LOGIN_PRIVATE_KEY', '') | ||
|
||
private_key_pem = private_key_pem.gsub('\n', "\n") | ||
host_env = HostingEnvironment.application_url | ||
private_key = OpenSSL::PKey::RSA.new(private_key_pem) | ||
rescue OpenSSL::PKey::RSAError => e | ||
raise e unless HostingEnvironment.development? | ||
|
||
builder.provider :govuk_one_login_openid_connect, | ||
name: :onelogin, | ||
allow_authorize_params: %i[session_id], | ||
callback_path: '/auth/onelogin/callback', | ||
discovery: true, | ||
issuer: onelogin_issuer_uri.to_s, | ||
path_prefix: '/auth', | ||
post_logout_redirect_uri: "#{host_env}/auth/onelogin/sign-out-complete", | ||
response_type: :code, | ||
scope: %w[email openid], | ||
client_auth_method: :jwt_bearer, | ||
client_options: { | ||
authorization_endpoint: '/oauth2/authorize', | ||
end_session_endpoint: '/oauth2/logout', | ||
token_endpoint: '/oauth2/token', | ||
userinfo_endpoint: '/oauth2/userinfo', | ||
host: onelogin_issuer_uri.host, | ||
identifier: client_id, | ||
port: 443, | ||
redirect_uri: "#{host_env}/auth/onelogin/callback", | ||
scheme: 'https', | ||
private_key: private_key, | ||
} | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
# This strategy ensures that the id_token_hint param is included in the post_logout_redirect_uri. | ||
# The node-oidc-provider library requires this param to be present in order for the redirect to work. | ||
# See: https://github.com/panva/node-oidc-provider/blob/03c9bc513860e68ee7be84f99bfc9dc930b224e8/lib/actions/end_session.js#L27 | ||
# See: https://github.com/omniauth/omniauth_openid_connect/blob/34370d655d39fe7980f89f55715888e0ebd7270e/lib/omniauth/strategies/openid_connect.rb#L423 | ||
# | ||
module OmniAuth | ||
module Strategies | ||
class GovukOneLoginOpenIDConnect < OmniAuth::Strategies::OpenIDConnect | ||
TOKEN_KEY = 'id_token_hint'.freeze | ||
|
||
def encoded_post_logout_redirect_uri | ||
return unless options.post_logout_redirect_uri | ||
|
||
logout_uri_params = { | ||
'post_logout_redirect_uri' => options.post_logout_redirect_uri, | ||
} | ||
|
||
if query_string.present? | ||
query_params = CGI.parse(query_string[1..]) | ||
logout_uri_params[TOKEN_KEY] = query_params[TOKEN_KEY].first if query_params.key?(TOKEN_KEY) | ||
end | ||
|
||
URI.encode_www_form(logout_uri_params) | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
FactoryBot.define do | ||
factory :one_login_auth do | ||
candidate | ||
token { SecureRandom.hex(10) } | ||
email_address { "#{SecureRandom.hex(5)}@example.com" } | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
require 'rails_helper' | ||
|
||
RSpec.describe OneLoginUser do | ||
subject(:authentificate) { one_login_user.authentificate } | ||
|
||
let(:one_login_user) { described_class.new(omniauth_object) } | ||
let(:omniauth_object) do | ||
OmniAuth::AuthHash.new( | ||
{ | ||
uid: '123', | ||
info: { | ||
email: '[email protected]', | ||
}, | ||
}, | ||
) | ||
end | ||
|
||
describe 'authentificate' do | ||
it 'authentificates successfuly using the token' do | ||
candidate = create(:candidate) | ||
create(:one_login_auth, candidate:, token: '123') | ||
|
||
expect { authentificate }.to not_change( | ||
candidate.reload.one_login_auth, | ||
:id, | ||
) | ||
|
||
expect(authentificate).to eq(candidate) | ||
end | ||
|
||
it 'authentificates successfuly using the email and creates a one_login_auth' do | ||
candidate = create(:candidate, email_address: '[email protected]') | ||
|
||
expect { authentificate }.to change { | ||
candidate.reload.one_login_auth.present? | ||
}.from(false).to(true) | ||
|
||
expect(authentificate).to eq(candidate) | ||
expect(authentificate.one_login_auth).to have_attributes( | ||
email_address: authentificate.email_address, | ||
token: '123', | ||
) | ||
end | ||
|
||
it "authentificates successfuly and creates a candidate if we can't find one" do | ||
expect { authentificate }.to change( | ||
Candidate, | ||
:count, | ||
).by(1) | ||
|
||
expect(authentificate).to eq(Candidate.last) | ||
expect(authentificate.one_login_auth).to have_attributes( | ||
email_address: authentificate.email_address, | ||
token: '123', | ||
) | ||
end | ||
|
||
it 'raises error if the candidate already has a different one login token' do | ||
candidate = create(:candidate, email_address: '[email protected]') | ||
create(:one_login_auth, candidate:, token: '456') | ||
|
||
expect { authentificate }.to raise_exception(OneLoginUser::Error).with_message( | ||
"Candidate #{candidate.id} has a different one login " \ | ||
'token than the user trying to login. Token used to auth 123', | ||
) | ||
end | ||
end | ||
end |
Oops, something went wrong.