File zammad-skip-omniauth-initializer.patch of Package zammad
From 37ebcb7928f8df471400321b1330de0dab61d189 Mon Sep 17 00:00:00 2001
From: Andreas Schneider <asn@cryptomilk.org>
Date: Tue, 24 Feb 2026 09:52:01 +0100
Subject: [PATCH] Make OmniAuth provider plugins optional.
This change allows selective installation of OmniAuth provider gems.
Provider gems are now marked with 'require: false' in the Gemfile and
only loaded when needed. The initializer checks if each provider
strategy class is defined before registering it.
---
.../concerns/handles_oidc_authorization.rb | 3 +
app/controllers/sessions_controller.rb | 6 +-
app/controllers/settings_controller.rb | 6 +-
.../useThirdPartyAuthentication.ts | 27 ++-
app/frontend/shared/types/config.ts | 1 +
.../logout/handles_oidc_authorization.rb | 2 +
app/graphql/gql/mutations/logout.rb | 2 +
app/graphql/gql/queries/application_config.rb | 6 +-
config/application.rb | 1 +
config/initializers/omniauth.rb | 74 ++++--
.../initializers/omniauth_openid_connect.rb | 48 ++--
lib/omni_auth/provider_availability.rb | 45 ++++
lib/omni_auth/strategies/facebook_database.rb | 24 +-
lib/omni_auth/strategies/github_database.rb | 24 +-
lib/omni_auth/strategies/gitlab_database.rb | 26 ++-
.../strategies/google_oauth2_database.rb | 24 +-
.../strategies/linked_in_database.rb | 34 +--
.../microsoft_office365_database.rb | 30 ++-
lib/omni_auth/strategies/oidc_database.rb | 68 +++---
lib/omni_auth/strategies/saml_database.rb | 211 +++++++++---------
lib/omni_auth/strategies/twitter_database.rb | 24 +-
lib/omni_auth/strategies/weibo_database.rb | 24 +-
23 files changed, 448 insertions(+), 282 deletions(-)
create mode 100644 lib/omni_auth/provider_availability.rb
diff --git a/app/controllers/concerns/handles_oidc_authorization.rb b/app/controllers/concerns/handles_oidc_authorization.rb
index cabd017cca..ab9485a812 100644
--- a/app/controllers/concerns/handles_oidc_authorization.rb
+++ b/app/controllers/concerns/handles_oidc_authorization.rb
@@ -7,6 +7,7 @@ module HandlesOidcAuthorization
skip_before_action :verify_csrf_token, only: %i[oidc_destroy oidc_bc_logout] # rubocop:disable Rails/LexicallyScopedActionFilter
def oidc_bc_logout
+ raise Exceptions::UnprocessableEntity, __("The required parameter 'oidc_database strategy' is not available.") if !defined?(OmniAuth::Strategies::OidcDatabase)
raise Exceptions::UnprocessableEntity, __("The required parameter 'logout_token' is missing.") if params[:logout_token].blank?
begin
@@ -24,6 +25,8 @@ module HandlesOidcAuthorization
private
def oidc_session?
+ return false if !defined?(OmniAuth::Strategies::OidcDatabase)
+
session[:oidc_id_token].present? && oidc_end_session_endpoint.present?
end
diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb
index 4916edaf73..37f2ebed14 100644
--- a/app/controllers/sessions_controller.rb
+++ b/app/controllers/sessions_controller.rb
@@ -316,8 +316,8 @@ class SessionsController < ApplicationController
# This is needed because the setting is not frontend related,
# but we still to display one of the options
# https://github.com/zammad/zammad/issues/4263
- config['auth_saml_display_name'] = Setting.get('auth_saml_credentials')[:display_name]
- config['auth_openid_connect_display_name'] = Setting.get('auth_openid_connect_credentials')[:display_name]
+ config['auth_saml_display_name'] = Setting.get('auth_saml_credentials')&.[](:display_name)
+ config['auth_openid_connect_display_name'] = Setting.get('auth_openid_connect_credentials')&.[](:display_name)
# Include the flag for JSON column type support (currently only on PostgreSQL backend).
config['column_type_json_supported'] =
@@ -343,6 +343,8 @@ class SessionsController < ApplicationController
end
def saml_session?
+ return false if !defined?(OmniAuth::Strategies::SamlDatabase)
+
(session['saml_uid'] || session['saml_session_index']) && OmniAuth::Strategies::SamlDatabase.setup.fetch('idp_slo_service_url', nil)
end
diff --git a/app/controllers/settings_controller.rb b/app/controllers/settings_controller.rb
index 3ef995c46f..6f9162d47f 100644
--- a/app/controllers/settings_controller.rb
+++ b/app/controllers/settings_controller.rb
@@ -5,11 +5,15 @@ class SettingsController < ApplicationController
# GET /settings
def index
+ unavailable = OmniAuth::ProviderAvailability.unavailable_settings
+
list = []
Setting.all.each do |setting|
next if !authorized?(setting, :show?)
- list.push setting
+ s = setting.as_json
+ s['preferences'] = (s['preferences'] || {}).merge('disabled' => true) if unavailable.include?(setting.name)
+ list.push s
end
render json: list, status: :ok
end
diff --git a/app/frontend/shared/composables/authentication/useThirdPartyAuthentication.ts b/app/frontend/shared/composables/authentication/useThirdPartyAuthentication.ts
index b6cb4d71f8..558fae8446 100644
--- a/app/frontend/shared/composables/authentication/useThirdPartyAuthentication.ts
+++ b/app/frontend/shared/composables/authentication/useThirdPartyAuthentication.ts
@@ -12,61 +12,67 @@ export const useThirdPartyAuthentication = () => {
const application = useApplicationStore()
const { config } = storeToRefs(application)
+ const availableProviders = computed(
+ () => config.value.omniauth_available_providers ?? [],
+ )
+
+ const available = (provider: string) => availableProviders.value.includes(provider)
+
const providers = computed<ThirdPartyAuthProvider[]>(() => {
return [
{
name: EnumAuthenticationProvider.Facebook,
label: i18n.t('Facebook'),
- enabled: !!config.value.auth_facebook,
+ enabled: !!config.value.auth_facebook && available('facebook'),
icon: 'facebook',
url: '/auth/facebook',
},
{
name: EnumAuthenticationProvider.Twitter,
label: i18n.t('Twitter'),
- enabled: !!config.value.auth_twitter,
+ enabled: !!config.value.auth_twitter && available('twitter'),
icon: 'twitter',
url: '/auth/twitter',
},
{
name: EnumAuthenticationProvider.Linkedin,
label: i18n.t('LinkedIn'),
- enabled: !!config.value.auth_linkedin,
+ enabled: !!config.value.auth_linkedin && available('linkedin'),
icon: 'linkedin',
url: '/auth/linkedin',
},
{
name: EnumAuthenticationProvider.Github,
label: i18n.t('GitHub'),
- enabled: !!config.value.auth_github,
+ enabled: !!config.value.auth_github && available('github'),
icon: 'github',
url: '/auth/github',
},
{
name: EnumAuthenticationProvider.Gitlab,
label: i18n.t('GitLab'),
- enabled: !!config.value.auth_gitlab,
+ enabled: !!config.value.auth_gitlab && available('gitlab'),
icon: 'gitlab',
url: '/auth/gitlab',
},
{
name: EnumAuthenticationProvider.MicrosoftOffice365,
label: i18n.t('Microsoft'),
- enabled: !!config.value.auth_microsoft_office365,
+ enabled: !!config.value.auth_microsoft_office365 && available('microsoft_office365'),
icon: 'microsoft',
url: '/auth/microsoft_office365',
},
{
name: EnumAuthenticationProvider.GoogleOauth2,
label: i18n.t('Google'),
- enabled: !!config.value.auth_google_oauth2,
+ enabled: !!config.value.auth_google_oauth2 && available('google_oauth2'),
icon: 'google',
url: '/auth/google_oauth2',
},
{
name: EnumAuthenticationProvider.Weibo,
label: i18n.t('Weibo'),
- enabled: !!config.value.auth_weibo,
+ enabled: !!config.value.auth_weibo && available('weibo'),
icon: 'weibo',
url: '/auth/weibo',
},
@@ -75,11 +81,12 @@ export const useThirdPartyAuthentication = () => {
label:
(config.value['auth_saml_credentials.display_name'] as string) ||
i18n.t('SAML'),
- enabled: !!config.value.auth_saml,
+ enabled: !!config.value.auth_saml && available('saml'),
icon: 'saml',
url: '/auth/saml',
},
{
+ // SSO uses HTTP headers, has no external gem dependency, and is always available when enabled.
name: EnumAuthenticationProvider.Sso,
label: i18n.t('SSO'),
enabled: !!config.value.auth_sso,
@@ -92,7 +99,7 @@ export const useThirdPartyAuthentication = () => {
(config.value[
'auth_openid_connect_credentials.display_name'
] as string) || i18n.t('OpenID Connect'),
- enabled: !!config.value.auth_openid_connect,
+ enabled: !!config.value.auth_openid_connect && available('openid_connect'),
icon: 'openid-connect',
url: '/auth/openid_connect',
},
diff --git a/app/frontend/shared/types/config.ts b/app/frontend/shared/types/config.ts
index d912b065a1..c8d74cae55 100644
--- a/app/frontend/shared/types/config.ts
+++ b/app/frontend/shared/types/config.ts
@@ -43,6 +43,7 @@ export interface ConfigList {
maintenance_login: boolean
maintenance_login_message: string
maintenance_mode: boolean
+ omniauth_available_providers?: string[]
organization: string
password_max_login_failed?: 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | null
pgp_config: unknown
diff --git a/app/graphql/gql/mutations/concerns/logout/handles_oidc_authorization.rb b/app/graphql/gql/mutations/concerns/logout/handles_oidc_authorization.rb
index 997d72509a..fde1cecf8f 100644
--- a/app/graphql/gql/mutations/concerns/logout/handles_oidc_authorization.rb
+++ b/app/graphql/gql/mutations/concerns/logout/handles_oidc_authorization.rb
@@ -5,6 +5,8 @@ module Gql::Mutations::Concerns::Logout::HandlesOidcAuthorization
included do
def oidc_session?
+ return false if !defined?(OmniAuth::Strategies::OidcDatabase)
+
session[:oidc_id_token].present? && oidc_end_session_endpoint.present?
end
diff --git a/app/graphql/gql/mutations/logout.rb b/app/graphql/gql/mutations/logout.rb
index 441e7d5b30..1f33f353a7 100644
--- a/app/graphql/gql/mutations/logout.rb
+++ b/app/graphql/gql/mutations/logout.rb
@@ -38,6 +38,8 @@ module Gql::Mutations
end
def saml_session?
+ return false if !defined?(OmniAuth::Strategies::SamlDatabase)
+
(session['saml_uid'] || session['saml_session_index']) && OmniAuth::Strategies::SamlDatabase.setup.fetch('idp_slo_service_url', nil)
end
diff --git a/app/graphql/gql/queries/application_config.rb b/app/graphql/gql/queries/application_config.rb
index eb9d707fa6..5c8a3870e4 100644
--- a/app/graphql/gql/queries/application_config.rb
+++ b/app/graphql/gql/queries/application_config.rb
@@ -47,7 +47,7 @@ module Gql::Queries
end
def custom_settings
- [
+ result = [
'auth_saml_credentials.display_name',
'auth_openid_connect_credentials.display_name',
].filter_map do |config_name|
@@ -59,6 +59,10 @@ module Gql::Queries
{ key: config_name, value: value }
end
+
+ # Intentionally included for unauthenticated users: the login page needs
+ # this to show only providers whose gem is installed.
+ result << { key: 'omniauth_available_providers', value: OmniAuth::ProviderAvailability.available_providers }
end
end
end
diff --git a/config/application.rb b/config/application.rb
index d6756a2fdc..29c99ae90a 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -25,6 +25,7 @@ module Zammad
Rails.autoloaders.each do |autoloader|
autoloader.ignore "#{config.root}/app/frontend"
autoloader.do_not_eager_load "#{config.root}/lib/core_ext"
+ autoloader.ignore "#{config.root}/lib/omni_auth/strategies"
autoloader.collapse "#{config.root}/lib/omniauth"
autoloader.collapse "#{config.root}/lib/generators"
autoloader.inflector.inflect(
diff --git a/config/initializers/omniauth.rb b/config/initializers/omniauth.rb
index 208b7f090d..45a1e753e8 100644
--- a/config/initializers/omniauth.rb
+++ b/config/initializers/omniauth.rb
@@ -1,47 +1,77 @@
# Copyright (C) 2012-2026 Zammad Foundation, https://zammad-foundation.org/
+# Explicitly load these files rather than relying on Zeitwerk autoloading.
+# OmniAuth is a cuckoo namespace (defined by the omniauth gem), so Zeitwerk
+# cannot set up autoloads under it reliably. Strategy files are also in the
+# Zeitwerk ignore list. Each file rescues LoadError when its provider gem is absent.
+require Rails.root.join('lib/omni_auth/provider_availability')
+Rails.root.glob('lib/omni_auth/strategies/*.rb').each { |f| require f }
+
+OmniAuth::ProviderAvailability.load!
+
Rails.application.config.middleware.use OmniAuth::Builder do
# twitter database connect
- provider :twitter_database, 'not_change_will_be_set_by_database', 'not_change_will_be_set_by_database', {
- client_options: {
- authorize_path: '/oauth/authorize',
- site: 'https://api.twitter.com',
+ if OmniAuth::ProviderAvailability.available?('twitter')
+ provider :twitter_database, 'not_change_will_be_set_by_database', 'not_change_will_be_set_by_database', {
+ client_options: {
+ authorize_path: '/oauth/authorize',
+ site: 'https://api.twitter.com',
+ }
}
- }
+ end
# facebook database connect
- provider :facebook_database, 'not_change_will_be_set_by_database', 'not_change_will_be_set_by_database'
+ if OmniAuth::ProviderAvailability.available?('facebook')
+ provider :facebook_database, 'not_change_will_be_set_by_database', 'not_change_will_be_set_by_database'
+ end
# linkedin database connect
- provider :linked_in_database, 'not_change_will_be_set_by_database', 'not_change_will_be_set_by_database'
+ if OmniAuth::ProviderAvailability.available?('linkedin')
+ provider :linked_in_database, 'not_change_will_be_set_by_database', 'not_change_will_be_set_by_database'
+ end
# google database connect
- provider :google_oauth2_database, 'not_change_will_be_set_by_database', 'not_change_will_be_set_by_database', access_type: 'online', prompt: ''
+ if OmniAuth::ProviderAvailability.available?('google_oauth2')
+ provider :google_oauth2_database, 'not_change_will_be_set_by_database', 'not_change_will_be_set_by_database', access_type: 'online', prompt: ''
+ end
# github database connect
- provider :github_database, 'not_change_will_be_set_by_database', 'not_change_will_be_set_by_database'
+ if OmniAuth::ProviderAvailability.available?('github')
+ provider :github_database, 'not_change_will_be_set_by_database', 'not_change_will_be_set_by_database'
+ end
# gitlab database connect
- provider :git_lab_database, 'not_change_will_be_set_by_database', 'not_change_will_be_set_by_database', {
- client_options: {
- site: 'https://not_change_will_be_set_by_database',
- authorize_url: '/oauth/authorize',
- token_url: '/oauth/token'
- },
- scope: 'read_user',
- }
+ if OmniAuth::ProviderAvailability.available?('gitlab')
+ provider :git_lab_database, 'not_change_will_be_set_by_database', 'not_change_will_be_set_by_database', {
+ client_options: {
+ site: 'https://not_change_will_be_set_by_database',
+ authorize_url: '/oauth/authorize',
+ token_url: '/oauth/token'
+ },
+ scope: 'read_user',
+ }
+ end
# microsoft_office365 database connect
- provider :microsoft_office365_database, 'not_change_will_be_set_by_database', 'not_change_will_be_set_by_database'
+ if OmniAuth::ProviderAvailability.available?('microsoft_office365')
+ provider :microsoft_office365_database, 'not_change_will_be_set_by_database', 'not_change_will_be_set_by_database'
+ end
# weibo database connect
- provider :weibo_database, 'not_change_will_be_set_by_database', 'not_change_will_be_set_by_database'
+ if OmniAuth::ProviderAvailability.available?('weibo')
+ provider :weibo_database, 'not_change_will_be_set_by_database', 'not_change_will_be_set_by_database'
+ end
- # SAML database connect
- provider :saml_database
+ # saml database connect
+ if OmniAuth::ProviderAvailability.available?('saml')
+ provider :saml_database
+ end
- provider :oidc_database
+ # openid_connect database connect
+ if OmniAuth::ProviderAvailability.available?('openid_connect')
+ provider :oidc_database
+ end
end
# This fixes issue #1642 and is required for setups in which Zammad is used
diff --git a/config/initializers/omniauth_openid_connect.rb b/config/initializers/omniauth_openid_connect.rb
index 3d87376a5b..daa45464b1 100644
--- a/config/initializers/omniauth_openid_connect.rb
+++ b/config/initializers/omniauth_openid_connect.rb
@@ -1,34 +1,38 @@
# Copyright (C) 2012-2026 Zammad Foundation, https://zammad-foundation.org/
-require 'omniauth/openid_connect'
+begin
+ require 'omniauth/openid_connect'
-# Monkey patch to support more different token endpoints. Can be removed when this PR is merged:
-# https://github.com/omniauth/omniauth_openid_connect/pull/192
-module OmniAuth
- module Strategies
- class OpenIDConnect
- def access_token
- return @access_token if @access_token
+ # Monkey patch to support more different token endpoints. Can be removed when this PR is merged:
+ # https://github.com/omniauth/omniauth_openid_connect/pull/192
+ module OmniAuth
+ module Strategies
+ class OpenIDConnect
+ def access_token
+ return @access_token if @access_token
- token_request_params = {
- scope: (options.scope if options.send_scope_to_token_endpoint),
- client_auth_method: options.client_auth_method,
- }
+ token_request_params = {
+ scope: (options.scope if options.send_scope_to_token_endpoint),
+ client_auth_method: options.client_auth_method,
+ }
- token_request_params[:code_verifier] = params['code_verifier'] || session.delete('omniauth.pkce.verifier') if options.pkce
+ token_request_params[:code_verifier] = params['code_verifier'] || session.delete('omniauth.pkce.verifier') if options.pkce
- if configured_response_type == 'code'
- token_request_params[:grant_type] = :authorization_code
- token_request_params[:code] = authorization_code
- token_request_params[:redirect_uri] = redirect_uri
- token_request_params[:client_id] = client_options.identifier
- end
+ if configured_response_type == 'code'
+ token_request_params[:grant_type] = :authorization_code
+ token_request_params[:code] = authorization_code
+ token_request_params[:redirect_uri] = redirect_uri
+ token_request_params[:client_id] = client_options.identifier
+ end
- @access_token = client.access_token!(token_request_params)
- verify_id_token!(@access_token.id_token) if configured_response_type == 'code'
+ @access_token = client.access_token!(token_request_params)
+ verify_id_token!(@access_token.id_token) if configured_response_type == 'code'
- @access_token
+ @access_token
+ end
end
end
end
+rescue LoadError
+ # Gem not installed, skip monkey patch
end
diff --git a/lib/omni_auth/provider_availability.rb b/lib/omni_auth/provider_availability.rb
new file mode 100644
index 0000000000..41889ff815
--- /dev/null
+++ b/lib/omni_auth/provider_availability.rb
@@ -0,0 +1,45 @@
+# Copyright (C) 2012-2026 Zammad Foundation, https://zammad-foundation.org/
+
+module OmniAuth
+ module ProviderAvailability
+ # Map from auth setting name suffix to strategy class name.
+ # Used to determine which providers have their gem installed.
+ # NOTE: Keep this in sync with the strategy files in lib/omni_auth/strategies/.
+ # Any strategy file added there must also be listed here.
+ PROVIDER_STRATEGIES = {
+ 'twitter' => 'OmniAuth::Strategies::TwitterDatabase',
+ 'facebook' => 'OmniAuth::Strategies::FacebookDatabase',
+ 'linkedin' => 'OmniAuth::Strategies::LinkedInDatabase',
+ 'google_oauth2' => 'OmniAuth::Strategies::GoogleOauth2Database',
+ 'github' => 'OmniAuth::Strategies::GithubDatabase',
+ 'gitlab' => 'OmniAuth::Strategies::GitLabDatabase',
+ 'microsoft_office365' => 'OmniAuth::Strategies::MicrosoftOffice365Database',
+ 'weibo' => 'OmniAuth::Strategies::WeiboDatabase',
+ 'saml' => 'OmniAuth::Strategies::SamlDatabase',
+ 'openid_connect' => 'OmniAuth::Strategies::OidcDatabase',
+ }.freeze
+
+ class << self
+ attr_reader :available_providers
+ end
+
+ # Populate available_providers based on which strategy classes are defined.
+ # Called from the omniauth initializer after strategy files are required.
+ def self.load!
+ @available_providers = PROVIDER_STRATEGIES.filter_map do |name, klass|
+ name if Object.const_defined?(klass)
+ end.freeze
+ end
+
+ def self.available?(provider)
+ raise 'OmniAuth::ProviderAvailability.load! has not been called' if @available_providers.nil?
+
+ @available_providers.include?(provider)
+ end
+
+ def self.unavailable_settings
+ unavailable_providers = PROVIDER_STRATEGIES.keys - available_providers
+ unavailable_providers.flat_map { |p| ["auth_#{p}", "auth_#{p}_credentials"] }.to_set
+ end
+ end
+end
diff --git a/lib/omni_auth/strategies/facebook_database.rb b/lib/omni_auth/strategies/facebook_database.rb
index 4c95e3f037..cd504f73fc 100644
--- a/lib/omni_auth/strategies/facebook_database.rb
+++ b/lib/omni_auth/strategies/facebook_database.rb
@@ -1,15 +1,21 @@
# Copyright (C) 2012-2026 Zammad Foundation, https://zammad-foundation.org/
-class OmniAuth::Strategies::FacebookDatabase < OmniAuth::Strategies::Facebook
- option :name, 'facebook'
+begin
+ require 'omniauth-facebook'
- def initialize(app, *args, &)
+ class OmniAuth::Strategies::FacebookDatabase < OmniAuth::Strategies::Facebook
+ option :name, 'facebook'
- # database lookup
- config = Setting.get('auth_facebook_credentials') || {}
- args[0] = config['app_id']
- args[1] = config['app_secret']
- super
- end
+ def initialize(app, *args, &)
+
+ # database lookup
+ config = Setting.get('auth_facebook_credentials') || {}
+ args[0] = config['app_id']
+ args[1] = config['app_secret']
+ super
+ end
+ end
+rescue LoadError
+ # Gem not installed, strategy not available.
end
diff --git a/lib/omni_auth/strategies/github_database.rb b/lib/omni_auth/strategies/github_database.rb
index 9cb763226f..1a7fdc9e36 100644
--- a/lib/omni_auth/strategies/github_database.rb
+++ b/lib/omni_auth/strategies/github_database.rb
@@ -1,15 +1,21 @@
# Copyright (C) 2012-2026 Zammad Foundation, https://zammad-foundation.org/
-class OmniAuth::Strategies::GithubDatabase < OmniAuth::Strategies::GitHub
- option :name, 'github'
+begin
+ require 'omniauth-github'
- def initialize(app, *args, &)
+ class OmniAuth::Strategies::GithubDatabase < OmniAuth::Strategies::GitHub
+ option :name, 'github'
- # database lookup
- config = Setting.get('auth_github_credentials') || {}
- args[0] = config['app_id']
- args[1] = config['app_secret']
- super
- end
+ def initialize(app, *args, &)
+
+ # database lookup
+ config = Setting.get('auth_github_credentials') || {}
+ args[0] = config['app_id']
+ args[1] = config['app_secret']
+ super
+ end
+ end
+rescue LoadError
+ # Gem not installed, strategy not available.
end
diff --git a/lib/omni_auth/strategies/gitlab_database.rb b/lib/omni_auth/strategies/gitlab_database.rb
index b8f30da6da..f69383a8c6 100644
--- a/lib/omni_auth/strategies/gitlab_database.rb
+++ b/lib/omni_auth/strategies/gitlab_database.rb
@@ -1,16 +1,22 @@
# Copyright (C) 2012-2026 Zammad Foundation, https://zammad-foundation.org/
-class OmniAuth::Strategies::GitLabDatabase < OmniAuth::Strategies::GitLab
- option :name, 'gitlab'
+begin
+ require 'omniauth-gitlab'
- def initialize(app, *args, &)
+ class OmniAuth::Strategies::GitLabDatabase < OmniAuth::Strategies::GitLab
+ option :name, 'gitlab'
- # database lookup
- config = Setting.get('auth_gitlab_credentials') || {}
- args[0] = config['app_id']
- args[1] = config['app_secret']
- args[2][:client_options] = args[2][:client_options].merge(config.symbolize_keys)
- super
- end
+ def initialize(app, *args, &)
+
+ # database lookup
+ config = Setting.get('auth_gitlab_credentials') || {}
+ args[0] = config['app_id']
+ args[1] = config['app_secret']
+ args[2][:client_options] = args[2][:client_options].merge(config.symbolize_keys)
+ super
+ end
+ end
+rescue LoadError
+ # Gem not installed, strategy not available.
end
diff --git a/lib/omni_auth/strategies/google_oauth2_database.rb b/lib/omni_auth/strategies/google_oauth2_database.rb
index 5746d9fcf4..6a65677d5f 100644
--- a/lib/omni_auth/strategies/google_oauth2_database.rb
+++ b/lib/omni_auth/strategies/google_oauth2_database.rb
@@ -1,15 +1,21 @@
# Copyright (C) 2012-2026 Zammad Foundation, https://zammad-foundation.org/
-class OmniAuth::Strategies::GoogleOauth2Database < OmniAuth::Strategies::GoogleOauth2
- option :name, 'google_oauth2'
+begin
+ require 'omniauth-google-oauth2'
- def initialize(app, *args, &)
+ class OmniAuth::Strategies::GoogleOauth2Database < OmniAuth::Strategies::GoogleOauth2
+ option :name, 'google_oauth2'
- # database lookup
- config = Setting.get('auth_google_oauth2_credentials') || {}
- args[0] = config['client_id']
- args[1] = config['client_secret']
- super
- end
+ def initialize(app, *args, &)
+
+ # database lookup
+ config = Setting.get('auth_google_oauth2_credentials') || {}
+ args[0] = config['client_id']
+ args[1] = config['client_secret']
+ super
+ end
+ end
+rescue LoadError
+ # Gem not installed, strategy not available.
end
diff --git a/lib/omni_auth/strategies/linked_in_database.rb b/lib/omni_auth/strategies/linked_in_database.rb
index 116c1f1242..b98f41529d 100644
--- a/lib/omni_auth/strategies/linked_in_database.rb
+++ b/lib/omni_auth/strategies/linked_in_database.rb
@@ -1,22 +1,28 @@
# Copyright (C) 2012-2026 Zammad Foundation, https://zammad-foundation.org/
-class OmniAuth::Strategies::LinkedInDatabase < OmniAuth::Strategies::LinkedIn
- option :name, 'linkedin'
+begin
+ require 'omniauth-linkedin-oauth2'
- def initialize(app, *args, &)
+ class OmniAuth::Strategies::LinkedInDatabase < OmniAuth::Strategies::LinkedIn
+ option :name, 'linkedin'
- # database lookup
- config = Setting.get('auth_linkedin_credentials') || {}
- args[0] = config['app_id']
- args[1] = config['app_secret']
- super
- end
+ def initialize(app, *args, &)
+
+ # database lookup
+ config = Setting.get('auth_linkedin_credentials') || {}
+ args[0] = config['app_id']
+ args[1] = config['app_secret']
+ super
+ end
- # Workaround from current omniauth-linkedin gem issue:
- # https://github.com/decioferreira/omniauth-linkedin-oauth2/issues/68
- def token_params
- super.tap do |params|
- params.client_secret = options.client_secret
+ # Workaround from current omniauth-linkedin gem issue:
+ # https://github.com/decioferreira/omniauth-linkedin-oauth2/issues/68
+ def token_params
+ super.tap do |params|
+ params.client_secret = options.client_secret
+ end
end
end
+rescue LoadError
+ # Gem not installed, strategy not available.
end
diff --git a/lib/omni_auth/strategies/microsoft_office365_database.rb b/lib/omni_auth/strategies/microsoft_office365_database.rb
index 6a4222ac9a..e0589b8d36 100644
--- a/lib/omni_auth/strategies/microsoft_office365_database.rb
+++ b/lib/omni_auth/strategies/microsoft_office365_database.rb
@@ -1,20 +1,26 @@
# Copyright (C) 2012-2026 Zammad Foundation, https://zammad-foundation.org/
-class OmniAuth::Strategies::MicrosoftOffice365Database < OmniAuth::Strategies::MicrosoftOffice365
- option :name, 'microsoft_office365'
+begin
+ require 'omniauth-microsoft-office365'
- def initialize(app, *args, &)
+ class OmniAuth::Strategies::MicrosoftOffice365Database < OmniAuth::Strategies::MicrosoftOffice365
+ option :name, 'microsoft_office365'
- # database lookup
- config = Setting.get('auth_microsoft_office365_credentials') || {}
- args[0] = config['app_id']
- args[1] = config['app_secret']
- tenant = config['app_tenant'].presence || 'common'
+ def initialize(app, *args, &)
- super
+ # database lookup
+ config = Setting.get('auth_microsoft_office365_credentials') || {}
+ args[0] = config['app_id']
+ args[1] = config['app_secret']
+ tenant = config['app_tenant'].presence || 'common'
- @options[:client_options][:authorize_url] = "/#{tenant}/oauth2/v2.0/authorize"
- @options[:client_options][:token_url] = "/#{tenant}/oauth2/v2.0/token"
- end
+ super
+
+ @options[:client_options][:authorize_url] = "/#{tenant}/oauth2/v2.0/authorize"
+ @options[:client_options][:token_url] = "/#{tenant}/oauth2/v2.0/token"
+ end
+ end
+rescue LoadError
+ # Gem not installed, strategy not available.
end
diff --git a/lib/omni_auth/strategies/oidc_database.rb b/lib/omni_auth/strategies/oidc_database.rb
index 69e0109e28..313747e9cc 100644
--- a/lib/omni_auth/strategies/oidc_database.rb
+++ b/lib/omni_auth/strategies/oidc_database.rb
@@ -1,46 +1,52 @@
# Copyright (C) 2012-2026 Zammad Foundation, https://zammad-foundation.org/
-class OmniAuth::Strategies::OidcDatabase < OmniAuth::Strategies::OpenIDConnect
- option :name, 'openid_connect'
+begin
+ require 'omniauth_openid_connect'
- def self.setup
- auth_openid_connect_credentials = Setting.get('auth_openid_connect_credentials') || {}
+ class OmniAuth::Strategies::OidcDatabase < OmniAuth::Strategies::OpenIDConnect
+ option :name, 'openid_connect'
- http_type = Setting.get('http_type')
- fqdn = Setting.get('fqdn')
+ def self.setup
+ auth_openid_connect_credentials = Setting.get('auth_openid_connect_credentials') || {}
- client_options = {
- identifier: auth_openid_connect_credentials['identifier'],
- redirect_uri: "#{http_type}://#{fqdn}/auth/openid_connect/callback",
- }
+ http_type = Setting.get('http_type')
+ fqdn = Setting.get('fqdn')
- auth_openid_connect_credentials['scope'] = %i[openid email profile] if auth_openid_connect_credentials['scope'].blank?
- auth_openid_connect_credentials['scope'] = auth_openid_connect_credentials['scope'].split.map(&:to_sym) if auth_openid_connect_credentials['scope'].is_a?(String)
+ client_options = {
+ identifier: auth_openid_connect_credentials['identifier'],
+ redirect_uri: "#{http_type}://#{fqdn}/auth/openid_connect/callback",
+ }
- auth_openid_connect_credentials.compact_blank.merge(
- discovery: true,
- response_type: :code,
- pkce: ActiveModel::Type::Boolean.new.cast(auth_openid_connect_credentials['pkce']),
- client_options:,
- )
- end
+ auth_openid_connect_credentials['scope'] = %i[openid email profile] if auth_openid_connect_credentials['scope'].blank?
+ auth_openid_connect_credentials['scope'] = auth_openid_connect_credentials['scope'].split.map(&:to_sym) if auth_openid_connect_credentials['scope'].is_a?(String)
- def self.destroy_session(env, session)
- session.delete('oidc_id_token')
+ auth_openid_connect_credentials.compact_blank.merge(
+ discovery: true,
+ response_type: :code,
+ pkce: ActiveModel::Type::Boolean.new.cast(auth_openid_connect_credentials['pkce']),
+ client_options:,
+ )
+ end
- @_current_user = nil
- env['rack.session.options'][:expire_after] = nil
+ def self.destroy_session(env, session)
+ session.delete('oidc_id_token')
- session.destroy
- end
+ @_current_user = nil
+ env['rack.session.options'][:expire_after] = nil
- def initialize(app, *args, &)
- args[0] = self.class.setup
+ session.destroy
+ end
- super
- end
+ def initialize(app, *args, &)
+ args[0] = self.class.setup
+
+ super
+ end
- def decode_logout_token(logout_token)
- decode_id_token(logout_token)
+ def decode_logout_token(logout_token)
+ decode_id_token(logout_token)
+ end
end
+rescue LoadError
+ # Gem not installed, strategy not available.
end
diff --git a/lib/omni_auth/strategies/saml_database.rb b/lib/omni_auth/strategies/saml_database.rb
index 0a8c151923..c514248fac 100644
--- a/lib/omni_auth/strategies/saml_database.rb
+++ b/lib/omni_auth/strategies/saml_database.rb
@@ -1,138 +1,145 @@
# Copyright (C) 2012-2026 Zammad Foundation, https://zammad-foundation.org/
-class OmniAuth::Strategies::SamlDatabase < OmniAuth::Strategies::SAML
- option :name, 'saml'
+begin
+ require 'omniauth-saml'
- def self.setup
- auth_saml_credentials = Setting.get('auth_saml_credentials') || {}
+ class OmniAuth::Strategies::SamlDatabase < OmniAuth::Strategies::SAML
+ option :name, 'saml'
- http_type = Setting.get('http_type')
- fqdn = Setting.get('fqdn')
+ def self.setup
+ auth_saml_credentials = Setting.get('auth_saml_credentials') || {}
- # Use meta URL as entity id/issues as it is best practice.
- # See: https://community.zammad.org/t/saml-oidc-third-party-authentication/2533/13
- entity_id = "#{http_type}://#{fqdn}/auth/saml/metadata"
- assertion_consumer_service_url = "#{http_type}://#{fqdn}/auth/saml/callback"
- single_logout_service_url = "#{http_type}://#{fqdn}/auth/saml/slo"
+ http_type = Setting.get('http_type')
+ fqdn = Setting.get('fqdn')
- config = auth_saml_credentials.compact_blank
- .merge(
- assertion_consumer_service_url: assertion_consumer_service_url,
- sp_entity_id: entity_id,
- single_logout_service_url: single_logout_service_url,
- idp_slo_session_destroy: proc { |env, session| destroy_session(env, session) },
- )
+ # Use meta URL as entity id/issues as it is best practice.
+ # See: https://community.zammad.org/t/saml-oidc-third-party-authentication/2533/13
+ entity_id = "#{http_type}://#{fqdn}/auth/saml/metadata"
+ assertion_consumer_service_url = "#{http_type}://#{fqdn}/auth/saml/callback"
+ single_logout_service_url = "#{http_type}://#{fqdn}/auth/saml/slo"
- apply_security_settings(config)
+ config = auth_saml_credentials.compact_blank
+ .merge(
+ assertion_consumer_service_url: assertion_consumer_service_url,
+ attribute_service_name: Setting.get('product_name'),
+ sp_entity_id: entity_id,
+ single_logout_service_url: single_logout_service_url,
+ idp_slo_session_destroy: proc { |env, session| destroy_session(env, session) },
+ )
- config
- end
-
- def self.destroy_session(env, session)
- session.delete('saml_uid')
- session.delete('saml_transaction_id')
- session.delete('saml_session_index')
+ apply_security_settings(config)
- @_current_user = nil
- env['rack.session.options'][:expire_after] = nil
+ config
+ end
- session.destroy
- end
+ def self.destroy_session(env, session)
+ session.delete('saml_uid')
+ session.delete('saml_transaction_id')
+ session.delete('saml_session_index')
- def initialize(app, *args, &)
- args[0] = self.class.setup
+ @_current_user = nil
+ env['rack.session.options'][:expire_after] = nil
- super
- end
+ session.destroy
+ end
- def self.apply_security_settings(settings)
- security = settings.delete(:security) || {}
- private_key = settings.delete(:private_key) || ''
- private_key_secret = settings.delete(:private_key_secret) || ''
- certificate = settings.delete(:certificate) || ''
+ def initialize(app, *args, &)
+ args[0] = self.class.setup
- return if !check_security_settings(settings, security, private_key, private_key_secret, certificate)
+ super
+ end
- apply_security_default_settings(settings)
- apply_sign_only_settings(settings, security)
- apply_encrypt_only_settings(settings, security)
+ def self.apply_security_settings(settings)
+ security = settings.delete(:security) || {}
+ private_key = settings.delete(:private_key) || ''
+ private_key_secret = settings.delete(:private_key_secret) || ''
+ certificate = settings.delete(:certificate) || ''
- true
- end
+ return if !check_security_settings(settings, security, private_key, private_key_secret, certificate)
- def self.check_security_settings(settings, security, private_key, private_key_secret, certificate)
- return false if security.blank? || security.eql?('off')
- return false if private_key.blank? || certificate.blank?
+ apply_security_default_settings(settings)
+ apply_sign_only_settings(settings, security)
+ apply_encrypt_only_settings(settings, security)
- begin
- pkey = OpenSSL::PKey.read(private_key, private_key_secret)
- rescue
- return false
+ true
end
- settings[:private_key] = pkey.to_pem
- settings[:certificate] = certificate
+ def self.check_security_settings(settings, security, private_key, private_key_secret, certificate)
+ return false if security.blank? || security.eql?('off')
+ return false if private_key.blank? || certificate.blank?
- true
- end
+ begin
+ pkey = OpenSSL::PKey.read(private_key, private_key_secret)
+ rescue
+ return false
+ end
- def self.apply_security_default_settings(settings)
- settings[:security] = {
- digest_method: XMLSecurity::Document::SHA256,
- signature_method: XMLSecurity::Document::RSA_SHA256,
- authn_requests_signed: true,
- logout_requests_signed: true,
- want_assertions_signed: true,
- want_assertions_encrypted: true,
- }
-
- true
- end
+ settings[:private_key] = pkey.to_pem
+ settings[:certificate] = certificate
- def self.apply_encrypt_only_settings(settings, security)
- return if !security.eql?('encrypt')
+ true
+ end
- settings[:security][:authn_requests_signed] = false
- settings[:security][:logout_requests_signed] = false
- settings[:security][:want_assertions_signed] = false
+ def self.apply_security_default_settings(settings)
+ settings[:security] = {
+ digest_method: XMLSecurity::Document::SHA256,
+ signature_method: XMLSecurity::Document::RSA_SHA256,
+ authn_requests_signed: true,
+ logout_requests_signed: true,
+ want_assertions_signed: true,
+ want_assertions_encrypted: true,
+ }
+
+ true
+ end
- true
- end
+ def self.apply_encrypt_only_settings(settings, security)
+ return if !security.eql?('encrypt')
- def self.apply_sign_only_settings(settings, security)
- return if !security.eql?('sign')
+ settings[:security][:authn_requests_signed] = false
+ settings[:security][:logout_requests_signed] = false
+ settings[:security][:want_assertions_signed] = false
- settings[:security][:want_assertions_encrypted] = false
+ true
+ end
- true
- end
+ def self.apply_sign_only_settings(settings, security)
+ return if !security.eql?('sign')
+
+ settings[:security][:want_assertions_encrypted] = false
- private_class_method %i[
- apply_security_settings
- check_security_settings
- apply_security_default_settings
- apply_encrypt_only_settings
- apply_sign_only_settings
- ].freeze
+ true
+ end
- private
+ private_class_method %i[
+ apply_security_settings
+ check_security_settings
+ apply_security_default_settings
+ apply_encrypt_only_settings
+ apply_sign_only_settings
+ ].freeze
- def handle_logout_response(raw_response, settings)
- logout_response = OneLogin::RubySaml::Logoutresponse.new(raw_response, settings, matches_request_id: session['saml_transaction_id'])
- logout_response.soft = false
- logout_response.validate
+ private
- redirect_path = if session['omniauth.origin']&.include?('/mobile')
- '/mobile'
- elsif session['omniauth.origin']&.include?('/desktop')
- '/desktop'
- else
- '/'
- end
+ def handle_logout_response(raw_response, settings)
+ logout_response = OneLogin::RubySaml::Logoutresponse.new(raw_response, settings, matches_request_id: session['saml_transaction_id'])
+ logout_response.soft = false
+ logout_response.validate
- self.class.destroy_session(env, session)
+ redirect_path = if session['omniauth.origin']&.include?('/mobile')
+ '/mobile'
+ elsif session['omniauth.origin']&.include?('/desktop')
+ '/desktop'
+ else
+ '/'
+ end
- redirect "#{Setting.get('http_type')}://#{Setting.get('fqdn')}#{redirect_path}"
- end
+ self.class.destroy_session(env, session)
+ redirect "#{Setting.get('http_type')}://#{Setting.get('fqdn')}#{redirect_path}"
+ end
+
+ end
+rescue LoadError
+ # Gem not installed, strategy not available.
end
diff --git a/lib/omni_auth/strategies/twitter_database.rb b/lib/omni_auth/strategies/twitter_database.rb
index 0ea40e0b2f..abdce49e62 100644
--- a/lib/omni_auth/strategies/twitter_database.rb
+++ b/lib/omni_auth/strategies/twitter_database.rb
@@ -1,15 +1,21 @@
# Copyright (C) 2012-2026 Zammad Foundation, https://zammad-foundation.org/
-class OmniAuth::Strategies::TwitterDatabase < OmniAuth::Strategies::Twitter
- option :name, 'twitter'
+begin
+ require 'omniauth-twitter'
- def initialize(app, *args, &)
+ class OmniAuth::Strategies::TwitterDatabase < OmniAuth::Strategies::Twitter
+ option :name, 'twitter'
- # database lookup
- config = Setting.get('auth_twitter_credentials') || {}
- args[0] = config['key']
- args[1] = config['secret']
- super
- end
+ def initialize(app, *args, &)
+
+ # database lookup
+ config = Setting.get('auth_twitter_credentials') || {}
+ args[0] = config['key']
+ args[1] = config['secret']
+ super
+ end
+ end
+rescue LoadError
+ # Gem not installed, strategy not available.
end
diff --git a/lib/omni_auth/strategies/weibo_database.rb b/lib/omni_auth/strategies/weibo_database.rb
index 297dd9ffd8..41bda6edc7 100644
--- a/lib/omni_auth/strategies/weibo_database.rb
+++ b/lib/omni_auth/strategies/weibo_database.rb
@@ -1,15 +1,21 @@
# Copyright (C) 2012-2026 Zammad Foundation, https://zammad-foundation.org/
-class OmniAuth::Strategies::WeiboDatabase < OmniAuth::Strategies::Weibo
- option :name, 'weibo'
+begin
+ require 'omniauth-weibo-oauth2'
- def initialize(app, *args, &)
+ class OmniAuth::Strategies::WeiboDatabase < OmniAuth::Strategies::Weibo
+ option :name, 'weibo'
- # database lookup
- config = Setting.get('auth_weibo_credentials') || {}
- args[0] = config['client_id']
- args[1] = config['client_secret']
- super
- end
+ def initialize(app, *args, &)
+
+ # database lookup
+ config = Setting.get('auth_weibo_credentials') || {}
+ args[0] = config['client_id']
+ args[1] = config['client_secret']
+ super
+ end
+ end
+rescue LoadError
+ # Gem not installed, strategy not available.
end
--
2.53.0