diff --git a/.yardopts b/.yardopts index 72d6af40f..6f31f194c 100644 --- a/.yardopts +++ b/.yardopts @@ -5,6 +5,7 @@ --markup markdown - cms/*.md +gov_one_login/*.md data/*.md uml/*.md adr/*.md diff --git a/Gemfile b/Gemfile index 53b80abc2..812cbe441 100644 --- a/Gemfile +++ b/Gemfile @@ -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' diff --git a/Gemfile.lock b/Gemfile.lock index d2dec3200..669f8994c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -68,11 +68,13 @@ 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) @@ -80,6 +82,7 @@ GEM 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) @@ -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) @@ -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) @@ -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) @@ -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) @@ -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) @@ -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) @@ -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) @@ -598,7 +656,10 @@ DEPENDENCIES importmap-rails jbuilder jsbundling-rails + jwt launchy + omniauth-rails_csrf_protection + omniauth_openid_connect pg pry-byebug pry-doc diff --git a/README.md b/README.md index b22169e93..c2d92d426 100644 --- a/README.md +++ b/README.md @@ -313,6 +313,11 @@ The status of GovUK notify can be checked here: , or in the UK Government digital slack workspace in the `#govuk-notify` channel. +--- + +# One Login + + --- diff --git a/adr/0017-gov-one-login.md b/adr/0017-gov-one-login.md new file mode 100644 index 000000000..951b07c06 --- /dev/null +++ b/adr/0017-gov-one-login.md @@ -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) + diff --git a/adr/ADR.md b/adr/ADR.md index fd3badbf9..f36b4cf60 100644 --- a/adr/ADR.md +++ b/adr/ADR.md @@ -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 diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss index 2bbae2dab..8f0b23c15 100644 --- a/app/assets/stylesheets/application.scss +++ b/app/assets/stylesheets/application.scss @@ -201,3 +201,7 @@ ul>li>ul>li { #available h2 .govuk-tag { margin-left: govuk-spacing(1); } + +.text-secondary { + color: $govuk-secondary-text-colour; +} diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index b7ca4f15e..3dd694581 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -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 diff --git a/app/controllers/gov_one_controller.rb b/app/controllers/gov_one_controller.rb new file mode 100644 index 000000000..25faf07d0 --- /dev/null +++ b/app/controllers/gov_one_controller.rb @@ -0,0 +1,7 @@ +class GovOneController < ApplicationController + layout 'hero' + + def show + redirect_to my_modules_path if current_user + end +end diff --git a/app/controllers/registration/terms_and_conditions_controller.rb b/app/controllers/registration/terms_and_conditions_controller.rb new file mode 100644 index 000000000..79166cabf --- /dev/null +++ b/app/controllers/registration/terms_and_conditions_controller.rb @@ -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 diff --git a/app/controllers/users/omniauth_callbacks_controller.rb b/app/controllers/users/omniauth_callbacks_controller.rb new file mode 100644 index 000000000..e74c107c4 --- /dev/null +++ b/app/controllers/users/omniauth_callbacks_controller.rb @@ -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 diff --git a/app/controllers/users/sessions_controller.rb b/app/controllers/users/sessions_controller.rb index d70bff7ee..aeb2584a5 100644 --- a/app/controllers/users/sessions_controller.rb +++ b/app/controllers/users/sessions_controller.rb @@ -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 diff --git a/app/forms/registration/terms_and_conditions_form.rb b/app/forms/registration/terms_and_conditions_form.rb new file mode 100644 index 000000000..e0e0c3a7a --- /dev/null +++ b/app/forms/registration/terms_and_conditions_form.rb @@ -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 diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index d7b80bbf6..a892f5b4a 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -11,14 +11,14 @@ def navigation header.with_navigation_item(text: 'Home', href: root_path, classes: %w[dfe-header__navigation-item]) if user_signed_in? header.with_action_link(text: 'My Account', href: user_path, options: { inverse: true }) - header.with_action_link(text: 'Sign out', href: destroy_user_session_path, options: { id: 'sign-out-desktop', data: { turbo_method: :get }, inverse: true }) + header.with_action_link(text: 'Sign out', href: logout_path, options: { id: 'sign-out-desktop', data: { turbo_method: :get }, inverse: true }) header.with_navigation_item(text: 'My modules', href: my_modules_path, classes: %w[dfe-header__navigation-item]) header.with_navigation_item(text: 'Learning log', href: user_notes_path, classes: %w[dfe-header__navigation-items]) if current_user.course_started? header.with_navigation_item(text: 'My account', href: user_path, classes: %w[dfe-header__navigation-item dfe-header-f-mob]) - header.with_navigation_item(text: 'Sign out', href: destroy_user_session_path, options: { data: { turbo_method: :get } }, classes: %w[dfe-header__navigation-item dfe-header-f-mob], html_attributes: { id: 'sign-out-f-mob' }) + header.with_navigation_item(text: 'Sign out', href: logout_path, options: { data: { turbo_method: :get } }, classes: %w[dfe-header__navigation-item dfe-header-f-mob], html_attributes: { id: 'sign-out-f-mob' }) else - header.with_action_link(text: 'Sign in', href: new_user_session_path, options: { inverse: true }) - header.with_navigation_item(text: 'Sign in', href: new_user_session_path, classes: %w[dfe-header__navigation-item dfe-header-f-mob]) + header.with_action_link(text: 'Sign in', href: login_path, options: { inverse: true }) + header.with_navigation_item(text: 'Sign in', href: login_path, classes: %w[dfe-header__navigation-item dfe-header-f-mob]) end end end @@ -59,4 +59,14 @@ def html_title(*parts) def calculate_module_state CalculateModuleState.new(user: current_user).call end + + # @return [String] + def login_path + Rails.application.gov_one_login? ? gov_one_info_path : new_user_session_path + end + + # @return [String] + def logout_path + Rails.application.gov_one_login? ? logout_uri.to_s : destroy_user_session_path + end end diff --git a/app/helpers/gov_one_helper.rb b/app/helpers/gov_one_helper.rb new file mode 100644 index 000000000..23cd93f05 --- /dev/null +++ b/app/helpers/gov_one_helper.rb @@ -0,0 +1,45 @@ +module GovOneHelper + # @return [URI] + def login_uri + params = { + redirect_uri: GovOneAuthService::CALLBACKS[:login], + client_id: Rails.application.config.gov_one_client_id, + response_type: 'code', + scope: 'email openid', + nonce: SecureRandom.alphanumeric(25), + state: SecureRandom.uuid, + } + + session[:gov_one_auth_state] = params[:state] + session[:gov_one_auth_nonce] = params[:nonce] + + gov_one_uri(:login, params) + end + + # @return [URI] + def logout_uri + params = { + post_logout_redirect_uri: GovOneAuthService::CALLBACKS[:logout], + id_token_hint: session[:id_token], + state: SecureRandom.uuid, + } + + gov_one_uri(:logout, params) + end + + # @return [String] + def login_button + govuk_button_link_to t('gov_one_info.button.sign_in'), login_uri.to_s + end + +private + + # @param endpoint [Symbol] + # @param params [Hash] + # @return [URI::HTTP, URI::HTTPS] + def gov_one_uri(endpoint, params) + uri = URI.parse(GovOneAuthService::ENDPOINTS[endpoint]) + uri.query = URI.encode_www_form(params) + uri + end +end diff --git a/app/models/data_analysis/local_authority_user.rb b/app/models/data_analysis/local_authority_user.rb index 72595ef2d..28a19035d 100644 --- a/app/models/data_analysis/local_authority_user.rb +++ b/app/models/data_analysis/local_authority_user.rb @@ -30,7 +30,7 @@ def authorities # @return [User::ActiveRecord_Relation] def public_beta_users - User.since_public_beta.with_local_authority + User.since_public_beta.with_local_authority.registration_complete end end end diff --git a/app/models/user.rb b/app/models/user.rb index f38fc30e3..4416344e8 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -21,12 +21,28 @@ def self.dashboard_headers DASHBOARD_ATTRS + Training::Module.live.map { |mod| "module_#{mod.position}_time" } end + # @param email [String] + # @param gov_one_id [String] + # @return [User] + def self.find_or_create_from_gov_one(email:, gov_one_id:) + if (user = find_by(email: email) || find_by(gov_one_id: gov_one_id)) + user.update_column(:email, email) + user.update_column(:gov_one_id, gov_one_id) if user.gov_one_id.nil? + user.save! + else + user = new(email: email, gov_one_id: gov_one_id, confirmed_at: Time.zone.now) + user.save!(validate: false) + end + user + end + # Include default devise modules. Others available are: # :timeoutable, :trackable, :recoverable and :omniauthable attr_accessor :context devise :database_authenticatable, :registerable, :recoverable, - :validatable, :rememberable, :confirmable, :lockable, :timeoutable, :secure_validatable + :validatable, :rememberable, :confirmable, :lockable, :timeoutable, + :secure_validatable, :omniauthable, omniauth_providers: [:openid_connect] devise :pwned_password unless Rails.env.test? has_many :responses diff --git a/app/services/gov_one_auth_service.rb b/app/services/gov_one_auth_service.rb new file mode 100644 index 000000000..0c36dab39 --- /dev/null +++ b/app/services/gov_one_auth_service.rb @@ -0,0 +1,121 @@ +# +# - exchange an authorisation code for tokens (access and id) +# - exchange an access token for user info +# - decode an id token to get the user's gov one id +# +# @see https://docs.sign-in.service.gov.uk/ +class GovOneAuthService + # @return [Hash{Symbol => String}] + CALLBACKS = { + login: "#{Rails.application.config.service_url}/users/auth/openid_connect/callback", + logout: "#{Rails.application.config.service_url}/users/sign_out", + }.freeze + + # @return [Hash{Symbol => String}] + ENDPOINTS = { + login: "#{Rails.application.config.gov_one_base_uri}/authorize", + logout: "#{Rails.application.config.gov_one_base_uri}/logout", + token: "#{Rails.application.config.gov_one_base_uri}/token", + userinfo: "#{Rails.application.config.gov_one_base_uri}/userinfo", + jwks: "#{Rails.application.config.gov_one_base_uri}/.well-known/jwks.json", + }.freeze + + extend Dry::Initializer + + option :code, Types::Strict::String + + # POST /token + # @return [Hash] + def tokens + uri, http = build_http(ENDPOINTS[:token]) + token_request = Net::HTTP::Post.new(uri.path, { 'Content-Type' => 'application/x-www-form-urlencoded' }) + token_request.set_form_data(token_body) + token_response = response(token_request, http) + + JSON.parse(token_response.body) + rescue StandardError => e + Rails.logger.error "GovOneAuthService.tokens: #{e.message}" + {} + end + + # GET /userinfo + # @param access_token [String] + # @return [Hash] + def user_info(access_token) + uri, http = build_http(ENDPOINTS[:userinfo]) + userinfo_request = Net::HTTP::Get.new(uri.path, { 'Authorization' => "Bearer #{access_token}" }) + userinfo_response = response(userinfo_request, http) + + JSON.parse(userinfo_response.body) + rescue StandardError => e + Rails.logger.error "GovOneAuthService.user_info: #{e.message}" + {} + end + + # @param request [Net::HTTP::Get, Net::HTTP::Post] + # @param http [Net::HTTP] + # @return [Net::HTTPResponse] + def response(request, http) + http.request(request) + end + + # @param token [String] + # @return [Array] + def decode_id_token(token) + kid = JWT.decode(token, nil, false).last['kid'] + key_params = jwks['keys'].find { |key| key['kid'] == kid } + jwk = JWT::JWK.new(key_params) + + JWT.decode(token, jwk.public_key, true, { verify_iat: true, algorithm: 'ES256' }) + end + + # @param address [String] + # @return [Array] + def build_http(address) + uri = URI.parse(address) + http = Net::HTTP.new(uri.host, uri.port) + http.use_ssl = true + [uri, http] + end + +private + + # GET /.well-known/jwks.json + # @return [Hash] + def jwks + Rails.cache.fetch('jwks', expires_in: 24.hours) do + uri, http = build_http(ENDPOINTS[:jwks]) + response = http.request(Net::HTTP::Get.new(uri.path)) + JSON.parse(response.body) + end + end + + # @return [String] + def jwt_assertion + rsa_private = OpenSSL::PKey::RSA.new(Rails.application.config.gov_one_private_key) + JWT.encode jwt_payload, rsa_private, 'RS256' + end + + # @return [Hash] + def jwt_payload + { + aud: ENDPOINTS[:token], + iss: Rails.application.config.gov_one_client_id, + sub: Rails.application.config.gov_one_client_id, + exp: Time.zone.now.to_i + 5 * 60, + jti: SecureRandom.uuid, + iat: Time.zone.now.to_i, + } + end + + # @return [Hash] + def token_body + { + grant_type: 'authorization_code', + code: code, + redirect_uri: CALLBACKS[:login], + client_assertion_type: 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer', + client_assertion: jwt_assertion, + } + end +end diff --git a/app/views/gov_one/show.html.slim b/app/views/gov_one/show.html.slim new file mode 100644 index 000000000..f6e82ab15 --- /dev/null +++ b/app/views/gov_one/show.html.slim @@ -0,0 +1,28 @@ +- content_for :page_title do + = html_title t('home.title') + +- content_for :hero do + .govuk-grid-row class='govuk-!-padding-top-9' + .govuk-grid-column-three-quarters + h1.dfe-heading-xl class='govuk-!-margin-bottom-4' + = t('gov_one_info.hero.header') + p.govuk-body-l = t('gov_one_info.hero.body') + + +.govuk-grid-row + .govuk-grid-column-full + . class='govuk-!-margin-bottom-5' + = m('gov_one_info.body') + hr + + = login_button + +.govuk-grid-row + .govuk-grid-column-three-quarters class='govuk-!-margin-top-4 govuk-!-margin-bottom-9' + details.govuk-details data-module='govuk-details' + summary.govuk-details__summary + span.govuk-details__summary-text + = t('gov_one_info.details_summary') + .govuk-details__text + = t('gov_one_info.details_text') + \ No newline at end of file diff --git a/app/views/home/_chevron.html.slim b/app/views/home/_chevron.html.slim new file mode 100644 index 000000000..8546e136b --- /dev/null +++ b/app/views/home/_chevron.html.slim @@ -0,0 +1,2 @@ +svg.govuk-button__start-icon xmlns='http://www.w3.org/2000/svg' width='17.5' height='19' viewBox='0 0 33 40' aria-hidden='true' focusable='false' + path fill='currentColor' d='M0 0h13l20 20-20 20H0l20-20z' \ No newline at end of file diff --git a/app/views/home/_hero.html.slim b/app/views/home/_hero.html.slim index 6a6870e3b..c4c919757 100644 --- a/app/views/home/_hero.html.slim +++ b/app/views/home/_hero.html.slim @@ -7,15 +7,22 @@ p.govuk-body-l = t('home.hero') - = govuk_button_link_to course_overview_path, class: 'govuk-button--start govuk-!-margin-bottom-4' do - - if current_user - | Learn more - - else - | Learn more and enrol - p.govuk-visually-hidden on the course + - if Rails.application.gov_one_login? + = link_to course_overview_path, class: 'govuk-!-margin-bottom-4 govuk-link--no-visited-state govuk-!-font-weight-bold govuk-body' do + - if current_user + | Learn more + - else + | Learn more about this training + p.govuk-visually-hidden on the course + - else + = govuk_button_link_to course_overview_path, class: 'govuk-button--start govuk-!-margin-bottom-4' do + - if current_user + | Learn more + - else + | Learn more and enrol + p.govuk-visually-hidden on the course - svg.govuk-button__start-icon xmlns='http://www.w3.org/2000/svg' width='17.5' height='19' viewBox='0 0 33 40' aria-hidden='true' focusable='false' - path fill='currentColor' d='M0 0h13l20 20-20 20H0l20-20z' + = render 'chevron' .govuk-grid-column-one-third class='govuk-!-text-align-right' = m('home.thumb') diff --git a/app/views/home/index.html.slim b/app/views/home/index.html.slim index 30c3efc9f..e8ebba061 100644 --- a/app/views/home/index.html.slim +++ b/app/views/home/index.html.slim @@ -1,26 +1,60 @@ -- content_for :page_title do - = html_title t('home.title') +- if Rails.application.gov_one_login? + - content_for :page_title do + = html_title t('home.title') -- content_for :hero do - = render 'hero' + - content_for :hero do + = render 'hero' -= render 'learning/cms_debug' -= render 'debug' + = render 'learning/cms_debug' + = render 'debug' -.govuk-grid-row - .govuk-grid-column-one-half - = m('home.about', headings_start_with: 'xl') + .govuk-grid-row + .govuk-grid-column-one-half + = m('home.about', headings_start_with: 'xl') - .govuk-grid-column-one-half - - unless current_user - .light-grey-box.enrol-box - = m('home.login', headings_start_with: 'xl') - = render 'enrol_buttons' + .prompt.prompt-home + .govuk-grid-row + .govuk-grid-column-one-quarter + i.fa-2x.fa-solid.fa-circle-info aria-describedby='info icon' + + .govuk-grid-column-three-quarters + = m('home.prompt', headings_start_with: 'xl') + + - unless current_user + .govuk-grid-row class="govuk-!-margin-top-9" + .govuk-grid-column-full + = govuk_button_link_to gov_one_info_path, class: "govuk-button--start" do + | #{t('home.gov_one_button')} + = render 'chevron' + +- else + - content_for :page_title do + = html_title t('home.title') + + - content_for :hero do + = render 'hero' + + = render 'learning/cms_debug' + = render 'debug' -.prompt.prompt-home .govuk-grid-row - .govuk-grid-column-one-quarter - i.fa-2x.fa-solid.fa-circle-info aria-describedby='info icon' + .govuk-grid-column-one-half + = m('home.about', headings_start_with: 'xl') + + - unless current_user + .govuk-grid-column-one-half + .light-grey-box.enrol-box + = m('home.login', headings_start_with: 'xl') + + .govuk-button-group + = govuk_button_link_to 'Sign in', new_user_session_path + .white-space-pre-wrap= ' or ' + = govuk_link_to 'create an account', new_user_registration_path + + .prompt.prompt-home + .govuk-grid-row + .govuk-grid-column-one-quarter + i.fa-2x.fa-solid.fa-circle-info aria-describedby='info icon' - .govuk-grid-column-three-quarters - = m('home.prompt', headings_start_with: 'xl') + .govuk-grid-column-three-quarters + = m('home.prompt', headings_start_with: 'xl') diff --git a/app/views/registration/terms_and_conditions/edit.html.slim b/app/views/registration/terms_and_conditions/edit.html.slim new file mode 100644 index 000000000..4d0e4f1a3 --- /dev/null +++ b/app/views/registration/terms_and_conditions/edit.html.slim @@ -0,0 +1,22 @@ += render 'user/debug' + +- content_for :page_title do + = html_title 'Terms and Conditions' + +.govuk-grid-row + .govuk-grid-column-two-thirds-from-desktop + = form_for form, url: registration_terms_and_conditions_path, method: :patch do |f| + = f.govuk_error_summary + + h1.govuk-heading-l = t('register_terms_and_conditions.heading') + + h3 = t('register_terms_and_conditions.subheading') + + = f.govuk_check_boxes_fieldset :terms_and_conditions_agreed_at, + legend: { class: 'govuk-visually-hidden', text: 'Terms and conditions'}, classes: 'light-grey-box' do + = m('register_terms_and_conditions.legend') + = f.terms_and_conditions_check_box + + + .govuk-button-group + = f.govuk_submit t('links.continue') diff --git a/app/views/user/show.html.slim b/app/views/user/show.html.slim index 1cf9f3e03..a30d69052 100644 --- a/app/views/user/show.html.slim +++ b/app/views/user/show.html.slim @@ -7,21 +7,28 @@ .govuk-grid-column-full h1.govuk-heading-l Manage your account h2.govuk-heading-m Your details - hr.govuk-section-break.govuk-section-break--l.govuk-section-break--visible + hr.govuk-section-break.govuk-section-break--l.govuk-section-break--visible class="govuk-!-margin-bottom-0" = govuk_summary_list do |your_details| - your_details.with_row do |row| - row.with_key { 'Name' } - row.with_value(text: current_user.name, classes: %w[data-hj-suppress]) - row.with_action(text: 'Change name', href: edit_registration_name_path, html_attributes: { id: :edit_name_registration }) - - your_details.with_row do |row| - - row.with_key { 'Email' } - - row.with_value(text:current_user.email, classes: %w[data-hj-suppress]) - - row.with_action(text: 'Change email', href: edit_email_user_path, html_attributes: { id: :edit_email_user }) - - your_details.with_row do |row| - - row.with_key { 'Password' } - - row.with_value { t('my_account.password_changed', date: current_user.password_last_changed) } - - row.with_action(text: 'Change password', href: edit_password_user_path, html_attributes: { id: :edit_password_user }) + + - unless Rails.application.gov_one_login? + - your_details.with_row do |row| + - row.with_key { 'Email' } + - row.with_value(text: current_user.email, classes: %w[data-hj-suppress]) + - row.with_action(text: 'Change email', href: edit_email_user_path, html_attributes: { id: :edit_email_user }) + - your_details.with_row do |row| + - row.with_key { 'Password' } + - row.with_value { t('my_account.password_changed', date: current_user.password_last_changed) } + - row.with_action(text: 'Change password', href: edit_password_user_path, html_attributes: { id: :edit_password_user }) + + - if Rails.application.gov_one_login? + p.text-secondary + = t('my_account.name_information') + = govuk_summary_list do |other_details| - other_details.with_row do |row| diff --git a/config/application.rb b/config/application.rb index c23062f67..f156a5e3d 100644 --- a/config/application.rb +++ b/config/application.rb @@ -30,6 +30,8 @@ class Application < Rails::Application # config.time_zone = ENV.fetch('TZ', 'Europe/London') config.service_name = 'Early years child development training' + config.service_url = (Rails.env.production? ? 'https://' : 'http://') + ENV.fetch('DOMAIN', 'child-development-training') + config.internal_mailbox = ENV.fetch('INTERNAL_MAILBOX', 'child-development.training@education.gov.uk') config.middleware.use Grover::Middleware config.active_record.yaml_column_permitted_classes = [Symbol] @@ -68,6 +70,11 @@ class Application < Rails::Application config.contentful_management_access_token = ENV.fetch('CONTENTFUL_MANAGEMENT_TOKEN', credentials.dig(:contentful, :management_access_token)) # TODO: use service account management token config.contentful_environment = ENV.fetch('CONTENTFUL_ENVIRONMENT', credentials.dig(:contentful, :environment)) + # Gov one + config.gov_one_base_uri = credentials.dig(:gov_one, :base_uri) + config.gov_one_private_key = credentials.dig(:gov_one, :private_key) + config.gov_one_client_id = credentials.dig(:gov_one, :client_id) + # @return [Boolean] def live? ENV['WORKSPACE'].eql?('production') || ENV['ENVIRONMENT'].eql?('production') @@ -96,6 +103,11 @@ def maintenance? Types::Params::Bool[ENV.fetch('MAINTENANCE', false)] end + # @return [Boolean] + def gov_one_login? + Types::Params::Bool[ENV.fetch('GOV_ONE_LOGIN', false)] + end + # @return [ActiveSupport::TimeWithZone] def public_beta_launch_date Time.zone.local(2023, 2, 9, 15, 0, 0) diff --git a/config/credentials/development.yml.enc b/config/credentials/development.yml.enc index 4fdfcc7f6..292c4ec32 100644 --- a/config/credentials/development.yml.enc +++ b/config/credentials/development.yml.enc @@ -1 +1 @@ -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--U4dwbZHHF88MfY4i--tryRPvAAHYaJFB+11sqGZQ== \ No newline at end of file +j+3O+4ERf1VNiloi4+9dK9dHO/vyf5lNlcCWuVzENYk5pq/4xhwYj167VBVIG7DvoAov5hs3elzjZmwTcKRummhn30rKgbBG//OFKMwuICheogcpHCoEH+wpLNHs6iVVT+ZIiUoTRuepnHMK3dPSXvj7dT64v8HPLmYZ+7bqfyOMulLUIoWq2TB7HPWwJGmBqdCtzuByD0BUHoftKdvM/4C78GHC2lwg+/nandwKH/hdNYYhUqchPIt1TWcrhrWMWSAXqJrwgDZtKj0o1P6Iddm/PgT8XPvBfJW6+XnmsOR/fKZw5xs0Ktzs9pOp9Sw/zW4/SdD6USofW8LZKJ2uFcZBDx+3LNHV6g5YlbQ/TBA+SFOcVGZZraSaa1QZKXmcuwAfXLPS1avUWDHL/HkzyuM6jXJdpTec9P9c2th63kp8jdq8AIpCPWKAX0sbOhfd4mCxMgltHiuY1/CGNs3WahpvvoCcooRw91bW01msC8wsBk50/dkxqktVm9GNofOpAf/oeLmcw136aFmUvGKQlCt2Z8usxVaqPKih3KkzzF4+fLWYdeOmXRaasALZIwR9QkBeWaL2FxCNWs7821o7t2k/PoWcV+mNwwQk3DZ8q6yQUlfd1bAANfVaAzBdI9q43Kgy9f6ymPfzmxzoHFpxHr1Eawyf3Tw92RvUWzsulNtLiE9UmxQ31gbI28LyY1HrZEEEabTgAYUs29KRSoGtn8jBRiEkdHIwmuH9I7Sn4ipe58LHqNlbYTxkDDWcJhTrVZxjJLQIGxG5W205UgqCxb4dXrW5uxy9h1xMnSpHsikg0SddPIMK6qf1No92syesHcjl+/ShVSeiJDVbplI3y1A75YBE55onsNAHFlYYb4lyNrdxUaJnzkDup2t2givuXlkuHTKW2URGAjXBy+maMxUBM6t8KwSsnhRj3VCuZVFY8rSmEQSNOEiVi9G0wEQLMy+F1Z5b3AJiLPqp5G5xJfD6dIXyoVib9AqSqsekLgq9INpsunUlNrB0S8xCuavyjsvy36H0bJkR1C+N5N/UvpNHYLkmhRuobALc0zB2TyPF46Eh9Utvgj/40SqvEynUxWTbsAUwAoUljjkCaoNC6K2Q0lYHyq8dBeKI24hwrZBElp6VmWDhgnvZbdMCas3O3I4nXGbe0HKfchH06WpGqqmcbzx/HOGse24c4lo0osXQOm7sqPh7QVgSpyDDt6mN+F0rlr7yltGJhSgHcjLBQB6Z4CpIv7xW02D0Aqf0ejBsLrNTeXto0b82kBMFkehy9uf7QN14xchCnTdoVEp0phPT42LFTZhXorArdPGfkLTkKEaS9COPWWaCRYdroOAPLZ6IuoXyhMdcEC3taRJ1euPDAjgJauSoQRtxrqTDf+aLWUH9+Ws37FF6IvPV81JnnKqucnmv2xpW40PLMSkR2ZNq7qRELrDsg6LqnBM21E0J9ypCtCdD864IB9bQDyknbPVnRGgdlQwMHvQLdCaynqZZcrwyG+0b/dAc2fTRZgKHn6p6rGiGFT/Q0dvpyZZpTnz8EPISVKNrIsbJ93UOcjPKOj6za4DWPbyPFedAyp1T6r7zPAAatGf7Ay2RbKEwv5Je+5Iez6FMoi1YKkGh1ZHNNMPrMyJumbtMT62EqsXhKfExf6Y51lcNPz6E0bxj0lxVUUJqIT57zF4ZbZNQ5QLkHUwz1ZahiPevUL+ukFBBgVnj4xoYsVb6HexlXz4umP76B7XZzVKuJDN7o7ODZBS2S3AkVFiA+FuFuLN3cneQhG4kNcAjlInNSG198y/chstPutAzXzsAcf8CtQY6QefEgyzCW/96elO7WDq41PV8r5LAMOB6RfSiXNa5WF13k/xOERh9YDjhm/9SoYdgv7BaCnyo7xNGOnLMeNvrionIzInTJbo7HIhiLQzr6GHCJRMXEtktwGjQg8B2DTeXCTsXUNd11JibW0+FEJYREhZgQ/sfUTZQRbpDXSIp4Z+PAVFWpY3ZMxEl04KxcOiDuAkJmewPPfgyNC5CQb7i7Vq3krHd73x7hf5hPfNR9DiVf9dfVQDFG/zBaH5QJT0fDKngo00SS0d9TyFi97gP9RiENtD78V6YynRRidq8RnjEu1ANv2oyNzypOMauX9u3QOk/2IVlK+zua9dea7NSDHpuclyySdDTBdcWZhuSV9DIJDRNHGgEwgoY7lamFOXUV7BY5u9M54LgAwOzotsPhSASiuEbuO57iu4/oV4Mxv9q1yKDgwrlA87UVQHMzgARg9r/6zoA7rdMXFf2plD1RW+F0BAQ6DWCKGuIxnXjPXUkzbWXSG8Jll8OVQo5IN7hkEhcRk1/2FeYKyvVZ4yMCcaz/Nx15YgpgkBjQGgw+2poA4gBNVkys4bGc61oHmNAPWB5Dxu3vEc7/8ion4o39cS5d7WipK5fGyiyyaCeEdTalT2e7VANw63HTP+flK2j2YKAZnleyGkYodji9m3kkZZ9sbc+GUxCtcsbUGRtan+UKoR+jsy8fObjlGvdPGuE/TU1qaDWTZpA+4f4cvQ0nxHeT4uxR8M37JvDm87tuhLtdEpisrSpp7wKvulBBqwd6fn6GBiXe54QTiDfcq7gx0LDmSJdQezzxUbNPUYKqoK53jM/jvVf8QqpE4eVP3gdca7pW+Wwiz2lmeq2Q6lHchuCPyZc2Sn/IBSfX2H+jFO5odfvPAUyrc1qcB1/VwjBQIOZrzcrbtHWXVBFLTF9HWRTflkiyl1z33zoEpvmKcKw/on3BKm6VoqNbF4DnRAU4cbIuUVZyvpMB+sF8wzcoiD8XLKiapjP47lXrzzlOwcxeGLFJOxuh92THcVnC2voxU8wZAzpWXUJIFS0nBXuEK2Vduh6HT3OWiwxia2+a62GPFQfepIsGOj8S6+viD9KzUdqyPRW9H6u4e7vLCqBup6Kzw4rQT+8Z6PxiiKwteCjc2gZ48OmoWTfkmFYmq2StJmpGSAfseHEiwFtIVk2QK4r/8xuCwhxxrJseytSAbnNOne7s8bSY4CRvZtfoHXRftaNZ7Bl8v+JCHb4cOerpkgKvw5jr4E5VXjXY02sOzugmpIaoZ4gYOQCnsO8RN2YgMsw7+DkleqPu0Hr0a2jJslpxE4ZXsexfyoCdu8vgtTl708HMBTTgTBcSe4MaPw5WxOdgEJITcppgBfEzQDhwHIDFpa0Vizhu7naUx1/6xk0tRPQrmwrnxrBb3zlV6HBemhJoeqRiG3/rxpLjiwTEDAsrvwsQ41rHW3hdgFtdLwci1leYAjU8HnOG+6XlRN8UYibMXJI1NNX4FH9BFilDeETAeowViaVOXKfMnOuXdM9XqtWSnLcG4JTvytzCwtpSnuXOAyca8wBkwFBpxlZ0p83x2qr3RhgIm5owYPTeuVmS1Yg67pIoncRVo9W2u1zWBLNlKbUEi9bnKOeGNhJQrCdFcJM0vWMqeYEyl8aqfyUax+O4MkPZZcCTwBGujR6cF5iv0vfDcm3GSHQrnx/C2uEzqc6UNGY1PcknFqfneWiOgJ/h934G+kis+NGTdM+6EG+WIGFvJo36dRdQ5DAq6L84sSbofmeLzGkebYsiO1OQiz3h3uwIgmVLrLZr8zNBi94pkBAkDUjtGMV19eG1uPPrZ2ocWOWx9iEdqGnBN5k4xv+qOmvESngpk6TKpoik1CrAcgtC6ur9Zy0D9vE9sDQq8BDuMXclSIVLnf+dWUomngmUkfWhTu+1acC8xfoDStSRuMw89cuXpAdhsr1eQdOdylMx4IwG7oayhtEP/vKYqQOJ+ufclpxZkwOyQlLWuxN2FGhtkaktR/Osqi623HXypGXH5NrSlALooy/7G/v3in9agqKx2Y3VDBUe7k3uJOnMyavc4E8n2INtzHgPDe5gK1KHJBgTE5tnlHuwBWecDI+57nH/GKNPWmq1G6RaRWgCgcFGNQ2M5jC0uI61eiXucFPtKB7XpeqJeU5fZssuUcG7u1t0TJ7nC9ibmuzQme1mVcwtPvfDow/h45T+psY3XnOwphnemOxX2IbjViNRk5gF6/OynjAxKgnGOjvQjH+WBjl6nIRGMzoPCl/06P6VWbMdx/CaOkn/y6yk3mvM/neZQZLIfrLWLzFFxT4ku9ELtlSHah0O9ba1q3lWX+wthrBwpD6eA54wpcPJBCXzD++HrcBYqZMVcaqWAqM+r0Nr0FFO71cY+k0uxopytZFUJYJB3Uhp+s6NrnnQiFufIuc2Smqsnem9XPZb6hSasO8m0XTw7LDFMep9CMUopzCpwGdYgDfRSqk/dfX4TqtWJ6BFh/cXmphq61v7VxIMGK/VfBEzVSngdih1e9MVp+4g0u00WnQ5rjjIrjsMWQ660DBBNeR72BjsFt8jpu25PtyxrE23uok4TeP9Fb7DNjfKq/O3yzU9mQWmI4sMl/I4IZam0aNf6M6alsKvBxRKGc/IYcvYt8UTh9K6bGz5FouOWKtdoL0vxbIRt4T8QkJRXFr4c2f7DnUiHhHNsWKcA9rhdiBysxPIZevLw/lgfcpnR/LU5n6XTRD/bhKltWXR9SRmmnkysMiaPxDailHd7gD4EvUqEl+XO2Oz2JpJvkGB5yFfi4+n7dVqfP7Vz7Jc1cGlYkpi4fxw0a2MX08qGQ2hEWOMnbHEqoFLLTxJM7at0NBtaeuL0BfXVbOMM7PvsJ/t5YMOZHfuPuvRZLyNAdcgd1j3cMrEO+vpsVIjjqUvXblq5/cFnXnuezxi2EnOLCd2x/uQY/ehnitIWEsLYHr3wbbbMZHcM5oBXehp/T1DY+n/Pdt8VK/ZLhP9RmEh1m3Pt1cZF8+4ggG9aLxPxpmz032wRele3RmyYN+m3Eb40u9bv8++ovRe+nipXnELm9I9w9ht6qlO/uorGwHSzdczvb9woUCwOfMevl9d6BBUlEYSiIOIVz37tVHLzrcBnhEmNh6MLlA6R7QCac3/8x2CQcr06KTqnFkupLTDihGFD/5+VtH4UJU33FwX84ZdLdq3gqii50WrH5km5zcCoC1GM+W2tdLyxMOcx37FjPZhiJg0XmZWdurJX09R1ZBdupqD2YsAiJMMUOYWs0QmlmnyJWhqyFM77XhBEGED/MJA9+9wH46UXPFEJVj6WR7LX3i0q7s6/LuiHWxNSZF1SrHmwAr2Bd6CdBDA0AF9dJpjGEICqxRt9jZSFfd8uFEc/D/u2QKaaco5SGDdUFv/nue0nCngAKMhzDhhDuesk3Ec5S56cKlA9NoEVKeHJQF6JMKQoeltqJu2xi+4t3NLGgoHE48hasy3Ezq0kBGEKlSEU3EZwnnbj33st0Sj6gyxcTSFH7N5ltc0xoieOaem4i31KVZhtGoYn1tKmM93AjmLUBxXxooUjeHYO7zBQWUxmD2v5T5roer8HX5dVCLLPc20z0UMF1K6FKC5fPl1Vv97Xe688JpY9H//Z+DVETU/qeWRx44L4ZI1rpixfRaeRs4lSjyKhXjdn1d4Y3cqOIEtpS0oD6utS9nqsatOoeJhpZTyg2sCreSCLSVDUgVkD1I45OT04t/zNBCVLw47rrBK31tdhRgHSlTCOheV0Y7eeynryTi+jxuzWURWI3AiCPpVd1GNfEoEgP3d+AnLbYIcYbha4jwI6eU+y6bEsEAcRnuzvWWDChE9WL+bZHlNa1TKmweX0eyUgrj5XFb4jkySsJWsZmzTfBZwiS4a1An4f0LEhMBmhYt+SyD4WWRuXdk3QBPTSSHuppyzlfE1o46HwOen3RlKx6Gpjpri6rlW1QExIVM7ABOgsII/YQuXsKbzCNZ6ieDZBpIhOjEkdK+bwDz9cMU0SI+9vagknh0VWp9F5W4neYq1iQWKDHBtRx/vS63c+hdMqiJ9JWIV4rJBSCnYnP+Sxryozd2fmH+wtLxZKErn5YxQfBpwy3dZ0auMh2b8b3ZhcUqXk26lhWbXTTIp+2m4ioppVEOg26O3xsjM6uT77hvB4V67jcDJRS65dPh9L/cZyyKeqFrVAyrAG44vscOZo/geHFonCJVKWEvN9yyal4Wvx27sbFbUcjTrJzIbNVaxPziu/TdaDRtFRATd4vCHBIsHoMdOsWWbhmP8Y2PlpH8NNOeE3d9tXp3g4+90dHljYW3EQkJzjZn4Et1b/LEoIZMN1lKSBSCM5R2URTXFvHZFBPgTd2vOlfDmKyKDdXW+/s/0WaQCHgEuTMF8I7T+oDLX5GxHCYdhPd67ygjZ3niePrp+xlYfa0t7OyGoMkNyaFBDXhYTuCFhvAoVD9Xe44iPjs8t6v/w9EMpSmnZ2f4AmI9oXgiXenfLJpgxtqUprvrt7JcRWGt74rRxCTyJZMJSwdY9CWmMeKl22lauYC8iWr0uOAskgyXdZuC8hlRqne2N6ftb7dxa4Rkoy0ZOvMlm+OqVZPtWHpm/SlypyWQhk/vJ/ZnI6oyWQRDKJuWjW+prCLx0AyIffb79WS9jSQew8fke5gYA0Duv/u5o6P/ZlxULC9IL+DNPtIu7+hEp4mszKhbnSBrPNeYktMj3E+hCgBkTntGR7e1r4pl0MrWoJWXq4hjzatWicz8y8rxDzeJTzcUSYLa677aLlkoxlhXQoVZxvLhDc7uetywRdO8kn/XMRq0Unenj7JIN8FZtipKi+H1NAIQRw8KJfjQcIfo4XXDVj88bd9RrPmY9Y2PsDKc7/xEMOs99ZIjkTZPkSB6SXt4gx1sxZxb1noxi/EQAbcLrM0XWrvlJmvqtD3VZeDzPci/vf1Jc0c4TZvqwhwLabulAf1sN5gCKf5z9twDJfntjQf1uLnUNUBhsymDqK6HC4lZTGgssktGH677cDeYasaIf/s642lrpbLHOocwBQhpgjvT/Txj5Qmd2aXZt4COMJ/PmkzKvoo3vnycur8M04VpCfBG0N18jp6az6wVDiAYE35kUTOMhXXsWJoBtDFSBR7HaGhy2vJqJ7FoNRpDwnEQwlUeLw/RzKWKoEBWUpF/pQUYb5PHQ9Nd79+mabyhO6jiZQKpEYWw4jEa1t+shAhLEn3G5y1SYziH7DyuTXuzZ4vS3xtvVSEqY85KEazYpfM4nx7Xdl2fvC7yAo5BbmLQtINer8uAB6fc0glbqp/dbjPhaJQbN9m4REuDr0+A4+BvF/hm1F0oSBdu2ziluNLg4aqHUoxTVmu1eaU6l6VXJ7o7rtGhl98F/deBWi3VCIuXKjYvSyfI0OgT9eAUZmRMtt4HEzpxYpn2zXY+vrh//aF834HdzI1pOqAnGizuwd08zm3ZGvVEiIyzkFmzuXOsfMi68cqp6kKxjnWnKUD+WqzIDGde80PPcQ31BWCBvS+6ge/gR6fXUycQkQ4B38LE5uwnmLWeVBLo7WsPuqU2pBitaoORa49hkkNdQaWTka/qLpDjsZjsd4urqKYutv3nMoE3RG2kFCHuqJw1K0PeF6DbjvNT3Cvatm40BZrLOnyc3/J2mBX1QXahbghthdC8sRbmXJyT9X3hW9VCyNoSl0NTFhU/2P7bI49X0Le5NLpGMf2t4wC5RQdsqA11mlhI8lRIs4GdN9LZVYTrJ0XdtMf2SaVUjR+Ija00h6rkjN8swBWq33RCVG274pyAQNV01r7DcsisWBmVwAiEmgYi+3a1Q5CKlblmBapbmaOoSn58v/qgGU/Wpy1KoKbFbzaM9+IvJc/bbYMClL09RCNBp8ajF3QzOOfmQ281bgemE1fXSZgPGuwF6XCYpWtNMUoRGLaFQjAdAWP9pXDlyGM/aqpEeJeW+MRXchrIFivMWnDtEhu2WDtRfSeb9MMrH2KP4d0xI2p2tEOPcoW5f9MYvshbyUt+S0pwaTIi82bTdSOuuOZDUFhH/iVcmudV94rtlZkEQJcZEw/I6KP7DRs1ee6veVdKb4+/OGEaIGdICbivlHPCzgQsAjddxz6K/gPvxGdXlwGNOM0T3zH3l1I7hv25+kqIWfH3AHDl4lxKgtNHTsRvgwVfPzeVyUfGrQFG0k2zFDe4HKRAx7kG/FyxKBiLWRLWX9Nstm5ccVGFOWcftOVVmfPA6A/GL3YWTGIhLRvHzjkIwzglkoeXFwWHDNL82B9qAzRef9GQR/14RSWI+zKY1SflwA==--QhoOqS2u5TN6yGex--D+M0HrxGhh3aOh6fIxi9SQ== \ No newline at end of file diff --git a/config/credentials/production.yml.enc b/config/credentials/production.yml.enc index dbc4d4155..0bf5120f8 100644 --- a/config/credentials/production.yml.enc +++ b/config/credentials/production.yml.enc @@ -1 +1 @@ -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--xd1sesNNvCLgfxxi--Uw8M7itth9vCF+0j3D+j0w== \ No newline at end of file +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--zJGkDqYx7hBVWmDr--IjW3HIJrI8vrbQ7r4gR4Mg== \ No newline at end of file diff --git a/config/credentials/test.yml.enc b/config/credentials/test.yml.enc index 5ee9a3078..d7eaeabb8 100644 --- a/config/credentials/test.yml.enc +++ b/config/credentials/test.yml.enc @@ -1 +1 @@ -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--524FMqbKrVmJ2sZ3--G4/cI/16dU0QC5Cp94Fn7g== \ No newline at end of file +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--Qdgcpq2DkjafVGqU--C3fQKZmL1SwRwBJpGQ3IZg== \ No newline at end of file diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb index f7ca631b4..6ff37f9ac 100644 --- a/config/initializers/devise.rb +++ b/config/initializers/devise.rb @@ -306,9 +306,21 @@ def i18n_message(default = nil) config.sign_out_via = :get # ==> OmniAuth - # Add a new OmniAuth provider. Check the wiki for more information on setting - # up on your models and hooks. - # config.omniauth :github, 'APP_ID', 'APP_SECRET', scope: 'user,public_repo' + config.omniauth :openid_connect, { + name: :gov_one, + scope: %i[openid email], + response_type: :code, + client_options: { + port: 443, + scheme: 'https', + host: Rails.application.config.gov_one_base_uri, + identifier: Rails.application.config.gov_one_client_id, + redirect_uri: 'users/auth/openid_connect/callback', + }, + authorize_params: { + prompt: 'login', + }, + } # ==> Warden configuration # If you want to use other strategies, that are not supported by Devise, or diff --git a/config/locales/en.yml b/config/locales/en.yml index f8c3fbe87..8e37c2149 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -299,6 +299,7 @@ en: # /my-account my_account: title: My account + name_information: This is the name that will appear on you end of module certificate. You can use this setting to change how your name appears. Changing your name on this account will not affect your GOV.UK One Login setting_details: | ## Your setting details email_preferences: | @@ -452,6 +453,13 @@ en: complete_registration: Thank you for creating an Early years child development training account. You can now start your first module. update_registration: Thank you for updating your Early years child development training account. You can now continue. + # /registration/terms-and-conditions/edit + register_terms_and_conditions: + heading: Set up your training account + subheading: Agree to our terms and conditions + legend: | + To use this service, you must accept the [terms and conditions](/terms-and-conditions) and [privacy policy](/privacy-policy). + # /registration/name/edit register_name: heading: About you @@ -535,6 +543,7 @@ en: ## Return to your training Sign in to continue learning, see your progress and download certificates. + gov_one_button: Start your training now # /about-training about: @@ -574,6 +583,18 @@ en: %{criteria} + # /gov-one/info + gov_one_info: + title: Gov One Info + hero: + header: How to access this training course + body: This service uses GOV.UK One Login which is managed by the Government Digital Service. + body: You will be asked to sign in to your account, or create a One Login account, in this service + button: + sign_in: Continue to GOV.UK One Login + details_summary: How to access an existing training account + details_text: If you have an existing early years child development training account but you do not yet have a GOV.UK One Login, you must use the same email address for both accounts. This will ensure that any progress you have made through the training is retained. + # /settings/cookie-policy cookie_policy: title: Cookie policy diff --git a/config/routes.rb b/config/routes.rb index 6c8742816..8f79e608a 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -3,6 +3,7 @@ get 'health', to: 'home#show' get 'audit', to: 'home#audit' get 'my-modules', to: 'learning#show' # @see User#course + get 'gov-one/info', to: 'gov_one#show' get '404', to: 'errors#not_found', via: :all get '422', to: 'errors#unprocessable_entity', via: :all @@ -16,6 +17,7 @@ confirmations: 'confirmations', passwords: 'passwords', registrations: 'registrations', + omniauth_callbacks: 'users/omniauth_callbacks', }, path_names: { sign_in: 'sign-in', @@ -29,9 +31,11 @@ get 'check_session_timeout', to: 'timeout#check' get 'extend_session', to: 'timeout#extend' get 'users/timeout', to: 'timeout#timeout_user' + get '/users/sign_out', to: 'users/sessions#destroy' end namespace :registration do + resource :terms_and_conditions, only: %i[edit update], path: 'terms-and-conditions' resource :name, only: %i[edit update] resource :setting_type, only: %i[edit update], path: 'setting-type' resource :setting_type_other, only: %i[edit update], path: 'setting-type-other' @@ -50,6 +54,7 @@ get 'check-email-confirmation' get 'check-email-password-reset' get 'edit-training-emails' + devise_for :users, controllers: { omniauth_callbacks: 'controllers/users/omniauth_callbacks' } patch 'update-training-emails' resource :close_account, only: %i[new update show], path: 'close' do diff --git a/config/sitemap.rb b/config/sitemap.rb index ebfff785f..397a244bb 100644 --- a/config/sitemap.rb +++ b/config/sitemap.rb @@ -14,8 +14,7 @@ # } # ] # -protocol = Rails.env.production? ? 'https://' : 'http://' -SitemapGenerator::Sitemap.default_host = protocol + ENV['DOMAIN'] +SitemapGenerator::Sitemap.default_host = Rails.application.config.service_url SitemapGenerator::Sitemap.compress = false # Run this command to update /public/sitemap.xml @@ -63,9 +62,13 @@ # account add user_path + # GOV.UK one login + add gov_one_info_path + # edit registration/account add edit_email_user_path add edit_password_user_path + add edit_registration_terms_and_conditions_path add edit_registration_name_path add edit_registration_setting_type_path add edit_registration_setting_type_other_path diff --git a/db/migrate/20231031094611_add_id_token_to_users.rb b/db/migrate/20231031094611_add_id_token_to_users.rb new file mode 100644 index 000000000..1423064b7 --- /dev/null +++ b/db/migrate/20231031094611_add_id_token_to_users.rb @@ -0,0 +1,6 @@ +class AddIdTokenToUsers < ActiveRecord::Migration[7.0] + def change + add_column :users, :id_token, :string + add_index :users, :id_token, unique: true + end +end diff --git a/db/migrate/20231031132907_rename_id_token_to_gov_one_id_in_users.rb b/db/migrate/20231031132907_rename_id_token_to_gov_one_id_in_users.rb new file mode 100644 index 000000000..433bedd3c --- /dev/null +++ b/db/migrate/20231031132907_rename_id_token_to_gov_one_id_in_users.rb @@ -0,0 +1,5 @@ +class RenameIdTokenToGovOneIdInUsers < ActiveRecord::Migration[7.0] + def change + rename_column :users, :id_token, :gov_one_id + end +end diff --git a/db/schema.rb b/db/schema.rb index bc371754d..166d883ee 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.0].define(version: 2023_08_23_163600) do +ActiveRecord::Schema[7.0].define(version: 2023_10_31_132907) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -229,8 +229,10 @@ t.string "closed_reason_custom" t.boolean "training_emails" t.boolean "early_years_emails" + t.string "gov_one_id" t.index ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true t.index ["email"], name: "index_users_on_email", unique: true + t.index ["gov_one_id"], name: "index_users_on_gov_one_id", unique: true t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true t.index ["unlock_token"], name: "index_users_on_unlock_token" end diff --git a/gov_one_login/GOV-ONE-LOGIN.md b/gov_one_login/GOV-ONE-LOGIN.md new file mode 100644 index 000000000..8d8a1e2c7 --- /dev/null +++ b/gov_one_login/GOV-ONE-LOGIN.md @@ -0,0 +1,54 @@ +# GOV.UK One Login + +## Integration Environment +- __Base URI__: https://oidc.integration.account.gov.uk +- __Redirect URLs__: + - http://localhost:3000/users/auth/openid_connect/callback +- __Post-logout redirect URLs__: + - http://localhost:3000/users/sign_out + + +## Technical Checklist + +### Authentication request requirements +| Requirement | Response | +| --- | --- | +| __Describe how you’re using the state parameter to prevent CSRF attacks__ | The state is generated as a random uuid, which is then stored in the Rails session storage. The state parameter for authorisation request responses must match the state stored in the session before the user is authenticated. +| __Describe how you’re generating the nonce parameter__ | The nonce is a randomly generated alphanumeric string of 25 characters, it is used to verify the `id_token`. | +| __Describe how you handle authorise endpoint errors__ | The errors are logged and the user is redirected to the homepage with an alert informing them of a problem | +| __Describe how you’re handling access_denied errors where session state is also missing__ | The user is redirected to the homepage with an alert informing them of a problem and encouraging them to try again | + +### Token request requirements +| Requirement | Response | +| --- | --- | +| __Describe how you ensure that your client secret / private key is not exposed to unauthorised parties__ | These are encrypted and stored in Rails credentials | +| __For the private_key_jwt confirm that each jti claim value in the JWT assertion is used once.__ | ✓ | + +### Token validation requirements +| Requirement | Response | +| --- | --- | +| __Confirm you validate the iss claim is https://oidc.account.gov.uk/__ | ✓ | +| __Confirm you validate the aud claim matches your client_id__ | ✓ | +| __Confirm you validate the nonce claim matches the your application generated__ | ✓ | +| __Confirm you validate the current time is before the time in the exp claim__ | ✓ | +| __Confirm you validate the current time is between the time in the auth_time claim and the exp claim__ | ✓ | +| __Confirm you validate the signature on the id-token__ | ✓ | +| __Describe how you handle token endpoint errors__ | The error is logged and the user is redirected to the homepage with an alert informing them of a problem | +| __Describe how you ensure that the GOV.UK One Login Access Token is not exposed to unauthorised parties outside of your trusted backend server__ | The access token is not exposed to the user and is only used to make requests to the UserInfo endpoint during the user session. Communication with GOV.UK One Login is over HTTPS. | + +### UserInfo request requirements +| Requirement | Response | +| --- | --- | +| __Confirm you validate the sub claim in the UserInfo response matches the id-token sub claim__ | ✓ | +| __Describe how you handle UserInfo endpoint errors__ | The error is logged and the user is redirected to the homepage with an alert informing them of a problem | +| __If you’re using the email address scope, confirm that you’re aware this represents the GOV.UK One Login username and may not be the user’s preferred contact email address__ | ✓ | + +### Key management requirements +| Requirement | Response | +| --- | --- | +| __If using the GOV.UK One Login OpenID Provider JWKS Endpoint for signature validation describe your approach to key rotation__ | The keys are cached and the cache expires every 24 hours | + +### Session management requirements +| Requirement | Response | +| --- | --- | +| __Confirm that you’ve implemented logout functionality and that your service calls the GOV.UK One Login logout endpoint__ | ✓ | diff --git a/spec/controllers/users/omniauth_callbacks_controller_spec.rb b/spec/controllers/users/omniauth_callbacks_controller_spec.rb new file mode 100644 index 000000000..47eb35f58 --- /dev/null +++ b/spec/controllers/users/omniauth_callbacks_controller_spec.rb @@ -0,0 +1,80 @@ +require 'rails_helper' + +RSpec.describe Users::OmniauthCallbacksController, type: :controller do + let(:auth_service) { instance_double(GovOneAuthService) } + let(:access_token) { 'mock_access_token' } + let(:id_token) { 'mock_id_token' } + let(:decoded_id_token) { { 'sub' => 'mock_sub', 'nonce' => 'mock_nonce', 'iss' => "#{Rails.application.config.gov_one_base_uri}/", 'aud' => Rails.application.config.gov_one_client_id } } + let(:email) { 'test@example.com' } + let(:params) do + { 'code' => 'mock_code', 'state' => 'mock_state' } + end + + before do + request.env['devise.mapping'] = Devise.mappings[:user] + session[:gov_one_auth_state] = 'mock_state' + session[:gov_one_auth_nonce] = 'mock_nonce' + + allow(GovOneAuthService).to receive(:new).and_return(auth_service) + allow(auth_service).to receive(:tokens).and_return({ 'access_token' => access_token, 'id_token' => id_token }) + allow(auth_service).to receive(:user_info).and_return({ 'email' => email, 'sub' => 'mock_sub' }) + allow(auth_service).to receive(:jwt_assertion).and_return('mock_jwt_assertion') + allow(auth_service).to receive(:decode_id_token).and_return([decoded_id_token]) + end + + context 'with a new user' do + before do + get :openid_connect, params: params + end + + it 'creates an account' do + expect(User.find_by(email: email)).to be_truthy + expect(User.find_by(gov_one_id: 'mock_sub')).to be_truthy + end + + it 'redirects to complete registration' do + expect(session[:id_token]).to eq id_token + expect(response).to redirect_to edit_registration_terms_and_conditions_path + end + end + + context 'with an existing non-gov-one user' do + before do + create :user, :registered, email: email + get :openid_connect, params: params + end + + it 'updates the account' do + expect(User.find_by(gov_one_id: 'mock_sub')).to be_truthy + end + + it 'redirects to /my-modules' do + expect(session[:id_token]).to eq id_token + expect(response).to redirect_to my_modules_path + end + end + + context 'with an existing gov-one user' do + before do + create :user, :registered, gov_one_id: 'mock_sub' + get :openid_connect, params: params + end + + it 'redirects to /my-modules' do + expect(session[:id_token]).to eq id_token + expect(response).to redirect_to my_modules_path + end + end + + context 'with invalid session parameters' do + before do + session[:gov_one_auth_state] = nil + get :openid_connect, params: params + end + + it 'redirects to root path with an error message' do + expect(flash[:alert]).to eq 'There was a problem signing in. Please try again.' + expect(response).to redirect_to root_path + end + end +end diff --git a/spec/helpers/gov_one_helper_spec.rb b/spec/helpers/gov_one_helper_spec.rb new file mode 100644 index 000000000..c3e333e24 --- /dev/null +++ b/spec/helpers/gov_one_helper_spec.rb @@ -0,0 +1,39 @@ +require 'rails_helper' + +describe 'GovOneHelper', type: :helper do + describe '#login_uri' do + subject(:login_uri) { helper.login_uri } + + it 'encodes the authorize endpoint params' do + expect(login_uri.host).to eq 'oidc.test.account.gov.uk' + expect(login_uri.path).to eq '/authorize' + expect(login_uri.query).to include 'redirect_uri=http%3A%2F%2Frecovery.app%2Fusers%2Fauth%2Fopenid_connect%2Fcallback' + expect(login_uri.query).to include 'client_id=some_client_id' + expect(login_uri.query).to include 'response_type=code' + expect(login_uri.query).to include 'scope=email+openid' + expect(login_uri.query).to include 'nonce=' + end + end + + describe '#logout_uri' do + subject(:logout_uri) { helper.logout_uri } + + it 'encodes the logout endpoint params' do + expect(logout_uri.host).to eq 'oidc.test.account.gov.uk' + expect(logout_uri.path).to eq '/logout' + expect(logout_uri.query).to include 'post_logout_redirect_uri=http%3A%2F%2Frecovery.app%2Fusers%2Fsign_out' + expect(logout_uri.query).to include 'id_token_hint' + expect(logout_uri.query).to include 'state=' + end + end + + describe '#login_button' do + subject(:login_button) { helper.login_button } + + it 'returns a button link to the gov one login uri' do + expect(login_button).to include 'govuk-button' + expect(login_button).to include 'Continue to GOV.UK One Login' + expect(login_button).to include 'href="https://oidc.test.account.gov.uk/authorize?redirect_uri=http%3A%2F%2Frecovery.app%2Fusers%2Fauth%2Fopenid_connect%2Fcallback&client_id=some_client_id&response_type=code&scope=email+openid&nonce=' + end + end +end diff --git a/spec/lib/seed_snippets_spec.rb b/spec/lib/seed_snippets_spec.rb index bac2ac9a8..eafcf2af5 100644 --- a/spec/lib/seed_snippets_spec.rb +++ b/spec/lib/seed_snippets_spec.rb @@ -5,7 +5,7 @@ subject(:locales) { described_class.new.call } it 'converts all translations' do - expect(locales.count).to be 192 + expect(locales.count).to be 204 end it 'dot separated key -> Page::Resource#name' do diff --git a/spec/models/data_analysis/local_authority_user_spec.rb b/spec/models/data_analysis/local_authority_user_spec.rb index 00ffcd090..a91658cbe 100644 --- a/spec/models/data_analysis/local_authority_user_spec.rb +++ b/spec/models/data_analysis/local_authority_user_spec.rb @@ -22,6 +22,10 @@ end before do + # Incomplete registration + create :user, :named, local_authority: 'LA3' + + # Registered user create :user, :registered, local_authority: 'LA1' create :user, :registered, local_authority: 'LA1' create :user, :registered, local_authority: 'LA3' diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index fd0217a17..f43490f5c 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -206,4 +206,55 @@ end end end + + describe '.find_or_create_from_gov_one' do + let(:email) { 'current@test.com' } + let(:gov_one_id) { 'urn:fdc:gov.uk:2022:23-random-alpha-numeric' } + + before do + described_class.find_or_create_from_gov_one(**params) + end + + context 'without an existing account' do + let(:params) do + { email: email, gov_one_id: gov_one_id } + end + + it 'creates a new user' do + expect(described_class.count).to eq 1 + expect(described_class.first.email).to eq params[:email] + expect(described_class.first.gov_one_id).to eq params[:gov_one_id] + end + end + + context 'with an existing account' do + context 'and using GovOne for the first time' do + let(:user) do + create :user, :registered, email: email + end + + let(:params) do + { email: user.email, gov_one_id: gov_one_id } + end + + it 'associates GovOne ID' do + expect(user.reload.gov_one_id).to eq gov_one_id + end + end + + context 'and using GovOne with a new email' do + let(:user) do + create :user, :registered, email: 'old@test.com', gov_one_id: gov_one_id + end + + let(:params) do + { email: email, gov_one_id: user.gov_one_id } + end + + it 'updates email' do + expect(user.reload.email).to eq email + end + end + end + end end diff --git a/spec/requests/authentication_spec.rb b/spec/requests/authentication_spec.rb index 66da4ccf2..466b3af5c 100644 --- a/spec/requests/authentication_spec.rb +++ b/spec/requests/authentication_spec.rb @@ -4,7 +4,7 @@ # RSpec.describe 'Authentication', type: :request do describe 'viewing authenticate_user! controller action' do - let(:action_path) { edit_registration_name_path } + let(:action_path) { edit_registration_terms_and_conditions_path } context 'with User not signed in' do it 'redirects to sign in page' do @@ -85,11 +85,14 @@ end context 'with partially registered User' do - before { sign_in create(:user, :confirmed) } + before do + allow(Rails.application).to receive(:gov_one_login?).and_return(true) + sign_in create(:user, :confirmed) + end it 'redirects to finish registration' do get action_path - expect(response).to redirect_to(edit_registration_name_path) + expect(response).to redirect_to(edit_registration_terms_and_conditions_path) end it 'displays message to complete registration' do diff --git a/spec/requests/bot_spec.rb b/spec/requests/bot_spec.rb index 7f46679c8..3a7109570 100644 --- a/spec/requests/bot_spec.rb +++ b/spec/requests/bot_spec.rb @@ -2,9 +2,10 @@ RSpec.describe 'Automated bot', type: :request do let(:bot_email) { 'bot_token@example.com' } + let(:bot_name) { 'Bot' } before do - create :user, :registered, email: bot_email + create :user, :registered, email: bot_email, first_name: bot_name end context 'with header' do @@ -14,7 +15,7 @@ it 'is not redirected and can access secure pages' do expect(response).not_to redirect_to new_user_session_path - expect(response.body).to include bot_email + expect(response.body).to include bot_name end end diff --git a/spec/requests/registration/terms_and_conditions_spec.rb b/spec/requests/registration/terms_and_conditions_spec.rb new file mode 100644 index 000000000..ba7c49cbc --- /dev/null +++ b/spec/requests/registration/terms_and_conditions_spec.rb @@ -0,0 +1,16 @@ +require 'rails_helper' + +RSpec.describe 'Registration names', type: :request do + subject(:user) { create(:user, :confirmed) } + + before do + sign_in user + end + + describe 'GET /registration/terms_and_conditions/edit' do + it 'returns http success' do + get edit_registration_name_path + expect(response).to have_http_status(:success) + end + end +end diff --git a/spec/services/gov_one_auth_service_spec.rb b/spec/services/gov_one_auth_service_spec.rb new file mode 100644 index 000000000..94e08d9ce --- /dev/null +++ b/spec/services/gov_one_auth_service_spec.rb @@ -0,0 +1,111 @@ +require 'rails_helper' + +shared_examples 'a one login request' do + it 'returns a hash of the expected payload' do + expect(result).to eq(payload) + expect(auth_service).to have_received(:response).with(an_instance_of(request_type), an_instance_of(Net::HTTP)) + end +end + +RSpec.describe GovOneAuthService do + let(:code) { 'mock_code' } + let(:mock_response) { instance_double('response') } + let(:auth_service) { described_class.new(code: code) } + let(:payload) { { 'info' => 'auth_info' } } + + before do + allow(auth_service).to receive(:response).and_return(mock_response) + allow(mock_response).to receive(:body).and_return(payload.to_json) + end + + describe '#tokens' do + let(:result) { auth_service.tokens } + let(:request_type) { Net::HTTP::Post } + + context 'when successful' do + it_behaves_like 'a one login request' + end + + context 'when unsuccessful' do + let(:payload) { {} } + + it_behaves_like 'a one login request' + end + end + + describe '#user_info' do + let(:access_token) { 'mock_access_token' } + let(:result) { auth_service.user_info(access_token) } + let(:request_type) { Net::HTTP::Get } + + context 'when successful' do + it_behaves_like 'a one login request' + end + + context 'when unsuccessful' do + let(:payload) { {} } + + it_behaves_like 'a one login request' + end + end + + describe '#token_body' do + let(:token_body) { auth_service.send(:token_body) } + + it 'returns a hash of correct token body' do + expect(token_body[:grant_type]).to eq('authorization_code') + expect(token_body[:code]).to eq(code) + expect(token_body[:redirect_uri]).to end_with('/users/auth/openid_connect/callback') + expect(token_body[:client_assertion_type]).to eq('urn:ietf:params:oauth:client-assertion-type:jwt-bearer') + end + end + + describe '#jwt_payload' do + let(:jwt_payload) { auth_service.send(:jwt_payload) } + + it 'returns a hash of correct jwt payload' do + expect(jwt_payload[:aud]).to eq 'https://oidc.test.account.gov.uk/token' + expect(jwt_payload[:iss]).to eq 'some_client_id' + expect(jwt_payload[:sub]).to eq 'some_client_id' + expect(jwt_payload[:exp]).to be_between(Time.zone.now.to_i + 4 * 60, Time.zone.now.to_i + 6 * 60) + expect(jwt_payload[:jti]).to be_a String + expect(jwt_payload[:iat]).to be_a Integer + end + end + + describe 'Internal callbacks' do + subject(:callbacks) { described_class::CALLBACKS } + + specify 'login' do + expect(callbacks[:login]).to eq 'http://recovery.app/users/auth/openid_connect/callback' + end + + specify 'logout' do + expect(callbacks[:logout]).to eq 'http://recovery.app/users/sign_out' + end + end + + describe 'OIDC endpoints' do + subject(:endpoints) { described_class::ENDPOINTS } + + specify 'login endpoint for starting gov one user session and redirecting back to service' do + expect(endpoints[:login]).to eq 'https://oidc.test.account.gov.uk/authorize' + end + + specify 'logout endpoint for ending gov one user session and redirecting back to service' do + expect(endpoints[:logout]).to eq 'https://oidc.test.account.gov.uk/logout' + end + + specify 'token endpoint for retrieving user access token and id token' do + expect(endpoints[:token]).to eq 'https://oidc.test.account.gov.uk/token' + end + + specify 'userinfo endpoint for retrieving user email' do + expect(endpoints[:userinfo]).to eq 'https://oidc.test.account.gov.uk/userinfo' + end + + specify 'jwks endpoint for retrieving public key for verifying user id token' do + expect(endpoints[:jwks]).to eq 'https://oidc.test.account.gov.uk/.well-known/jwks.json' + end + end +end diff --git a/spec/system/account_page_spec.rb b/spec/system/account_page_spec.rb new file mode 100644 index 000000000..ac3a0547d --- /dev/null +++ b/spec/system/account_page_spec.rb @@ -0,0 +1,36 @@ +require 'rails_helper' + +RSpec.describe 'Account page', type: :system do + subject(:user) { create :user, :registered } + + include_context 'with user' + + context 'when gov_one login is disabled' do + before do + allow(Rails.application).to receive(:gov_one_login?).and_return(false) + visit '/my-account' + end + + it 'displays account details and password options' do + expect(page).to have_text('Manage your account') + expect(page).to have_css('a', text: 'Change name') + expect(page).to have_css('a', text: 'Change password') + expect(page).to have_css('a', text: 'Change setting details') + expect(page).to have_css('a', text: 'Change email preferences') + expect(page).to have_text('Closing your account') + end + end + + context 'when gov_one login is enabled' do + before do + allow(Rails.application).to receive(:gov_one_login?).and_return(true) + visit '/my-account' + end + + it 'password options are not listed and helper text is displayed' do + expect(page).to have_text('Manage your account') + expect(page).not_to have_css('a', text: 'Change password') + expect(page).to have_content('Changing your name on this account will not affect your Gov.UK One Login') + end + end +end diff --git a/spec/system/confirmed_user/completing_registration_spec.rb b/spec/system/confirmed_user/completing_registration_spec.rb index 420391fb0..105e843c9 100644 --- a/spec/system/confirmed_user/completing_registration_spec.rb +++ b/spec/system/confirmed_user/completing_registration_spec.rb @@ -1,11 +1,24 @@ require 'rails_helper' RSpec.describe 'Confirmed users completing registration' do - include_context 'with user' + before do + allow(Rails.application).to receive(:gov_one_login?).and_return(true) + end + include_context 'with user' let(:user) { create :user, :confirmed } it 'requires name and a setting type and email preferences and a complete' do + expect(page).to have_text('Terms and conditions') + click_button 'Continue' + expect(page).to have_text('There is a problem') + .and have_text('You must accept the terms and conditions and privacy policy to create an account.') + + expect(page).to have_text('Agree to our terms and conditions') + + check 'I confirm that I accept the terms and conditions and privacy policy.' + click_button 'Continue' + expect(page).to have_text('About you') click_button 'Continue' @@ -48,14 +61,12 @@ expect(page).to have_text('What is your role?') .and have_text('Enter your job title.') - click_button 'Continue' expect(page).to have_text('There is a problem') .and have_text('Enter your job title.') fill_in 'Enter your job title.', with: 'user defined job title' - click_button 'Continue' expect(page).to have_text('Do you want to get email updates about this training course?') diff --git a/spec/system/gov_one_info_spec.rb b/spec/system/gov_one_info_spec.rb new file mode 100644 index 000000000..4107f9c25 --- /dev/null +++ b/spec/system/gov_one_info_spec.rb @@ -0,0 +1,25 @@ +require 'rails_helper' + +RSpec.describe 'Gov One Info' do + before do + allow(Rails.application).to receive(:gov_one_login?).and_return(false) + visit '/gov-one/info' + end + + context 'with an unauthenticated visitor' do + it 'displays the correct content' do + expect(page).to have_css('h1', text: 'How to access this training course') + expect(page).to have_css('p', text: 'This service uses GOV.UK One Login which is managed by the Government Digital Service.') + expect(page).to have_css('p', text: 'You will be asked to sign in to your account, or create a One Login account, in this service') + expect(page).to have_css('a', text: 'Continue to GOV.UK One Login') + end + end + + context 'with an authenticated user' do + include_context 'with user' + + it 'redirects to the my modules page' do + expect(page).to have_current_path('/my-modules') + end + end +end diff --git a/spec/system/registered_user/changing_password_spec.rb b/spec/system/registered_user/changing_password_spec.rb index 8870164c0..3e0c48d63 100644 --- a/spec/system/registered_user/changing_password_spec.rb +++ b/spec/system/registered_user/changing_password_spec.rb @@ -30,8 +30,7 @@ click_button 'Save' expect(page).to have_current_path '/my-account' expect(page).to have_text('Manage your account') # page heading - .and have_text('Your new password has been saved.') # flash message - .and have_text("Password last changed on #{today}") # event + .and have_text('Your new password has been saved.') end end diff --git a/spec/system/sign_in_spec.rb b/spec/system/sign_in_spec.rb index 2a235da7c..790d6c79e 100644 --- a/spec/system/sign_in_spec.rb +++ b/spec/system/sign_in_spec.rb @@ -5,6 +5,7 @@ let(:password) { Rails.configuration.user_password } before do + allow(Rails.application).to receive(:gov_one_login?).and_return(true) visit '/users/sign-in' fill_in 'Email address', with: email_address fill_in 'Password', with: password @@ -45,7 +46,7 @@ context 'and enters valid credentials' do it 'signs in successfully' do - expect(page).to have_text('About you') # extra registration + expect(page).to have_text('Agree to our terms and conditions') # extra registration end end