diff --git a/app/controllers/errors_controller.rb b/app/controllers/errors_controller.rb new file mode 100644 index 000000000..139e79952 --- /dev/null +++ b/app/controllers/errors_controller.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +class ErrorsController < ApplicationController + layout false + + skip_before_action :authenticate_auth_user! + + def auth_error + # Renders app/views/errors/auth_error.html.erb + end +end diff --git a/app/controllers/omniauth_callbacks_controller.rb b/app/controllers/omniauth_callbacks_controller.rb index b10a54bd6..d0ed242e3 100644 --- a/app/controllers/omniauth_callbacks_controller.rb +++ b/app/controllers/omniauth_callbacks_controller.rb @@ -6,7 +6,8 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController def keycloak_openid omniauth_auth = request.env['omniauth.auth'] @user = AuthUser.from_omniauth(omniauth_auth) - if @user.persisted? + @relevant_role = AuthConfig.relevant_keycloak_role + if @user.persisted? && omniauth_auth[:extra][:raw_info][:pitc][:roles].include?(@relevant_role) sign_in_and_redirect @user, event: :authentication set_flash_message(:notice, :success, kind: 'Keycloak') if is_navigational_format? else @@ -15,6 +16,6 @@ def keycloak_openid end def failure - redirect_to root_path + redirect_to '/auth_error' end end diff --git a/app/views/errors/auth_error.html.erb b/app/views/errors/auth_error.html.erb new file mode 100644 index 000000000..5623a2a09 --- /dev/null +++ b/app/views/errors/auth_error.html.erb @@ -0,0 +1,10 @@ +
+
+
+

Authentication Error

+

You do not have the necessary permissions to access this application.

+

Please contact an administrator if you believe this is an error.

+ <%= link_to 'Go back to sign in', '/sign_in', class: 'btn btn-primary' %> +
+
+
diff --git a/config/docker/keycloak/rails.env b/config/docker/keycloak/rails.env index b638787eb..92c5a17d5 100644 --- a/config/docker/keycloak/rails.env +++ b/config/docker/keycloak/rails.env @@ -3,6 +3,7 @@ LOCAL=false ADMIN_ROLE=ADMIN REALM=pitc +RELEVANT_KEYCLOAK_ROLE=puzzle HOST_URL=http://keycloak:8080 KEYCLOAK_REDIRECT_HOST_URL=http://localhost:8080 diff --git a/config/routes.rb b/config/routes.rb index eff067b88..7200e6ba7 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -20,6 +20,7 @@ get 'sentry_error', to: 'status#sentry_error' end + get '/auth_error', to: 'errors#auth_error' get root to: redirect(path: "/people") scope "(:locale)", locale: LOCALE_REGEX do diff --git a/lib/auth_config.rb b/lib/auth_config.rb index c9cd67995..49abf5217 100644 --- a/lib/auth_config.rb +++ b/lib/auth_config.rb @@ -37,6 +37,10 @@ def conf_admin_role get_var_from_environment(:conf_admin_role, required: false) end + def relevant_keycloak_role + get_var_from_environment(:relevant_keycloak_role, required: true) + end + def keycloak? to_boolean(get_var_from_environment(:keycloak, required: false, default: false)) end diff --git a/spec/controllers/errors_controller_spec.rb b/spec/controllers/errors_controller_spec.rb new file mode 100644 index 000000000..72fa09e5b --- /dev/null +++ b/spec/controllers/errors_controller_spec.rb @@ -0,0 +1,23 @@ +require 'rails_helper' + +RSpec.describe ErrorsController, type: :request do + describe 'GET #auth_error' do + before { get '/auth_error' } + + it 'returns a successful response' do + expect(response).to be_successful + end + + it 'renders the auth_error template' do + expect(response).to render_template(:auth_error) + end + + it 'does not use the application layout' do + expect(response.body).not_to include('PuzzleSkills') + end + + it 'contains the correct error message' do + expect(response.body).to include('You do not have the necessary permissions') + end + end +end diff --git a/spec/controllers/omniauth_callbacks_controller_spec.rb b/spec/controllers/omniauth_callbacks_controller_spec.rb new file mode 100644 index 000000000..b1fb1740a --- /dev/null +++ b/spec/controllers/omniauth_callbacks_controller_spec.rb @@ -0,0 +1,41 @@ +require 'rails_helper' + +describe OmniauthCallbacksController do + before do + request.env['devise.mapping'] = Devise.mappings[:auth_user] + end + + describe '#keycloak_openid' do + context 'when user has the required role' do + before do + allow(AuthConfig).to receive(:relevant_keycloak_role).and_return('puzzle') + request.env['omniauth.auth'] = mock_omniauth_user(['puzzle']) + get :keycloak_openid + end + + it 'signs in the user' do + expect(subject.current_auth_user).not_to be_nil + end + + it 'redirects to the root path' do + expect(response).to redirect_to(root_path) + end + end + + context 'when user does not have the required role' do + before do + allow(AuthConfig).to receive(:relevant_keycloak_role).and_return('puzzle') + request.env['omniauth.auth'] = mock_omniauth_user(['another_role']) + get :keycloak_openid + end + + it 'does not sign in the user' do + expect(subject.current_auth_user).to be_nil + end + + it 'redirects to the auth_error path' do + expect(response).to redirect_to('/auth_error') + end + end + end +end diff --git a/spec/support/omniauth.rb b/spec/support/omniauth.rb new file mode 100644 index 000000000..e07295c3a --- /dev/null +++ b/spec/support/omniauth.rb @@ -0,0 +1,24 @@ +module OmniauthMacros + def mock_omniauth_user(roles = []) + OmniAuth::AuthHash.new( + provider: 'keycloak_openid', + uid: '123545', + info: { + name: 'Test User', + email: 'test@example.com' + }, + extra: { + raw_info: { + pitc: { + roles: roles + } + } + } + ) + end +end + +RSpec.configure do |config| + config.include OmniauthMacros + OmniAuth.config.test_mode = true +end