diff --git a/app/controllers/users/omniauth_callbacks_controller.rb b/app/controllers/users/omniauth_callbacks_controller.rb index 4a6057d69..62016a421 100644 --- a/app/controllers/users/omniauth_callbacks_controller.rb +++ b/app/controllers/users/omniauth_callbacks_controller.rb @@ -18,10 +18,11 @@ module Users def customazureactivedirectory auth = request.env['omniauth.auth'] provider_id = auth.dig(:extra, :raw_info, :aud) - provider_conf = Rails.configuration.x.azure_ad_apps[provider_id] + settings = ApplicationSettings.instance + provider_conf = settings.values['azure_ad_apps'].find { |v| v['enable_sign_in'] && v['app_id'] == provider_id } raise StandardError, 'No matching Azure AD provider config found' if provider_conf.blank? - auth.provider = provider_conf[:provider] + auth.provider = provider_conf['provider_name'] return redirect_to connected_accounts_path if current_user diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 4bada48a7..a9f5f1fdb 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -219,6 +219,14 @@ module ApplicationHelper 'icon_small/missing.png' end + def okta_configured? + ApplicationSettings.instance.values['okta'].present? + end + + def azure_ad_configured? + ApplicationSettings.instance.values['azure_ad_apps'].present? + end + def wopi_enabled? ENV['WOPI_ENABLED'] == 'true' end diff --git a/app/views/users/shared/_azure_sign_in_links.html.erb b/app/views/users/shared/_azure_sign_in_links.html.erb index fb5c5fab8..284530242 100644 --- a/app/views/users/shared/_azure_sign_in_links.html.erb +++ b/app/views/users/shared/_azure_sign_in_links.html.erb @@ -1,7 +1,7 @@ -<% Rails.configuration.x.azure_ad_apps.select { |uid, config| config[:enable_sign_in] }.each do |uid, config| %> +<% ApplicationSettings.instance.values['azure_ad_apps'].select { |v| v['enable_sign_in'] }.each do |config| %>
- <%= form_tag user_customazureactivedirectory_omniauth_authorize_path(provider: config[:provider]), method: :post do %> - <%= submit_tag config[:sign_in_label], class: 'btn btn-azure-ad' %> + <%= form_tag user_customazureactivedirectory_omniauth_authorize_path(provider: config['provider']), method: :post do %> + <%= submit_tag config['sign_in_label'] || t('devise.sessions.new.azure_ad_submit'), class: 'btn btn-azure-ad' %> <% end %>
<% end %> diff --git a/app/views/users/shared/_links.html.erb b/app/views/users/shared/_links.html.erb index f8782f162..db7ce9d64 100644 --- a/app/views/users/shared/_links.html.erb +++ b/app/views/users/shared/_links.html.erb @@ -27,7 +27,7 @@
<% end -%> - <%- if devise_mapping.omniauthable? && Devise.omniauth_configs[:okta].present? %> + <%- if devise_mapping.omniauthable? && okta_configured? %>
<%= form_tag omniauth_authorize_path(resource_name, :okta), method: :post do %> <%= submit_tag t('devise.okta.sign_in_label'), class: 'btn btn-okta' %> @@ -41,7 +41,9 @@ <% end -%> <% end -%> -
- <%= render partial: "users/shared/azure_sign_in_links", locals: { resource_name: resource_name } %> -
+ <% if devise_mapping.omniauthable? && azure_ad_configured? %> +
+ <%= render partial: "users/shared/azure_sign_in_links", locals: { resource_name: resource_name } %> +
+ <% end %>
diff --git a/config/initializers/azure_ad.rb b/config/initializers/azure_ad.rb index caecfadba..f785a79e0 100644 --- a/config/initializers/azure_ad.rb +++ b/config/initializers/azure_ad.rb @@ -1,88 +1,58 @@ # frozen_string_literal: true -Rails.application.configure do - vars = ENV.select { |name, _| name =~ /^[[:alnum:]]*_AZURE_AD_APP_ID/ } - config.x.azure_ad_apps = HashWithIndifferentAccess.new if vars.present? +begin + azure_app_ids = ENV.select { |name, _| name =~ /^[[:alnum:]]*_AZURE_AD_APP_ID/ } + settings = ApplicationSettings.instance - vars.each do |name, value| + azure_app_ids.each do |name, value| app_name = name.sub('_AZURE_AD_APP_ID', '') - config.x.azure_ad_apps[value] = {} + app_config = {} + app_config['app_id'] = value - tenant_id = ENV["#{app_name}_AZURE_AD_TENANT_ID"] + tenant_id = ENV.fetch("#{app_name}_AZURE_AD_TENANT_ID") raise StandardError, "No Tenant ID for #{app_name} Azure app" unless tenant_id - config.x.azure_ad_apps[value][:tenant_id] = tenant_id + app_config['tenant_id'] = tenant_id - client_secret = ENV["#{app_name}_AZURE_AD_CLIENT_SECRET"] + client_secret = ENV.fetch("#{app_name}_AZURE_AD_CLIENT_SECRET") raise StandardError, "No Client Secret for #{app_name} Azure app" unless client_secret - config.x.azure_ad_apps[value][:client_secret] = client_secret + app_config['client_secret'] = client_secret - iss = ENV["#{app_name}_AZURE_AD_ISS"] + iss = ENV.fetch("#{app_name}_AZURE_AD_ISS") raise StandardError, "No ISS for #{app_name} Azure app" unless iss - config.x.azure_ad_apps[value][:iss] = iss + app_config['iss'] = iss - conf_url = ENV["#{app_name}_AZURE_AD_CONF_URL"] + conf_url = ENV.fetch("#{app_name}_AZURE_AD_CONF_URL") raise StandardError, "No CONF_URL for #{app_name} Azure app" unless conf_url - config.x.azure_ad_apps[value][:conf_url] = conf_url + app_config['conf_url'] = conf_url - provider = ENV["#{app_name}_AZURE_AD_PROVIDER_NAME"] + provider = ENV.fetch("#{app_name}_AZURE_AD_PROVIDER_NAME") raise StandardError, "No PROVIDER_NAME for #{app_name} Azure app" unless provider - config.x.azure_ad_apps[value][:provider] = provider + app_config['provider_name'] = provider - config.x.azure_ad_apps[value][:enable_sign_in] = ENV["#{app_name}_AZURE_AD_ENABLE_SIGN_IN"] == 'true' + app_config['enable_sign_in'] = ENV["#{app_name}_AZURE_AD_ENABLE_SIGN_IN"] == 'true' - next unless config.x.azure_ad_apps[value][:enable_sign_in] + next unless app_config['enable_sign_in'] - config.x.azure_ad_apps[value][:sign_in_label] = ENV["#{app_name}_AZURE_AD_SIGN_IN_LABEL"] || 'Sign in with Azure AD' - config.x.azure_ad_apps[value][:auto_link_on_sign_in] = ENV["#{app_name}_AZURE_AD_AUTO_LINK_ON_SIGN_IN"] == 'true' + app_config['sign_in_label'] = ENV.fetch("#{app_name}_AZURE_AD_SIGN_IN_LABEL") + app_config['auto_link_on_sign_in'] = ENV["#{app_name}_AZURE_AD_AUTO_LINK_ON_SIGN_IN"] == 'true' if ENV["#{app_name}_AZURE_AD_SIGN_IN_POLICY"] - config.x.azure_ad_apps[value][:sign_in_policy] = ENV["#{app_name}_AZURE_AD_SIGN_IN_POLICY"] + app_config['sign_in_policy'] = ENV["#{app_name}_AZURE_AD_SIGN_IN_POLICY"] + end + + existing_index = settings.values['azure_ad_apps'].find_index { |v| v['app_id'] == value } + if existing_index + settings.values['azure_ad_apps'][existing_index] = app_config + else + settings.values['azure_ad_apps'] << app_config end end - - # Checking additional configurations in ApplicationSettings JSON. Key and values should be strings there. - begin - if ApplicationSettings.instance.values['azure_ad_apps']&.is_a?(Array) - config.x.azure_ad_apps ||= HashWithIndifferentAccess.new - settings = ApplicationSettings.instance - - settings.values['azure_ad_apps'].each do |azure_ad_app| - app_config = {} - app_id = azure_ad_app['app_id'] - Rails.logger.error('No app_id present for the entry in Azure app settings') && next unless app_id - - app_config[:tenant_id] = azure_ad_app['tenant_id'] - Rails.logger.error("No tenant id for #{app_id} Azure app") && next unless app_config[:tenant_id] - - app_config[:client_secret] = azure_ad_app['client_secret'] - Rails.logger.error("No client secret for #{app_id} Azure app") && next unless app_config[:client_secret] - - app_config[:iss] = azure_ad_app['iss'] - Rails.logger.error("No iss for #{app_id} Azure app") && next unless app_config[:iss] - - app_config[:conf_url] = azure_ad_app['conf_url'] - Rails.logger.error("No conf_url for #{app_id} Azure app") && next unless app_config[:conf_url] - - app_config[:provider] = azure_ad_app['provider_name'] - Rails.logger.error("No provider_name for #{app_id} Azure app") && next unless app_config[:provider] - - app_config[:enable_sign_in] = azure_ad_app['enable_sign_in'] == 'true' - - if app_config[:enable_sign_in] - app_config[:sign_in_label] = azure_ad_app['sign_in_label'] || 'Sign in with Azure AD' - app_config[:auto_link_on_sign_in] = azure_ad_app['auto_link_on_sign_in'] == 'true' - app_config[:sign_in_policy] = azure_ad_app['sign_in_policy'] if azure_ad_app['sign_in_policy'] - end - - config.x.azure_ad_apps[app_id] = app_config - end - end - rescue ActiveRecord::ActiveRecordError, PG::ConnectionBad - Rails.logger.info('Not connected to database, skipping additional Azure AD configuration') - end + settings.save! if azure_app_ids.present? +rescue ActiveRecord::ActiveRecordError, PG::ConnectionBad + Rails.logger.info('Not connected to database, skipping additional Azure AD configuration') end diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb index ef4f8a7c5..f1ecbfbe1 100644 --- a/config/initializers/devise.rb +++ b/config/initializers/devise.rb @@ -301,36 +301,6 @@ Devise.setup do |config| Rails.application.config.x.disable_local_passwords = ENV['DISABLE_LOCAL_PASSWORDS'] == 'true' - if [ENV['OKTA_CLIENT_ID'], ENV['OKTA_CLIENT_SECRET'], ENV['OKTA_DOMAIN']].all?(&:present?) - OKTA_OAUTH2_BASE_URL = - if ENV['OKTA_AUTH_SERVER_ID'].blank? - "https://#{ENV['OKTA_DOMAIN']}/oauth2" - else - "https://#{ENV['OKTA_DOMAIN']}/oauth2/#{ENV['OKTA_AUTH_SERVER_ID']}" - end - client_options = { - site: "https://#{ENV['OKTA_DOMAIN']}", - authorize_url: "#{OKTA_OAUTH2_BASE_URL}/v1/authorize", - token_url: "#{OKTA_OAUTH2_BASE_URL}/v1/token", - user_info_url: "#{OKTA_OAUTH2_BASE_URL}/v1/userinfo" - } - client_options[:audience] = ENV['OKTA_CLIENT_ID'] if ENV['OKTA_AUDIENCE'].present? - if ENV['OKTA_AUTH_SERVER_ID'].present? - client_options[:authorization_server] = ENV['OKTA_AUTH_SERVER_ID'] - else - client_options[:use_org_auth_server] = true - end - config.omniauth( - :okta, - ENV['OKTA_CLIENT_ID'], - ENV['OKTA_CLIENT_SECRET'], - scope: 'openid profile email', - fields: %w(profile email), - client_options: client_options, - 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/okta.rb b/config/initializers/okta.rb new file mode 100644 index 000000000..735633957 --- /dev/null +++ b/config/initializers/okta.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +begin + settings = ApplicationSettings.instance + provider_conf = settings.values['okta'] + if provider_conf.blank? && %w(OKTA_CLIENT_ID OKTA_CLIENT_SECRET OKTA_DOMAIN).all? { |v| ENV.fetch(v, nil).present? } + provider_conf = {} + provider_conf['client_id'] = ENV.fetch('OKTA_CLIENT_ID') + provider_conf['client_secret'] = ENV.fetch('OKTA_CLIENT_SECRET') + provider_conf['domain'] = ENV.fetch('OKTA_DOMAIN') + provider_conf['auth_server_id'] = ENV['OKTA_AUTH_SERVER_ID'] if ENV['OKTA_AUTH_SERVER_ID'].present? + provider_conf['audience'] = ENV['OKTA_AUDIENCE'] if ENV['OKTA_AUDIENCE'].present? + settings.values['okta'] = provider_conf + settings.save! + end +rescue ActiveRecord::ActiveRecordError, PG::ConnectionBad + Rails.logger.info('Not connected to database, skipping additional Okta configuration') +end diff --git a/config/initializers/omniauth.rb b/config/initializers/omniauth.rb index bd240a6cc..c1f7c45ff 100644 --- a/config/initializers/omniauth.rb +++ b/config/initializers/omniauth.rb @@ -3,7 +3,8 @@ require 'omniauth/strategies/custom_azure_active_directory' AZURE_SETUP_PROC = lambda do |env| - providers = Rails.configuration.x.azure_ad_apps.select { |_, v| v[:enable_sign_in] == true } + 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? req = Rack::Request.new(env) @@ -11,28 +12,60 @@ AZURE_SETUP_PROC = lambda do |env| if providers.size > 1 if req.params['id_token'].present? # Callback phase unverified_jwt_payload, = JWT.decode(req.params['id_token'], nil, false) - raise StandardError, 'No Azure AD config available for sign in' if providers[unverified_jwt_payload['aud']].blank? - - provider_id = unverified_jwt_payload['aud'] + provider_conf = providers.select { |v| v['app_id'] == unverified_jwt_payload['aud'] } else # Authorization phase raise ActionController::ParameterMissing, 'Provider name is missing' if req.params['provider'].blank? - provider_id = providers.select { |_, v| v[:provider] == req.params['provider'] }.keys.first - raise StandardError, 'No Azure AD config available for sign in' if provider_id.blank? + provider_conf = providers.find { |v| v['provider_name'] == req.params['provider'] } end + raise StandardError, 'No Azure AD config available for sign in' if provider_conf.blank? end - provider_id ||= providers.keys.first - provider_conf = providers[provider_id] - env['omniauth.strategy'].options[:client_id] = provider_id.to_s - env['omniauth.strategy'].options[:client_secret] = provider_conf[:client_secret] - env['omniauth.strategy'].options[:tenant_id] = provider_conf[:tenant_id] - env['omniauth.strategy'].options[:sign_in_policy] = provider_conf[:sign_in_policy] + provider_conf ||= providers.first + env['omniauth.strategy'].options[:client_id] = provider_conf['app_id'] + env['omniauth.strategy'].options[:client_secret] = provider_conf['client_secret'] + env['omniauth.strategy'].options[:tenant_id] = provider_conf['tenant_id'] + env['omniauth.strategy'].options[:sign_in_policy] = provider_conf['sign_in_policy'] env['omniauth.strategy'].options[:name] = 'customazureactivedirectory' 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? + + oauth2_base_url = + if provider_conf['auth_server_id'].blank? + "https://#{provider_conf['domain']}/oauth2" + else + "https://#{provider_conf['domain']}/oauth2/#{provider_conf['auth_server_id']}" + end + + client_options = { + site: "https://#{provider_conf['domain']}", + authorize_url: "#{oauth2_base_url}/v1/authorize", + token_url: "#{oauth2_base_url}/v1/token", + user_info_url: "#{oauth2_base_url}/v1/userinfo" + } + client_options[:audience] = provider_conf['audience'] if provider_conf['audience'].present? + if provider_conf['auth_server_id'].present? + client_options[:authorization_server] = provider_conf['auth_server_id'] + client_options[:use_org_auth_server] = false + else + client_options[:use_org_auth_server] = true + end + + env['omniauth.strategy'].options[:client_id] = provider_conf['client_id'] + env['omniauth.strategy'].options[:client_secret] = provider_conf['client_secret'] + env['omniauth.strategy'].options[:client_options] = client_options +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::Okta, setup: OKTA_SETUP_PROC +end + OmniAuth.config.logger = Rails.logger diff --git a/config/locales/en.yml b/config/locales/en.yml index ea90f2033..46ccf3a37 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -33,6 +33,7 @@ en: password_placeholder: "Enter password" remember_me: "Remember me" submit: "Log in" + azure_ad_submit: "Sign in with Azure AD" 2fa: title: "Two-factor authentication" description: "Enter the one-time code found in your authenticator app to log in to SciNote."