Skip to content

Commit

Permalink
Merge pull request #3023 from DFE-Digital/CAPT-1766-one-login-bypass
Browse files Browse the repository at this point in the history
CAPT-1766 one login bypass
  • Loading branch information
alkesh authored Jul 30, 2024
2 parents f3b2758 + 0111604 commit 4666fee
Show file tree
Hide file tree
Showing 20 changed files with 333 additions and 12 deletions.
3 changes: 3 additions & 0 deletions .env.development
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ ADMIN_ALLOWED_IPS=::1,127.0.0.1
ENVIRONMENT_NAME=local

BYPASS_DFE_SIGN_IN=true
BYPASS_ONELOGIN_SIGN_IN=true
SUPPRESS_DFE_ANALYTICS_INIT=true

HMRC_API_BASE_URL=https://test-api.service.hmrc.gov.uk
Expand All @@ -21,3 +22,5 @@ TID_SIGN_IN_API_ENDPOINT=https://preprod.teaching-identity.education.gov.uk:433
TID_BASE_URL=https://localhost:3000
TID_SIGN_IN_CLIENT_ID=claim

ONELOGIN_SIGN_IN_ISSUER=https://oidc.integration.account.gov.uk/
ONELOGIN_REDIRECT_BASE_URL=http://localhost:3000
1 change: 1 addition & 0 deletions .env.test
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,4 @@ TID_SIGN_IN_ISSUER=https://preprod.teaching-identity.education.gov.uk/
TID_SIGN_IN_API_ENDPOINT=https://preprod.teaching-identity.education.gov.uk:433
TID_SIGN_IN_CLIENT_ID=claim

BYPASS_ONELOGIN_SIGN_IN=true
72 changes: 72 additions & 0 deletions app/controllers/omniauth_callbacks_controller.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
class OmniauthCallbacksController < ApplicationController
include JourneyConcern

ONELOGIN_JWT_CORE_IDENTITY_HASH_KEY = "https://vocab.account.gov.uk/v1/coreIdentityJWT".freeze

def callback
auth = request.env["omniauth.auth"]

Expand All @@ -25,6 +27,20 @@ def failure
render layout: false
end

def onelogin
auth = if OneLoginSignIn.bypass?
test_user_auth_hash
else
request.env["omniauth.auth"]
end

core_identity_jwt = auth.extra.raw_info[ONELOGIN_JWT_CORE_IDENTITY_HASH_KEY]
return process_one_login_identity_verification_callback(core_identity_jwt) if core_identity_jwt
process_one_login_authentication_callback(auth)
rescue Rack::OAuth2::Client::Error => e
render plain: e.message
end

private

def current_journey_routing_name
Expand All @@ -36,4 +52,60 @@ def current_journey_routing_name
Journeys::TeacherStudentLoanReimbursement::ROUTING_NAME
end
end

def process_one_login_authentication_callback(auth)
onelogin_user_info_attributes = auth.info.to_h.slice(
*SignInForm::OneloginUserInfoForm::ONELOGIN_USER_INFO_ATTRIBUTES.map(&:to_s)
)

journey_session.answers.assign_attributes(onelogin_user_info: onelogin_user_info_attributes)
journey_session.save!

redirect_to(
claim_path(
journey: current_journey_routing_name,
slug: "sign-in",
claim: {
logged_in_with_onelogin: true
}
)
)
end

def process_one_login_identity_verification_callback(core_identity_jwt)
first_name, surname = extract_name_from_jwt(core_identity_jwt)
redirect_to(
claim_path(
journey: current_journey_routing_name,
slug: "sign-in",
claim: {
identity_confirmed_with_onelogin: true,
first_name: first_name,
surname: surname
}
)
)
end

def extract_name_from_jwt(jwt)
if OneLoginSignIn.bypass?
first_name = "TEST"
surname = "USER"
else
identity_jwt_public_key = OpenSSL::PKey::EC.new(Base64.decode64(ENV["ONELOGIN_IDENTITY_JWT_PUBLIC_KEY_BASE64"]))
decoded_jwt = JSON::JWT.decode(jwt, identity_jwt_public_key)
name_parts = decoded_jwt["vc"]["credentialSubject"]["name"][0]["nameParts"]
first_name = name_parts.find { |part| part["type"] == "GivenName" }["value"]
surname = name_parts.find { |part| part["type"] == "FamilyName" }["value"]
end
[first_name, surname]
end

def test_user_auth_hash
if request.path == "/auth/onelogin"
OmniAuth::AuthHash.new(info: {email: "[email protected]"}, extra: {raw_info: {}})
elsif request.path == "/auth/onelogin_identity"
OmniAuth::AuthHash.new(info: {email: ""}, extra: {raw_info: {ONELOGIN_JWT_CORE_IDENTITY_HASH_KEY => "test"}})
end
end
end
48 changes: 48 additions & 0 deletions app/forms/sign_in_form.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
class SignInForm < Form
class OneloginUserInfoForm
include ActiveModel::Model
include ActiveModel::Attributes

ONELOGIN_USER_INFO_ATTRIBUTES = %i[
email
phone
]

ONELOGIN_USER_INFO_ATTRIBUTES.each do |attribute_name|
attribute attribute_name
end
end

attribute :logged_in_with_onelogin, :boolean, default: false
attribute :identity_confirmed_with_onelogin, :boolean, default: false
attribute :onelogin_user_info_attributes
attribute :first_name
attribute :surname

def onelogin_user_info_attributes=(attributes)
onelogin_user_info.assign_attributes(
journey_session.answers.onelogin_user_info
)
end

def onelogin_user_info
@onelogin_user_info ||= OneloginUserInfoForm.new
end

def save
journey_session.answers.assign_attributes(
onelogin_user_info: onelogin_user_info.attributes,
first_name: first_name,
surname: surname
)
journey_session.save!
end

private

def permitted_attributes
super + [
onelogin_user_info_attributes: OneloginUserInfoForm::ONELOGIN_USER_INFO_ATTRIBUTES
]
end
end
1 change: 1 addition & 0 deletions app/models/journeys/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ module Journeys
module Base
SHARED_FORMS = {
"claims" => {
"sign-in" => SignInForm,
"sign-in-or-continue" => SignInOrContinueForm,
"current-school" => CurrentSchoolForm,
"gender" => GenderForm,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ class SlugSequence
]

PERSONAL_DETAILS_SLUGS = %w[
one-login-placeholder
sign-in
information-provided
personal-details
postcode-search
Expand Down
2 changes: 2 additions & 0 deletions app/models/journeys/session_answers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,10 @@ class SessionAnswers
attribute :hmrc_bank_validation_succeeded, :boolean
attribute :hmrc_bank_validation_responses, default: []
attribute :logged_in_with_tid, :boolean
attribute :logged_in_with_onelogin, :boolean
attribute :details_check, :boolean
attribute :teacher_id_user_info, default: {}
attribute :onelogin_user_info, default: {}
attribute :email_address_check, :boolean
attribute :mobile_check, :string
attribute :qualifications_details_check, :boolean
Expand Down
49 changes: 49 additions & 0 deletions app/views/claims/_sign_in.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<h1 class="govuk-heading-l">
<% if @form.logged_in_with_onelogin %>
You've successfully signed in to GOV.UK One Login
<% elsif @form.identity_confirmed_with_onelogin %>
You've successfully proved your identity with GOV.UK One Login
<% else %>
You're now going to GOV.UK One Login
<% end %>
</h1>

<% if @form.logged_in_with_onelogin %>
<p>
Before you can continue your application, you'll need to prove your identity through GOV.UK One Login.
</p>
<p>
When you've proved your identity through GOV.UK One Login, you'll return to this service to complete your applciation.
</p>
<% elsif @form.identity_confirmed_with_onelogin %>
<p>
You can now continue your application
</p>
<% else %>
<p>
To continue your application, you'll need to create a GOV.UK One Login or sign in.
</p>
<p>
When you've signed in through GOV.UK One Login, your progress will be saved
and you'll be able to return to complete your application.
</p>
<% end %>

<div class="govuk-button-group">
<% if @form.logged_in_with_onelogin %>
<%= button_to "Continue", "/auth/onelogin_identity", class: "govuk-button", method: :post %>
<% elsif @form.identity_confirmed_with_onelogin %>
<%= form_for @form, url: claim_path(current_journey_routing_name) do |f| %>
<%= f.fields_for :onelogin_user_info do |ff| %>
<% ff.object.attribute_names.each do |attribute| %>
<%= ff.hidden_field attribute %>
<% end %>
<% end %>
<%= f.hidden_field :first_name %>
<%= f.hidden_field :surname %>
<%= f.submit "Continue", class: "govuk-button", data: {module: "govuk-button"} %>
<% end %>
<% else %>
<%= button_to "Continue", "/auth/onelogin", class: "govuk-button", method: :post %>
<% end %>
</div>
16 changes: 16 additions & 0 deletions app/views/claims/sign_in.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<% if @form.errors.any? %>
<div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds">
<%= render(
"shared/error_summary",
instance: @form,
errored_field_id_overrides: { details_check: "claim_details_check_true" }) %>
</div>
</div>
<% end %>

<div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds govuk-body">
<%= render partial: "sign_in" %>
</div>
</div>

This file was deleted.

57 changes: 57 additions & 0 deletions config/initializers/omniauth.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,26 @@
end
end

onelogin_sign_in_issuer_uri = ENV["ONELOGIN_SIGN_IN_ISSUER"].present? ? URI(ENV["ONELOGIN_SIGN_IN_ISSUER"]) : nil
if ENV["ONELOGIN_REDIRECT_BASE_URL"].present?
onelogin_sign_in_redirect_uri = URI.join(ENV["ONELOGIN_REDIRECT_BASE_URL"], "/auth/onelogin")
end
if ENV["ONELOGIN_SIGN_IN_SECRET_BASE64"].present?
onelogin_sign_in_secret_key = OpenSSL::PKey::RSA.new(Base64.decode64(ENV["ONELOGIN_SIGN_IN_SECRET_BASE64"] + "\n"))
end

module ::DfESignIn
def self.bypass?
(Rails.env.development? || ENV["ENVIRONMENT_NAME"].start_with?("review")) && ENV["BYPASS_DFE_SIGN_IN"] == "true"
end
end

module ::OneLoginSignIn
def self.bypass?
(!Rails.env.production? || ENV["ENVIRONMENT_NAME"].start_with?("review")) && ENV["BYPASS_ONELOGIN_SIGN_IN"] == "true"
end
end

Rails.application.config.middleware.use OmniAuth::Builder do
if DfESignIn.bypass?
provider :developer
Expand Down Expand Up @@ -73,4 +87,47 @@ def self.bypass?
scope: ["email", "openid", "profile", "dqt:read"],
send_scope_to_token_endpoint: false
}

if OneLoginSignIn.bypass?
provider :developer
else
provider :openid_connect, {
name: :onelogin,
callback_path: "/auth/onelogin",
client_auth_method: "jwt_bearer",
client_options: {
host: onelogin_sign_in_issuer_uri&.host,
identifier: ENV["ONELOGIN_SIGN_IN_CLIENT_ID"],
port: onelogin_sign_in_issuer_uri&.port,
redirect_uri: onelogin_sign_in_redirect_uri&.to_s,
scheme: onelogin_sign_in_issuer_uri&.scheme,
secret: onelogin_sign_in_secret_key
},
discovery: true,
issuer: ENV["ONELOGIN_SIGN_IN_ISSUER"],
response_type: :code,
scope: %i[openid email phone],
send_scope_to_token_endpoint: false
}

provider :openid_connect, {
name: :onelogin_identity,
callback_path: "/auth/onelogin_identity",
client_options: {
host: onelogin_sign_in_issuer_uri&.host,
identifier: ENV["ONELOGIN_SIGN_IN_CLIENT_ID"],
port: onelogin_sign_in_issuer_uri&.port,
redirect_uri: onelogin_sign_in_redirect_uri&.to_s,
scheme: onelogin_sign_in_issuer_uri&.scheme
},
discovery: true,
extra_authorize_params: {
vtr: '["Cl.Cm.P2"]',
claims: {userinfo: {"https://vocab.account.gov.uk/v1/coreIdentityJWT": nil}}.to_json
},
issuer: ENV["ONELOGIN_SIGN_IN_ISSUER"],
response_type: :code,
send_scope_to_token_endpoint: false
}
end
end
5 changes: 5 additions & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@

get "/claim/auth/tid/callback", to: "omniauth_callbacks#callback"
get "/auth/failure", to: "omniauth_callbacks#failure"
get "/auth/onelogin", to: "omniauth_callbacks#onelogin"
if OneLoginSignIn.bypass?
post "/auth/onelogin", to: "omniauth_callbacks#onelogin"
post "/auth/onelogin_identity", to: "omniauth_callbacks#onelogin"
end

# /early-career-payments is now /additional-payments - redirect old urls to a gov page
get "early-career-payments(/*anything)", to: redirect("https://www.gov.uk/government/collections/additional-payments-for-teaching-eligibility-and-payment-details")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
FactoryBot.define do
factory :further_education_payments_answers, class: "Journeys::FurtherEducationPayments::SessionAnswers" do
trait :with_details_from_onelogin do
first_name { "Jo" }
surname { "Bloggs" }
onelogin_user_info { {email: "[email protected]"} }
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,7 @@
expect(page).to have_content("Apply now")
click_button "Apply now"

expect(page).to have_content("FE ONE LOGIN PLACEHOLDER")
click_button "Continue"
sign_in_with_one_login

expect(page).to have_content("How we will use the information you provide in your application")
click_button "Continue"
Expand Down
3 changes: 1 addition & 2 deletions spec/features/further_education_payments/happy_path_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,7 @@
expect(page).to have_content("Apply now")
click_button "Apply now"

expect(page).to have_content("FE ONE LOGIN PLACEHOLDER")
click_button "Continue"
sign_in_with_one_login

expect(page).to have_content("How we will use the information you provide in your application")
click_button "Continue"
Expand Down
Loading

0 comments on commit 4666fee

Please sign in to comment.