mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2025-10-04 02:45:53 +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-okta', git: 'https://github.com/scinote-eln/omniauth-okta', branch: 'org_auth_server_support'
|
||||||
gem 'omniauth_openid_connect'
|
gem 'omniauth_openid_connect'
|
||||||
gem 'omniauth-rails_csrf_protection', '~> 1.0'
|
gem 'omniauth-rails_csrf_protection', '~> 1.0'
|
||||||
|
gem 'omniauth-saml'
|
||||||
|
|
||||||
# Gems for API implementation
|
# Gems for API implementation
|
||||||
gem 'active_model_serializers', '~> 0.10.7'
|
gem 'active_model_serializers', '~> 0.10.7'
|
||||||
|
|
|
@ -478,6 +478,9 @@ GEM
|
||||||
omniauth_openid_connect (0.7.1)
|
omniauth_openid_connect (0.7.1)
|
||||||
omniauth (>= 1.9, < 3)
|
omniauth (>= 1.9, < 3)
|
||||||
openid_connect (~> 2.2)
|
openid_connect (~> 2.2)
|
||||||
|
omniauth-saml (2.1.0)
|
||||||
|
omniauth (~> 2.0)
|
||||||
|
ruby-saml (~> 1.12)
|
||||||
openid_connect (2.2.0)
|
openid_connect (2.2.0)
|
||||||
activemodel
|
activemodel
|
||||||
attr_required (>= 1.0.0)
|
attr_required (>= 1.0.0)
|
||||||
|
@ -646,6 +649,9 @@ GEM
|
||||||
rubocop (>= 1.33.0, < 2.0)
|
rubocop (>= 1.33.0, < 2.0)
|
||||||
ruby-progressbar (1.13.0)
|
ruby-progressbar (1.13.0)
|
||||||
ruby-rc4 (0.1.5)
|
ruby-rc4 (0.1.5)
|
||||||
|
ruby-saml (1.16.0)
|
||||||
|
nokogiri (>= 1.13.10)
|
||||||
|
rexml
|
||||||
ruby-vips (2.1.4)
|
ruby-vips (2.1.4)
|
||||||
ffi (~> 1.12)
|
ffi (~> 1.12)
|
||||||
ruby2_keywords (0.0.5)
|
ruby2_keywords (0.0.5)
|
||||||
|
@ -815,6 +821,7 @@ DEPENDENCIES
|
||||||
omniauth-linkedin-oauth2
|
omniauth-linkedin-oauth2
|
||||||
omniauth-okta!
|
omniauth-okta!
|
||||||
omniauth-rails_csrf_protection (~> 1.0)
|
omniauth-rails_csrf_protection (~> 1.0)
|
||||||
|
omniauth-saml
|
||||||
omniauth_openid_connect
|
omniauth_openid_connect
|
||||||
overcommit
|
overcommit
|
||||||
pg (~> 1.5)
|
pg (~> 1.5)
|
||||||
|
|
|
@ -46,17 +46,7 @@ module Users
|
||||||
|
|
||||||
if user.blank?
|
if user.blank?
|
||||||
# Create new user and identity
|
# Create new user and identity
|
||||||
full_name = "#{auth.info.first_name} #{auth.info.last_name}"
|
user = create_user_from_auth(email, auth)
|
||||||
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
|
|
||||||
|
|
||||||
sign_in_and_redirect(user)
|
sign_in_and_redirect(user)
|
||||||
elsif provider_conf['auto_link_on_sign_in']
|
elsif provider_conf['auto_link_on_sign_in']
|
||||||
# Link to existing local account
|
# Link to existing local account
|
||||||
|
@ -146,16 +136,7 @@ module Users
|
||||||
user = User.find_by(email: auth.info.email.downcase)
|
user = User.find_by(email: auth.info.email.downcase)
|
||||||
|
|
||||||
if user.blank?
|
if user.blank?
|
||||||
# Create new user and identity
|
user = create_user_from_auth(email, auth)
|
||||||
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
|
else
|
||||||
# Link to existing local account
|
# Link to existing local account
|
||||||
user.user_identities.create!(provider: auth.provider, uid: auth.uid)
|
user.user_identities.create!(provider: auth.provider, uid: auth.uid)
|
||||||
|
@ -201,17 +182,7 @@ module Users
|
||||||
|
|
||||||
if user.blank?
|
if user.blank?
|
||||||
# Create new user and identity
|
# Create new user and identity
|
||||||
full_name = "#{auth.info.first_name} #{auth.info.last_name}"
|
user = create_user_from_auth(email, auth)
|
||||||
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
|
|
||||||
|
|
||||||
sign_in_and_redirect(user)
|
sign_in_and_redirect(user)
|
||||||
elsif provider_conf['auto_link_on_sign_in']
|
elsif provider_conf['auto_link_on_sign_in']
|
||||||
# Link to existing local account
|
# Link to existing local account
|
||||||
|
@ -237,6 +208,56 @@ module Users
|
||||||
end
|
end
|
||||||
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:
|
# More info at:
|
||||||
# https://github.com/plataformatec/devise#omniauth
|
# https://github.com/plataformatec/devise#omniauth
|
||||||
|
|
||||||
|
@ -273,5 +294,33 @@ module Users
|
||||||
initials = initials.strip.blank? ? 'PLCH' : initials[0..3]
|
initials = initials.strip.blank? ? 'PLCH' : initials[0..3]
|
||||||
initials
|
initials
|
||||||
end
|
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
|
||||||
end
|
end
|
||||||
|
|
|
@ -209,6 +209,10 @@ module ApplicationHelper
|
||||||
ApplicationSettings.instance.values['openid_connect'].present?
|
ApplicationSettings.instance.values['openid_connect'].present?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def saml_configured?
|
||||||
|
ApplicationSettings.instance.values['saml'].present?
|
||||||
|
end
|
||||||
|
|
||||||
def wopi_enabled?
|
def wopi_enabled?
|
||||||
ENV['WOPI_ENABLED'] == 'true'
|
ENV['WOPI_ENABLED'] == 'true'
|
||||||
end
|
end
|
||||||
|
|
|
@ -55,5 +55,13 @@
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% 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 %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -162,7 +162,7 @@ class Extends
|
||||||
'RepositoryStatusValue' => 'status',
|
'RepositoryStatusValue' => 'status',
|
||||||
'RepositoryStockValue' => 'stock' }
|
'RepositoryStockValue' => 'stock' }
|
||||||
|
|
||||||
OMNIAUTH_PROVIDERS = %i(linkedin customazureactivedirectory okta openid_connect)
|
OMNIAUTH_PROVIDERS = %i(linkedin customazureactivedirectory okta openid_connect saml)
|
||||||
|
|
||||||
INITIAL_USER_OPTIONS = {}
|
INITIAL_USER_OPTIONS = {}
|
||||||
|
|
||||||
|
|
|
@ -87,6 +87,17 @@ OKTA_SETUP_PROC = lambda do |env|
|
||||||
env['omniauth.strategy'].options[:client_options] = client_options
|
env['omniauth.strategy'].options[:client_options] = client_options
|
||||||
end
|
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
|
Rails.application.config.middleware.use OmniAuth::Builder do
|
||||||
provider OmniAuth::Strategies::CustomAzureActiveDirectory, setup: AZURE_SETUP_PROC
|
provider OmniAuth::Strategies::CustomAzureActiveDirectory, setup: AZURE_SETUP_PROC
|
||||||
end
|
end
|
||||||
|
@ -99,4 +110,8 @@ Rails.application.config.middleware.use OmniAuth::Builder do
|
||||||
provider OmniAuth::Strategies::Okta, setup: OKTA_SETUP_PROC
|
provider OmniAuth::Strategies::Okta, setup: OKTA_SETUP_PROC
|
||||||
end
|
end
|
||||||
|
|
||||||
|
Rails.application.config.middleware.use OmniAuth::Builder do
|
||||||
|
provider OmniAuth::Strategies::SAML, setup: SAML_SETUP_PROC
|
||||||
|
end
|
||||||
|
|
||||||
OmniAuth.config.logger = Rails.logger
|
OmniAuth.config.logger = Rails.logger
|
||||||
|
|
|
@ -34,6 +34,7 @@ en:
|
||||||
remember_me: "Remember me"
|
remember_me: "Remember me"
|
||||||
submit: "Log in"
|
submit: "Log in"
|
||||||
azure_ad_submit: "Sign in with Azure AD"
|
azure_ad_submit: "Sign in with Azure AD"
|
||||||
|
saml_submit: "Sign in with SAML"
|
||||||
2fa:
|
2fa:
|
||||||
title: "Two-factor authentication"
|
title: "Two-factor authentication"
|
||||||
description: "Enter the one-time code found in your authenticator app to log in to SciNote."
|
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"
|
generic: "Failed to sign in user"
|
||||||
no_local_user_map: "No local user record found"
|
no_local_user_map: "No local user record found"
|
||||||
failed_to_save: "Failed to create new user"
|
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:
|
doorkeeper:
|
||||||
errors:
|
errors:
|
||||||
|
|
Loading…
Add table
Reference in a new issue