mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2024-09-20 14:45:56 +08:00
Merge branch 'features/sso-improvements' into develop
This commit is contained in:
commit
c740452086
5
Gemfile
5
Gemfile
|
@ -4,6 +4,7 @@ source 'http://rubygems.org'
|
|||
|
||||
ruby '3.2.2'
|
||||
|
||||
gem 'activerecord-session_store'
|
||||
gem 'bootsnap', require: false
|
||||
gem 'devise', '~> 4.8.1'
|
||||
gem 'devise_invitable'
|
||||
|
@ -24,7 +25,9 @@ gem 'omniauth', '~> 2.1'
|
|||
gem 'omniauth-azure-activedirectory-v2'
|
||||
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'
|
||||
|
@ -91,7 +94,7 @@ gem 'graphviz'
|
|||
gem 'cssbundling-rails'
|
||||
gem 'jsbundling-rails'
|
||||
|
||||
gem 'tailwindcss-rails', '~> 2.0'
|
||||
gem 'tailwindcss-rails', '~> 2.4'
|
||||
|
||||
gem 'base62' # Used for smart annotations
|
||||
gem 'newrelic_rpm'
|
||||
|
|
64
Gemfile.lock
64
Gemfile.lock
|
@ -120,6 +120,13 @@ GEM
|
|||
activesupport (= 7.0.8.1)
|
||||
activerecord-import (1.4.1)
|
||||
activerecord (>= 4.2)
|
||||
activerecord-session_store (2.1.0)
|
||||
actionpack (>= 6.1)
|
||||
activerecord (>= 6.1)
|
||||
cgi (>= 0.3.6)
|
||||
multi_json (~> 1.11, >= 1.11.2)
|
||||
rack (>= 2.0.8, < 4)
|
||||
railties (>= 6.1)
|
||||
activestorage (7.0.8.1)
|
||||
actionpack (= 7.0.8.1)
|
||||
activejob (= 7.0.8.1)
|
||||
|
@ -141,6 +148,7 @@ GEM
|
|||
railties (>= 3.1)
|
||||
aspector (0.14.0)
|
||||
ast (2.4.2)
|
||||
attr_required (1.0.1)
|
||||
auto_strip_attributes (2.6.0)
|
||||
activerecord (>= 4.0)
|
||||
awesome_print (1.9.2)
|
||||
|
@ -219,6 +227,7 @@ GEM
|
|||
mail
|
||||
case_transform (0.2)
|
||||
activesupport
|
||||
cgi (0.4.1)
|
||||
childprocess (4.1.0)
|
||||
chunky_png (1.4.0)
|
||||
coderay (1.1.3)
|
||||
|
@ -478,6 +487,25 @@ GEM
|
|||
omniauth-rails_csrf_protection (1.0.1)
|
||||
actionpack (>= 4.2)
|
||||
omniauth (~> 2.0)
|
||||
omniauth-saml (2.1.0)
|
||||
omniauth (~> 2.0)
|
||||
ruby-saml (~> 1.12)
|
||||
omniauth_openid_connect (0.7.1)
|
||||
omniauth (>= 1.9, < 3)
|
||||
openid_connect (~> 2.2)
|
||||
openid_connect (2.2.0)
|
||||
activemodel
|
||||
attr_required (>= 1.0.0)
|
||||
faraday (~> 2.0)
|
||||
faraday-follow_redirects
|
||||
json-jwt (>= 1.16)
|
||||
net-smtp
|
||||
rack-oauth2 (~> 2.2)
|
||||
swd (~> 2.0)
|
||||
tzinfo
|
||||
validate_email
|
||||
validate_url
|
||||
webfinger (~> 2.0)
|
||||
orm_adapter (0.5.0)
|
||||
overcommit (0.60.0)
|
||||
childprocess (>= 0.6.3, < 5)
|
||||
|
@ -518,6 +546,13 @@ GEM
|
|||
rack (>= 1.0, < 3)
|
||||
rack-cors (2.0.2)
|
||||
rack (>= 2.0.0)
|
||||
rack-oauth2 (2.2.0)
|
||||
activesupport
|
||||
attr_required
|
||||
faraday (~> 2.0)
|
||||
faraday-follow_redirects
|
||||
json-jwt (>= 1.11.0)
|
||||
rack (>= 2.1.0)
|
||||
rack-protection (3.0.6)
|
||||
rack
|
||||
rack-test (2.1.0)
|
||||
|
@ -626,6 +661,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)
|
||||
rubyzip (2.3.2)
|
||||
|
@ -663,13 +701,18 @@ GEM
|
|||
activesupport (>= 5.2)
|
||||
sprockets (>= 3.0.0)
|
||||
stream (0.5.5)
|
||||
swd (2.0.2)
|
||||
activesupport (>= 3)
|
||||
attr_required (>= 0.0.5)
|
||||
faraday (~> 2.0)
|
||||
faraday-follow_redirects
|
||||
sys-uname (1.2.3)
|
||||
ffi (~> 1.1)
|
||||
tailwindcss-rails (2.0.29)
|
||||
tailwindcss-rails (2.4.0)
|
||||
railties (>= 6.0.0)
|
||||
tailwindcss-rails (2.0.29-arm64-darwin)
|
||||
tailwindcss-rails (2.4.0-arm64-darwin)
|
||||
railties (>= 6.0.0)
|
||||
tailwindcss-rails (2.0.29-x86_64-linux)
|
||||
tailwindcss-rails (2.4.0-x86_64-linux)
|
||||
railties (>= 6.0.0)
|
||||
thor (1.3.1)
|
||||
tilt (2.2.0)
|
||||
|
@ -688,6 +731,12 @@ GEM
|
|||
unf_ext (0.0.8.2)
|
||||
unicode-display_width (2.4.2)
|
||||
uniform_notifier (1.16.0)
|
||||
validate_email (0.1.6)
|
||||
activemodel (>= 3.0)
|
||||
mail (>= 2.2.5)
|
||||
validate_url (1.0.15)
|
||||
activemodel (>= 3.0.0)
|
||||
public_suffix
|
||||
uri (0.13.0)
|
||||
version_gem (1.1.3)
|
||||
view_component (3.9.0)
|
||||
|
@ -696,6 +745,10 @@ GEM
|
|||
method_source (~> 1.0)
|
||||
warden (1.2.9)
|
||||
rack (>= 2.0.9)
|
||||
webfinger (2.1.2)
|
||||
activesupport
|
||||
faraday (~> 2.0)
|
||||
faraday-follow_redirects
|
||||
webmock (3.18.1)
|
||||
addressable (>= 2.8.0)
|
||||
crack (>= 0.3.2)
|
||||
|
@ -722,6 +775,7 @@ PLATFORMS
|
|||
DEPENDENCIES
|
||||
active_model_serializers (~> 0.10.7)
|
||||
activerecord-import
|
||||
activerecord-session_store
|
||||
acts_as_list
|
||||
ajax-datatables-rails (~> 0.3.1)
|
||||
aspector
|
||||
|
@ -780,6 +834,8 @@ DEPENDENCIES
|
|||
omniauth-linkedin-oauth2
|
||||
omniauth-okta!
|
||||
omniauth-rails_csrf_protection (~> 1.0)
|
||||
omniauth-saml
|
||||
omniauth_openid_connect
|
||||
overcommit
|
||||
pg (~> 1.5)
|
||||
pg_search
|
||||
|
@ -814,7 +870,7 @@ DEPENDENCIES
|
|||
simplecov
|
||||
sneaky-save!
|
||||
sprockets-rails
|
||||
tailwindcss-rails (~> 2.0)
|
||||
tailwindcss-rails (~> 2.4)
|
||||
timecop
|
||||
turbolinks (~> 5.2.0)
|
||||
tzinfo-data
|
||||
|
|
|
@ -36,7 +36,7 @@
|
|||
timeoutID = setTimeout(functionCallback, timeoutTime);
|
||||
}
|
||||
|
||||
function toogleDocumentTitle(timeString = null) {
|
||||
function toggleDocumentTitle(timeString = null) {
|
||||
var sleepEmoticon = String.fromCodePoint(0x1F62A);
|
||||
var originalTitle = document.title.split(sleepEmoticon).pop().trim();
|
||||
|
||||
|
@ -70,21 +70,21 @@
|
|||
|
||||
function reviveSession() {
|
||||
$.post($('meta[name=\'revive-url\']').attr('content'));
|
||||
toogleDocumentTitle();
|
||||
toggleDocumentTitle();
|
||||
window.localStorage.removeItem('sessionEnd');
|
||||
setSessionTimeout(initializeSessionCountdown, oneSecondTimeout);
|
||||
}
|
||||
|
||||
function initializeSessionReviveCallbacks() {
|
||||
$('#session-expire').modal().off('hide.bs.modal').on('hide.bs.modal', function() {
|
||||
if (sessionExpireIn() > 0) {
|
||||
if (sessionExpireIn() > 0 && sessionExpireIn() < expireLimit) {
|
||||
reviveSession();
|
||||
}
|
||||
});
|
||||
|
||||
// for manual page reload
|
||||
$(window).off('beforeunload').on('beforeunload', function() {
|
||||
if (sessionExpireIn() > 0) {
|
||||
if (sessionExpireIn() > 0 && sessionExpireIn() < expireLimit) {
|
||||
reviveSession();
|
||||
}
|
||||
});
|
||||
|
@ -98,7 +98,7 @@
|
|||
initializeSessionCountdown();
|
||||
} else if (expireIn > 0 && expireIn <= expireLimit) {
|
||||
timeString = newTimerStr(expireIn / 1000);
|
||||
toogleDocumentTitle(timeString);
|
||||
toggleDocumentTitle(timeString);
|
||||
$('.expiring').text(I18n.t('devise.sessions.expire_modal.session_end_in.header', { time: timeString }));
|
||||
|
||||
if (!$('#session-expire').hasClass('in')) {
|
||||
|
@ -107,7 +107,7 @@
|
|||
|
||||
setSessionTimeout(sessionCountdown, oneSecondTimeout);
|
||||
} else if (expireIn <= 0) {
|
||||
toogleDocumentTitle();
|
||||
toggleDocumentTitle();
|
||||
$('#session-expire').modal('hide');
|
||||
$('#session-finished').modal();
|
||||
}
|
||||
|
@ -130,7 +130,7 @@
|
|||
}
|
||||
setSessionTimeout(initializeSessionCountdown, oneSecondTimeout);
|
||||
} else if (expireOn && !event.originalEvent.newValue) {
|
||||
toogleDocumentTitle();
|
||||
toggleDocumentTitle();
|
||||
}
|
||||
|
||||
expireOn = event.originalEvent.newValue;
|
||||
|
|
|
@ -8,7 +8,7 @@ module Users
|
|||
skip_before_action :verify_authenticity_token
|
||||
before_action :sign_up_with_provider_enabled?,
|
||||
only: :linkedin
|
||||
before_action :check_sso_status, only: %i(customazureactivedirectory okta)
|
||||
before_action :check_sso_status, only: %i(customazureactivedirectory okta openid_connect)
|
||||
|
||||
# You should configure your model like this:
|
||||
# devise :omniauthable, omniauth_providers: [:twitter]
|
||||
|
@ -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, event: :authentication)
|
||||
elsif provider_conf['auto_link_on_sign_in']
|
||||
# Link to existing local account
|
||||
|
@ -147,16 +137,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)
|
||||
|
@ -177,6 +158,107 @@ module Users
|
|||
end
|
||||
end
|
||||
|
||||
def openid_connect
|
||||
auth = request.env['omniauth.auth']
|
||||
settings = ApplicationSettings.instance
|
||||
provider_conf = settings.values['openid_connect']
|
||||
raise StandardError, 'No matching OpenID Connect AD provider config found' if provider_conf.blank?
|
||||
|
||||
return redirect_to connected_accounts_path if current_user
|
||||
|
||||
email = auth.info.email
|
||||
email ||= auth.dig(:extra, :raw_info, :id_token_claims, :emails)&.first
|
||||
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.openid_connect.errors.no_email')
|
||||
return redirect_to after_omniauth_failure_path_for(resource_name)
|
||||
end
|
||||
|
||||
user = User.find_by(email: email.downcase)
|
||||
|
||||
if user.blank?
|
||||
# Create new user and identity
|
||||
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.openid_connect.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.openid_connect.errors.failed_to_save') if e.is_a?(ActiveRecord::RecordInvalid)
|
||||
error_message ||= I18n.t('devise.openid_connect.errors.generic')
|
||||
redirect_to after_omniauth_failure_path_for(resource_name)
|
||||
ensure
|
||||
if error_message
|
||||
set_flash_message(:alert, :failure, kind: I18n.t('devise.openid_connect.provider_name'), reason: error_message)
|
||||
else
|
||||
set_flash_message(:notice, :success, kind: I18n.t('devise.openid_connect.provider_name'))
|
||||
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
|
||||
|
||||
|
@ -213,5 +295,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
|
||||
|
|
|
@ -21,7 +21,7 @@ class Users::PasswordsController < Devise::PasswordsController
|
|||
|
||||
if resource.errors.blank?
|
||||
resource.unlock_access! if unlockable?(resource)
|
||||
if !resource.two_factor_auth_enabled?
|
||||
if !two_factor_auth_enabled_for(resource)
|
||||
flash_message = resource.active_for_authentication? ? :updated : :updated_not_active
|
||||
set_flash_message!(:notice, flash_message)
|
||||
resource.after_database_authentication
|
||||
|
@ -39,7 +39,11 @@ class Users::PasswordsController < Devise::PasswordsController
|
|||
protected
|
||||
|
||||
def after_resetting_password_path_for(resource)
|
||||
resource.two_factor_auth_enabled? ? new_session_path(resource_name) : after_sign_in_path_for(resource)
|
||||
two_factor_auth_enabled_for(resource) ? new_session_path(resource_name) : after_sign_in_path_for(resource)
|
||||
end
|
||||
|
||||
def two_factor_auth_enabled_for(user)
|
||||
user.two_factor_auth_enabled?
|
||||
end
|
||||
|
||||
# The path used after sending reset password instructions
|
||||
|
|
|
@ -199,12 +199,21 @@ module ApplicationHelper
|
|||
ENV['SSO_ENABLED'] == 'true'
|
||||
end
|
||||
|
||||
def okta_configured?
|
||||
ApplicationSettings.instance.values['okta'].present?
|
||||
def okta_enabled?
|
||||
ApplicationSettings.instance.values.dig('okta', 'enabled')
|
||||
end
|
||||
|
||||
def azure_ad_configured?
|
||||
ApplicationSettings.instance.values['azure_ad_apps'].present?
|
||||
def azure_ad_enabled?
|
||||
provider_conf = ApplicationSettings.instance.values['azure_ad_apps']
|
||||
provider_conf.present? && provider_conf[0]['enabled']
|
||||
end
|
||||
|
||||
def saml_enabled?
|
||||
ApplicationSettings.instance.values.dig('saml', 'enabled')
|
||||
end
|
||||
|
||||
def openid_connect_enabled?
|
||||
ApplicationSettings.instance.values.dig('openid_connect', 'enabled')
|
||||
end
|
||||
|
||||
def wopi_enabled?
|
||||
|
|
|
@ -10,7 +10,9 @@
|
|||
{{ experiment.name }}
|
||||
</h4>
|
||||
</div>
|
||||
<div class="modal-body whitespace-pre-wrap" v-html="experiment.sa_description"></div>
|
||||
<div class="modal-body">
|
||||
<div class="[&_.atwho-user-container]:!whitespace-normal whitespace-pre-wrap" v-html="experiment.sa_description"></div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-primary" data-dismiss="modal">{{ i18n.t('general.close') }}</button>
|
||||
</div>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<div class="group relative flex items-center group-hover:marker text-xs h-full w-full"
|
||||
:style="{ lineHeight: 'unset' }">
|
||||
<div class="flex gap-2"
|
||||
<div class="flex gap-2 w-full"
|
||||
:style="{ lineHeight: 'unset' }"
|
||||
:class="{
|
||||
'items-center text-sm': params.dtComponent.currentViewRender === 'table',
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
<div class="panel panel-default">
|
||||
<div class="panel-body">
|
||||
<div class="col-xs-8 col-sm-9 col-md-9 col-lg-9">
|
||||
<strong><%= t('users.settings.account.connected_accounts.openid_connect.title') %></strong> <br>
|
||||
<p><%= t('users.settings.account.connected_accounts.openid_connect.connect_hint') %></p>
|
||||
</div>
|
||||
<div class="pull-right">
|
||||
<div>
|
||||
<strong>
|
||||
<%= t('users.settings.account.connected_accounts.openid_connect.connected') %>
|
||||
<span class="sn-icon sn-icon-check" aria-hidden="true"></span>
|
||||
</strong>
|
||||
</div>
|
||||
<div>
|
||||
<%= link_to t('users.settings.account.connected_accounts.openid_connect.unlink_button'),
|
||||
'#unlinkOpenIdConnectModal',
|
||||
class: 'btn btn-danger',
|
||||
data: { toggle: 'modal'} %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%= render partial: 'users/settings/account/connected_accounts/unlink_modals/openid_connect_modal', locals: { provider: provider } %>
|
|
@ -0,0 +1,24 @@
|
|||
<div class="panel panel-default">
|
||||
<div class="panel-body">
|
||||
<div class="col-xs-8 col-sm-9 col-md-9 col-lg-9">
|
||||
<strong><%= t('users.settings.account.connected_accounts.saml.title') %></strong> <br>
|
||||
<p><%= t('users.settings.account.connected_accounts.saml.connect_hint') %></p>
|
||||
</div>
|
||||
<div class="pull-right">
|
||||
<div>
|
||||
<strong>
|
||||
<%= t('users.settings.account.connected_accounts.saml.connected') %>
|
||||
<span class="sn-icon sn-icon-check" aria-hidden="true"></span>
|
||||
</strong>
|
||||
</div>
|
||||
<div>
|
||||
<%= link_to t('users.settings.account.connected_accounts.saml.unlink_button'),
|
||||
'#unlinksamlModal',
|
||||
class: 'btn btn-danger',
|
||||
data: { toggle: 'modal'} %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%= render partial: 'users/settings/account/connected_accounts/unlink_modals/saml_modal', locals: { provider: provider } %>
|
|
@ -0,0 +1,23 @@
|
|||
<div class="modal fade" id="unlinkOpenIdConnectModal" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="<%= t('general.close') %>">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
<h4 class="modal-title" >
|
||||
<%= t('users.settings.account.connected_accounts.openid_connect.unlink_modal.title') %>
|
||||
</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p><%= t('users.settings.account.connected_accounts.openid_connect.unlink_modal.description_1') %></p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<%= form_tag(unlink_connected_account_path, method: :delete) do %>
|
||||
<%= hidden_field_tag :provider, provider %>
|
||||
<%= submit_tag t('users.settings.account.connected_accounts.openid_connect.unlink_modal.submit_button'), class: 'btn btn-danger' %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,23 @@
|
|||
<div class="modal fade" id="unlinksamlModal" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="<%= t('general.close') %>">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
<h4 class="modal-title" >
|
||||
<%= t('users.settings.account.connected_accounts.saml.unlink_modal.title') %>
|
||||
</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p><%= t('users.settings.account.connected_accounts.saml.unlink_modal.description_1') %></p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<%= form_tag(unlink_connected_account_path, method: :delete) do %>
|
||||
<%= hidden_field_tag :provider, provider %>
|
||||
<%= submit_tag t('users.settings.account.connected_accounts.saml.unlink_modal.submit_button'), class: 'btn btn-danger' %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -28,7 +28,7 @@
|
|||
<% end -%>
|
||||
|
||||
<% if controller_name != 'passwords'%>
|
||||
<%- if sso_enabled? && okta_configured? %>
|
||||
<%- if sso_enabled? && okta_enabled? %>
|
||||
<div class="okta-sign-in-actions">
|
||||
<%= form_tag user_okta_omniauth_authorize_path, method: :post, id: 'oktaForm' do %>
|
||||
<%= submit_tag t('devise.okta.sign_in_label'), class: 'btn btn-okta' %>
|
||||
|
@ -42,10 +42,26 @@
|
|||
<% end -%>
|
||||
<% end -%>
|
||||
|
||||
<% if sso_enabled? && azure_ad_configured? %>
|
||||
<% if sso_enabled? && azure_ad_enabled? %>
|
||||
<div class="azure-sign-in-actions">
|
||||
<%= render partial: "users/shared/azure_sign_in_links", locals: { resource_name: resource_name } %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<%- if sso_enabled? && openid_connect_enabled? %>
|
||||
<div class="azure-sign-in-actions">
|
||||
<%= form_tag user_openid_connect_omniauth_authorize_path, method: :post do %>
|
||||
<%= submit_tag t('devise.sessions.new.openid_connect_submit'), class: 'btn btn-primary' %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<% if sso_enabled? && saml_enabled? %>
|
||||
<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>
|
||||
|
|
|
@ -82,5 +82,7 @@ module Scinote
|
|||
config.action_view.field_error_proc = Proc.new { |html_tag, instance|
|
||||
"<div class=\"field_with_errors sci-input-container\">#{html_tag}</div>".html_safe
|
||||
}
|
||||
|
||||
ActiveRecord::SessionStore::Session.serializer = :json
|
||||
end
|
||||
end
|
||||
|
|
|
@ -11,9 +11,9 @@ ActiveSupport::Reloader.to_prepare do
|
|||
policy.font_src :self, :https, :data
|
||||
policy.img_src :self, :https, :data, :blob
|
||||
policy.object_src :none
|
||||
policy.script_src :self, :unsafe_eval, *Extends::EXTERNAL_SERVICES
|
||||
policy.script_src :self, :unsafe_eval, *Extends::EXTERNAL_SCRIPT_SERVICES
|
||||
policy.style_src :self, :https, :unsafe_inline, :data
|
||||
policy.connect_src :self, :data, *Extends::EXTERNAL_SERVICES
|
||||
policy.connect_src :self, :data, *Extends::EXTERNAL_CONNECT_SERVICES
|
||||
|
||||
# Specify URI for violation reports
|
||||
# policy.report_uri "/csp-violation-report-endpoint"
|
||||
|
@ -44,8 +44,8 @@ Rails.application.config.content_security_policy_nonce_directives = %w(script-sr
|
|||
Rails.application.configure do
|
||||
config.after_initialize do
|
||||
if ActiveStorage::Blob.service.name == :amazon
|
||||
Extends::EXTERNAL_SERVICES += [ActiveStorage::Blob.service.bucket.url]
|
||||
Rails.application.config.content_security_policy.connect_src :self, :data, *Extends::EXTERNAL_SERVICES
|
||||
Extends::EXTERNAL_CONNECT_SERVICES += [ActiveStorage::Blob.service.bucket.url]
|
||||
Rails.application.config.content_security_policy.connect_src :self, :data, *Extends::EXTERNAL_CONNECT_SERVICES
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -146,7 +146,7 @@ class Extends
|
|||
API_VERSIONS = ['v1']
|
||||
|
||||
# Array used for injecting names of additional authentication methods for API
|
||||
API_PLUGABLE_AUTH_METHODS = [:azure_jwt_auth]
|
||||
API_PLUGABLE_AUTH_METHODS = []
|
||||
|
||||
API_REPOSITORY_DATA_TYPE_MAPPINGS = { 'RepositoryTextValue' => 'text',
|
||||
'RepositoryDateValue' => 'date',
|
||||
|
@ -162,7 +162,7 @@ class Extends
|
|||
'RepositoryStatusValue' => 'status',
|
||||
'RepositoryStockValue' => 'stock' }
|
||||
|
||||
OMNIAUTH_PROVIDERS = %i(linkedin customazureactivedirectory okta)
|
||||
OMNIAUTH_PROVIDERS = %i(linkedin customazureactivedirectory okta openid_connect saml)
|
||||
|
||||
INITIAL_USER_OPTIONS = {}
|
||||
|
||||
|
@ -589,22 +589,25 @@ class Extends
|
|||
'FluicsLabelTemplate' => 'Fluics'
|
||||
}
|
||||
|
||||
EXTERNAL_SERVICES = %w(
|
||||
EXTERNAL_SCRIPT_SERVICES = %w(
|
||||
https://marvinjs.chemicalize.com/
|
||||
www.recaptcha.net/
|
||||
www.gstatic.com/recaptcha/
|
||||
)
|
||||
|
||||
EXTERNAL_CONNECT_SERVICES = %w(
|
||||
https://www.protocols.io/
|
||||
http://127.0.0.1:9100/
|
||||
https://marvinjs.chemicalize.com/
|
||||
newrelic.com
|
||||
*.newrelic.com
|
||||
*.nr-data.net
|
||||
www.recaptcha.net/
|
||||
www.gstatic.com/recaptcha/
|
||||
extras.scinote.net
|
||||
https://www.scinote.net
|
||||
)
|
||||
|
||||
if Constants::ASSET_SYNC_URL && EXTERNAL_SERVICES.exclude?(Constants::ASSET_SYNC_URL)
|
||||
if Constants::ASSET_SYNC_URL && EXTERNAL_CONNECT_SERVICES.exclude?(Constants::ASSET_SYNC_URL)
|
||||
asset_sync_url = URI.parse(Constants::ASSET_SYNC_URL)
|
||||
EXTERNAL_SERVICES << "#{asset_sync_url.scheme}://#{asset_sync_url.host}:#{asset_sync_url.port}"
|
||||
EXTERNAL_CONNECT_SERVICES << "#{asset_sync_url.scheme}://#{asset_sync_url.host}:#{asset_sync_url.port}"
|
||||
end
|
||||
|
||||
COLORED_BACKGROUND_ACTIONS = %w(
|
||||
|
|
|
@ -5,7 +5,7 @@ require 'omniauth/strategies/custom_azure_active_directory'
|
|||
AZURE_SETUP_PROC = lambda do |env|
|
||||
settings = ApplicationSettings.instance
|
||||
providers = settings.values['azure_ad_apps'].select { |v| v['enable_sign_in'] }
|
||||
raise StandardError, 'No Azure AD config available for sign in' if providers.blank?
|
||||
raise StandardError, 'No Azure AD config available for sign in' unless providers.present? && providers[0]['enabled']
|
||||
|
||||
req = Rack::Request.new(env)
|
||||
|
||||
|
@ -32,10 +32,36 @@ AZURE_SETUP_PROC = lambda do |env|
|
|||
env['omniauth.strategy'].options[:base_azure_url] = "#{conf_uri.scheme || 'https'}://#{conf_uri.host}"
|
||||
end
|
||||
|
||||
OPENID_CONNECT_SETUP_PROC = lambda do |env|
|
||||
settings = ApplicationSettings.instance
|
||||
provider_conf = settings.values['openid_connect']
|
||||
raise StandardError, 'No OpenID Connect config available for sign in' if provider_conf.blank?
|
||||
|
||||
client_options = {
|
||||
identifier: provider_conf['client_id'],
|
||||
secret: provider_conf['client_secret'],
|
||||
redirect_uri: Rails.application.routes.url_helpers.user_openid_connect_omniauth_callback_url
|
||||
}
|
||||
|
||||
unless provider_conf['discovery']
|
||||
client_options[:host] = provider_conf['host']
|
||||
client_options[:authorization_endpoint] = provider_conf['authorization_endpoint']
|
||||
client_options[:token_endpoint] = provider_conf['token_endpoint']
|
||||
client_options[:userinfo_endpoint] = provider_conf['userinfo_endpoint']
|
||||
client_options[:jwks_uri] = provider_conf['jwks_uri']
|
||||
end
|
||||
|
||||
env['omniauth.strategy'].options[:name] = 'openid_connect'
|
||||
env['omniauth.strategy'].options[:scope] = %i(openid email profile)
|
||||
env['omniauth.strategy'].options[:issuer] = provider_conf['issuer_url']
|
||||
env['omniauth.strategy'].options[:discovery] = provider_conf['discovery'] == true
|
||||
env['omniauth.strategy'].options[:client_options] = client_options
|
||||
end
|
||||
|
||||
OKTA_SETUP_PROC = lambda do |env|
|
||||
settings = ApplicationSettings.instance
|
||||
provider_conf = settings.values['okta']
|
||||
raise StandardError, 'No Okta config available for sign in' if provider_conf.blank?
|
||||
raise StandardError, 'No Okta config available for sign in' unless provider_conf.present? && provider_conf['enabled']
|
||||
|
||||
oauth2_base_url =
|
||||
if provider_conf['auth_server_id'].blank?
|
||||
|
@ -63,12 +89,31 @@ 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
|
||||
|
||||
Rails.application.config.middleware.use OmniAuth::Builder do
|
||||
provider OmniAuth::Strategies::OpenIDConnect, setup: OPENID_CONNECT_SETUP_PROC
|
||||
end
|
||||
|
||||
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
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
# Be sure to restart your server when you modify this file.
|
||||
|
||||
Rails.application.config.session_store :cookie_store, key: '_scinote_session'
|
||||
session_store = ENV['SCINOTE_SERVERSIDE_SESSIONS'] == 'true' ? :active_record_store : :cookie_store
|
||||
|
||||
Rails.application.config.session_store session_store, key: '_scinote_session'
|
||||
|
|
|
@ -34,6 +34,8 @@ en:
|
|||
remember_me: "Remember me"
|
||||
submit: "Log in"
|
||||
azure_ad_submit: "Sign in with Azure AD"
|
||||
openid_connect_submit: "Sign in with OpenID Connect"
|
||||
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."
|
||||
|
@ -86,6 +88,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"
|
||||
openid_connect:
|
||||
provider_name: "OpenID Connect"
|
||||
errors:
|
||||
generic: "Failed to sign in user"
|
||||
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"
|
||||
|
@ -93,6 +102,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:
|
||||
|
@ -2883,13 +2899,13 @@ en:
|
|||
not_connected: "You have no Connected accounts"
|
||||
unlink_success: "Successfully unlinked"
|
||||
azure_ad:
|
||||
title: "Your Azure AD Account"
|
||||
connect_hint: "Allows you to sign in with your Azure AD account."
|
||||
title: "Your Microsoft Entra ID Account"
|
||||
connect_hint: "Allows you to sign in with your Microsoft Entra ID account."
|
||||
connected: "Connected"
|
||||
unlink_button: "Unlink"
|
||||
unlink_modal:
|
||||
title: "Unlink Azure AD account?"
|
||||
description_1: "Are you sure you would like unlink Azure AD and SciNote accounts?"
|
||||
title: "Unlink Microsoft Entra ID account?"
|
||||
description_1: "Are you sure you would like unlink Microsoft Entra ID and SciNote accounts?"
|
||||
submit_button: "Submit"
|
||||
okta:
|
||||
title: "Your Okta Account"
|
||||
|
@ -2900,6 +2916,24 @@ en:
|
|||
title: "Unlink Okta account?"
|
||||
description_1: "Are you sure you would like unlink Okta and SciNote accounts?"
|
||||
submit_button: "Submit"
|
||||
openid_connect:
|
||||
title: "Your OpenID Connect Account"
|
||||
connect_hint: "Allows you to sign in with your OpenID Connect account."
|
||||
connected: "Connected"
|
||||
unlink_button: "Unlink"
|
||||
unlink_modal:
|
||||
title: "Unlink OpenID Connect account?"
|
||||
description_1: "Are you sure you would like unlink OpenID Connect and SciNote accounts?"
|
||||
submit_button: "Submit"
|
||||
saml:
|
||||
title: "Your SAML Account"
|
||||
connect_hint: "Allows you to sign in with your SAML account."
|
||||
connected: "Connected"
|
||||
unlink_button: "Unlink"
|
||||
unlink_modal:
|
||||
title: "Unlink SAML account?"
|
||||
description_1: "Are you sure you would like unlink SAML and SciNote accounts?"
|
||||
submit_button: "Submit"
|
||||
errors:
|
||||
not_found: "You have no Connected accounts for this provider"
|
||||
generic: "Unable to unlink linked account"
|
||||
|
|
14
db/migrate/20240118094253_add_sessions_table.rb
Normal file
14
db/migrate/20240118094253_add_sessions_table.rb
Normal file
|
@ -0,0 +1,14 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddSessionsTable < ActiveRecord::Migration[7.0]
|
||||
def change
|
||||
create_table :sessions do |t|
|
||||
t.string :session_id, null: false
|
||||
t.jsonb :data
|
||||
t.timestamps
|
||||
end
|
||||
|
||||
add_index :sessions, :session_id, unique: true
|
||||
add_index :sessions, :updated_at
|
||||
end
|
||||
end
|
11
db/schema.rb
11
db/schema.rb
|
@ -10,7 +10,7 @@
|
|||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema[7.0].define(version: 2024_01_15_140943) do
|
||||
ActiveRecord::Schema[7.0].define(version: 2024_01_18_094253) do
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "btree_gist"
|
||||
enable_extension "pg_trgm"
|
||||
|
@ -994,6 +994,15 @@ ActiveRecord::Schema[7.0].define(version: 2024_01_15_140943) do
|
|||
t.index ["user_id"], name: "index_results_on_user_id"
|
||||
end
|
||||
|
||||
create_table "sessions", force: :cascade do |t|
|
||||
t.string "session_id", null: false
|
||||
t.jsonb "data"
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.index ["session_id"], name: "index_sessions_on_session_id", unique: true
|
||||
t.index ["updated_at"], name: "index_sessions_on_updated_at"
|
||||
end
|
||||
|
||||
create_table "settings", force: :cascade do |t|
|
||||
t.text "type", null: false
|
||||
t.jsonb "values", default: {}, null: false
|
||||
|
|
Loading…
Reference in a new issue