devise_saml_authenticatable 1.3.1 → 1.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +0 -2
  3. data/.travis.yml +37 -22
  4. data/Gemfile +2 -10
  5. data/README.md +127 -44
  6. data/app/controllers/devise/saml_sessions_controller.rb +38 -7
  7. data/devise_saml_authenticatable.gemspec +2 -1
  8. data/lib/devise_saml_authenticatable.rb +70 -0
  9. data/lib/devise_saml_authenticatable/default_attribute_map_resolver.rb +26 -0
  10. data/lib/devise_saml_authenticatable/default_idp_entity_id_reader.rb +10 -2
  11. data/lib/devise_saml_authenticatable/exception.rb +1 -1
  12. data/lib/devise_saml_authenticatable/model.rb +20 -32
  13. data/lib/devise_saml_authenticatable/routes.rb +17 -6
  14. data/lib/devise_saml_authenticatable/saml_mapped_attributes.rb +38 -0
  15. data/lib/devise_saml_authenticatable/saml_response.rb +16 -0
  16. data/lib/devise_saml_authenticatable/strategy.rb +10 -2
  17. data/lib/devise_saml_authenticatable/version.rb +1 -1
  18. data/spec/controllers/devise/saml_sessions_controller_spec.rb +118 -11
  19. data/spec/devise_saml_authenticatable/default_attribute_map_resolver_spec.rb +58 -0
  20. data/spec/devise_saml_authenticatable/default_idp_entity_id_reader_spec.rb +34 -4
  21. data/spec/devise_saml_authenticatable/model_spec.rb +199 -5
  22. data/spec/devise_saml_authenticatable/saml_mapped_attributes_spec.rb +50 -0
  23. data/spec/devise_saml_authenticatable/strategy_spec.rb +18 -0
  24. data/spec/features/saml_authentication_spec.rb +45 -21
  25. data/spec/rails_helper.rb +6 -2
  26. data/spec/routes/routes_spec.rb +102 -0
  27. data/spec/spec_helper.rb +7 -0
  28. data/spec/support/Gemfile.rails4 +24 -6
  29. data/spec/support/Gemfile.rails5 +25 -0
  30. data/spec/support/Gemfile.rails5.1 +25 -0
  31. data/spec/support/Gemfile.rails5.2 +25 -0
  32. data/spec/support/attribute-map.yml +12 -0
  33. data/spec/support/attribute_map_resolver.rb.erb +14 -0
  34. data/spec/support/idp_settings_adapter.rb.erb +5 -5
  35. data/spec/support/idp_template.rb +8 -1
  36. data/spec/support/rails_app.rb +110 -16
  37. data/spec/support/saml_idp_controller.rb.erb +22 -10
  38. data/spec/support/sp_template.rb +52 -21
  39. metadata +26 -10
  40. data/spec/support/Gemfile.ruby-saml-1.3 +0 -23
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: eeabad37a5473557499db0b8de604afb7740608d
4
- data.tar.gz: 952108992254613cdf27f72aedbf6e2a772f95a3
2
+ SHA256:
3
+ metadata.gz: 8f372d3d801220ab4ce4a7e83501f0e5d7a9cc2590eb64aa1c91a0a5ad053d7d
4
+ data.tar.gz: bcdfc0370f0926a542164ea577030e9ff17666adb9e0d5ae8367a605e4966866
5
5
  SHA512:
6
- metadata.gz: 720d7b21c40596b762a85c5c0df2f9dc3d1af7fdce86a7c8562428b866e79eab6a768e070c648fd4c657415bca175102d4eaa0dff6866c7cc8b14025d1bf5101
7
- data.tar.gz: c3368984264f54a99a37ce9e4b546b7bbd2460a834bc57300dfaf242232619bad50f51bb66adc4d2e1e41d287ce4f3f7f85923697835a06bf14f73968a31f1ed
6
+ metadata.gz: d9bd026db0bb199aaa9b72d1c03fccef49a905dc7270739b48e682623036f945b74f16c99f1d457086df47ff03f8e71aee5c4a412d01cd9f9c80b4f84e79d58a
7
+ data.tar.gz: c2f32e5297c0e9a4aadce6069282d8c17e91d95550a777e2f5755745effc0fda5eaa958e4656d5776e2a507251d230fdaba3d944189874d0166d3f2cefbe153c
data/.gitignore CHANGED
@@ -13,6 +13,4 @@ lib/bundler/man
13
13
  pkg
14
14
  rdoc
15
15
  spec/reports
16
- spec/support/idp/
17
- spec/support/sp/
18
16
  tmp
@@ -1,37 +1,52 @@
1
1
  language: ruby
2
2
  rvm:
3
- - "1.9.3"
4
3
  - "2.0.0"
5
- - "2.1.9"
6
- - "2.2.5"
7
- - "2.3.1"
4
+ - "2.1.10"
5
+ - "2.2.10"
6
+ - "2.3.8"
7
+ - "2.4.10"
8
+ - "2.5.8"
9
+ - "2.6.6"
10
+ - "2.7.1"
8
11
  gemfile:
9
12
  - Gemfile
13
+ - spec/support/Gemfile.rails5.2
14
+ - spec/support/Gemfile.rails5.1
15
+ - spec/support/Gemfile.rails5
10
16
  - spec/support/Gemfile.rails4
11
- - spec/support/Gemfile.ruby-saml-1.3
12
17
  matrix:
13
18
  allow_failures:
14
- - rvm: "1.9.3"
15
- gemfile: Gemfile
16
- - rvm: "1.9.3"
17
- gemfile: spec/support/Gemfile.ruby-saml-1.3
18
19
  - rvm: "2.0.0"
19
20
  gemfile: Gemfile
20
21
  - rvm: "2.0.0"
21
- gemfile: spec/support/Gemfile.ruby-saml-1.3
22
- - rvm: "2.1.9"
22
+ gemfile: spec/support/Gemfile.rails5
23
+ - rvm: "2.0.0"
24
+ gemfile: spec/support/Gemfile.rails5.1
25
+ - rvm: "2.0.0"
26
+ gemfile: spec/support/Gemfile.rails5.2
27
+ - rvm: "2.1.10"
28
+ gemfile: Gemfile
29
+ - rvm: "2.1.10"
30
+ gemfile: spec/support/Gemfile.rails5
31
+ - rvm: "2.1.10"
32
+ gemfile: spec/support/Gemfile.rails5.1
33
+ - rvm: "2.1.10"
34
+ gemfile: spec/support/Gemfile.rails5.2
35
+ - rvm: "2.2.10"
23
36
  gemfile: Gemfile
24
- - rvm: "2.1.9"
25
- gemfile: spec/support/Gemfile.ruby-saml-1.3
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"
46
+ gemfile: spec/support/Gemfile.rails4
47
+
48
+ before_install:
49
+ - command -v bundle || gem install bundler -v '~> 1.17.3'
26
50
 
27
51
  script:
28
52
  - bundle exec rake
29
-
30
- notifications:
31
- hipchat:
32
- rooms:
33
- secure: cuDak5a6fBeg+sp61COqxQfzdcFEsjwCqtwvCISso0RNh5SR8v+uVYKcA8rlK+GE1l9uR7tLRHeHF3ZmzvFSOat07NvpScvjZXi+OSpWlc6rwQ6Pl6bBP6gu6sREiKVe0eT/uGrvJloyWKZaXIhiiBzQ+ZERx/ssGA9WMmNkhlwy1OgGnPNurNNHZLBjEZn1V6kdyxiXx6QPASNpjNEgN1G8dUh3qzcWUGVQGNZSJk65A6ie1MveNyecTjDhw+ADBU8nS28Ja4y6ohRm4FzofSgespYrvfygIZ5rYF0HPMj5FW1ZDWtM5355ojCk8RLT+ZkuhssCn1OJk7ogaOVjnYcOFRxEfpu3eIbjtMmUz3j4umatFqbgas+6SXMVIPkr5HUoTrP8HNFssIpcEBOnPwAF8QCpx+daHc0r2cc8lGuXhtJfpW0P2F0dmwJNiQ7//nz2y2xs84x4Gb7MV9tEDYp0FqEClMuFBkPNizBljarm04PkiLSrqvR52aMDfQz7YAX2oXAvFjPzI1GC0K8x7xX8TuHT9yuHy7fI+rUSNivZYLKO+IEZqPPDdJpXISUbVwanZoNvmQYk5PZV21MfDSGwQrz8eO/uFiAblj18yIlNbAfb2hdZDVYsm4EvWxELJtfaTxgrj6M3Y3m/KbCbCoDp+2jE307M2rxL0Gum2gk=
34
- template:
35
- - '%{repository}<a href="%{build_url}">#%{build_number}</a> (%{branch} - <a href="%{compare_url}">%{commit}</a> : %{author}): %{message}'
36
- format: html
37
- on_pull_requests: true
data/Gemfile CHANGED
@@ -6,17 +6,9 @@ gemspec
6
6
  group :test do
7
7
  gem 'rake'
8
8
  gem 'rspec', '~> 3.0'
9
- gem 'rails', '~> 5.0'
9
+ gem 'rails', '~> 6.0'
10
10
  gem 'rspec-rails'
11
- gem 'sqlite3'
11
+ gem 'sqlite3', '~> 1.4.0'
12
12
  gem 'capybara'
13
13
  gem 'poltergeist'
14
-
15
- # Lock down versions of gems for older versions of Ruby
16
- if Gem::Version.new(RUBY_VERSION.dup) < Gem::Version.new("2.0")
17
- gem 'mime-types', '~> 2.99'
18
- end
19
- if Gem::Version.new(RUBY_VERSION.dup) < Gem::Version.new("2.1")
20
- gem 'devise', '~> 3.5'
21
- end
22
14
  end
data/README.md CHANGED
@@ -2,25 +2,27 @@
2
2
  # DeviseSamlAuthenticatable
3
3
 
4
4
  Devise Saml Authenticatable is a Single-Sign-On authentication strategy for devise that relies on SAML.
5
- It uses [ruby-saml][] to handle all SAML related stuff.
5
+ It uses [ruby-saml][] to handle all SAML-related stuff.
6
6
 
7
7
  ## Installation
8
8
 
9
- Add this line to your application's Gemfile:
9
+ Add this gem to your application's Gemfile:
10
10
 
11
- gem 'devise_saml_authenticatable'
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
+ ## Usage
18
19
 
19
- $ gem install devise_saml_authenticatable
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.
20
21
 
21
- ## Usage
22
+ ### Configuring Models
22
23
 
23
24
  In `app/models/<YOUR_MODEL>.rb` set the `:saml_authenticatable` strategy.
25
+
24
26
  In the example the model is `user.rb`:
25
27
 
26
28
  ```ruby
@@ -31,7 +33,38 @@ In the example the model is `user.rb`:
31
33
  end
32
34
  ```
33
35
 
34
- In config/initializers/devise.rb
36
+ ### Configuring routes
37
+
38
+ In `config/routes.rb` add `devise_for` to set up helper methods and routes:
39
+
40
+ ```ruby
41
+ devise_for :users
42
+ ```
43
+
44
+ The named routes can be customized in the initializer config file.
45
+
46
+ ### Configuring the IdP
47
+
48
+ An extra step in SAML SSO setup is adding your application to your identity provider. The required setup is specific to each IdP, but we have some examples in [our wiki](https://github.com/apokalipto/devise_saml_authenticatable/wiki). You'll need to tell your IdP how to send requests and responses to your application.
49
+
50
+ - Creating a new session: `/users/saml/auth`
51
+ - IdPs may call this the "consumer," "recipient," "destination," or even "single sign-on." This is where they send a SAML response for an authenticated user.
52
+ - Metadata: `/users/saml/metadata`
53
+ - IdPs may call this the "audience."
54
+ - Single Logout: `/users/saml/idp_sign_out`
55
+ - if desired, you can ask the IdP to send a Logout request to this endpoint to sign the user out of your application when they sign out of the IdP itself.
56
+
57
+ Your IdP should give you some information you need to configure in [ruby-saml](https://github.com/onelogin/ruby-saml), as in the next section:
58
+
59
+ - Issuer (`idp_entity_id`)
60
+ - SSO endpoint (`idp_sso_target_url`)
61
+ - SLO endpoint (`idp_slo_target_url`)
62
+ - Certificate fingerprint (`idp_cert_fingerprint`) and algorithm (`idp_cert_fingerprint_algorithm`)
63
+ - Or the certificate itself (`idp_cert`)
64
+
65
+ ### Configuring handling of IdP requests and responses
66
+
67
+ In `config/initializers/devise.rb`:
35
68
 
36
69
  ```ruby
37
70
  Devise.setup do |config|
@@ -52,8 +85,8 @@ In config/initializers/devise.rb
52
85
  # for the user's session to facilitate an IDP initiated logout request.
53
86
  config.saml_session_index_key = :session_index
54
87
 
55
- # You can set this value to use Subject or SAML assertation as info to which email will be compared
56
- # If you don't set it then email will be extracted from SAML assertation attributes
88
+ # You can set this value to use Subject or SAML assertation as info to which email will be compared.
89
+ # If you don't set it then email will be extracted from SAML assertation attributes.
57
90
  config.saml_use_subject = true
58
91
 
59
92
  # You can support multiple IdPs by setting this value to a class that implements a #settings method which takes
@@ -68,8 +101,19 @@ In config/initializers/devise.rb
68
101
  # and implements a #handle method. This method can then redirect the user, return error messages, etc.
69
102
  # config.saml_failed_callback = nil
70
103
 
71
- # Configure with your SAML settings (see [ruby-saml][] for more information).
104
+ # You can customize the named routes generated in case of named route collisions with
105
+ # other Devise modules or libraries. Set the saml_route_helper_prefix to a string that will
106
+ # be appended to the named route.
107
+ # If saml_route_helper_prefix = 'saml' then the new_user_session route becomes new_saml_user_session
108
+ # config.saml_route_helper_prefix = 'saml'
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
+
114
+ # Configure with your SAML settings (see ruby-saml's README for more information: https://github.com/onelogin/ruby-saml).
72
115
  config.saml_configure do |settings|
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
73
117
  settings.assertion_consumer_service_url = "http://localhost:3000/users/saml/auth"
74
118
  settings.assertion_consumer_service_binding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
75
119
  settings.name_identifier_format = "urn:oasis:names:tc:SAML:2.0:nameid-format:transient"
@@ -77,29 +121,32 @@ In config/initializers/devise.rb
77
121
  settings.authn_context = ""
78
122
  settings.idp_slo_target_url = "http://localhost/simplesaml/www/saml2/idp/SingleLogoutService.php"
79
123
  settings.idp_sso_target_url = "http://localhost/simplesaml/www/saml2/idp/SSOService.php"
80
- settings.idp_cert = <<-CERT.chomp
81
- -----BEGIN CERTIFICATE-----
82
- 1111111111111111111111111111111111111111111111111111111111111111
83
- 1111111111111111111111111111111111111111111111111111111111111111
84
- 1111111111111111111111111111111111111111111111111111111111111111
85
- 1111111111111111111111111111111111111111111111111111111111111111
86
- 1111111111111111111111111111111111111111111111111111111111111111
87
- 1111111111111_______IDP_CERTIFICATE________111111111111111111111
88
- 1111111111111111111111111111111111111111111111111111111111111111
89
- 1111111111111111111111111111111111111111111111111111111111111111
90
- 1111111111111111111111111111111111111111111111111111111111111111
91
- 1111111111111111111111111111111111111111111111111111111111111111
92
- 1111111111111111111111111111111111111111111111111111111111111111
93
- 1111111111111111111111111111111111111111111111111111111111111111
94
- 1111111111111111111111111111111111111111111111111111111111111111
95
- 111111111111111111
96
- -----END CERTIFICATE-----
97
- CERT
124
+ settings.idp_cert_fingerprint = "00:A1:2B:3C:44:55:6F:A7:88:CC:DD:EE:22:33:44:55:D6:77:8F:99"
125
+ settings.idp_cert_fingerprint_algorithm = "http://www.w3.org/2000/09/xmldsig#sha1"
98
126
  end
99
127
  end
100
128
  ```
101
129
 
102
- In config directory create a YAML file (`attribute-map.yml`) that maps SAML attributes with your model's fields:
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:
103
150
 
104
151
  ```yaml
105
152
  # attribute-map.yml
@@ -110,17 +157,43 @@ In config directory create a YAML file (`attribute-map.yml`) that maps SAML attr
110
157
  "urn:mace:dir:attribute-def:givenName": "name"
111
158
  ```
112
159
 
113
- The attribute mappings are very dependent on the way the IdP encodes the attributes.
114
- In this example the attributes are given in URN style.
115
- Other IdPs might provide them as OID's or other means.
160
+ ##### Attribute map initializer
116
161
 
117
- You are now ready to test it against an IdP.
118
- When the user goes to `/users/saml/sign_in` he will be redirected to the login page of the IdP.
119
- Upon successful login the user is redirected to devise `user_root_path`.
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.
165
+
166
+ ```ruby
167
+ # config/initializers/devise.rb
168
+ Devise.setup do |config|
169
+ ...
170
+ # ==> Configuration for :saml_authenticatable
171
+
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
+ ```
120
193
 
121
194
  ## Supporting Multiple IdPs
122
195
 
123
- If you must support multiple Identity Providers you can implement an adapter class with a `#settings` method that takes an IdP entity id and returns a hash of settings for the corresponding IdP. The `config.idp_settings_adapter` then must be set to point to your adapter in config/initializers/devise.rb. The implementation of the adapter is up to you. A simple example may look like this:
196
+ If you must support multiple Identity Providers you can implement an adapter class with a `#settings` method that takes an IdP entity id and returns a hash of settings for the corresponding IdP. The `config.idp_settings_adapter` then must be set to point to your adapter in `config/initializers/devise.rb`. The implementation of the adapter is up to you. A simple example may look like this:
124
197
 
125
198
  ```ruby
126
199
  class IdPSettingsAdapter
@@ -156,23 +229,29 @@ class IdPSettingsAdapter
156
229
  end
157
230
  end
158
231
  ```
232
+ Settings specified in the adapter will override settings in `config/initializers/devise.rb`. This is useful for establishing common settings or defaults across all IdPs.
159
233
 
160
234
  Detecting the entity ID passed to the `settings` method is done by `config.idp_entity_id_reader`.
235
+
161
236
  By default this will find the `Issuer` in the SAML request.
237
+
162
238
  You can support more use cases by writing your own and implementing the `.entity_id` method.
239
+
163
240
  If you use encrypted assertions, your entity ID reader will need to understand how to decrypt the response from each of the possible IdPs.
164
241
 
165
242
  ## Identity Provider
166
243
 
167
- If you don't have an identity provider an you would like to test the authentication against your app there are some options:
244
+ If you don't have an identity provider and you would like to test the authentication against your app, there are some options:
168
245
 
169
- 1. Use [ruby-saml-idp](https://github.com/lawrencepit/ruby-saml-idp). You can add your own logic to your IdP, or you can also set it as a dummy IdP that always sends a valid authentication response to your app.
170
- 2. Use an online service that can act as an IdP. Onelogin, Salesforce, Okta and some others provide you with this functionality
246
+ 1. Use [ruby-saml-idp](https://github.com/lawrencepit/ruby-saml-idp). You can add your own logic to your IdP, or you can also set it up as a dummy IdP that always sends a valid authentication response to your app.
247
+ 2. Use an online service that can act as an IdP. OneLogin, Salesforce, Okta and some others provide you with this functionality.
171
248
  3. Install your own IdP.
172
249
 
173
- There are numerous IdPs that support SAML 2.0, there are propietary (like Microsoft ADFS 2.0 or Ping federate) and there are also open source solutions like Shibboleth and simplesamlphp.
250
+ There are numerous IdPs that support SAML 2.0, there are propietary (like Microsoft ADFS 2.0 or Ping federate) and there are also open source solutions like Shibboleth and [SimpleSAMLphp].
251
+
252
+ [SimpleSAMLphp] was my choice for development since it is a production-ready SAML solution, that is also really easy to install, configure and use.
174
253
 
175
- [SimpleSAMLphp](http://simplesamlphp.org/) was my choice for development since it is a production-ready SAML solution, that is also really easy to install, configure and use.
254
+ [SimpleSAMLphp]: http://simplesamlphp.org/
176
255
 
177
256
  ## Logout
178
257
 
@@ -180,18 +259,22 @@ Logout support is included by immediately terminating the local session and then
180
259
 
181
260
  ## Logout Request
182
261
 
183
- Logout requests from the IDP are supported by the `idp_sign_out` end point. Directing logout requests to `users/saml/idp_sign_out` will logout the respective user by invalidating their current sessions.
262
+ Logout requests from the IDP are supported by the `idp_sign_out` endpoint. Directing logout requests to `users/saml/idp_sign_out` will log out the respective user by invalidating their current sessions.
263
+
184
264
  `saml_session_index_key` must be configured to support this feature.
185
265
 
186
- ## Signing and Encrypting Authentication Requests
266
+ ## Signing and Encrypting Authentication Requests and Assertions
267
+
268
+ ruby-saml 1.0.0 supports signature and decrypt. The only requirement is to set the public certificate and the private key. For more information, see [the ruby-saml documentation](https://github.com/onelogin/ruby-saml#signing).
187
269
 
188
- ruby-saml 1.0.0 supports signature and decrypt. Teh only requirement is to place the public certificate and the private key. Please reffer to these features in the ruby-saml documentation [here](https://github.com/onelogin/ruby-saml#signing)
270
+ If you have multiple IdPs, the certificate and private key must be in the shared settings in `config/initializers/devise.rb`.
189
271
 
190
272
  ## Thanks
191
273
 
192
274
  The continued maintenance of this gem could not have been possible without the hard work of [Adam Stegman](https://github.com/adamstegman) and [Mitch Lindsay](https://github.com/mitch-lindsay). Thank you guys for keeping this project alive.
193
275
 
194
276
  Thanks to all other contributors that have also helped us make this software better.
277
+
195
278
  ## Contributing
196
279
 
197
280
  1. Fork it
@@ -3,7 +3,13 @@ require "ruby-saml"
3
3
  class Devise::SamlSessionsController < Devise::SessionsController
4
4
  include DeviseSamlAuthenticatable::SamlConfig
5
5
  unloadable if Rails::VERSION::MAJOR < 4
6
- skip_before_filter :verify_authenticity_token, raise: false
6
+ if Rails::VERSION::MAJOR < 5
7
+ skip_before_filter :verify_authenticity_token
8
+ prepend_before_filter :store_info_for_sp_initiated_logout, only: :destroy
9
+ else
10
+ skip_before_action :verify_authenticity_token, raise: false
11
+ prepend_before_action :store_info_for_sp_initiated_logout, only: :destroy
12
+ end
7
13
 
8
14
  def new
9
15
  idp_entity_id = get_idp_entity_id(params)
@@ -26,16 +32,16 @@ class Devise::SamlSessionsController < Devise::SessionsController
26
32
 
27
33
  redirect_to generate_idp_logout_response(saml_config, logout_request.id)
28
34
  elsif params[:SAMLResponse]
29
- #Currently Devise handles the session invalidation when the request is made.
30
- #To support a true SP initiated logout response, the request ID would have to be tracked and session invalidated
31
- #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.
32
38
  if Devise.saml_sign_out_success_url
33
39
  redirect_to Devise.saml_sign_out_success_url
34
40
  else
35
41
  redirect_to action: :new
36
42
  end
37
43
  else
38
- head :invalid_request
44
+ head 500
39
45
  end
40
46
  end
41
47
 
@@ -47,13 +53,38 @@ class Devise::SamlSessionsController < Devise::SessionsController
47
53
  end
48
54
  end
49
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
+
50
65
  # Override devise to send user to IdP logout for SLO
51
66
  def after_sign_out_path_for(_)
67
+ idp_entity_id = get_idp_entity_id(params)
52
68
  request = OneLogin::RubySaml::Logoutrequest.new
53
- request.create(saml_config)
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)
54
79
  end
55
80
 
56
81
  def generate_idp_logout_response(saml_config, logout_request_id)
57
- OneLogin::RubySaml::SloLogoutresponse.new.create(saml_config, logout_request_id, nil)
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)
58
89
  end
59
90
  end