Fixes issue #2741: Authenticate users via SAML.

This commit is contained in:
Thorsten Eckel 2019-09-16 15:03:21 +02:00
parent 128e8dea72
commit a93250e210
8 changed files with 125 additions and 2 deletions

View file

@ -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'

View file

@ -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)

View file

@ -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'
})

View file

@ -72,7 +72,7 @@
<th>Icon</th>
<th>Name</th>
</tr>
<% 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']: %>
<tr>
<td>
<%- @Icon( "#{icon}") %>
@ -84,4 +84,4 @@
<% end %>
</table>
</div>
</div>

View file

@ -3081,6 +3081,10 @@ ol.tabs li {
background: hsl(0,0%,27%);
}
&.auth-provider--saml {
background: hsl(0,0%,27%);
}
.provider-name {
flex: 1;
}

View file

@ -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

View file

@ -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

26
vendor/lib/saml_database.rb vendored Normal file
View file

@ -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