Skip to content

Commit

Permalink
Merge branch 'main' into ER-892-assessment-result-banner-html-css-update
Browse files Browse the repository at this point in the history
  • Loading branch information
martikat authored Jan 5, 2024
2 parents cb89e6a + 399f3fa commit a0c87d4
Show file tree
Hide file tree
Showing 50 changed files with 1,113 additions and 64 deletions.
1 change: 1 addition & 0 deletions .yardopts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
--markup markdown
-
cms/*.md
gov_one_login/*.md
data/*.md
uml/*.md
adr/*.md
Expand Down
3 changes: 3 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ gem 'bootsnap', require: false

# User authentication
gem 'devise'
gem 'jwt'
gem 'omniauth_openid_connect'
gem 'omniauth-rails_csrf_protection'

# HTML abstraction markup language
gem 'slim-rails'
Expand Down
61 changes: 61 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -68,18 +68,21 @@ GEM
tzinfo (~> 2.0)
addressable (2.8.5)
public_suffix (>= 2.0.2, < 6.0)
aes_key_wrap (1.1.0)
ahoy_matey (5.0.2)
activesupport (>= 6.1)
device_detector (>= 1)
safely_block (>= 0.4)
ast (2.4.2)
attr_required (1.0.1)
backports (3.24.1)
base64 (0.1.1)
bcrypt (3.1.20)
better_errors (2.10.1)
erubi (>= 1.0.0)
rack (>= 0.9.0)
rouge (>= 1.0.0)
bindata (2.4.15)
bindex (0.8.1)
binding_of_caller (1.0.0)
debug_inspector (>= 0.0.1)
Expand Down Expand Up @@ -185,6 +188,8 @@ GEM
base64
faraday-net_http (>= 2.0, < 3.1)
ruby2_keywords (>= 0.0.4)
faraday-follow_redirects (0.3.0)
faraday (>= 1, < 3)
faraday-net_http (3.0.2)
ffi (1.16.3)
ffi-compiler (1.0.1)
Expand Down Expand Up @@ -277,6 +282,12 @@ GEM
jsbundling-rails (1.2.1)
railties (>= 6.0.0)
json (2.6.3)
json-jwt (1.16.3)
activesupport (>= 4.2)
aes_key_wrap
bindata
faraday (~> 2.0)
faraday-follow_redirects
jwt (2.7.1)
language_server-protocol (3.17.0.3)
launchy (2.5.2)
Expand Down Expand Up @@ -320,6 +331,29 @@ GEM
racc (~> 1.4)
notifications-ruby-client (5.4.0)
jwt (>= 1.5, < 3)
omniauth (2.1.1)
hashie (>= 3.4.6)
rack (>= 2.2.3)
rack-protection
omniauth-rails_csrf_protection (1.0.1)
actionpack (>= 4.2)
omniauth (~> 2.0)
omniauth_openid_connect (0.7.1)
omniauth (>= 1.9, < 3)
openid_connect (~> 2.2)
openid_connect (2.2.0)
activemodel
attr_required (>= 1.0.0)
faraday (~> 2.0)
faraday-follow_redirects
json-jwt (>= 1.16)
net-smtp
rack-oauth2 (~> 2.2)
swd (~> 2.0)
tzinfo
validate_email
validate_url
webfinger (~> 2.0)
orm_adapter (0.5.0)
os (1.1.4)
pagy (6.2.0)
Expand Down Expand Up @@ -354,6 +388,15 @@ GEM
raabro (1.4.0)
racc (1.7.3)
rack (2.2.8)
rack-oauth2 (2.2.0)
activesupport
attr_required
faraday (~> 2.0)
faraday-follow_redirects
json-jwt (>= 1.11.0)
rack (>= 2.1.0)
rack-protection (3.1.0)
rack (~> 2.2, >= 2.2.4)
rack-test (2.1.0)
rack (>= 1.3)
rails (7.0.8)
Expand Down Expand Up @@ -513,6 +556,11 @@ GEM
stimulus-rails (1.3.0)
railties (>= 6.0.0)
stringio (3.0.8)
swd (2.0.2)
activesupport (>= 3)
attr_required (>= 0.0.5)
faraday (~> 2.0)
faraday-follow_redirects
temple (0.10.3)
thor (1.3.0)
tilt (2.3.0)
Expand All @@ -529,6 +577,12 @@ GEM
unf_ext
unf_ext (0.0.8.2)
unicode-display_width (2.5.0)
validate_email (0.1.6)
activemodel (>= 3.0)
mail (>= 2.2.5)
validate_url (1.0.15)
activemodel (>= 3.0.0)
public_suffix
view_component (3.6.0)
activesupport (>= 5.2.0, < 8.0)
concurrent-ruby (~> 1.0)
Expand All @@ -540,6 +594,10 @@ GEM
activemodel (>= 6.0.0)
bindex (>= 0.4.0)
railties (>= 6.0.0)
webfinger (2.1.2)
activesupport
faraday (~> 2.0)
faraday-follow_redirects
webrick (1.8.1)
websocket (1.2.10)
websocket-driver (0.7.6)
Expand Down Expand Up @@ -598,7 +656,10 @@ DEPENDENCIES
importmap-rails
jbuilder
jsbundling-rails
jwt
launchy
omniauth-rails_csrf_protection
omniauth_openid_connect
pg
pry-byebug
pry-doc
Expand Down
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,11 @@ The status of GovUK notify can be checked here: <https://status.notifications.se
For more information the Notify team can be contacted here: <https://www.notifications.service.gov.uk/support>,
or in the UK Government digital slack workspace in the `#govuk-notify` channel.

---

# One Login

<https://signin.integration.account.gov.uk/sign-in-or-create>

---

Expand Down
19 changes: 19 additions & 0 deletions adr/0017-gov-one-login.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# GOV.UK One Login

* Status: accepted

## Context and Problem Statement
The integration of GOV.UK One Login user authentication is a requirement of the service going live. This single sign on will allow users to login to the service using their GOV.UK One Login account.

## Decision Drivers
* GOV.UK One Login reccomends using an off-the-shelf OIDC library
* We currently use Devise for user authentication
* Omniauth would allow us to use Devise and integrate with GOV.UK One Login

## Considered Options
* [omniauth](https://github.com/omniauth/omniauth)
* [omniauth_openid_connect](https://github.com/omniauth/omniauth_openid_connect)

## Decision Outcome
Chosen option: [omniauth_openid_connect](https://github.com/omniauth/omniauth_openid_connect)

1 change: 1 addition & 0 deletions adr/ADR.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ This log lists the architectural decisions for EYFS Recovery
* [ADR-0014](0014-user-tracking.md) - Use Hotjar for tracking user journeys
* [ADR-0015](0015-background-jobs.md) - Use Que for processing background jobs
* [ADR-0016](0016-devise-security-and-pwned-passwords-gems.md) - Use Devise Security and Devise Pwned Password gems
* [ADR-0017](0017-gov-one-login.md) - GOV.UK One Login

<!-- adrlogstop -->

Expand Down
4 changes: 4 additions & 0 deletions app/assets/stylesheets/application.scss
Original file line number Diff line number Diff line change
Expand Up @@ -201,3 +201,7 @@ ul>li>ul>li {
#available h2 .govuk-tag {
margin-left: govuk-spacing(1);
}

.text-secondary {
color: $govuk-secondary-text-colour;
}
6 changes: 5 additions & 1 deletion app/controllers/application_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,11 @@ def authenticate_registered_user!
authenticate_user! unless user_signed_in?
return true if current_user.registration_complete?

redirect_to edit_registration_name_path, notice: 'Please complete registration'
if Rails.application.gov_one_login?
redirect_to edit_registration_terms_and_conditions_path, notice: 'Please complete registration'
else
redirect_to edit_registration_name_path, notice: 'Please complete registration'
end
end

def configure_permitted_parameters
Expand Down
7 changes: 7 additions & 0 deletions app/controllers/gov_one_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
class GovOneController < ApplicationController
layout 'hero'

def show
redirect_to my_modules_path if current_user
end
end
35 changes: 35 additions & 0 deletions app/controllers/registration/terms_and_conditions_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
module Registration
class TermsAndConditionsController < BaseController
def edit; end

def update
form.terms_and_conditions_agreed_at = user_params[:terms_and_conditions_agreed_at]

if form.save
if current_user.registration_complete?
redirect_to user_path, notice: t(:details_updated)
else
redirect_to edit_registration_name_path
end
else
render :edit, status: :unprocessable_entity
end
end

private

# @return [Hash]
def user_params
params.require(:user).permit(:terms_and_conditions_agreed_at)
end

# @return [Registration::NameForm]
def form
@form ||=
TermsAndConditionsForm.new(
user: current_user,
terms_and_conditions_agreed_at: current_user.terms_and_conditions_agreed_at,
)
end
end
end
104 changes: 104 additions & 0 deletions app/controllers/users/omniauth_callbacks_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
# Controller handling OmniAuth callbacks for user authentication.
# This controller uses the GovOneAuthService to retrieve user informaton and create or sign in an user based on the email address or gov one id

class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
# This method is called by Devise after successful Gov One Login authentication
# @return [nil]
def openid_connect
if params['error'].present?
Rails.logger.error("Authentication error: #{params['error']}, #{params['error_description']}")
return error_redirect
end

return error_redirect unless session_params? && valid_params?

auth_service = GovOneAuthService.new(code: params['code'])
tokens_response = auth_service.tokens
return error_redirect unless valid_tokens?(tokens_response)

id_token = auth_service.decode_id_token(tokens_response['id_token'])[0]
return error_redirect unless valid_id_token?(id_token)

session[:id_token] = tokens_response['id_token']
gov_one_id = id_token['sub']

user_info_response = auth_service.user_info(tokens_response['access_token'])
email = user_info_response['email']
return error_redirect unless valid_user_info?(user_info_response, gov_one_id)

gov_user = User.find_or_create_from_gov_one(email: email, gov_one_id: gov_one_id)

delete_session_params
sign_in_and_redirect gov_user if gov_user
end

private

# @return [Boolean]
def valid_params?
params['code'].present? && params['state'].present? && params['state'] == session[:gov_one_auth_state]
end

# @return [Boolean]
def session_params?
session[:gov_one_auth_state].present? && session[:gov_one_auth_nonce].present?
end

# @param tokens_response [Hash]
# @return [Boolean]
def valid_tokens?(tokens_response)
tokens_response.present? &&
tokens_response['access_token'].present? &&
tokens_response['id_token'].present? &&
tokens_response['error'].blank?
end

# @param id_token [Hash]
# @return [Boolean]
def valid_id_token?(id_token)
id_token.present? &&
id_token['nonce'] == session[:gov_one_auth_nonce] &&
id_token['iss'] == "#{Rails.application.config.gov_one_base_uri}/" &&
id_token['aud'] == Rails.application.config.gov_one_client_id
end

# @param user_info_response [Hash]
# @return [Boolean]
def valid_user_info?(user_info_response, gov_one_id)
user_info_response.present? &&
user_info_response['email'].present? &&
user_info_response['sub'] == gov_one_id &&
user_info_response['error'].blank?
end

# @return [nil]
def error_redirect
flash[:alert] = 'There was a problem signing in. Please try again.'
redirect_to root_path
end

# @return [nil]
def delete_session_params
session.delete(:gov_one_auth_state)
session.delete(:gov_one_auth_nonce)
end

# @return [String]
def after_sign_in_path_for(resource)
if resource.registration_complete?
if resource.display_whats_new?
resource.display_whats_new = false
resource.save!
static_path('whats-new')
elsif !resource.email_preferences_complete?
static_path('email-preferences')
else
my_modules_path
end
elsif resource.private_beta_registration_complete?
static_path('new-registration')
else
edit_registration_terms_and_conditions_path
end
end
end
2 changes: 2 additions & 0 deletions app/controllers/users/sessions_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ def after_sign_in_path_for(resource)
end
elsif resource.private_beta_registration_complete?
static_path('new-registration')
elsif Rails.application.gov_one_login?
edit_registration_terms_and_conditions_path
else
edit_registration_name_path
end
Expand Down
14 changes: 14 additions & 0 deletions app/forms/registration/terms_and_conditions_form.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
module Registration
class TermsAndConditionsForm < BaseForm
attr_accessor :terms_and_conditions_agreed_at

validates :terms_and_conditions_agreed_at, presence: true

# @return [Boolean]
def save
return false unless valid?

user.update!(terms_and_conditions_agreed_at: terms_and_conditions_agreed_at)
end
end
end
Loading

0 comments on commit a0c87d4

Please sign in to comment.