From 47b8347ce6100952cff2f238c15db4d12522cf87 Mon Sep 17 00:00:00 2001 From: Oleksii Kriuchykhin Date: Tue, 2 Nov 2021 16:19:56 +0100 Subject: [PATCH] Add support of Okta SSO provider [SCI-6184] --- Gemfile | 1 + Gemfile.lock | 4 ++ app/assets/stylesheets/themes/scinote.scss | 10 +++++ .../users/omniauth_callbacks_controller.rb | 41 ++++++++++++++++++- app/views/users/shared/_links.html.erb | 8 ++++ config/initializers/devise.rb | 17 ++++++++ config/initializers/extends.rb | 6 ++- config/initializers/omniauth.rb | 4 +- config/locales/en.yml | 7 ++++ 9 files changed, 94 insertions(+), 4 deletions(-) diff --git a/Gemfile b/Gemfile index 2c95dd400..40b38f8a3 100644 --- a/Gemfile +++ b/Gemfile @@ -25,6 +25,7 @@ gem 'doorkeeper', '>= 4.6' gem 'omniauth' gem 'omniauth-azure-activedirectory' gem 'omniauth-linkedin-oauth2' +gem 'omniauth-okta' # TODO: remove this when omniauth gem resolves CVE issues # Prevents CVE-2015-9284 (https://github.com/omniauth/omniauth/wiki/FAQ#cve-2015-9284-warnings) diff --git a/Gemfile.lock b/Gemfile.lock index 9b0fe960b..84668d848 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -407,6 +407,9 @@ GEM omniauth-oauth2 (1.7.1) oauth2 (~> 1.4) omniauth (>= 1.9, < 3) + omniauth-okta (0.1.3) + omniauth (~> 1.5) + omniauth-oauth2 (>= 1.6.0, < 2.0) omniauth-rails_csrf_protection (0.1.2) actionpack (>= 4.2) omniauth (>= 1.3.1) @@ -692,6 +695,7 @@ DEPENDENCIES omniauth omniauth-azure-activedirectory omniauth-linkedin-oauth2 + omniauth-okta omniauth-rails_csrf_protection (~> 0.1) overcommit pg (~> 1.1) diff --git a/app/assets/stylesheets/themes/scinote.scss b/app/assets/stylesheets/themes/scinote.scss index 75d800949..49efda8e5 100644 --- a/app/assets/stylesheets/themes/scinote.scss +++ b/app/assets/stylesheets/themes/scinote.scss @@ -401,6 +401,16 @@ a[data-toggle="tooltip"] { } } +.okta-sign-in-actions { + margin-bottom: 10px; + margin-top: 10px; + + .btn-okta { + background-color: #00297a; + color: $color-white; + } +} + .navbar-secondary { transition: .4s $timing-function-sharp; } diff --git a/app/controllers/users/omniauth_callbacks_controller.rb b/app/controllers/users/omniauth_callbacks_controller.rb index c1f963cfb..e4c56921e 100644 --- a/app/controllers/users/omniauth_callbacks_controller.rb +++ b/app/controllers/users/omniauth_callbacks_controller.rb @@ -127,11 +127,50 @@ module Users redirect_to after_omniauth_failure_path_for(resource_name) and return end # Confirm user - @user.update_column(:confirmed_at, @user.created_at) + @user.update!(confirmed_at: @user.created_at) redirect_to users_sign_up_provider_path(user: @user) end end + def okta + auth = request.env['omniauth.auth'] + user = User.from_omniauth(auth) + # User found in database so just signing in + return sign_in_and_redirect(user) if user.present? + + user = User.find_by(email: auth.info.email.downcase) + + if user.blank? + # Create new user and identity + user = User.new(full_name: auth.info.name, + initials: generate_initials(auth.info.name), + email: auth.info.email, + password: generate_user_password) + User.transaction do + user.save! + user.user_identities.create!(provider: auth.provider, uid: auth.uid) + user.update!(confirmed_at: user.created_at) + end + else + # Link to existing local account + user.user_identities.create!(provider: auth.provider, uid: auth.uid) + user.update!(confirmed_at: user.created_at) if user.confirmed_at.blank? + end + sign_in_and_redirect(user) + rescue StandardError => e + Rails.logger.error e.message + Rails.logger.error e.backtrace.join("\n") + error_message = I18n.t('devise.okta.errors.failed_to_save') if e.is_a?(ActiveRecord::RecordInvalid) + error_message ||= I18n.t('devise.okta.errors.generic') + redirect_to after_omniauth_failure_path_for(resource_name) + ensure + if error_message + set_flash_message(:alert, :failure, kind: I18n.t('devise.okta.provider_name'), reason: error_message) + else + set_flash_message(:notice, :success, kind: I18n.t('devise.okta.provider_name')) + end + end + # More info at: # https://github.com/plataformatec/devise#omniauth diff --git a/app/views/users/shared/_links.html.erb b/app/views/users/shared/_links.html.erb index 1d520d0f6..2e796a990 100644 --- a/app/views/users/shared/_links.html.erb +++ b/app/views/users/shared/_links.html.erb @@ -25,6 +25,14 @@
<% end -%> + <%- if devise_mapping.omniauthable? && Devise.omniauth_configs[:okta].present? %> +
+ <%= form_tag omniauth_authorize_path(resource_name, :okta), method: :post do %> + <%= submit_tag t('devise.okta.sign_in_label'), class: 'btn btn-okta' %> + <% end %> +
+ <% end %> + <%- unless defined?(linkedin_skip) %> <%- if Rails.configuration.x.enable_user_registration && Rails.configuration.x.linkedin_signin_enabled && @oauth_authorize != true %> <%= render partial: "users/shared/linkedin_sign_in_links", locals: { resource_name: resource_name } %> diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb index b75ab2b81..4ffc10175 100644 --- a/config/initializers/devise.rb +++ b/config/initializers/devise.rb @@ -295,6 +295,23 @@ Devise.setup do |config| config.omniauth :linkedin, ENV['LINKEDIN_KEY'], ENV['LINKEDIN_SECRET'], scope: 'r_liteprofile r_emailaddress' end + if [ENV['OKTA_CLIENT_ID'], ENV['OKTA_CLIENT_SECRET'], ENV['OKTA_DOMAIN'], ENV['OKTA_AUTH_SERVER_ID']].all? + config.omniauth( + :okta, + ENV['OKTA_CLIENT_ID'], + ENV['OKTA_CLIENT_SECRET'], + scope: 'openid profile email', + fields: %w(profile email), + client_options: { + site: "https://#{ENV['OKTA_DOMAIN']}", + authorize_url: "https://#{ENV['OKTA_DOMAIN']}/oauth2/#{ENV['OKTA_AUTH_SERVER_ID']}/v1/authorize", + token_url: "https://#{ENV['OKTA_DOMAIN']}/oauth2/#{ENV['OKTA_AUTH_SERVER_ID']}/v1/token", + user_info_url: "https://#{ENV['OKTA_DOMAIN']}/oauth2/#{ENV['OKTA_AUTH_SERVER_ID']}/v1/userinfo" + }, + strategy_class: OmniAuth::Strategies::Okta + ) + end + # ==> Warden configuration # If you want to use other strategies, that are not supported by Devise, or # change the failure app, you can configure them inside the config.warden block. diff --git a/config/initializers/extends.rb b/config/initializers/extends.rb index bc9c14a62..e48fceb99 100644 --- a/config/initializers/extends.rb +++ b/config/initializers/extends.rb @@ -1,6 +1,8 @@ # Extends class holds the arrays for the models enum fields # so that can be extended in sub modules. +# rubocop:disable Style/MutableConstant + class Extends # To extend the enum fields in the engine you have to put in # lib/engine_name/engine.rb file as in the example: @@ -109,7 +111,7 @@ class Extends 'RepositoryAssetValue' => 'file', 'RepositoryStatusValue' => 'status' } - OMNIAUTH_PROVIDERS = [:linkedin, :customazureactivedirectory] + OMNIAUTH_PROVIDERS = %i(linkedin customazureactivedirectory okta) INITIAL_USER_OPTIONS = {} @@ -395,3 +397,5 @@ class Extends change_user_role_on_my_module ) end + +# rubocop:enable Style/MutableConstant diff --git a/config/initializers/omniauth.rb b/config/initializers/omniauth.rb index e216bdb89..233f74eb4 100644 --- a/config/initializers/omniauth.rb +++ b/config/initializers/omniauth.rb @@ -2,7 +2,7 @@ require 'omniauth/strategies/custom_azure_active_directory' -SETUP_PROC = lambda do |env| +AZURE_SETUP_PROC = lambda do |env| providers = Rails.configuration.x.azure_ad_apps.select { |_, v| v[:enable_sign_in] == true } raise StandardError, 'No Azure AD config available for sign in' if providers.blank? @@ -31,7 +31,7 @@ SETUP_PROC = lambda do |env| end Rails.application.config.middleware.use OmniAuth::Builder do - provider OmniAuth::Strategies::CustomAzureActiveDirectory, setup: SETUP_PROC + provider OmniAuth::Strategies::CustomAzureActiveDirectory, setup: AZURE_SETUP_PROC end OmniAuth.config.logger = Rails.logger diff --git a/config/locales/en.yml b/config/locales/en.yml index 49116c754..98752f257 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -74,6 +74,13 @@ en: no_local_user_map: "No local user record found" no_email: "Email is missing in auth token" failed_to_save: "Failed to create new user" + okta: + provider_name: "Okta" + sign_in_label: "Sign in with Okta" + errors: + generic: "Failed to sign in user" + no_local_user_map: "No local user record found" + failed_to_save: "Failed to create new user" doorkeeper: errors: