devise_saml_authenticatable 1.5.0 → 1.6.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 +5 -5
- data/.gitignore +0 -2
- data/.travis.yml +20 -21
- data/Gemfile +2 -2
- data/README.md +57 -13
- data/app/controllers/devise/saml_sessions_controller.rb +32 -6
- data/lib/devise_saml_authenticatable.rb +13 -0
- data/lib/devise_saml_authenticatable/default_attribute_map_resolver.rb +26 -0
- data/lib/devise_saml_authenticatable/exception.rb +1 -1
- data/lib/devise_saml_authenticatable/model.rb +2 -17
- data/lib/devise_saml_authenticatable/strategy.rb +1 -1
- data/lib/devise_saml_authenticatable/version.rb +1 -1
- data/spec/controllers/devise/saml_sessions_controller_spec.rb +69 -11
- data/spec/devise_saml_authenticatable/default_attribute_map_resolver_spec.rb +58 -0
- data/spec/devise_saml_authenticatable/model_spec.rb +13 -8
- data/spec/features/saml_authentication_spec.rb +43 -36
- data/spec/rails_helper.rb +2 -2
- data/spec/spec_helper.rb +7 -0
- data/spec/support/Gemfile.rails4 +20 -10
- data/spec/support/Gemfile.rails5 +13 -2
- data/spec/support/Gemfile.rails5.1 +13 -2
- data/spec/support/Gemfile.rails5.2 +25 -0
- data/spec/support/attribute_map_resolver.rb.erb +14 -0
- data/spec/support/idp_settings_adapter.rb.erb +5 -5
- data/spec/support/idp_template.rb +3 -1
- data/spec/support/rails_app.rb +75 -17
- data/spec/support/saml_idp_controller.rb.erb +13 -6
- data/spec/support/sp_template.rb +42 -20
- metadata +10 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 8f372d3d801220ab4ce4a7e83501f0e5d7a9cc2590eb64aa1c91a0a5ad053d7d
|
4
|
+
data.tar.gz: bcdfc0370f0926a542164ea577030e9ff17666adb9e0d5ae8367a605e4966866
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d9bd026db0bb199aaa9b72d1c03fccef49a905dc7270739b48e682623036f945b74f16c99f1d457086df47ff03f8e71aee5c4a412d01cd9f9c80b4f84e79d58a
|
7
|
+
data.tar.gz: c2f32e5297c0e9a4aadce6069282d8c17e91d95550a777e2f5755745effc0fda5eaa958e4656d5776e2a507251d230fdaba3d944189874d0166d3f2cefbe153c
|
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
@@ -1,53 +1,52 @@
|
|
1
1
|
language: ruby
|
2
2
|
rvm:
|
3
|
-
- "1.9.3"
|
4
3
|
- "2.0.0"
|
5
4
|
- "2.1.10"
|
6
5
|
- "2.2.10"
|
7
6
|
- "2.3.8"
|
8
|
-
- "2.4.
|
9
|
-
- "2.5.
|
10
|
-
- "2.6.
|
7
|
+
- "2.4.10"
|
8
|
+
- "2.5.8"
|
9
|
+
- "2.6.6"
|
10
|
+
- "2.7.1"
|
11
11
|
gemfile:
|
12
12
|
- Gemfile
|
13
|
+
- spec/support/Gemfile.rails5.2
|
13
14
|
- spec/support/Gemfile.rails5.1
|
14
15
|
- spec/support/Gemfile.rails5
|
15
16
|
- spec/support/Gemfile.rails4
|
16
17
|
matrix:
|
17
18
|
allow_failures:
|
18
|
-
- rvm: "1.9.3"
|
19
|
-
gemfile: Gemfile
|
20
|
-
- rvm: "1.9.3"
|
21
|
-
gemfile: spec/support/Gemfile.rails5
|
22
|
-
- rvm: "1.9.3"
|
23
|
-
gemfile: spec/support/Gemfile.rails5.1
|
24
19
|
- rvm: "2.0.0"
|
25
20
|
gemfile: Gemfile
|
26
21
|
- rvm: "2.0.0"
|
27
22
|
gemfile: spec/support/Gemfile.rails5
|
28
23
|
- rvm: "2.0.0"
|
29
24
|
gemfile: spec/support/Gemfile.rails5.1
|
25
|
+
- rvm: "2.0.0"
|
26
|
+
gemfile: spec/support/Gemfile.rails5.2
|
30
27
|
- rvm: "2.1.10"
|
31
28
|
gemfile: Gemfile
|
32
29
|
- rvm: "2.1.10"
|
33
30
|
gemfile: spec/support/Gemfile.rails5
|
34
31
|
- rvm: "2.1.10"
|
35
32
|
gemfile: spec/support/Gemfile.rails5.1
|
36
|
-
- rvm: "2.
|
33
|
+
- rvm: "2.1.10"
|
34
|
+
gemfile: spec/support/Gemfile.rails5.2
|
35
|
+
- rvm: "2.2.10"
|
36
|
+
gemfile: Gemfile
|
37
|
+
- rvm: "2.2.10"
|
38
|
+
gemfile: spec/support/Gemfile.rails5.2
|
39
|
+
- rvm: "2.3.8"
|
40
|
+
gemfile: Gemfile
|
41
|
+
- rvm: "2.4.10"
|
42
|
+
gemfile: Gemfile
|
43
|
+
- rvm: "2.6.6"
|
44
|
+
gemfile: spec/support/Gemfile.rails4
|
45
|
+
- rvm: "2.7.1"
|
37
46
|
gemfile: spec/support/Gemfile.rails4
|
38
47
|
|
39
48
|
before_install:
|
40
|
-
# update bundler to avoid https://github.com/travis-ci/travis-ci/issues/5239
|
41
49
|
- command -v bundle || gem install bundler -v '~> 1.17.3'
|
42
50
|
|
43
51
|
script:
|
44
52
|
- bundle exec rake
|
45
|
-
|
46
|
-
notifications:
|
47
|
-
hipchat:
|
48
|
-
rooms:
|
49
|
-
secure: cuDak5a6fBeg+sp61COqxQfzdcFEsjwCqtwvCISso0RNh5SR8v+uVYKcA8rlK+GE1l9uR7tLRHeHF3ZmzvFSOat07NvpScvjZXi+OSpWlc6rwQ6Pl6bBP6gu6sREiKVe0eT/uGrvJloyWKZaXIhiiBzQ+ZERx/ssGA9WMmNkhlwy1OgGnPNurNNHZLBjEZn1V6kdyxiXx6QPASNpjNEgN1G8dUh3qzcWUGVQGNZSJk65A6ie1MveNyecTjDhw+ADBU8nS28Ja4y6ohRm4FzofSgespYrvfygIZ5rYF0HPMj5FW1ZDWtM5355ojCk8RLT+ZkuhssCn1OJk7ogaOVjnYcOFRxEfpu3eIbjtMmUz3j4umatFqbgas+6SXMVIPkr5HUoTrP8HNFssIpcEBOnPwAF8QCpx+daHc0r2cc8lGuXhtJfpW0P2F0dmwJNiQ7//nz2y2xs84x4Gb7MV9tEDYp0FqEClMuFBkPNizBljarm04PkiLSrqvR52aMDfQz7YAX2oXAvFjPzI1GC0K8x7xX8TuHT9yuHy7fI+rUSNivZYLKO+IEZqPPDdJpXISUbVwanZoNvmQYk5PZV21MfDSGwQrz8eO/uFiAblj18yIlNbAfb2hdZDVYsm4EvWxELJtfaTxgrj6M3Y3m/KbCbCoDp+2jE307M2rxL0Gum2gk=
|
50
|
-
template:
|
51
|
-
- '%{repository}<a href="%{build_url}">#%{build_number}</a> (%{branch} - <a href="%{compare_url}">%{commit}</a> : %{author}): %{message}'
|
52
|
-
format: html
|
53
|
-
on_pull_requests: true
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -6,18 +6,15 @@ It uses [ruby-saml][] to handle all SAML-related stuff.
|
|
6
6
|
|
7
7
|
## Installation
|
8
8
|
|
9
|
-
Add this
|
9
|
+
Add this gem to your application's Gemfile:
|
10
10
|
|
11
|
-
|
11
|
+
git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
|
12
|
+
gem "devise_saml_authenticatable", github: "apokalipto/devise_saml_authenticatable"
|
12
13
|
|
13
14
|
And then execute:
|
14
15
|
|
15
16
|
$ bundle
|
16
17
|
|
17
|
-
Or install it yourself as:
|
18
|
-
|
19
|
-
$ gem install devise_saml_authenticatable
|
20
|
-
|
21
18
|
## Usage
|
22
19
|
|
23
20
|
Follow the [normal devise installation process](https://github.com/plataformatec/devise/tree/master#getting-started). The controller filters and helpers are unchanged from normal devise usage.
|
@@ -110,6 +107,10 @@ In `config/initializers/devise.rb`:
|
|
110
107
|
# If saml_route_helper_prefix = 'saml' then the new_user_session route becomes new_saml_user_session
|
111
108
|
# config.saml_route_helper_prefix = 'saml'
|
112
109
|
|
110
|
+
# You can add allowance for clock drift between the sp and idp.
|
111
|
+
# This is a time in seconds.
|
112
|
+
# config.allowed_clock_drift_in_seconds = 0
|
113
|
+
|
113
114
|
# Configure with your SAML settings (see ruby-saml's README for more information: https://github.com/onelogin/ruby-saml).
|
114
115
|
config.saml_configure do |settings|
|
115
116
|
# assertion_consumer_service_url is required starting with ruby-saml 1.4.3: https://github.com/onelogin/ruby-saml#updating-from-142-to-143
|
@@ -126,7 +127,26 @@ In `config/initializers/devise.rb`:
|
|
126
127
|
end
|
127
128
|
```
|
128
129
|
|
129
|
-
|
130
|
+
#### Attributes
|
131
|
+
|
132
|
+
There are two ways to map SAML attributes to User attributes:
|
133
|
+
|
134
|
+
- [initializer](#attribute-map-initializer)
|
135
|
+
- [config file](#attribute-map-config-file)
|
136
|
+
|
137
|
+
The attribute mappings are very dependent on the way the IdP encodes the attributes.
|
138
|
+
In these examples the attributes are given in URN style.
|
139
|
+
Other IdPs might provide them as OID's, or by other means.
|
140
|
+
|
141
|
+
You are now ready to test it against an IdP.
|
142
|
+
|
143
|
+
When the user visits `/users/saml/sign_in` they will be redirected to the login page of the IdP.
|
144
|
+
|
145
|
+
Upon successful login the user is redirected to the Devise `user_root_path`.
|
146
|
+
|
147
|
+
##### Attribute map config file
|
148
|
+
|
149
|
+
Create a YAML file (`config/attribute-map.yml`) that maps SAML attributes with your model's fields:
|
130
150
|
|
131
151
|
```yaml
|
132
152
|
# attribute-map.yml
|
@@ -137,15 +157,39 @@ In the config directory, create a YAML file (`attribute-map.yml`) that maps SAML
|
|
137
157
|
"urn:mace:dir:attribute-def:givenName": "name"
|
138
158
|
```
|
139
159
|
|
140
|
-
|
141
|
-
In this example the attributes are given in URN style.
|
142
|
-
Other IdPs might provide them as OID's, or by other means.
|
160
|
+
##### Attribute map initializer
|
143
161
|
|
144
|
-
|
162
|
+
In `config/initializers/devise.rb` (see above), add an attribute map resolver.
|
163
|
+
The resolver gets the [SAML response from the IdP](https://github.com/onelogin/ruby-saml/blob/master/lib/onelogin/ruby-saml/response.rb) so it can decide which attribute map to load.
|
164
|
+
If you only have one IdP, you can use the config file above, or just return a single hash.
|
145
165
|
|
146
|
-
|
166
|
+
```ruby
|
167
|
+
# config/initializers/devise.rb
|
168
|
+
Devise.setup do |config|
|
169
|
+
...
|
170
|
+
# ==> Configuration for :saml_authenticatable
|
147
171
|
|
148
|
-
|
172
|
+
config.saml_attribute_map_resolver = MyAttributeMapResolver
|
173
|
+
end
|
174
|
+
```
|
175
|
+
|
176
|
+
```ruby
|
177
|
+
# app/lib/my_attribute_map_resolver
|
178
|
+
class MyAttributeMapResolver < DeviseSamlAuthenticatable::DefaultAttributeMapResolver
|
179
|
+
def attribute_map
|
180
|
+
issuer = saml_response.issuers.first
|
181
|
+
case issuer
|
182
|
+
when "idp_entity_id"
|
183
|
+
{
|
184
|
+
"urn:mace:dir:attribute-def:uid" => "user_name",
|
185
|
+
"urn:mace:dir:attribute-def:email" => "email",
|
186
|
+
"urn:mace:dir:attribute-def:name" => "last_name",
|
187
|
+
"urn:mace:dir:attribute-def:givenName" => "name",
|
188
|
+
}
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
```
|
149
193
|
|
150
194
|
## Supporting Multiple IdPs
|
151
195
|
|
@@ -5,8 +5,10 @@ class Devise::SamlSessionsController < Devise::SessionsController
|
|
5
5
|
unloadable if Rails::VERSION::MAJOR < 4
|
6
6
|
if Rails::VERSION::MAJOR < 5
|
7
7
|
skip_before_filter :verify_authenticity_token
|
8
|
+
prepend_before_filter :store_info_for_sp_initiated_logout, only: :destroy
|
8
9
|
else
|
9
10
|
skip_before_action :verify_authenticity_token, raise: false
|
11
|
+
prepend_before_action :store_info_for_sp_initiated_logout, only: :destroy
|
10
12
|
end
|
11
13
|
|
12
14
|
def new
|
@@ -30,16 +32,16 @@ class Devise::SamlSessionsController < Devise::SessionsController
|
|
30
32
|
|
31
33
|
redirect_to generate_idp_logout_response(saml_config, logout_request.id)
|
32
34
|
elsif params[:SAMLResponse]
|
33
|
-
#Currently Devise handles the session invalidation when the request is made.
|
34
|
-
#To support a true SP initiated logout response, the request ID would have to be tracked and session invalidated
|
35
|
-
#based on that.
|
35
|
+
# Currently Devise handles the session invalidation when the request is made.
|
36
|
+
# To support a true SP initiated logout response, the request ID would have to be tracked and session invalidated
|
37
|
+
# based on that.
|
36
38
|
if Devise.saml_sign_out_success_url
|
37
39
|
redirect_to Devise.saml_sign_out_success_url
|
38
40
|
else
|
39
41
|
redirect_to action: :new
|
40
42
|
end
|
41
43
|
else
|
42
|
-
head
|
44
|
+
head 500
|
43
45
|
end
|
44
46
|
end
|
45
47
|
|
@@ -51,14 +53,38 @@ class Devise::SamlSessionsController < Devise::SessionsController
|
|
51
53
|
end
|
52
54
|
end
|
53
55
|
|
56
|
+
# For non transient name ID, save info to identify user for logout purpose
|
57
|
+
# before that user's session got destroyed. These info are used in the
|
58
|
+
# `after_sign_out_path_for` method below.
|
59
|
+
def store_info_for_sp_initiated_logout
|
60
|
+
return if Devise.saml_config.name_identifier_format == "urn:oasis:names:tc:SAML:2.0:nameid-format:transient"
|
61
|
+
@name_identifier_value_for_sp_initiated_logout = Devise.saml_name_identifier_retriever.call(current_user)
|
62
|
+
@sessionindex_for_sp_initiated_logout = current_user.public_send(Devise.saml_session_index_key) if Devise.saml_session_index_key
|
63
|
+
end
|
64
|
+
|
54
65
|
# Override devise to send user to IdP logout for SLO
|
55
66
|
def after_sign_out_path_for(_)
|
56
67
|
idp_entity_id = get_idp_entity_id(params)
|
57
68
|
request = OneLogin::RubySaml::Logoutrequest.new
|
58
|
-
|
69
|
+
saml_settings = saml_config(idp_entity_id).dup
|
70
|
+
|
71
|
+
# Add attributes to saml_settings which will later be used to create the SP
|
72
|
+
# initiated logout request
|
73
|
+
unless Devise.saml_config.name_identifier_format == 'urn:oasis:names:tc:SAML:2.0:nameid-format:transient'
|
74
|
+
saml_settings.name_identifier_value = @name_identifier_value_for_sp_initiated_logout
|
75
|
+
saml_settings.sessionindex = @sessionindex_for_sp_initiated_logout
|
76
|
+
end
|
77
|
+
|
78
|
+
request.create(saml_settings)
|
59
79
|
end
|
60
80
|
|
61
81
|
def generate_idp_logout_response(saml_config, logout_request_id)
|
62
|
-
|
82
|
+
|
83
|
+
params = {}
|
84
|
+
if relay_state
|
85
|
+
params[:RelayState] = relay_state
|
86
|
+
end
|
87
|
+
|
88
|
+
OneLogin::RubySaml::SloLogoutresponse.new.create(saml_config, logout_request_id, nil, params)
|
63
89
|
end
|
64
90
|
end
|
@@ -5,6 +5,7 @@ require "devise_saml_authenticatable/exception"
|
|
5
5
|
require "devise_saml_authenticatable/logger"
|
6
6
|
require "devise_saml_authenticatable/routes"
|
7
7
|
require "devise_saml_authenticatable/saml_config"
|
8
|
+
require "devise_saml_authenticatable/default_attribute_map_resolver"
|
8
9
|
require "devise_saml_authenticatable/default_idp_entity_id_reader"
|
9
10
|
|
10
11
|
begin
|
@@ -66,6 +67,10 @@ module Devise
|
|
66
67
|
mattr_accessor :saml_relay_state
|
67
68
|
@@saml_relay_state
|
68
69
|
|
70
|
+
# Instead of storing the attribute_map in attribute-map.yml, store it in the database, or set it programatically
|
71
|
+
mattr_accessor :saml_attribute_map_resolver
|
72
|
+
@@saml_attribute_map_resolver ||= ::DeviseSamlAuthenticatable::DefaultAttributeMapResolver
|
73
|
+
|
69
74
|
# Implements a #validate method that takes the retrieved resource and response right after retrieval,
|
70
75
|
# and returns true if it's valid. False will cause authentication to fail.
|
71
76
|
# Only one of saml_resource_validator and saml_resource_validator_hook may be used.
|
@@ -124,6 +129,14 @@ module Devise
|
|
124
129
|
# See saml_default_resource_locator above for an example.
|
125
130
|
mattr_accessor :saml_resource_locator
|
126
131
|
@@saml_resource_locator = @@saml_default_resource_locator
|
132
|
+
|
133
|
+
# Proc that is called to resolve the name identifier to use in a LogoutRequest for the current user.
|
134
|
+
# Receives the logged-in user.
|
135
|
+
# Is expected to return the identifier the IdP understands for this user, e.g. email address or username.
|
136
|
+
mattr_accessor :saml_name_identifier_retriever
|
137
|
+
@@saml_name_identifier_retriever = Proc.new do |current_user|
|
138
|
+
current_user.public_send(Devise.saml_default_user_key)
|
139
|
+
end
|
127
140
|
end
|
128
141
|
|
129
142
|
# Add saml_authenticatable strategy to defaults.
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module DeviseSamlAuthenticatable
|
2
|
+
class DefaultAttributeMapResolver
|
3
|
+
def initialize(saml_response)
|
4
|
+
@saml_response = saml_response
|
5
|
+
end
|
6
|
+
|
7
|
+
def attribute_map
|
8
|
+
return {} unless File.exist?(attribute_map_path)
|
9
|
+
|
10
|
+
attribute_map = YAML.load(File.read(attribute_map_path))
|
11
|
+
if attribute_map.key?(Rails.env)
|
12
|
+
attribute_map[Rails.env]
|
13
|
+
else
|
14
|
+
attribute_map
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
attr_reader :saml_response
|
21
|
+
|
22
|
+
def attribute_map_path
|
23
|
+
Rails.root.join("config", "attribute-map.yml")
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -33,9 +33,9 @@ module Devise
|
|
33
33
|
key = Devise.saml_default_user_key
|
34
34
|
decorated_response = ::SamlAuthenticatable::SamlResponse.new(
|
35
35
|
saml_response,
|
36
|
-
attribute_map
|
36
|
+
Devise.saml_attribute_map_resolver.new(saml_response).attribute_map,
|
37
37
|
)
|
38
|
-
if
|
38
|
+
if Devise.saml_use_subject
|
39
39
|
auth_value = saml_response.name_id
|
40
40
|
else
|
41
41
|
auth_value = decorated_response.attribute_value_by_resource_key(key)
|
@@ -80,21 +80,6 @@ module Devise
|
|
80
80
|
def find_for_shibb_authentication(conditions)
|
81
81
|
find_for_authentication(conditions)
|
82
82
|
end
|
83
|
-
|
84
|
-
def attribute_map
|
85
|
-
@attribute_map ||= attribute_map_for_environment
|
86
|
-
end
|
87
|
-
|
88
|
-
private
|
89
|
-
|
90
|
-
def attribute_map_for_environment
|
91
|
-
attribute_map = YAML.load(File.read("#{Rails.root}/config/attribute-map.yml"))
|
92
|
-
if attribute_map.has_key?(Rails.env)
|
93
|
-
attribute_map[Rails.env]
|
94
|
-
else
|
95
|
-
attribute_map
|
96
|
-
end
|
97
|
-
end
|
98
83
|
end
|
99
84
|
end
|
100
85
|
end
|
@@ -8,7 +8,7 @@ module Devise
|
|
8
8
|
if params[:SAMLResponse]
|
9
9
|
OneLogin::RubySaml::Response.new(
|
10
10
|
params[:SAMLResponse],
|
11
|
-
settings:
|
11
|
+
settings: saml_config(get_idp_entity_id(params)),
|
12
12
|
allowed_clock_drift: Devise.allowed_clock_drift_in_seconds,
|
13
13
|
)
|
14
14
|
else
|
@@ -1,31 +1,38 @@
|
|
1
1
|
require 'rails_helper'
|
2
2
|
|
3
|
-
|
4
|
-
|
3
|
+
# The important parts from devise
|
4
|
+
class DeviseController < ApplicationController
|
5
|
+
attr_accessor :current_user
|
6
|
+
|
5
7
|
def resource_class
|
6
8
|
User
|
7
9
|
end
|
8
10
|
|
11
|
+
def require_no_authentication
|
12
|
+
end
|
13
|
+
end
|
14
|
+
class Devise::SessionsController < DeviseController
|
9
15
|
def destroy
|
10
16
|
sign_out
|
11
17
|
redirect_to after_sign_out_path_for(:user)
|
12
18
|
end
|
13
19
|
|
14
|
-
def
|
20
|
+
def verify_signed_out_user
|
21
|
+
# no-op for these tests
|
15
22
|
end
|
16
23
|
end
|
17
24
|
|
18
25
|
require_relative '../../../app/controllers/devise/saml_sessions_controller'
|
19
26
|
|
20
27
|
describe Devise::SamlSessionsController, type: :controller do
|
21
|
-
let(:saml_config) { Devise.saml_config }
|
22
28
|
let(:idp_providers_adapter) { spy("Stub IDPSettings Adaptor") }
|
23
29
|
|
24
30
|
before do
|
31
|
+
@request.env["devise.mapping"] = Devise.mappings[:user]
|
25
32
|
allow(idp_providers_adapter).to receive(:settings).and_return({
|
26
33
|
assertion_consumer_service_url: "acs_url",
|
27
34
|
assertion_consumer_service_binding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST",
|
28
|
-
name_identifier_format: "urn:oasis:names:tc:SAML:
|
35
|
+
name_identifier_format: "urn:oasis:names:tc:SAML:2.0:nameid-format:transient",
|
29
36
|
issuer: "sp_issuer",
|
30
37
|
idp_entity_id: "http://www.example.com",
|
31
38
|
authn_context: "",
|
@@ -35,6 +42,20 @@ describe Devise::SamlSessionsController, type: :controller do
|
|
35
42
|
})
|
36
43
|
end
|
37
44
|
|
45
|
+
before do
|
46
|
+
if Rails::VERSION::MAJOR < 5 && Gem::Version.new(RUBY_VERSION) > Gem::Version.new("2.6")
|
47
|
+
# we still want to support Rails 4
|
48
|
+
# patch tests using snippet from https://github.com/rails/rails/issues/34790#issuecomment-483607370
|
49
|
+
class ActionController::TestResponse < ActionDispatch::TestResponse
|
50
|
+
def recycle!
|
51
|
+
@mon_mutex_owner_object_id = nil
|
52
|
+
@mon_mutex = nil
|
53
|
+
initialize
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
38
59
|
describe '#new' do
|
39
60
|
let(:saml_response) { File.read(File.join(File.dirname(__FILE__), '../../support', 'response_encrypted_nameid.xml.base64')) }
|
40
61
|
|
@@ -113,6 +134,8 @@ describe Devise::SamlSessionsController, type: :controller do
|
|
113
134
|
end
|
114
135
|
|
115
136
|
describe '#metadata' do
|
137
|
+
let(:saml_config) { Devise.saml_config.dup }
|
138
|
+
|
116
139
|
context "with the default configuration" do
|
117
140
|
it 'generates metadata' do
|
118
141
|
get :metadata
|
@@ -132,7 +155,7 @@ describe Devise::SamlSessionsController, type: :controller do
|
|
132
155
|
Devise.saml_configure do |settings|
|
133
156
|
settings.assertion_consumer_service_url = "http://localhost:3000/users/saml/auth"
|
134
157
|
settings.assertion_consumer_service_binding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
|
135
|
-
settings.name_identifier_format = "urn:oasis:names:tc:SAML:
|
158
|
+
settings.name_identifier_format = "urn:oasis:names:tc:SAML:2.0:nameid-format:transient"
|
136
159
|
settings.issuer = "http://localhost:3000"
|
137
160
|
end
|
138
161
|
end
|
@@ -149,23 +172,47 @@ describe Devise::SamlSessionsController, type: :controller do
|
|
149
172
|
end
|
150
173
|
|
151
174
|
describe '#destroy' do
|
175
|
+
before do
|
176
|
+
allow(controller).to receive(:sign_out)
|
177
|
+
end
|
178
|
+
|
152
179
|
context "when using the default saml config" do
|
153
|
-
it
|
154
|
-
expect(controller).to receive(:sign_out)
|
180
|
+
it "signs out and redirects to the IdP" do
|
155
181
|
delete :destroy
|
182
|
+
expect(controller).to have_received(:sign_out)
|
156
183
|
expect(response).to redirect_to(%r(\Ahttp://localhost:8009/saml/logout\?SAMLRequest=))
|
157
184
|
end
|
158
185
|
end
|
159
186
|
|
187
|
+
context "when configured to use a non-transient name identifier" do
|
188
|
+
before do
|
189
|
+
allow(Devise.saml_config).to receive(:name_identifier_format).and_return("urn:oasis:names:tc:SAML:2.0:nameid-format:persistent")
|
190
|
+
end
|
191
|
+
|
192
|
+
it "includes a LogoutRequest with the name identifier and session index", :aggregate_failures do
|
193
|
+
controller.current_user = Struct.new(:email, :session_index).new("user@example.com", "sessionindex")
|
194
|
+
|
195
|
+
actual_settings = nil
|
196
|
+
expect_any_instance_of(OneLogin::RubySaml::Logoutrequest).to receive(:create) do |_, settings|
|
197
|
+
actual_settings = settings
|
198
|
+
"http://localhost:8009/saml/logout"
|
199
|
+
end
|
200
|
+
|
201
|
+
delete :destroy
|
202
|
+
expect(actual_settings.name_identifier_value).to eq("user@example.com")
|
203
|
+
expect(actual_settings.sessionindex).to eq("sessionindex")
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
160
207
|
context "with a specified idp" do
|
161
208
|
before do
|
162
209
|
Devise.idp_settings_adapter = idp_providers_adapter
|
163
210
|
end
|
164
211
|
|
165
212
|
it "redirects to the associated IdP SSO target url" do
|
166
|
-
expect(controller).to receive(:sign_out)
|
167
213
|
expect(DeviseSamlAuthenticatable::DefaultIdpEntityIdReader).to receive(:entity_id)
|
168
214
|
delete :destroy
|
215
|
+
expect(controller).to have_received(:sign_out)
|
169
216
|
expect(response).to redirect_to(%r(\Ahttp://idp_slo_url\?SAMLRequest=))
|
170
217
|
end
|
171
218
|
|
@@ -194,8 +241,8 @@ describe Devise::SamlSessionsController, type: :controller do
|
|
194
241
|
end
|
195
242
|
|
196
243
|
it "redirects to the associated IdP SLO target url" do
|
197
|
-
expect(controller).to receive(:sign_out)
|
198
244
|
do_delete
|
245
|
+
expect(controller).to have_received(:sign_out)
|
199
246
|
expect(idp_providers_adapter).to have_received(:settings).with("http://www.example.com")
|
200
247
|
expect(response).to redirect_to(%r(\Ahttp://idp_slo_url\?SAMLRequest=))
|
201
248
|
end
|
@@ -263,12 +310,13 @@ describe Devise::SamlSessionsController, type: :controller do
|
|
263
310
|
let(:name_id) { '12312312' }
|
264
311
|
before do
|
265
312
|
allow(OneLogin::RubySaml::SloLogoutrequest).to receive(:new).and_return(saml_request)
|
313
|
+
allow(User).to receive(:reset_session_key_for)
|
266
314
|
end
|
267
315
|
|
268
316
|
it 'direct the resource to reset the session key' do
|
269
|
-
expect(User).to receive(:reset_session_key_for).with(name_id)
|
270
317
|
do_post
|
271
318
|
expect(response).to redirect_to response_url
|
319
|
+
expect(User).to have_received(:reset_session_key_for).with(name_id)
|
272
320
|
end
|
273
321
|
|
274
322
|
context "with a specified idp" do
|
@@ -285,6 +333,16 @@ describe Devise::SamlSessionsController, type: :controller do
|
|
285
333
|
end
|
286
334
|
end
|
287
335
|
|
336
|
+
context "with a relay_state lambda defined" do
|
337
|
+
let(:relay_state) { ->(request) { "123" } }
|
338
|
+
|
339
|
+
it "includes the RelayState param in the request to the IdP" do
|
340
|
+
expect(Devise).to receive(:saml_relay_state).at_least(:once).and_return(relay_state)
|
341
|
+
do_post
|
342
|
+
expect(saml_response).to have_received(:create).with(Devise.saml_config, saml_request.id, nil, {RelayState: "123"})
|
343
|
+
end
|
344
|
+
end
|
345
|
+
|
288
346
|
context 'when saml_session_index_key is not configured' do
|
289
347
|
before do
|
290
348
|
Devise.saml_session_index_key = nil
|