From 53432b45820f0c636c47a6e8709e2feebd414eac Mon Sep 17 00:00:00 2001 From: Steve Laing Date: Mon, 4 Sep 2023 11:07:55 +0100 Subject: [PATCH] Call DSI API for user roles on successful authentication DfE SignIn API will return 404 if the user isn't found for the given API call. Checks the roles in the response for a valid authorised role code. --- .env.development | 4 ++ .env.test | 4 ++ .../omniauth_callbacks_controller.rb | 18 ++++++- app/lib/dfe_sign_in_api/client.rb | 33 +++++++++++++ .../get_user_access_to_service.rb | 47 +++++++++++++++++++ .../dfe_sign_in_api/invalid_token_error.rb | 3 ++ .../user_not_authorised_error.rb | 3 ++ .../check_records/authentication_steps.rb | 19 ++++++++ 8 files changed, 130 insertions(+), 1 deletion(-) create mode 100644 app/lib/dfe_sign_in_api/client.rb create mode 100644 app/lib/dfe_sign_in_api/get_user_access_to_service.rb create mode 100644 app/lib/dfe_sign_in_api/invalid_token_error.rb create mode 100644 app/lib/dfe_sign_in_api/user_not_authorised_error.rb diff --git a/.env.development b/.env.development index 2fdb4a96..1e3b9b26 100644 --- a/.env.development +++ b/.env.development @@ -4,6 +4,10 @@ DFE_SIGN_IN_CLIENT_ID=checkrecordteacher DFE_SIGN_IN_REDIRECT_URL=http://check.localhost:3000/check-records/auth/dfe/callback DFE_SIGN_IN_SECRET=override-locally DFE_SIGN_IN_ISSUER=https://dev-oidc.signin.education.gov.uk +DFE_SIGN_IN_API_BASE_URL=https://dev-api.signin.education.gov.uk +DFE_SIGN_IN_API_SECRET=override-locally +DFE_SIGN_IN_API_AUDIENCE=signin.education.gov.uk +DFE_SIGN_IN_API_ROLE_CODE=override-locally GOVUK_NOTIFY_API_KEY=override-locally HOSTING_DOMAIN=http://localhost:3000 HOSTING_ENVIRONMENT_NAME=local diff --git a/.env.test b/.env.test index 74973a77..be47a339 100644 --- a/.env.test +++ b/.env.test @@ -3,6 +3,10 @@ DFE_SIGN_IN_CLIENT_ID=checkrecordteacher DFE_SIGN_IN_ISSUER=test DFE_SIGN_IN_REDIRECT_URL=test DFE_SIGN_IN_SECRET=override-locally +DFE_SIGN_IN_API_BASE_URL=https://dev-api.signin.education.gov.uk +DFE_SIGN_IN_API_SECRET=override-locally +DFE_SIGN_IN_API_AUDIENCE=signin.education.gov.uk +DFE_SIGN_IN_API_ROLE_CODE=override-locally GOVUK_NOTIFY_API_KEY=override-locally HOSTING_DOMAIN=http://qualifications.localhost HOSTING_ENVIRONMENT_NAME=local diff --git a/app/controllers/check_records/omniauth_callbacks_controller.rb b/app/controllers/check_records/omniauth_callbacks_controller.rb index b79ff8ba..e16730ff 100644 --- a/app/controllers/check_records/omniauth_callbacks_controller.rb +++ b/app/controllers/check_records/omniauth_callbacks_controller.rb @@ -4,7 +4,23 @@ class CheckRecords::OmniauthCallbacksController < ApplicationController protect_from_forgery except: :dfe_bypass def dfe - @dsi_user = DsiUser.create_or_update_from_dsi(request.env["omniauth.auth"]) + auth = request.env["omniauth.auth"] + + unless CheckRecords::DfESignIn.bypass? + begin + DfESignInApi::GetUserAccessToService.new( + org_id: auth.extra.raw_info.organisation.id, + user_id: auth.uid, + ).call + rescue DfESignInApi::InvalidTokenError + return redirect_to check_records_sign_out_path + rescue DfESignInApi::UserNotAuthorisedError + flash[:warning] = "User not permitted to access this service" + return redirect_to check_records_sign_out_path + end + end + + @dsi_user = DsiUser.create_or_update_from_dsi(auth) session[:dsi_user_id] = @dsi_user.id session[:dsi_user_session_expiry] = 2.hours.from_now.to_i diff --git a/app/lib/dfe_sign_in_api/client.rb b/app/lib/dfe_sign_in_api/client.rb new file mode 100644 index 00000000..06062f65 --- /dev/null +++ b/app/lib/dfe_sign_in_api/client.rb @@ -0,0 +1,33 @@ +require "jwt" + +module DfESignInApi + class Client + TIMEOUT_IN_SECONDS = 5 + + def client + @client ||= + Faraday.new( + url: ENV.fetch("DFE_SIGN_IN_API_BASE_URL"), + request: { + timeout: TIMEOUT_IN_SECONDS + } + ) do |faraday| + faraday.request :authorization, "Bearer", jwt + faraday.request :json + faraday.response :json + faraday.adapter Faraday.default_adapter + end + end + + def jwt + @jwt ||= JWT.encode( + { + iss: ENV.fetch("DFE_SIGN_IN_CLIENT_ID"), + aud: ENV.fetch("DFE_SIGN_IN_API_AUDIENCE"), + }, + ENV.fetch("DFE_SIGN_IN_API_SECRET"), + "HS256", + ) + end + end +end diff --git a/app/lib/dfe_sign_in_api/get_user_access_to_service.rb b/app/lib/dfe_sign_in_api/get_user_access_to_service.rb new file mode 100644 index 00000000..07e59ad4 --- /dev/null +++ b/app/lib/dfe_sign_in_api/get_user_access_to_service.rb @@ -0,0 +1,47 @@ +module DfESignInApi + class GetUserAccessToService + attr_reader :org_id, :user_id + + def initialize(org_id:, user_id:) + @org_id = org_id + @user_id = user_id + end + + def call + response = client.get(endpoint) + + case response.status + when 200 + if response.body.key?("roles") + response.body["roles"].map { |role| role["code"] }.include?(authorised_role_code) + else + raise DfESignInApi::UserNotAuthorisedError, "Response does not contain appropriate role" + end + when 404 + raise DfESignInApi::UserNotAuthorisedError + when 401 + raise DfESignInApi::InvalidTokenError + when 500 + raise StandardError, "Internal Server Error" + end + end + + private + + def client + @client ||= Client.new.client + end + + def endpoint + "/services/#{service_id}/organisations/#{org_id}/users/#{user_id}" + end + + def service_id + ENV["DFE_SIGN_IN_CLIENT_ID"] + end + + def authorised_role_code + ENV["DFE_SIGN_IN_API_ROLE_CODE"] + end + end +end diff --git a/app/lib/dfe_sign_in_api/invalid_token_error.rb b/app/lib/dfe_sign_in_api/invalid_token_error.rb new file mode 100644 index 00000000..0186d5b5 --- /dev/null +++ b/app/lib/dfe_sign_in_api/invalid_token_error.rb @@ -0,0 +1,3 @@ +module DfESignInApi + class InvalidTokenError < StandardError; end +end diff --git a/app/lib/dfe_sign_in_api/user_not_authorised_error.rb b/app/lib/dfe_sign_in_api/user_not_authorised_error.rb new file mode 100644 index 00000000..e5217e3e --- /dev/null +++ b/app/lib/dfe_sign_in_api/user_not_authorised_error.rb @@ -0,0 +1,3 @@ +module DfESignInApi + class UserNotAuthorisedError < StandardError; end +end diff --git a/spec/support/system/check_records/authentication_steps.rb b/spec/support/system/check_records/authentication_steps.rb index 399d039d..956eb44d 100644 --- a/spec/support/system/check_records/authentication_steps.rb +++ b/spec/support/system/check_records/authentication_steps.rb @@ -16,9 +16,24 @@ def given_dsi_auth_is_mocked email: "test@example.com", first_name: "Test", last_name: "User" + }, + extra: { + raw_info: { + organisation: { + id: org_id, + } + } } } ) + + stub_request( + :get, + "#{ENV.fetch("DFE_SIGN_IN_API_BASE_URL")}/services/checkrecordteacher/organisations/#{org_id}/users/123456", + ).to_return_json( + status: 200, + body: { "roles" => [{ "code" => ENV["DFE_SIGN_IN_API_ROLE_CODE"] }] }, + ) end def when_i_visit_the_sign_in_page @@ -28,5 +43,9 @@ def when_i_visit_the_sign_in_page def and_click_the_dsi_sign_in_button click_button "Sign in with DSI" end + + def org_id + "12345678-1234-1234-1234-123456789012" + end end end