concerto_saml_auth 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE +14 -0
- data/README.md +64 -0
- data/Rakefile +34 -0
- data/app/assets/javascripts/concerto_saml_auth/application.js +13 -0
- data/app/assets/stylesheets/concerto_saml_auth/application.css +15 -0
- data/app/controllers/concerto_saml_auth/application_controller.rb +190 -0
- data/app/controllers/concerto_saml_auth/omniauth_callback_controller.rb +35 -0
- data/app/helpers/concerto_saml_auth/application_helper.rb +4 -0
- data/app/views/concerto_saml_auth/omniauth_saml/_signin.html.erb +1 -0
- data/config/initializers/omniauth.rb +123 -0
- data/config/routes.rb +3 -0
- data/lib/concerto_saml_auth/engine.rb +31 -0
- data/lib/concerto_saml_auth/version.rb +3 -0
- data/lib/concerto_saml_auth.rb +4 -0
- data/lib/tasks/concerto_saml_auth_tasks.rake +18 -0
- metadata +103 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: b6ebfa7bb437454efca0513642b2bfbf5464e5f2
|
4
|
+
data.tar.gz: 59ac1d64b4f418bc48019657687edc7bb94ddc98
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 2e16f9fa978ded9c51250f5a3e2b2aee663db06cceabd22a2ae1c5068b9c2e1e667d91e1acc5879d3f8c1d319e39a1408dfbcbb2199270ef3969a827f0c73ede
|
7
|
+
data.tar.gz: 6d466a2c368cd922ec36fb2d7cc6d0d8249ba5dfef681d03f211905fd1f2e387239febe9bef305e756db91e1a25a57e25255f9baaefd4bcba9c03cbf05b94cb8
|
data/LICENSE
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
Copyright 2014 Concerto Authors
|
2
|
+
Copyright 2017 Thorben Dahl
|
3
|
+
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
+
you may not use this file except in compliance with the License.
|
6
|
+
You may obtain a copy of the License at
|
7
|
+
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
+
See the License for the specific language governing permissions and
|
14
|
+
limitations under the License.
|
data/README.md
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
Concerto SAML Auth
|
2
|
+
=====================
|
3
|
+
|
4
|
+
Authenticate Concerto users through your own [SAML](https://en.wikipedia.org/wiki/Security_Assertion_Markup_Language) deployment.
|
5
|
+
|
6
|
+
Please note that this project is not affiliated with or endorsed by the Concerto Digital Signage project.
|
7
|
+
|
8
|
+
The aim of this project is to customize the Concerto CAS Auth plugin so it works with `omniauth-saml` instead of `omniauth-cas`.
|
9
|
+
|
10
|
+
Installing the plugin
|
11
|
+
----------------------
|
12
|
+
|
13
|
+
1. Log in using a system admin account in your Concerto deployment. You should have set up your Concerto installation already.
|
14
|
+
2. Click on the "plugins" button on the top navigation bar under the admin section.
|
15
|
+
3. On the right side of the page, click on the "new plugin" button.
|
16
|
+
4. With Git Repository selected as the source, write `concerto_saml_auth` as the Gem Name, and this repository's clone URL as the Source URL.
|
17
|
+
5. Click save, you will now stop your Concerto web server, run the ```bundle``` command (while in the Concerto directory), and start your web server again.
|
18
|
+
6. Since the SAML plugin is not configured yet, you can log back into your Concerto accounts by visiting the ```your.concerto.url/users/sign_in``` route, using
|
19
|
+
any user that was not created using through SAML login.
|
20
|
+
7. If the plugin was installed successfully, you will see a new "SAML User Authentication" settings tab under the "settings" page. This page can be found by clicking the "settings" button on the top navigation bar under the admin section.
|
21
|
+
8. Configure the plugin and restart the web server, as explained below.
|
22
|
+
9. Add your Service Provider's metadata to the Identity Provider. You can find your metadata at `your.concerto.url/auth/saml/metadata`.
|
23
|
+
|
24
|
+
Configuring the plugin
|
25
|
+
----------------------
|
26
|
+
|
27
|
+
**The details of how to configure this plugin are not finalized.**
|
28
|
+
|
29
|
+
1. Log in using a system admin account in your Concerto deployment
|
30
|
+
2. Click on the "settings" button on the top navigation bar under the admin section.
|
31
|
+
3. Click on the "SAML User Authentication" tab.
|
32
|
+
4. Configure the different values (see explanation below).
|
33
|
+
7. After saving these settings, you will need to restart your Concerto web server.
|
34
|
+
8. Your log in links at the top of the page should now point to your CAS authentication.
|
35
|
+
|
36
|
+
Explanation of the configuration parameters:
|
37
|
+
|
38
|
+
**Saml Idp Metadata**: URL to the Identity Provider's metadata. For a simpleSAML installation, this could be `https://idp.example.com/simplesaml/saml2/idp/metadata.php`. All information about the Identity Provider is gathered through this.
|
39
|
+
|
40
|
+
**Saml Issuer**: Unique identification of this application, used by the Identity Provider to separate different Service Provider. It is normal to use an URL you control, so there is no chance of collision. Example: `https://concerto.example.com/saml2`.
|
41
|
+
|
42
|
+
The rest of the configuration items maps information received from the Identity Provider to fields in Concerto.
|
43
|
+
|
44
|
+
**Saml Uid Key**: Name of field containing user ID, used to map between users logging in and the local Concerto user they should log in as.
|
45
|
+
|
46
|
+
**Saml Email Key**: Name of field containing the user's email address.
|
47
|
+
|
48
|
+
**Saml First Name Key**: Name of field containing the user's first name.
|
49
|
+
|
50
|
+
**Saml Last Name Key**: Name of field containing the user's last name.
|
51
|
+
|
52
|
+
**Saml Member Of Key**: Name of fields containing the groups the user is a member of, as expressed by LDAP's memberOf field. You can leave this out to not synchronize groups.
|
53
|
+
|
54
|
+
**Saml Member Of Filter**: Comma-separated list of case-insensitive assertions. A group must match at least one of those assertions in order to be included in Concerto. You can use the common name to whitelist groups, like `CN=Administrators, CN=Graphics`.
|
55
|
+
|
56
|
+
**Saml Admin Groups**: Comma-separated case-insensitive list of groups whose members should be made admin when they log in through SAML.
|
57
|
+
|
58
|
+
note: This plugin is essentially a wrapper around [omniauth-saml](https://github.com/omniauth/omniauth-saml) with added logic for creating Concerto user accounts with the returned SAML information and synchronize privileges. Feel free to follow the omniauth-saml link and see a more detailed description of the configuration items.
|
59
|
+
|
60
|
+
Known Issues
|
61
|
+
------------
|
62
|
+
|
63
|
+
* Localization is not a thing with this plugin. All error messages and changes made to interface are in English only.
|
64
|
+
* Somewhat specific: This plugin was made to fit the needs of Studentmediene i Trondheim AS. Some details of its operation may not be suitable for others.
|
data/Rakefile
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
begin
|
2
|
+
require 'bundler/setup'
|
3
|
+
rescue LoadError
|
4
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
5
|
+
end
|
6
|
+
|
7
|
+
require 'rdoc/task'
|
8
|
+
|
9
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
10
|
+
rdoc.rdoc_dir = 'rdoc'
|
11
|
+
rdoc.title = 'ConcertoSamlAuth'
|
12
|
+
rdoc.options << '--line-numbers'
|
13
|
+
rdoc.rdoc_files.include('README.rdoc')
|
14
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
15
|
+
end
|
16
|
+
|
17
|
+
APP_RAKEFILE = File.expand_path("../test/dummy/Rakefile", __FILE__)
|
18
|
+
load 'rails/tasks/engine.rake'
|
19
|
+
|
20
|
+
|
21
|
+
|
22
|
+
Bundler::GemHelper.install_tasks
|
23
|
+
|
24
|
+
require 'rake/testtask'
|
25
|
+
|
26
|
+
Rake::TestTask.new(:test) do |t|
|
27
|
+
t.libs << 'lib'
|
28
|
+
t.libs << 'test'
|
29
|
+
t.pattern = 'test/**/*_test.rb'
|
30
|
+
t.verbose = false
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
task default: :test
|
@@ -0,0 +1,13 @@
|
|
1
|
+
// This is a manifest file that'll be compiled into application.js, which will include all the files
|
2
|
+
// listed below.
|
3
|
+
//
|
4
|
+
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
|
5
|
+
// or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
|
6
|
+
//
|
7
|
+
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
|
8
|
+
// compiled file.
|
9
|
+
//
|
10
|
+
// Read Sprockets README (https://github.com/sstephenson/sprockets#sprockets-directives) for details
|
11
|
+
// about supported directives.
|
12
|
+
//
|
13
|
+
//= require_tree .
|
@@ -0,0 +1,15 @@
|
|
1
|
+
/*
|
2
|
+
* This is a manifest file that'll be compiled into application.css, which will include all the files
|
3
|
+
* listed below.
|
4
|
+
*
|
5
|
+
* Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
|
6
|
+
* or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path.
|
7
|
+
*
|
8
|
+
* You're free to add application-wide styles to this file and they'll appear at the bottom of the
|
9
|
+
* compiled file so the styles you add here take precedence over styles defined in any styles
|
10
|
+
* defined in the other CSS/SCSS files in this directory. It is generally better to create a new
|
11
|
+
* file per style scope.
|
12
|
+
*
|
13
|
+
*= require_tree .
|
14
|
+
*= require_self
|
15
|
+
*/
|
@@ -0,0 +1,190 @@
|
|
1
|
+
module ConcertoSamlAuth
|
2
|
+
class ApplicationController < ::ApplicationController
|
3
|
+
|
4
|
+
# Used to map a user id with a corresponding authentication provider in the
|
5
|
+
# database (in this case it's SAML)
|
6
|
+
require 'concerto_identity'
|
7
|
+
|
8
|
+
# Find or create a new user based on values returned by the SAML callback
|
9
|
+
def find_from_omniauth(saml_hash)
|
10
|
+
# Get configuration options for customized SAML return value identifiers
|
11
|
+
omniauth_config = ConcertoSamlAuth::Engine.config.omniauth_config
|
12
|
+
uid = saml_hash[:uid]
|
13
|
+
uid = uid.downcase
|
14
|
+
|
15
|
+
# Check if an identity records exists for the user attempting to sign in
|
16
|
+
if identity = ConcertoIdentity::Identity.find_by_external_id(uid)
|
17
|
+
# Return the matching user record
|
18
|
+
user_existed = true
|
19
|
+
user = identity.user
|
20
|
+
else
|
21
|
+
# Add a new user via omniauth SAML details
|
22
|
+
user_existed = false
|
23
|
+
user = User.new
|
24
|
+
end
|
25
|
+
|
26
|
+
# Set user attributes
|
27
|
+
|
28
|
+
# First name is required for user validation
|
29
|
+
if !saml_hash[:info][:first_name].nil?
|
30
|
+
user.first_name = saml_hash[:info][:first_name]
|
31
|
+
else
|
32
|
+
user.first_name = uid
|
33
|
+
end
|
34
|
+
|
35
|
+
if !saml_hash[:info][:last_name].nil?
|
36
|
+
user.last_name = saml_hash[:info][:last_name]
|
37
|
+
end
|
38
|
+
|
39
|
+
# Email is required for user validation
|
40
|
+
if saml_hash[:info][:email].nil?
|
41
|
+
flash.notice = "No email was provided by the identity provider"
|
42
|
+
return nil
|
43
|
+
else
|
44
|
+
user.email = saml_hash[:info][:email]
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
# Set user password and confirmation to random tokens
|
49
|
+
user.password,user.password_confirmation=Devise.friendly_token
|
50
|
+
|
51
|
+
# Attempt to save our new user
|
52
|
+
if user.save
|
53
|
+
# Saved
|
54
|
+
|
55
|
+
if omniauth_config[:member_of_key].present?
|
56
|
+
synchronize_group_membership(user, saml_hash, omniauth_config)
|
57
|
+
end
|
58
|
+
|
59
|
+
if omniauth_config[:admin_groups].present?
|
60
|
+
synchronize_is_admin(user, omniauth_config)
|
61
|
+
end
|
62
|
+
|
63
|
+
user.save!
|
64
|
+
|
65
|
+
if !user_existed
|
66
|
+
# Create a matching identity to track our new user for future
|
67
|
+
# sessions and return our new user record
|
68
|
+
ConcertoIdentity::Identity.create(provider: "saml",
|
69
|
+
external_id: uid,
|
70
|
+
user_id: user.id)
|
71
|
+
end
|
72
|
+
|
73
|
+
return {user: user, existed: user_existed}
|
74
|
+
else
|
75
|
+
# User save failed, an error occurred
|
76
|
+
flash.notice = "Failed to sign in with SAML.
|
77
|
+
#{user.errors.full_messages.to_sentence}."
|
78
|
+
return nil
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def synchronize_group_membership(user, saml_hash, omniauth_config)
|
83
|
+
desired_group_names = find_user_groups(saml_hash, omniauth_config)
|
84
|
+
existing_group_memberships = Membership.where(user: user)
|
85
|
+
existing_group_names = existing_group_memberships.map do |membership|
|
86
|
+
membership.group.name
|
87
|
+
end
|
88
|
+
groups_to_add = desired_group_names - existing_group_names
|
89
|
+
groups_to_remove = existing_group_names - desired_group_names
|
90
|
+
add_user_to(groups_to_add, user)
|
91
|
+
remove_user_from(groups_to_remove, user)
|
92
|
+
end
|
93
|
+
|
94
|
+
def find_user_groups(saml_hash, omniauth_config)
|
95
|
+
member_of_key = omniauth_config[:member_of_key]
|
96
|
+
member_of_lines = saml_hash[:extra][:response_object].attributes.multi(member_of_key)
|
97
|
+
if member_of_lines.nil?
|
98
|
+
Rails.logger.debug "No user groups found"
|
99
|
+
return nil
|
100
|
+
end
|
101
|
+
# Go from array of "CN=Broadcast Engineer,OU=Commission,OU=Groups,DC=example,DC=com"
|
102
|
+
# to array of ["CN=Broadcast Engineer", "OU=Commission", "OU=Groups", "DC=example", "DC=com"]
|
103
|
+
groups_splitted = member_of_lines.map do |single_member_of_line|
|
104
|
+
single_member_of_line.split(",")
|
105
|
+
end
|
106
|
+
# Downcase every string
|
107
|
+
groups_splitted.map! do |single_splitted_group|
|
108
|
+
single_splitted_group.map do |group_part|
|
109
|
+
group_part.downcase
|
110
|
+
end
|
111
|
+
end
|
112
|
+
Rails.logger.debug "Interpreting the memberOf field:"
|
113
|
+
Rails.logger.debug groups_splitted
|
114
|
+
# Remove elements which are not matched by the filter
|
115
|
+
if !omniauth_config[:member_of_filter].blank?
|
116
|
+
filter = omniauth_config[:member_of_filter]
|
117
|
+
filter = (filter.strip).downcase
|
118
|
+
filter_list = filter.split(",")
|
119
|
+
Rails.logger.debug filter_list
|
120
|
+
matching_groups = groups_splitted.select do |single_splitted_group|
|
121
|
+
# Check if the intersection of single_splitted_group and filter_list has elements
|
122
|
+
# (meaning that at least one element in single_splitted_group matches one element
|
123
|
+
# in filter_list)
|
124
|
+
!(single_splitted_group & filter_list).empty?
|
125
|
+
end
|
126
|
+
else
|
127
|
+
matching_groups = groups_splitted
|
128
|
+
end
|
129
|
+
Rails.logger.debug matching_groups
|
130
|
+
# Go to array of {:cn => ["broadcast engineer"], :ou => ["commission", "groups"], :dc => ["example", "com"]}
|
131
|
+
group_hashes = matching_groups.map do |single_splitted_group|
|
132
|
+
# Create a hash which returns new Arrays for missing entries
|
133
|
+
resulting_group_hash = Hash.new{|h,k| h[k] = []}
|
134
|
+
single_splitted_group.each do |single_group_attribute|
|
135
|
+
key_and_value = single_group_attribute.split("=")
|
136
|
+
key = key_and_value.first
|
137
|
+
value = key_and_value.second
|
138
|
+
resulting_group_hash[key].push(value)
|
139
|
+
end
|
140
|
+
resulting_group_hash
|
141
|
+
end
|
142
|
+
Rails.logger.debug group_hashes
|
143
|
+
# Go to array of "broadcast engineer", but ensure all CNs are included
|
144
|
+
common_names = []
|
145
|
+
group_hashes.each do |single_group_hash|
|
146
|
+
single_group_hash["cn"].each do |group_name|
|
147
|
+
common_names.push(group_name)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
Rails.logger.debug common_names
|
151
|
+
# Go to array of "Broadcast engineer" (normalizing names)
|
152
|
+
common_names.map do |single_common_name|
|
153
|
+
single_common_name.capitalize
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
def add_user_to(all_groups, user)
|
158
|
+
all_groups.each do |group_name|
|
159
|
+
group = Group.where(:name => group_name).first_or_create!
|
160
|
+
membership = Membership.create!(:user_id => user.id, :group_id => group.id, :level => Membership::LEVELS[:regular])
|
161
|
+
membership.perms[:screen] = :all
|
162
|
+
membership.perms[:feed] = :all
|
163
|
+
membership.save!
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
def remove_user_from(all_groups, user)
|
168
|
+
all_groups.each do |group_name|
|
169
|
+
group = Group.where(:name => group_name).first!
|
170
|
+
Membership.where(:user_id => user.id, :group_id => group.id).destroy
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
def synchronize_is_admin(user, omniauth_config)
|
175
|
+
admin_group_string = omniauth_config[:admin_groups]
|
176
|
+
admin_group_names = admin_group_string.split(",")
|
177
|
+
admin_group_names.map! do |group|
|
178
|
+
(group.strip).capitalize
|
179
|
+
end
|
180
|
+
|
181
|
+
user_groups = Membership.where(:user_id => user.id)
|
182
|
+
user_group_names = user_groups.map do |membership|
|
183
|
+
membership.group.name
|
184
|
+
end
|
185
|
+
should_be_admin = admin_group_names & user_group_names
|
186
|
+
user.is_admin = !should_be_admin.empty?
|
187
|
+
end
|
188
|
+
|
189
|
+
end
|
190
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require_dependency "concerto_saml_auth/application_controller"
|
2
|
+
|
3
|
+
module ConcertoSamlAuth
|
4
|
+
class OmniauthCallbackController < ApplicationController
|
5
|
+
|
6
|
+
# We will be receiving a POST request initiated by the identity provider.
|
7
|
+
# To check its authenticity, OneLogin SAML will check its signature.
|
8
|
+
# Therefore, we must skip the CSRF protection, since it interferes with
|
9
|
+
# normal operation (it disconnects us from the session, basically).
|
10
|
+
skip_before_action :verify_authenticity_token
|
11
|
+
|
12
|
+
def saml_auth
|
13
|
+
saml_hash = request.env["omniauth.auth"]
|
14
|
+
result = find_from_omniauth(saml_hash)
|
15
|
+
|
16
|
+
if !result
|
17
|
+
# Redirect showing flash notice with errors
|
18
|
+
redirect_to "/"
|
19
|
+
else
|
20
|
+
user = result[:user]
|
21
|
+
user_existed = result[:existed]
|
22
|
+
session["devise.user_attributes"] = user.attributes
|
23
|
+
sign_in user
|
24
|
+
if user_existed
|
25
|
+
flash[:notice] = "Signed in as #{user.first_name} #{user.last_name}"
|
26
|
+
redirect_to "/"
|
27
|
+
else
|
28
|
+
flash[:notice] = "Welcome to Concerto, #{user.first_name} #{user.last_name}! Have a look at the options"
|
29
|
+
redirect_to "/manage/users/#{user.id}/edit"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
<%= link_to t('.sign_in'), root_url + 'auth/saml/' %>
|
@@ -0,0 +1,123 @@
|
|
1
|
+
require 'omniauth'
|
2
|
+
if ActiveRecord::Base.connection.table_exists? 'concerto_configs'
|
3
|
+
# Concerto Configs are created if they don't exist already
|
4
|
+
# these are used to initialize and configure omniauth-cas
|
5
|
+
# TODO: Finalize which settings are available
|
6
|
+
default_saml_idp_metadata = "https://example.com/auth/saml2/idp/metadata"
|
7
|
+
ConcertoConfig.make_concerto_config("saml_idp_metadata", default_saml_idp_metadata,
|
8
|
+
:value_type => "string",
|
9
|
+
:category => "SAML User Authentication",
|
10
|
+
:seq_no => 1,
|
11
|
+
:description =>"URL at which the Identity Provider's metadata can be found.")
|
12
|
+
|
13
|
+
ConcertoConfig.make_concerto_config("saml_issuer", "https://concerto.example.com/SAML2",
|
14
|
+
:value_type => "string",
|
15
|
+
:category => "SAML User Authentication",
|
16
|
+
:seq_no => 2,
|
17
|
+
:description => "A unique identifier used by this application to identify itself to the identity provider.")
|
18
|
+
|
19
|
+
ConcertoConfig.make_concerto_config("saml_uid_key", "uid",
|
20
|
+
:value_type => "string",
|
21
|
+
:category => "SAML User Authentication",
|
22
|
+
:seq_no => 3,
|
23
|
+
:description => "SAML field name containing user login names")
|
24
|
+
|
25
|
+
ConcertoConfig.make_concerto_config("saml_email_key", "email",
|
26
|
+
:value_type => "string",
|
27
|
+
:category => "SAML User Authentication",
|
28
|
+
:seq_no => 4,
|
29
|
+
:description => "SAML field name containing user email addresses. Leave blank if using email_suffix below")
|
30
|
+
|
31
|
+
ConcertoConfig.make_concerto_config("saml_first_name_key", "first_name",
|
32
|
+
:value_type => "string",
|
33
|
+
:category => "SAML User Authentication",
|
34
|
+
:seq_no => 6,
|
35
|
+
:description => "SAML field name containing first name")
|
36
|
+
|
37
|
+
ConcertoConfig.make_concerto_config("saml_last_name_key", "last_name",
|
38
|
+
:value_type => "string",
|
39
|
+
:category => "SAML User Authentication",
|
40
|
+
:seq_no => 7,
|
41
|
+
:description => "SAML field name containing last name")
|
42
|
+
|
43
|
+
ConcertoConfig.make_concerto_config("saml_member_of_key", "memberOf",
|
44
|
+
:value_type => "string",
|
45
|
+
:category => "SAML User Authentication",
|
46
|
+
:seq_no => 8,
|
47
|
+
:description => "SAML field name containing the memberOf attribute, as retrieved from LDAP")
|
48
|
+
|
49
|
+
ConcertoConfig.make_concerto_config("saml_member_of_filter", "OU=Access control",
|
50
|
+
:value_type => "string",
|
51
|
+
:category => "SAML User Authentication",
|
52
|
+
:seq_no => 9,
|
53
|
+
:description => "Filter determining which groups are made in Concerto. At least one of the assertions provided here, separated by comma, must match a memberOf field for it to be included.")
|
54
|
+
|
55
|
+
ConcertoConfig.make_concerto_config("saml_admin_groups", "administrator group",
|
56
|
+
:value_type => "string",
|
57
|
+
:category => "SAML User Authentication",
|
58
|
+
:seq_no => 10,
|
59
|
+
:description => "Common name of groups, separated by comma, whose members should be granted administrator permission in Concerto")
|
60
|
+
|
61
|
+
# Store omniauth config values from main application's ConcertoConfig
|
62
|
+
saml_idp_metadata = ConcertoConfig[:saml_idp_metadata]
|
63
|
+
if saml_idp_metadata.present? && saml_idp_metadata != default_saml_idp_metadata
|
64
|
+
|
65
|
+
idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new
|
66
|
+
begin
|
67
|
+
omniauth_config = idp_metadata_parser.parse_remote_to_hash(saml_idp_metadata)
|
68
|
+
rescue
|
69
|
+
Rails.logger.error "Failed to fetch or parse IDP metadata. Error message: #{$!}"
|
70
|
+
omniauth_config = {}
|
71
|
+
end
|
72
|
+
else
|
73
|
+
omniauth_config = {}
|
74
|
+
Rails.logger.warn "No URL (or default URL) defined for IDP metadata, SAML authentication will not be working."
|
75
|
+
end
|
76
|
+
|
77
|
+
request_attributes = []
|
78
|
+
if ConcertoConfig[:saml_email_key].present?
|
79
|
+
request_attributes.push({:name => ConcertoConfig[:saml_email_key], :friendly_name => "Email", :is_required => true})
|
80
|
+
end
|
81
|
+
if ConcertoConfig[:saml_first_name_key].present?
|
82
|
+
request_attributes.push({:name => ConcertoConfig[:saml_first_name_key], :friendly_name => "First Name", :is_required => true})
|
83
|
+
end
|
84
|
+
if ConcertoConfig[:saml_last_name_key].present?
|
85
|
+
request_attributes.push({:name => ConcertoConfig[:saml_last_name_key], :friendly_name => "Last Name", :is_required => true})
|
86
|
+
end
|
87
|
+
if ConcertoConfig[:saml_member_of_key].present?
|
88
|
+
request_attributes.push({:name => ConcertoConfig[:saml_member_of_key], :friendly_name => "Member Of", :is_required => true})
|
89
|
+
end
|
90
|
+
|
91
|
+
request_attributes.map! do |attr|
|
92
|
+
attr[:name_format] = "urn:oasis:names:tc:SAML:2.0:attrname-format:basic"
|
93
|
+
attr
|
94
|
+
end
|
95
|
+
|
96
|
+
omniauth_config.merge!(
|
97
|
+
:issuer => ConcertoConfig[:saml_issuer],
|
98
|
+
:uid_attribute => ConcertoConfig[:saml_uid_key],
|
99
|
+
:attribute_statements => {
|
100
|
+
:email => [ConcertoConfig[:saml_email_key]],
|
101
|
+
:first_name => [ConcertoConfig[:saml_first_name_key]],
|
102
|
+
:last_name => [ConcertoConfig[:saml_last_name_key]],
|
103
|
+
},
|
104
|
+
:request_attributes => request_attributes,
|
105
|
+
:member_of_key => ConcertoConfig[:saml_member_of_key],
|
106
|
+
:member_of_filter => ConcertoConfig[:saml_member_of_filter],
|
107
|
+
:admin_groups => ConcertoConfig[:saml_admin_groups],
|
108
|
+
# :callback_url => "/auth/saml/callback"
|
109
|
+
)
|
110
|
+
|
111
|
+
Rails.logger.debug omniauth_config
|
112
|
+
|
113
|
+
# configure omniauth-cas gem based on specified yml configs
|
114
|
+
Rails.application.config.middleware.use OmniAuth::Builder do
|
115
|
+
provider :saml, omniauth_config
|
116
|
+
end
|
117
|
+
|
118
|
+
# save omniauth configuration for later use in application
|
119
|
+
# to reference any unique identifiers for extra CAS options
|
120
|
+
ConcertoSamlAuth::Engine.configure do
|
121
|
+
config.omniauth_config = omniauth_config
|
122
|
+
end
|
123
|
+
end
|
data/config/routes.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
module ConcertoSamlAuth
|
2
|
+
|
3
|
+
require 'omniauth'
|
4
|
+
require 'omniauth-saml'
|
5
|
+
require 'concerto_identity'
|
6
|
+
|
7
|
+
class Engine < ::Rails::Engine
|
8
|
+
isolate_namespace ConcertoSamlAuth
|
9
|
+
engine_name 'concerto_saml_auth'
|
10
|
+
|
11
|
+
# Define plugin information for the Concerto application to read.
|
12
|
+
# Do not modify @plugin_info outside of this static configuration block.
|
13
|
+
def plugin_info(plugin_info_class)
|
14
|
+
@plugin_info ||= plugin_info_class.new do
|
15
|
+
|
16
|
+
# Add our concerto_saml_auth route to the main application
|
17
|
+
add_route("concerto_saml_auth", ConcertoSamlAuth::Engine)
|
18
|
+
|
19
|
+
# View hook to override Devise sign in links in the main application
|
20
|
+
add_view_hook "ApplicationController", :signin_hook,
|
21
|
+
:partial => "concerto_saml_auth/omniauth_saml/signin"
|
22
|
+
|
23
|
+
# Controller hook to supply a redirect route (example: non public Concerto instances)
|
24
|
+
add_controller_hook "ApplicationController", :auth_plugin, :before do
|
25
|
+
@auth_url = "/auth/saml"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
namespace :concerto_saml_auth do
|
2
|
+
desc "Creates identities from existing user emails"
|
3
|
+
task :identity_from_email => :environment do
|
4
|
+
User.all.each do |u|
|
5
|
+
identity = ConcertoIdentity::Identity.new(
|
6
|
+
user_id: u.id,
|
7
|
+
external_id: u.email[/[^@]+/],
|
8
|
+
provider: "saml"
|
9
|
+
)
|
10
|
+
|
11
|
+
if identity.save
|
12
|
+
puts "Created Identity: #{identity.external_id} -> #{identity.user_id}"
|
13
|
+
else
|
14
|
+
puts "Error creating Identity for User #{identity.user_id}"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
metadata
ADDED
@@ -0,0 +1,103 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: concerto_saml_auth
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Gabe Perez
|
8
|
+
- Thorben Dahl
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2017-07-16 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rails
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - ">="
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: '0'
|
21
|
+
type: :runtime
|
22
|
+
prerelease: false
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - ">="
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: '0'
|
28
|
+
- !ruby/object:Gem::Dependency
|
29
|
+
name: omniauth-saml
|
30
|
+
requirement: !ruby/object:Gem::Requirement
|
31
|
+
requirements:
|
32
|
+
- - ">="
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: '0'
|
35
|
+
type: :runtime
|
36
|
+
prerelease: false
|
37
|
+
version_requirements: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - ">="
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '0'
|
42
|
+
- !ruby/object:Gem::Dependency
|
43
|
+
name: concerto_identity
|
44
|
+
requirement: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - ">="
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '0'
|
49
|
+
type: :runtime
|
50
|
+
prerelease: false
|
51
|
+
version_requirements: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - ">="
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '0'
|
56
|
+
description: Authorize Concerto users with SAML
|
57
|
+
email:
|
58
|
+
- perez283@gmail.com
|
59
|
+
- thorben@thorbendahl.com
|
60
|
+
executables: []
|
61
|
+
extensions: []
|
62
|
+
extra_rdoc_files: []
|
63
|
+
files:
|
64
|
+
- LICENSE
|
65
|
+
- README.md
|
66
|
+
- Rakefile
|
67
|
+
- app/assets/javascripts/concerto_saml_auth/application.js
|
68
|
+
- app/assets/stylesheets/concerto_saml_auth/application.css
|
69
|
+
- app/controllers/concerto_saml_auth/application_controller.rb
|
70
|
+
- app/controllers/concerto_saml_auth/omniauth_callback_controller.rb
|
71
|
+
- app/helpers/concerto_saml_auth/application_helper.rb
|
72
|
+
- app/views/concerto_saml_auth/omniauth_saml/_signin.html.erb
|
73
|
+
- config/initializers/omniauth.rb
|
74
|
+
- config/routes.rb
|
75
|
+
- lib/concerto_saml_auth.rb
|
76
|
+
- lib/concerto_saml_auth/engine.rb
|
77
|
+
- lib/concerto_saml_auth/version.rb
|
78
|
+
- lib/tasks/concerto_saml_auth_tasks.rake
|
79
|
+
homepage: http://www.concerto-signage.org
|
80
|
+
licenses:
|
81
|
+
- Apache-2.0
|
82
|
+
metadata: {}
|
83
|
+
post_install_message:
|
84
|
+
rdoc_options: []
|
85
|
+
require_paths:
|
86
|
+
- lib
|
87
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
88
|
+
requirements:
|
89
|
+
- - ">="
|
90
|
+
- !ruby/object:Gem::Version
|
91
|
+
version: '0'
|
92
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
requirements: []
|
98
|
+
rubyforge_project:
|
99
|
+
rubygems_version: 2.5.2
|
100
|
+
signing_key:
|
101
|
+
specification_version: 4
|
102
|
+
summary: Provides user authentication using SAML
|
103
|
+
test_files: []
|