From a93250e210a1b2fcbfe61843dd2e3f6c3451ff12 Mon Sep 17 00:00:00 2001 From: Thorsten Eckel Date: Mon, 16 Sep 2019 15:03:21 +0200 Subject: [PATCH] Fixes issue #2741: Authenticate users via SAML. --- Gemfile | 1 + Gemfile.lock | 6 ++ .../_profile/linked_accounts.coffee | 5 ++ .../app/views/layout_ref/ui.jst.eco | 4 +- app/assets/stylesheets/zammad.scss | 4 + config/initializers/omniauth.rb | 2 + db/migrate/20190715141227_saml_auth.rb | 79 +++++++++++++++++++ vendor/lib/saml_database.rb | 26 ++++++ 8 files changed, 125 insertions(+), 2 deletions(-) create mode 100644 db/migrate/20190715141227_saml_auth.rb create mode 100644 vendor/lib/saml_database.rb diff --git a/Gemfile b/Gemfile index da451f7be..8561f8891 100644 --- a/Gemfile +++ b/Gemfile @@ -74,6 +74,7 @@ gem 'omniauth-google-oauth2' gem 'omniauth-linkedin-oauth2' gem 'omniauth-microsoft-office365' gem 'omniauth-oauth2' +gem 'omniauth-saml' gem 'omniauth-twitter' gem 'omniauth-weibo-oauth2' diff --git a/Gemfile.lock b/Gemfile.lock index fdb353fc2..bce39abb3 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -345,6 +345,9 @@ GEM omniauth-rails_csrf_protection (0.1.2) actionpack (>= 4.2) omniauth (>= 1.3.1) + omniauth-saml (1.10.1) + omniauth (~> 1.3, >= 1.3.2) + ruby-saml (~> 1.7) omniauth-twitter (1.4.0) omniauth-oauth (~> 1.1) rack @@ -456,6 +459,8 @@ GEM rubocop-rspec (1.33.0) rubocop (>= 0.60.0) ruby-progressbar (1.10.1) + ruby-saml (1.10.2) + nokogiri (>= 1.5.10) ruby_dep (1.5.0) rubyzip (1.2.2) safe_yaml (1.0.5) @@ -610,6 +615,7 @@ DEPENDENCIES omniauth-microsoft-office365 omniauth-oauth2 omniauth-rails_csrf_protection + omniauth-saml omniauth-twitter omniauth-weibo-oauth2 pg (= 0.21.0) diff --git a/app/assets/javascripts/app/controllers/_profile/linked_accounts.coffee b/app/assets/javascripts/app/controllers/_profile/linked_accounts.coffee index a5c431bbd..d8ee745ef 100644 --- a/app/assets/javascripts/app/controllers/_profile/linked_accounts.coffee +++ b/app/assets/javascripts/app/controllers/_profile/linked_accounts.coffee @@ -106,4 +106,9 @@ App.Config.set('auth_provider_all', { name: 'Weibo' config: 'auth_weibo' class: 'weibo' + saml: + url: '/auth/saml' + name: 'SAML' + config: 'auth_saml' + class: 'saml' }) diff --git a/app/assets/javascripts/app/views/layout_ref/ui.jst.eco b/app/assets/javascripts/app/views/layout_ref/ui.jst.eco index 962a6e4b0..4301d6ca5 100644 --- a/app/assets/javascripts/app/views/layout_ref/ui.jst.eco +++ b/app/assets/javascripts/app/views/layout_ref/ui.jst.eco @@ -72,7 +72,7 @@ Icon Name - <% for icon in ['archived-modifier','arrow-down','arrow-left','arrow-right','arrow-up','bold','chain','chat','checkbox-checked','checkbox-indeterminate','checkbox','checkmark','clipboard','clock','cloud','cog','crown','danger','dashboard','diagonal-cross','document','download','draft-modifier','draggable','dropdown-list','email-button','email','external','eye','eyedropper','facebook-button','facebook','file-archive','file-code','file-email','file-excel','file-pdf','file-powerpoint','file-text','file-unknown','file-word','form','forward','full-logo','github-button','gitlab-button','google-button','group','help','horizontal-rule','important','in-process','inactive-organization','inactive-user','info','internal-modifier','italic','knowledge-base-answer','knowledge-base','line-left-arrow','line-right-arrow','linkedin-button','list','loading','lock-open','lock','logo','logotype','long-arrow-down','long-arrow-right','low-priority','magnifier','marker','message','minus-small','minus','mood-bad','mood-good','mood-ok','mood-sad','mood-superbad','mood-supergood','mute','note','oauth2-button','office365-button','one-ticket','organization','outbound-calls','overflow-button','overviews','package','paperclip','pen','person','phone','plus-small','plus','printer','radio-checked','radio','rearange','received-calls','reload','reopening','reply-all','reply','report','searchdetail','signout','small-dot','sms','spinner-small','split','status-modified-outer-circle','status','stopwatch','strikethrough','switchView','task-state','team','telegram','templates','tools','total-tickets','trash','twitter-button','twitter','underline','unmute','unordered-list','user','web','weibo-button','zoom-in','zoom-out']: %> + <% for icon in ['archived-modifier','arrow-down','arrow-left','arrow-right','arrow-up','bold','chain','chat','checkbox-checked','checkbox-indeterminate','checkbox','checkmark','clipboard','clock','cloud','cog','crown','danger','dashboard','diagonal-cross','document','download','draft-modifier','draggable','dropdown-list','email-button','email','external','eye','eyedropper','facebook-button','facebook','file-archive','file-code','file-email','file-excel','file-pdf','file-powerpoint','file-text','file-unknown','file-word','form','forward','full-logo','github-button','gitlab-button','google-button','group','help','horizontal-rule','important','in-process','inactive-organization','inactive-user','info','internal-modifier','italic','knowledge-base-answer','knowledge-base','line-left-arrow','line-right-arrow','linkedin-button','list','loading','lock-open','lock','logo','logotype','long-arrow-down','long-arrow-right','low-priority','magnifier','marker','message','minus-small','minus','mood-bad','mood-good','mood-ok','mood-sad','mood-superbad','mood-supergood','mute','note','oauth2-button','office365-button','one-ticket','organization','outbound-calls','overflow-button','overviews','package','paperclip','pen','person','phone','plus-small','plus','printer','radio-checked','radio','rearange','received-calls','reload','reopening','reply-all','reply','report','saml-button','searchdetail','signout','small-dot','sms','spinner-small','split','status-modified-outer-circle','status','stopwatch','strikethrough','switchView','task-state','team','telegram','templates','tools','total-tickets','trash','twitter-button','twitter','underline','unmute','unordered-list','user','web','saml-button','zoom-in','zoom-out']: %> <%- @Icon( "#{icon}") %> @@ -84,4 +84,4 @@ <% end %> - \ No newline at end of file + diff --git a/app/assets/stylesheets/zammad.scss b/app/assets/stylesheets/zammad.scss index 6bcb25d58..1561debe0 100644 --- a/app/assets/stylesheets/zammad.scss +++ b/app/assets/stylesheets/zammad.scss @@ -3081,6 +3081,10 @@ ol.tabs li { background: hsl(0,0%,27%); } + &.auth-provider--saml { + background: hsl(0,0%,27%); + } + .provider-name { flex: 1; } diff --git a/config/initializers/omniauth.rb b/config/initializers/omniauth.rb index 99391fc43..1f7682c97 100644 --- a/config/initializers/omniauth.rb +++ b/config/initializers/omniauth.rb @@ -49,6 +49,8 @@ Rails.application.config.middleware.use OmniAuth::Builder do # weibo database connect provider :weibo_database, 'not_change_will_be_set_by_database', 'not_change_will_be_set_by_database' + # SAML database connect + provider :saml_database end # This fixes issue #1642 and is required for setups in which Zammad is used diff --git a/db/migrate/20190715141227_saml_auth.rb b/db/migrate/20190715141227_saml_auth.rb new file mode 100644 index 000000000..6314a46a1 --- /dev/null +++ b/db/migrate/20190715141227_saml_auth.rb @@ -0,0 +1,79 @@ +class SamlAuth < ActiveRecord::Migration[5.2] + def up + # return if it's a new setup + return if !Setting.find_by(name: 'system_init_done') + + Setting.create_if_not_exists( + title: 'Authentication via %s', + name: 'auth_saml', + area: 'Security::ThirdPartyAuthentication', + description: 'Enables user authentication via %s.', + options: { + form: [ + { + display: '', + null: true, + name: 'auth_saml', + tag: 'boolean', + options: { + true => 'yes', + false => 'no', + }, + }, + ], + }, + preferences: { + controller: 'SettingsAreaSwitch', + sub: ['auth_saml_credentials'], + title_i18n: ['SAML'], + description_i18n: ['SAML'], + permission: ['admin.security'], + }, + state: false, + frontend: true + ) + Setting.create_if_not_exists( + title: 'SAML App Credentials', + name: 'auth_saml_credentials', + area: 'Security::ThirdPartyAuthentication::SAML', + description: 'Enables user authentication via SAML.', + options: { + form: [ + { + display: 'IDP SSO target URL', + null: true, + name: 'idp_sso_target_url', + tag: 'input', + placeholder: 'https://capriza.github.io/samling/samling.html', + }, + { + display: 'IDP certificate', + null: true, + name: 'idp_cert', + tag: 'input', + placeholder: '-----BEGIN CERTIFICATE-----\n...-----END CERTIFICATE-----', + }, + { + display: 'IDP certificate fingerprint', + null: true, + name: 'idp_cert_fingerprint', + tag: 'input', + placeholder: 'E7:91:B2:E1:...', + }, + { + display: 'Name Identifier Format', + null: true, + name: 'name_identifier_format', + tag: 'input', + placeholder: 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress', + }, + ], + }, + state: {}, + preferences: { + permission: ['admin.security'], + }, + frontend: false + ) + end +end diff --git a/vendor/lib/saml_database.rb b/vendor/lib/saml_database.rb new file mode 100644 index 000000000..766dbd937 --- /dev/null +++ b/vendor/lib/saml_database.rb @@ -0,0 +1,26 @@ +class SamlDatabase < OmniAuth::Strategies::SAML + option :name, 'saml' + + def initialize(app, *args, &block) + + http_type = Setting.get('http_type') + fqdn = Setting.get('fqdn') + + # 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" + + config = Setting.get('auth_saml_credentials') || {} + options = config.reject { |k,v| v.blank? } + .merge( + :assertion_consumer_service_url => assertion_consumer_service_url, + :issuer => entity_id, + ) + + args[0] = options + + super + end + +end