mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2025-10-01 01:14:30 +08:00
Add generic SAML SSO strategy implementation [SCI-9579] (#6775)
This commit is contained in:
parent
dbedac3a9b
commit
3de2f6d0f1
8 changed files with 125 additions and 33 deletions
1
Gemfile
1
Gemfile
|
@ -26,6 +26,7 @@ gem 'omniauth-linkedin-oauth2'
|
|||
gem 'omniauth-okta', git: 'https://github.com/scinote-eln/omniauth-okta', branch: 'org_auth_server_support'
|
||||
gem 'omniauth_openid_connect'
|
||||
gem 'omniauth-rails_csrf_protection', '~> 1.0'
|
||||
gem 'omniauth-saml'
|
||||
|
||||
# Gems for API implementation
|
||||
gem 'active_model_serializers', '~> 0.10.7'
|
||||
|
|
|
@ -478,6 +478,9 @@ GEM
|
|||
omniauth_openid_connect (0.7.1)
|
||||
omniauth (>= 1.9, < 3)
|
||||
openid_connect (~> 2.2)
|
||||
omniauth-saml (2.1.0)
|
||||
omniauth (~> 2.0)
|
||||
ruby-saml (~> 1.12)
|
||||
openid_connect (2.2.0)
|
||||
activemodel
|
||||
attr_required (>= 1.0.0)
|
||||
|
@ -646,6 +649,9 @@ GEM
|
|||
rubocop (>= 1.33.0, < 2.0)
|
||||
ruby-progressbar (1.13.0)
|
||||
ruby-rc4 (0.1.5)
|
||||
ruby-saml (1.16.0)
|
||||
nokogiri (>= 1.13.10)
|
||||
rexml
|
||||
ruby-vips (2.1.4)
|
||||
ffi (~> 1.12)
|
||||
ruby2_keywords (0.0.5)
|
||||
|
@ -815,6 +821,7 @@ DEPENDENCIES
|
|||
omniauth-linkedin-oauth2
|
||||
omniauth-okta!
|
||||
omniauth-rails_csrf_protection (~> 1.0)
|
||||
omniauth-saml
|
||||
omniauth_openid_connect
|
||||
overcommit
|
||||
pg (~> 1.5)
|
||||
|
|
|
@ -46,17 +46,7 @@ module Users
|
|||
|
||||
if user.blank?
|
||||
# Create new user and identity
|
||||
full_name = "#{auth.info.first_name} #{auth.info.last_name}"
|
||||
user = User.new(full_name: full_name,
|
||||
initials: generate_initials(full_name),
|
||||
email: 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
|
||||
|
||||
user = create_user_from_auth(email, auth)
|
||||
sign_in_and_redirect(user)
|
||||
elsif provider_conf['auto_link_on_sign_in']
|
||||
# Link to existing local account
|
||||
|
@ -146,16 +136,7 @@ module Users
|
|||
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
|
||||
user = create_user_from_auth(email, auth)
|
||||
else
|
||||
# Link to existing local account
|
||||
user.user_identities.create!(provider: auth.provider, uid: auth.uid)
|
||||
|
@ -201,17 +182,7 @@ module Users
|
|||
|
||||
if user.blank?
|
||||
# Create new user and identity
|
||||
full_name = "#{auth.info.first_name} #{auth.info.last_name}"
|
||||
user = User.new(full_name: full_name,
|
||||
initials: generate_initials(full_name),
|
||||
email: 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
|
||||
|
||||
user = create_user_from_auth(email, auth)
|
||||
sign_in_and_redirect(user)
|
||||
elsif provider_conf['auto_link_on_sign_in']
|
||||
# Link to existing local account
|
||||
|
@ -237,6 +208,56 @@ module Users
|
|||
end
|
||||
end
|
||||
|
||||
def saml
|
||||
auth = request.env['omniauth.auth']
|
||||
|
||||
settings = ApplicationSettings.instance
|
||||
provider_conf = settings.values['saml']
|
||||
raise StandardError, 'No matching SAML provider config found' if provider_conf.blank?
|
||||
|
||||
return redirect_to connected_accounts_path if current_user
|
||||
|
||||
email = auth.info.email
|
||||
user = User.from_omniauth(auth)
|
||||
|
||||
# User found in database so just signing in
|
||||
return sign_in_and_redirect(user) if user.present?
|
||||
|
||||
if email.blank?
|
||||
# No email in the token so can not link or create user
|
||||
error_message = I18n.t('devise.saml.errors.no_email')
|
||||
return redirect_to after_omniauth_failure_path_for(resource_name)
|
||||
end
|
||||
|
||||
user = User.find_by(email: email.downcase)
|
||||
|
||||
if user.blank?
|
||||
user = create_user_from_auth(email, auth)
|
||||
sign_in_and_redirect(user)
|
||||
elsif provider_conf['auto_link_on_sign_in']
|
||||
# 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?
|
||||
sign_in_and_redirect(user)
|
||||
else
|
||||
# Cannot do anything with it, so just return an error
|
||||
error_message = I18n.t('devise.saml.errors.no_local_user_map')
|
||||
redirect_to after_omniauth_failure_path_for(resource_name)
|
||||
end
|
||||
rescue StandardError => e
|
||||
Rails.logger.error e.message
|
||||
Rails.logger.error e.backtrace.join("\n")
|
||||
error_message = I18n.t('devise.saml.errors.failed_to_save') if e.is_a?(ActiveRecord::RecordInvalid)
|
||||
error_message ||= I18n.t('devise.saml.errors.generic')
|
||||
redirect_to after_omniauth_failure_path_for(resource_name)
|
||||
ensure
|
||||
if error_message
|
||||
set_flash_message(:alert, :failure, kind: I18n.t('devise.saml.provider_name'), reason: error_message)
|
||||
else
|
||||
set_flash_message(:notice, :success, kind: I18n.t('devise.saml.provider_name'))
|
||||
end
|
||||
end
|
||||
|
||||
# More info at:
|
||||
# https://github.com/plataformatec/devise#omniauth
|
||||
|
||||
|
@ -273,5 +294,33 @@ module Users
|
|||
initials = initials.strip.blank? ? 'PLCH' : initials[0..3]
|
||||
initials
|
||||
end
|
||||
|
||||
def create_user_from_auth(email, auth)
|
||||
full_name = "#{auth.info.first_name} #{auth.info.last_name}"
|
||||
user = User.new(full_name: full_name,
|
||||
initials: generate_initials(full_name),
|
||||
email: 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
|
||||
user
|
||||
end
|
||||
|
||||
def create_user_from_auth(email, auth)
|
||||
full_name = "#{auth.info.first_name} #{auth.info.last_name}"
|
||||
user = User.new(full_name: full_name,
|
||||
initials: generate_initials(full_name),
|
||||
email: 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
|
||||
user
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -209,6 +209,10 @@ module ApplicationHelper
|
|||
ApplicationSettings.instance.values['openid_connect'].present?
|
||||
end
|
||||
|
||||
def saml_configured?
|
||||
ApplicationSettings.instance.values['saml'].present?
|
||||
end
|
||||
|
||||
def wopi_enabled?
|
||||
ENV['WOPI_ENABLED'] == 'true'
|
||||
end
|
||||
|
|
|
@ -55,5 +55,13 @@
|
|||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<% if sso_enabled? && saml_configured? %>
|
||||
<div class="azure-sign-in-actions">
|
||||
<%= form_tag user_saml_omniauth_authorize_path, method: :post do %>
|
||||
<%= submit_tag t('devise.sessions.new.saml_submit'), class: 'btn btn-primary' %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
|
|
|
@ -162,7 +162,7 @@ class Extends
|
|||
'RepositoryStatusValue' => 'status',
|
||||
'RepositoryStockValue' => 'stock' }
|
||||
|
||||
OMNIAUTH_PROVIDERS = %i(linkedin customazureactivedirectory okta openid_connect)
|
||||
OMNIAUTH_PROVIDERS = %i(linkedin customazureactivedirectory okta openid_connect saml)
|
||||
|
||||
INITIAL_USER_OPTIONS = {}
|
||||
|
||||
|
|
|
@ -87,6 +87,17 @@ OKTA_SETUP_PROC = lambda do |env|
|
|||
env['omniauth.strategy'].options[:client_options] = client_options
|
||||
end
|
||||
|
||||
SAML_SETUP_PROC = lambda do |env|
|
||||
settings = ApplicationSettings.instance
|
||||
provider_conf = settings.values['saml']
|
||||
raise StandardError, 'No SAML config available for sign in' if provider_conf.blank?
|
||||
|
||||
env['omniauth.strategy'].options[:idp_sso_service_url] = provider_conf['idp_sso_service_url']
|
||||
env['omniauth.strategy'].options[:idp_cert] = provider_conf['idp_cert']
|
||||
env['omniauth.strategy'].options[:sp_entity_id] = provider_conf['sp_entity_id']
|
||||
env['omniauth.strategy'].options[:uid_attribute] = 'uid'
|
||||
end
|
||||
|
||||
Rails.application.config.middleware.use OmniAuth::Builder do
|
||||
provider OmniAuth::Strategies::CustomAzureActiveDirectory, setup: AZURE_SETUP_PROC
|
||||
end
|
||||
|
@ -99,4 +110,8 @@ Rails.application.config.middleware.use OmniAuth::Builder do
|
|||
provider OmniAuth::Strategies::Okta, setup: OKTA_SETUP_PROC
|
||||
end
|
||||
|
||||
Rails.application.config.middleware.use OmniAuth::Builder do
|
||||
provider OmniAuth::Strategies::SAML, setup: SAML_SETUP_PROC
|
||||
end
|
||||
|
||||
OmniAuth.config.logger = Rails.logger
|
||||
|
|
|
@ -34,6 +34,7 @@ en:
|
|||
remember_me: "Remember me"
|
||||
submit: "Log in"
|
||||
azure_ad_submit: "Sign in with Azure AD"
|
||||
saml_submit: "Sign in with SAML"
|
||||
2fa:
|
||||
title: "Two-factor authentication"
|
||||
description: "Enter the one-time code found in your authenticator app to log in to SciNote."
|
||||
|
@ -100,6 +101,13 @@ en:
|
|||
generic: "Failed to sign in user"
|
||||
no_local_user_map: "No local user record found"
|
||||
failed_to_save: "Failed to create new user"
|
||||
saml:
|
||||
provider_name: "SAML"
|
||||
sign_in_label: "Sign in with SAML"
|
||||
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:
|
||||
|
|
Loading…
Add table
Reference in a new issue