-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Improve error handling and sign out flow when authenticating with DfE Sign-in #150
Merged
Merged
Changes from 7 commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
52c5e2a
Refactor omniauth_callbacks_controller
malcolmbaig 7f196a0
Store the organisation name and id token on sign in
malcolmbaig a2518c3
Sign out of DSI when signing out of the service
malcolmbaig dc82a05
Update 401 page content
malcolmbaig 35e01f3
Hide sign in/out links on 401 page
malcolmbaig 4918de5
Add oauth error handling
malcolmbaig ccc8cdf
Improve oauth error handling
malcolmbaig cb53358
Minor refactor in OmniAuthCallbacksController
malcolmbaig File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
<%= govuk_notification_banner( | ||
title_text: title, | ||
classes: classes, | ||
html_attributes: { role: role }, | ||
) do |notification_banner| %> | ||
<% notification_banner.with_heading(text: heading) %> | ||
<%= body %> | ||
<% end %> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
# frozen_string_literal: true | ||
class FlashMessageComponent < ViewComponent::Base | ||
ALLOWED_PRIMARY_KEYS = %i[info warning success].freeze | ||
DEVISE_PRIMARY_KEYS = { alert: :warning, notice: :info }.freeze | ||
|
||
def initialize(flash:) | ||
super | ||
@flash = flash.to_hash.symbolize_keys! | ||
end | ||
|
||
def message_key | ||
key = | ||
flash.keys.detect do |k| | ||
ALLOWED_PRIMARY_KEYS.include?(k) || DEVISE_PRIMARY_KEYS.keys.include?(k) | ||
end | ||
DEVISE_PRIMARY_KEYS[key] || key | ||
end | ||
|
||
def title | ||
I18n.t(message_key, scope: :notification_banner) | ||
end | ||
|
||
def classes | ||
"govuk-notification-banner--#{message_key}" | ||
end | ||
|
||
def role | ||
%i[warning success].include?(message_key) ? "alert" : "region" | ||
end | ||
|
||
def heading | ||
messages.is_a?(Array) ? messages[0] : messages | ||
end | ||
|
||
def body | ||
tag.p(messages[1], class: "govuk-body") if messages.is_a?(Array) && messages.count >= 2 | ||
end | ||
|
||
def render? | ||
!flash.empty? && message_key | ||
end | ||
|
||
private | ||
|
||
def messages | ||
flash[message_key] || flash[DEVISE_PRIMARY_KEYS.key(message_key)] | ||
end | ||
|
||
attr_reader :flash | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
class AuthFailuresController < ApplicationController | ||
skip_before_action :authenticate_dsi_user! | ||
skip_before_action :handle_expired_session! | ||
|
||
class OpenIdConnectProtocolError < StandardError; end | ||
|
||
def failure | ||
return redirect_to(dsi_sign_out_path(id_token_hint: session[:id_token])) if session_expired? | ||
handle_failure_then_redirect_to sign_in_path(oauth_failure: true) | ||
end | ||
|
||
private | ||
|
||
def handle_failure_then_redirect_to(path) | ||
oidc_error = OpenIdConnectProtocolError.new(error_message) | ||
unless Rails.env.development? | ||
Sentry.capture_exception(oidc_error) | ||
redirect_to(path) and return | ||
end | ||
|
||
raise oidc_error | ||
end | ||
|
||
def error_message | ||
@error_message ||= request.env["omniauth.error"]&.message | ||
end | ||
|
||
def session_expired? | ||
error_message == "sessionexpired" | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,15 @@ | ||
class SignInController < ApplicationController | ||
skip_before_action :authenticate_dsi_user! | ||
skip_before_action :handle_expired_session! | ||
before_action :reset_session | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💯 |
||
before_action :handle_failed_sign_in, if: -> { params[:oauth_failure] == "true" } | ||
|
||
def new | ||
end | ||
|
||
private | ||
|
||
def handle_failed_sign_in | ||
flash.now[:warning] = I18n.t("generic_oauth_failure") | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,7 @@ | ||
class SignOutController < ApplicationController | ||
skip_before_action :handle_expired_session! | ||
before_action :reset_session | ||
|
||
def new | ||
session[:dsi_user_id] = nil if dsi_user_signed_in? | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,6 +3,7 @@ en: | |
name: Check the Children’s Barred List | ||
email: [email protected] | ||
dbs_email: [email protected] | ||
generic_oauth_failure: There was a problem signing you in. Please try again. | ||
activemodel: | ||
errors: | ||
models: | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
# This strategy ensures that the id_token_hint param is included in the post_logout_redirect_uri. | ||
# The node-oidc-provider library requires this param to be present in order for the redirect to work. | ||
# See: https://github.com/panva/node-oidc-provider/blob/03c9bc513860e68ee7be84f99bfc9dc930b224e8/lib/actions/end_session.js#L27 | ||
# See: https://github.com/omniauth/omniauth_openid_connect/blob/34370d655d39fe7980f89f55715888e0ebd7270e/lib/omniauth/strategies/openid_connect.rb#L423 | ||
# | ||
module OmniAuth | ||
module Strategies | ||
class DfEOpenIDConnect < OmniAuth::Strategies::OpenIDConnect | ||
TOKEN_KEY = "id_token_hint".freeze | ||
|
||
def encoded_post_logout_redirect_uri | ||
return unless options.post_logout_redirect_uri | ||
|
||
logout_uri_params = { | ||
"post_logout_redirect_uri" => options.post_logout_redirect_uri | ||
} | ||
|
||
if query_string.present? | ||
query_params = CGI.parse(query_string[1..]) | ||
logout_uri_params[TOKEN_KEY] = query_params[TOKEN_KEY].first if query_params.key?(TOKEN_KEY) | ||
end | ||
|
||
URI.encode_www_form(logout_uri_params) | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,6 +11,9 @@ def given_dsi_auth_is_mocked(authorised:) | |
{ | ||
provider: "dfe", | ||
uid: "123456", | ||
credentials: { | ||
id_token: "abc123", | ||
}, | ||
info: { | ||
email: "[email protected]", | ||
first_name: "Test", | ||
|
@@ -20,6 +23,7 @@ def given_dsi_auth_is_mocked(authorised:) | |
raw_info: { | ||
organisation: { | ||
id: org_id, | ||
name: "Test School", | ||
} | ||
} | ||
} | ||
|
@@ -34,6 +38,21 @@ def given_dsi_auth_is_mocked(authorised:) | |
) | ||
end | ||
|
||
def given_dsi_auth_is_mocked_with_a_failure(message) | ||
allow(Sentry).to receive(:capture_exception) | ||
OmniAuth.config.mock_auth[:dfe] = message.to_sym | ||
global_failure_handler = OmniAuth.config.on_failure | ||
local_failure_handler = proc do |env| | ||
env["omniauth.error"] = OmniAuth::Strategies::OpenIDConnect::CallbackError.new(error: message) | ||
env | ||
end | ||
OmniAuth.config.on_failure = global_failure_handler << local_failure_handler | ||
|
||
yield if block_given? | ||
|
||
OmniAuth.config.on_failure = global_failure_handler | ||
end | ||
|
||
def user_roles_endpoint | ||
"#{ENV.fetch("DFE_SIGN_IN_API_BASE_URL")}/services/checkchildrensbarredlist/organisations/#{org_id}/users/123456" | ||
end | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
# frozen_string_literal: true | ||
|
||
require "rails_helper" | ||
|
||
RSpec.describe "DSI authentication", type: :system do | ||
include ActivateFeaturesSteps | ||
include AuthenticationSteps | ||
|
||
before do | ||
given_the_service_is_open | ||
allow(Sentry).to receive(:capture_exception) | ||
end | ||
|
||
scenario "User has oauth error when signing in", test: :with_stubbed_auth do | ||
given_dsi_auth_is_mocked_with_a_failure("invalid_credentials") do | ||
when_i_visit_the_sign_in_page | ||
and_click_the_dsi_sign_in_button | ||
then_i_see_a_sign_in_error | ||
end | ||
end | ||
|
||
scenario "User has sessionexpiry oauth error", test: :with_stubbed_auth do | ||
given_dsi_auth_is_mocked_with_a_failure("sessionexpired") do | ||
when_i_visit_the_sign_in_page | ||
and_click_the_dsi_sign_in_button | ||
then_i_am_redirected_to_sign_in | ||
end | ||
end | ||
|
||
private | ||
|
||
def then_i_see_a_sign_in_error | ||
expect(page).to have_content "There was a problem signing you in. Please try again." | ||
end | ||
|
||
def then_i_am_redirected_to_sign_in | ||
expect(page).to have_current_path(sign_in_path) | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@felixclack made a point on a related PR that renaming this method to
role
simplifies the code somewhat. Not a blocker.