Skip to content

Commit

Permalink
Merge pull request #2401 from DFE-Digital/AQTS-551-spike-gov-one-logi…
Browse files Browse the repository at this point in the history
…n-authentication

[AQTS-551] GovOne Login Authentication
  • Loading branch information
Hassanmir92 authored Sep 25, 2024
2 parents 700607c + 39a6f4b commit 02d4388
Show file tree
Hide file tree
Showing 25 changed files with 483 additions and 12 deletions.
2 changes: 2 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ gem "libreconv"
gem "matrix"
gem "okcomputer"
gem "omniauth-azure-activedirectory-v2"
gem "omniauth_openid_connect"
gem "omniauth-rails_csrf_protection"
gem "pagy"
gem "pg"
Expand Down Expand Up @@ -77,6 +78,7 @@ group :test do
gem "capybara"
gem "climate_control"
gem "cuprite"
gem "pry"
gem "rspec"
gem "rspec-rails"
gem "shoulda-matchers"
Expand Down
44 changes: 44 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -109,11 +109,13 @@ GEM
tzinfo (~> 2.0, >= 2.0.5)
addressable (2.8.7)
public_suffix (>= 2.0.2, < 7.0)
aes_key_wrap (1.1.0)
amazing_print (1.6.0)
annotate (3.2.0)
activerecord (>= 3.2, < 8.0)
rake (>= 10.4, < 14.0)
ast (2.4.2)
attr_required (1.0.2)
azure-storage-blob (2.0.3)
azure-storage-common (~> 2.0)
nokogiri (~> 1, >= 1.10.8)
Expand All @@ -125,6 +127,7 @@ GEM
base64 (0.2.0)
bcrypt (3.1.20)
bigdecimal (3.1.8)
bindata (2.5.0)
bindex (0.8.1)
bootsnap (1.18.4)
msgpack (~> 1.2)
Expand All @@ -142,6 +145,7 @@ GEM
cgi (0.4.1)
choice (0.2.0)
climate_control (1.2.0)
coderay (1.1.3)
combine_pdf (1.0.26)
matrix
ruby-rc4 (>= 0.1.5)
Expand Down Expand Up @@ -295,6 +299,11 @@ GEM
jsbundling-rails (1.3.1)
railties (>= 6.0.0)
json (2.7.2)
json-jwt (1.15.3.1)
activesupport (>= 4.2)
aes_key_wrap
bindata
httpclient
jwt (2.9.0)
base64
language_server-protocol (3.17.0.3)
Expand Down Expand Up @@ -366,6 +375,20 @@ GEM
omniauth-rails_csrf_protection (1.0.2)
actionpack (>= 4.2)
omniauth (~> 2.0)
omniauth_openid_connect (0.6.1)
omniauth (>= 1.9, < 3)
openid_connect (~> 1.1)
openid_connect (1.4.2)
activemodel
attr_required (>= 1.0.0)
json-jwt (>= 1.15.0)
net-smtp
rack-oauth2 (~> 1.21)
swd (~> 1.3)
tzinfo
validate_email
validate_url
webfinger (~> 1.2)
orm_adapter (0.5.0)
os (1.1.4)
pagy (9.0.9)
Expand All @@ -386,6 +409,9 @@ GEM
activesupport (>= 7.0.0)
rack
railties (>= 7.0.0)
pry (0.14.2)
coderay (~> 1.1)
method_source (~> 1.0)
psych (5.1.2)
stringio
public_suffix (6.0.1)
Expand All @@ -398,6 +424,12 @@ GEM
rack (2.2.9)
rack-attack (6.7.0)
rack (>= 1.0, < 4)
rack-oauth2 (1.21.3)
activesupport
attr_required
httpclient
json-jwt (>= 1.11.0)
rack (>= 2.1.0)
rack-protection (3.2.0)
base64 (>= 0.1.0)
rack (~> 2.2, >= 2.2.4)
Expand Down Expand Up @@ -570,6 +602,10 @@ GEM
version_gem (~> 1.1, >= 1.1.1)
stringio (3.1.1)
strscan (3.1.0)
swd (1.3.0)
activesupport (>= 3)
attr_required (>= 0.0.5)
httpclient (>= 2.4)
syntax_tree (6.2.0)
prettier_print (>= 1.2.0)
syntax_tree-haml (4.0.3)
Expand All @@ -595,6 +631,9 @@ GEM
unf_ext (0.0.9.1)
unicode-display_width (2.5.0)
useragent (0.16.10)
validate_email (0.1.6)
activemodel (>= 3.0)
mail (>= 2.2.5)
validate_url (1.0.15)
activemodel (>= 3.0.0)
public_suffix
Expand All @@ -610,6 +649,9 @@ GEM
activemodel (>= 6.0.0)
bindex (>= 0.4.0)
railties (>= 6.0.0)
webfinger (1.2.0)
activesupport
httpclient (>= 2.4)
webmock (3.23.1)
addressable (>= 2.8.0)
crack (>= 0.3.2)
Expand Down Expand Up @@ -666,11 +708,13 @@ DEPENDENCIES
okcomputer
omniauth-azure-activedirectory-v2
omniauth-rails_csrf_protection
omniauth_openid_connect
pagy
pg
prawn
prettier_print
propshaft
pry
puma
pundit
rack-attack
Expand Down
3 changes: 2 additions & 1 deletion adr/00010-govuk-one-login.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,10 @@ Before we can make the technical changes to adopt GOV.UK One Login we will need

## Consequences

We will adopt GOV.UK One Login using OAuth via the [Omniauth Gem].
We will adopt GOV.UK One Login using OAuth via the [Omniauth Gem] & [Omiauth OpenId Connect Gem].

We will use the shared DfE [sector identifier].

[Omniauth Gem]: https://github.com/omniauth/omniauth
[Omiauth OpenId Connect Gem]: https://github.com/omniauth/omniauth_openid_connect
[sector identifier]: https://docs.sign-in.service.gov.uk/before-integrating/choose-your-sector-identifier/
39 changes: 39 additions & 0 deletions app/controllers/teachers/omniauth_callbacks_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# frozen_string_literal: true

class Teachers::OmniauthCallbacksController < ApplicationController
def gov_one
auth = request.env["omniauth.auth"]
email = auth&.info&.email
gov_one_id = auth&.uid

session[:id_token] = auth&.credentials&.id_token

teacher =
FindOrCreateTeacherFromGovOne.call(
email:,
gov_one_id:,
eligibility_check_id: session[:eligibility_check_id],
)

return error_redirect unless teacher

sign_in_and_redirect teacher
end

def failure
error_redirect
end

private

def error_redirect
return if teacher_signed_in?

flash[:alert] = "There was a problem signing in. Please try again."
redirect_to new_teacher_session_path
end

def after_sign_in_path_for(resource)
stored_location_for(resource) || teacher_interface_root_path
end
end
15 changes: 15 additions & 0 deletions app/helpers/gov_one_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# frozen_string_literal: true

module GovOneHelper
def logout_uri
params = {
post_logout_redirect_uri: destroy_teacher_session_url,
id_token_hint: session[:id_token],
}

uri = URI.parse("#{Rails.configuration.gov_one.base_uri}logout")
uri.query = URI.encode_www_form(params)

uri.to_s
end
end
2 changes: 2 additions & 0 deletions app/models/teacher.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,13 @@
# uuid :uuid not null
# created_at :datetime not null
# updated_at :datetime not null
# gov_one_id :string
#
# Indexes
#
# index_teacher_on_lower_email (lower((email)::text)) UNIQUE
# index_teachers_on_canonical_email (canonical_email)
# index_teachers_on_gov_one_id (gov_one_id) UNIQUE
# index_teachers_on_uuid (uuid) UNIQUE
#
class Teacher < ApplicationRecord
Expand Down
58 changes: 58 additions & 0 deletions app/services/find_or_create_teacher_from_gov_one.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# frozen_string_literal: true

class FindOrCreateTeacherFromGovOne
include ServicePattern

attr_reader :teacher

def initialize(email:, gov_one_id:, eligibility_check_id:)
@email = email
@gov_one_id = gov_one_id
@eligibility_check_id = eligibility_check_id
end

def call
ActiveRecord::Base.transaction do
find_or_create_teacher!

create_application_form! if teacher_requires_application_form?
end

teacher
rescue StandardError => e
Sentry.capture_exception(e)

nil
end

private

attr_reader :email, :gov_one_id, :eligibility_check_id

def find_or_create_teacher!
@teacher =
Teacher.find_by(gov_one_id:) || Teacher.find_by(email:) ||
Teacher.create!(email:)

teacher.update!(gov_one_id:) if teacher.gov_one_id.nil?
end

def create_application_form!
if valid_eligibility_check?
ApplicationFormFactory.call(teacher:, region: eligibility_check.region)
end
end

def valid_eligibility_check?
eligibility_check.present? && eligibility_check.region.present? &&
eligibility_check.country.eligibility_enabled?
end

def eligibility_check
@eligibility_check ||= EligibilityCheck.find_by(id: eligibility_check_id)
end

def teacher_requires_application_form?
teacher.persisted? && teacher.application_form.nil?
end
end
3 changes: 3 additions & 0 deletions app/views/shared/_header.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@
<% when "teacher" %>
<% if current_teacher %>
<% header.with_navigation_item(text: "Sign out", href: main_app.destroy_teacher_session_path) %>
<% if FeatureFlags::FeatureFlag.active?(:gov_one_applicant_login) %>
<% header.with_navigation_item(text: "Sign out with GovOne", href: logout_uri) %>
<% end %>
<% end %>
<% end %>
<% end %>
6 changes: 6 additions & 0 deletions app/views/teachers/registrations/new.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,9 @@
<%= f.govuk_email_field :email, autocomplete: "email", label: { size: "m" } %>
<%= f.govuk_submit %>
<% end %>

<% if FeatureFlags::FeatureFlag.active?(:gov_one_applicant_login) %>
<h2 class="govuk-heading-l">Gov One Login</h2>

<%= button_to "Sign up in with Gov One", "/teacher/auth/gov_one", class: "govuk-button" %>
<% end %>
6 changes: 6 additions & 0 deletions app/views/teachers/sessions/new.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,9 @@

<%= f.govuk_submit %>
<% end %>

<% if FeatureFlags::FeatureFlag.active?(:gov_one_applicant_login) %>
<h2 class="govuk-heading-l">Gov One Login</h2>

<%= button_to "Sign in with Gov One", "/teacher/auth/gov_one", class: "govuk-button" %>
<% end %>
1 change: 1 addition & 0 deletions config/analytics_blocklist.yml
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@
- note
:teachers:
- access_your_teaching_qualifications_url
- gov_one_id
:timeline_events:
- age_range_max
- age_range_min
Expand Down
1 change: 1 addition & 0 deletions config/application.rb
Original file line number Diff line number Diff line change
Expand Up @@ -54,5 +54,6 @@ class Application < Rails::Application
config.action_mailer.deliver_later_queue_name = "mailer"

config.dqt = config_for(:dqt)
config.gov_one = config_for(:gov_one)
end
end
4 changes: 4 additions & 0 deletions config/feature_flags.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ feature_flags:
author: Thomas Leese
description: Enable suitability records and matching.

gov_one_applicant_login:
author: Hassan Mir
description: Allow teacher applicants to sign in using GovOne Login

teacher_applications:
author: Thomas Leese
description: >
Expand Down
14 changes: 14 additions & 0 deletions config/gov_one.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
development:
base_uri: <%= ENV.fetch("GOV_ONE_OAUTH_BASE_URI", "https://oidc.integration.account.gov.uk/") %>
client_id: <%= ENV["GOV_ONE_OAUTH_CLIENT_ID"] %>
base_62_private_key: <%= ENV["GOV_ONE_OAUTH_BASE64_PRIVATE_KEY"] %>

production:
base_uri: <%= ENV["GOV_ONE_OAUTH_BASE_URI"] %>
client_id: <%= ENV["GOV_ONE_OAUTH_CLIENT_ID"] %>
base_62_private_key: <%= ENV["GOV_ONE_OAUTH_BASE64_PRIVATE_KEY"] %>

test:
base_uri: https://oidc.integration.account.gov.uk/
client_id: test
base_62_private_key: test
Loading

0 comments on commit 02d4388

Please sign in to comment.