devise_saml_authenticatable 1.3.1 → 1.3.2
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 +4 -4
- data/.travis.yml +13 -5
- data/Gemfile +1 -9
- data/README.md +26 -16
- data/app/controllers/devise/saml_sessions_controller.rb +5 -1
- data/lib/devise_saml_authenticatable.rb +45 -0
- data/lib/devise_saml_authenticatable/default_idp_entity_id_reader.rb +8 -2
- data/lib/devise_saml_authenticatable/model.rb +15 -16
- data/lib/devise_saml_authenticatable/saml_mapped_attributes.rb +25 -0
- data/lib/devise_saml_authenticatable/saml_response.rb +16 -0
- data/lib/devise_saml_authenticatable/strategy.rb +9 -2
- data/lib/devise_saml_authenticatable/version.rb +1 -1
- data/spec/devise_saml_authenticatable/default_idp_entity_id_reader_spec.rb +34 -4
- data/spec/devise_saml_authenticatable/model_spec.rb +152 -0
- data/spec/devise_saml_authenticatable/strategy_spec.rb +18 -0
- data/spec/support/Gemfile.rails4 +1 -0
- data/spec/support/Gemfile.rails5 +14 -0
- data/spec/support/Gemfile.ruby-saml-1.3 +1 -0
- data/spec/support/idp_template.rb +4 -1
- data/spec/support/rails_app.rb +43 -7
- data/spec/support/saml_idp_controller.rb.erb +9 -4
- data/spec/support/sp_template.rb +8 -1
- metadata +6 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 269c0505d92b49c3b9a79d1b8aece87533018b10
|
4
|
+
data.tar.gz: 259f7e012808aabe3ab06e854a01deab8959843a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 83a89cbe087accbcf239e047027188a35793c9cdcb3198fd207edeb95652bb6e8957ef8e0aeaa9b919f01cbb91677bf4c5ade49fae0a85b86cf95edc4b09592b
|
7
|
+
data.tar.gz: 230f5490186a0853cd63f401b390afa29129b8f103de04cbb64a734274edd88de9467030784122cccdc743fbd5eb7babf75ad3635be5b51d2a5af467d4e17b1f
|
data/.travis.yml
CHANGED
@@ -2,26 +2,34 @@ language: ruby
|
|
2
2
|
rvm:
|
3
3
|
- "1.9.3"
|
4
4
|
- "2.0.0"
|
5
|
-
- "2.1.
|
6
|
-
- "2.2.
|
7
|
-
- "2.3.
|
5
|
+
- "2.1.10"
|
6
|
+
- "2.2.7"
|
7
|
+
- "2.3.4"
|
8
|
+
- "2.4.1"
|
8
9
|
gemfile:
|
9
10
|
- Gemfile
|
11
|
+
- spec/support/Gemfile.rails5
|
10
12
|
- spec/support/Gemfile.rails4
|
11
13
|
- spec/support/Gemfile.ruby-saml-1.3
|
12
14
|
matrix:
|
13
15
|
allow_failures:
|
14
16
|
- rvm: "1.9.3"
|
15
17
|
gemfile: Gemfile
|
18
|
+
- rvm: "1.9.3"
|
19
|
+
gemfile: spec/support/Gemfile.rails5
|
16
20
|
- rvm: "1.9.3"
|
17
21
|
gemfile: spec/support/Gemfile.ruby-saml-1.3
|
18
22
|
- rvm: "2.0.0"
|
19
23
|
gemfile: Gemfile
|
24
|
+
- rvm: "2.0.0"
|
25
|
+
gemfile: spec/support/Gemfile.rails5
|
20
26
|
- rvm: "2.0.0"
|
21
27
|
gemfile: spec/support/Gemfile.ruby-saml-1.3
|
22
|
-
- rvm: "2.1.
|
28
|
+
- rvm: "2.1.10"
|
23
29
|
gemfile: Gemfile
|
24
|
-
- rvm: "2.1.
|
30
|
+
- rvm: "2.1.10"
|
31
|
+
gemfile: spec/support/Gemfile.rails5
|
32
|
+
- rvm: "2.1.10"
|
25
33
|
gemfile: spec/support/Gemfile.ruby-saml-1.3
|
26
34
|
|
27
35
|
script:
|
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.
|
9
|
+
gem 'rails', '~> 5.1'
|
10
10
|
gem 'rspec-rails'
|
11
11
|
gem 'sqlite3'
|
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,7 +2,7 @@
|
|
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
|
5
|
+
It uses [ruby-saml][] to handle all SAML-related stuff.
|
6
6
|
|
7
7
|
## Installation
|
8
8
|
|
@@ -21,6 +21,7 @@ Or install it yourself as:
|
|
21
21
|
## Usage
|
22
22
|
|
23
23
|
In `app/models/<YOUR_MODEL>.rb` set the `:saml_authenticatable` strategy.
|
24
|
+
|
24
25
|
In the example the model is `user.rb`:
|
25
26
|
|
26
27
|
```ruby
|
@@ -31,7 +32,7 @@ In the example the model is `user.rb`:
|
|
31
32
|
end
|
32
33
|
```
|
33
34
|
|
34
|
-
In config/initializers/devise.rb
|
35
|
+
In `config/initializers/devise.rb`:
|
35
36
|
|
36
37
|
```ruby
|
37
38
|
Devise.setup do |config|
|
@@ -52,8 +53,8 @@ In config/initializers/devise.rb
|
|
52
53
|
# for the user's session to facilitate an IDP initiated logout request.
|
53
54
|
config.saml_session_index_key = :session_index
|
54
55
|
|
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
|
56
|
+
# You can set this value to use Subject or SAML assertation as info to which email will be compared.
|
57
|
+
# If you don't set it then email will be extracted from SAML assertation attributes.
|
57
58
|
config.saml_use_subject = true
|
58
59
|
|
59
60
|
# You can support multiple IdPs by setting this value to a class that implements a #settings method which takes
|
@@ -99,7 +100,7 @@ In config/initializers/devise.rb
|
|
99
100
|
end
|
100
101
|
```
|
101
102
|
|
102
|
-
In config directory create a YAML file (`attribute-map.yml`) that maps SAML attributes with your model's fields:
|
103
|
+
In the config directory, create a YAML file (`attribute-map.yml`) that maps SAML attributes with your model's fields:
|
103
104
|
|
104
105
|
```yaml
|
105
106
|
# attribute-map.yml
|
@@ -112,15 +113,17 @@ In config directory create a YAML file (`attribute-map.yml`) that maps SAML attr
|
|
112
113
|
|
113
114
|
The attribute mappings are very dependent on the way the IdP encodes the attributes.
|
114
115
|
In this example the attributes are given in URN style.
|
115
|
-
Other IdPs might provide them as OID's or other means.
|
116
|
+
Other IdPs might provide them as OID's, or by other means.
|
116
117
|
|
117
118
|
You are now ready to test it against an IdP.
|
118
|
-
|
119
|
-
|
119
|
+
|
120
|
+
When the user visits `/users/saml/sign_in` they will be redirected to the login page of the IdP.
|
121
|
+
|
122
|
+
Upon successful login the user is redirected to the Devise `user_root_path`.
|
120
123
|
|
121
124
|
## Supporting Multiple IdPs
|
122
125
|
|
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
|
126
|
+
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
127
|
|
125
128
|
```ruby
|
126
129
|
class IdPSettingsAdapter
|
@@ -158,21 +161,26 @@ end
|
|
158
161
|
```
|
159
162
|
|
160
163
|
Detecting the entity ID passed to the `settings` method is done by `config.idp_entity_id_reader`.
|
164
|
+
|
161
165
|
By default this will find the `Issuer` in the SAML request.
|
166
|
+
|
162
167
|
You can support more use cases by writing your own and implementing the `.entity_id` method.
|
168
|
+
|
163
169
|
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
170
|
|
165
171
|
## Identity Provider
|
166
172
|
|
167
|
-
If you don't have an identity provider
|
173
|
+
If you don't have an identity provider and you would like to test the authentication against your app, there are some options:
|
168
174
|
|
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.
|
175
|
+
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.
|
176
|
+
2. Use an online service that can act as an IdP. OneLogin, Salesforce, Okta and some others provide you with this functionality.
|
171
177
|
3. Install your own IdP.
|
172
178
|
|
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
|
179
|
+
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].
|
180
|
+
|
181
|
+
[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
182
|
|
175
|
-
[SimpleSAMLphp]
|
183
|
+
[SimpleSAMLphp]: http://simplesamlphp.org/
|
176
184
|
|
177
185
|
## Logout
|
178
186
|
|
@@ -180,18 +188,20 @@ Logout support is included by immediately terminating the local session and then
|
|
180
188
|
|
181
189
|
## Logout Request
|
182
190
|
|
183
|
-
Logout requests from the IDP are supported by the `idp_sign_out`
|
191
|
+
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.
|
192
|
+
|
184
193
|
`saml_session_index_key` must be configured to support this feature.
|
185
194
|
|
186
195
|
## Signing and Encrypting Authentication Requests
|
187
196
|
|
188
|
-
ruby-saml 1.0.0 supports signature and decrypt.
|
197
|
+
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).
|
189
198
|
|
190
199
|
## Thanks
|
191
200
|
|
192
201
|
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
202
|
|
194
203
|
Thanks to all other contributors that have also helped us make this software better.
|
204
|
+
|
195
205
|
## Contributing
|
196
206
|
|
197
207
|
1. Fork it
|
@@ -3,7 +3,11 @@ require "ruby-saml"
|
|
3
3
|
class Devise::SamlSessionsController < Devise::SessionsController
|
4
4
|
include DeviseSamlAuthenticatable::SamlConfig
|
5
5
|
unloadable if Rails::VERSION::MAJOR < 4
|
6
|
-
|
6
|
+
if Rails::VERSION::MAJOR < 5
|
7
|
+
skip_before_filter :verify_authenticity_token
|
8
|
+
else
|
9
|
+
skip_before_action :verify_authenticity_token, raise: false
|
10
|
+
end
|
7
11
|
|
8
12
|
def new
|
9
13
|
idp_entity_id = get_idp_entity_id(params)
|
@@ -62,11 +62,56 @@ module Devise
|
|
62
62
|
mattr_accessor :saml_relay_state
|
63
63
|
@@saml_relay_state
|
64
64
|
|
65
|
+
# Implements a #validate method that takes the retrieved resource and response right after retrieval,
|
66
|
+
# and returns true if it's valid. False will cause authentication to fail.
|
67
|
+
mattr_accessor :saml_resource_validator
|
68
|
+
@@saml_resource_validator
|
69
|
+
|
70
|
+
# Custom value for ruby-saml allowed_clock_drift
|
71
|
+
mattr_accessor :allowed_clock_drift_in_seconds
|
72
|
+
@@allowed_clock_drift_in_seconds
|
73
|
+
|
65
74
|
mattr_accessor :saml_config
|
66
75
|
@@saml_config = OneLogin::RubySaml::Settings.new
|
67
76
|
def self.saml_configure
|
68
77
|
yield saml_config
|
69
78
|
end
|
79
|
+
|
80
|
+
# Default update resource hook. Updates each attribute on the model that is mapped, updates the
|
81
|
+
# saml_default_user_key if saml_use_subject is true and saves the user model.
|
82
|
+
# See saml_update_resource_hook for more information.
|
83
|
+
mattr_reader :saml_default_update_resource_hook
|
84
|
+
@@saml_default_update_resource_hook = Proc.new do |user, saml_response, auth_value|
|
85
|
+
saml_response.attributes.resource_keys.each do |key|
|
86
|
+
user.send "#{key}=", saml_response.attribute_value_by_resource_key(key)
|
87
|
+
end
|
88
|
+
|
89
|
+
if (Devise.saml_use_subject)
|
90
|
+
user.send "#{Devise.saml_default_user_key}=", auth_value
|
91
|
+
end
|
92
|
+
|
93
|
+
user.save!
|
94
|
+
end
|
95
|
+
|
96
|
+
# Proc that is called if Devise.saml_update_user and/or Devise.saml_create_user are true.
|
97
|
+
# Recieves the user object, saml_response and auth_value, and defines how the object's values are
|
98
|
+
# updated with regards to the SAML response. See saml_default_update_resource_hook for an example.
|
99
|
+
mattr_accessor :saml_update_resource_hook
|
100
|
+
@@saml_update_resource_hook = @@saml_default_update_resource_hook
|
101
|
+
|
102
|
+
# Default resource locator. Uses saml_default_user_key and auth_value to resolve user.
|
103
|
+
# See saml_resource_locator for more information.
|
104
|
+
mattr_reader :saml_default_resource_locator
|
105
|
+
@@saml_default_resource_locator = Proc.new do |model, saml_response, auth_value|
|
106
|
+
model.where(Devise.saml_default_user_key => auth_value).first
|
107
|
+
end
|
108
|
+
|
109
|
+
# Proc that is called to resolve the saml_response and auth_value into the correct user object.
|
110
|
+
# Recieves a copy of the ActiveRecord::Model, saml_response and auth_value. Is expected to return
|
111
|
+
# one instance of the provided model that is the matched account, or nil if none exists.
|
112
|
+
# See saml_default_resource_locator above for an example.
|
113
|
+
mattr_accessor :saml_resource_locator
|
114
|
+
@@saml_resource_locator = @@saml_default_resource_locator
|
70
115
|
end
|
71
116
|
|
72
117
|
# Add saml_authenticatable strategy to defaults.
|
@@ -2,9 +2,15 @@ module DeviseSamlAuthenticatable
|
|
2
2
|
class DefaultIdpEntityIdReader
|
3
3
|
def self.entity_id(params)
|
4
4
|
if params[:SAMLRequest]
|
5
|
-
OneLogin::RubySaml::SloLogoutrequest.new(
|
5
|
+
OneLogin::RubySaml::SloLogoutrequest.new(
|
6
|
+
params[:SAMLRequest],
|
7
|
+
allowed_clock_drift: Devise.allowed_clock_drift_in_seconds,
|
8
|
+
).issuer
|
6
9
|
elsif params[:SAMLResponse]
|
7
|
-
OneLogin::RubySaml::Response.new(
|
10
|
+
OneLogin::RubySaml::Response.new(
|
11
|
+
params[:SAMLResponse],
|
12
|
+
allowed_clock_drift: Devise.allowed_clock_drift_in_seconds,
|
13
|
+
).issuers.first
|
8
14
|
end
|
9
15
|
end
|
10
16
|
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'devise_saml_authenticatable/strategy'
|
2
|
+
require 'devise_saml_authenticatable/saml_response'
|
2
3
|
|
3
4
|
module Devise
|
4
5
|
module Models
|
@@ -30,16 +31,25 @@ module Devise
|
|
30
31
|
module ClassMethods
|
31
32
|
def authenticate_with_saml(saml_response, relay_state)
|
32
33
|
key = Devise.saml_default_user_key
|
33
|
-
|
34
|
+
decorated_response = ::SamlAuthenticatable::SamlResponse.new(
|
35
|
+
saml_response,
|
36
|
+
attribute_map
|
37
|
+
)
|
34
38
|
if (Devise.saml_use_subject)
|
35
39
|
auth_value = saml_response.name_id
|
36
40
|
else
|
37
|
-
|
38
|
-
auth_value = attributes[inv_attr[key.to_s]]
|
41
|
+
auth_value = decorated_response.attribute_value_by_resource_key(key)
|
39
42
|
end
|
40
43
|
auth_value.try(:downcase!) if Devise.case_insensitive_keys.include?(key)
|
41
44
|
|
42
|
-
resource =
|
45
|
+
resource = Devise.saml_resource_locator.call(self, decorated_response, auth_value)
|
46
|
+
|
47
|
+
if Devise.saml_resource_validator
|
48
|
+
if not Devise.saml_resource_validator.new.validate(resource, saml_response)
|
49
|
+
logger.info("User(#{auth_value}) did not pass custom validation.")
|
50
|
+
return nil
|
51
|
+
end
|
52
|
+
end
|
43
53
|
|
44
54
|
if resource.nil?
|
45
55
|
if Devise.saml_create_user
|
@@ -52,11 +62,7 @@ module Devise
|
|
52
62
|
end
|
53
63
|
|
54
64
|
if Devise.saml_update_user || (resource.new_record? && Devise.saml_create_user)
|
55
|
-
|
56
|
-
if (Devise.saml_use_subject)
|
57
|
-
resource.send "#{key}=", auth_value
|
58
|
-
end
|
59
|
-
resource.save!
|
65
|
+
Devise.saml_update_resource_hook.call(resource, decorated_response, auth_value)
|
60
66
|
end
|
61
67
|
|
62
68
|
resource
|
@@ -77,13 +83,6 @@ module Devise
|
|
77
83
|
|
78
84
|
private
|
79
85
|
|
80
|
-
def set_user_saml_attributes(user,attributes)
|
81
|
-
attribute_map.each do |k,v|
|
82
|
-
Rails.logger.info "Setting: #{v}, #{attributes[k]}"
|
83
|
-
user.send "#{v}=", attributes[k]
|
84
|
-
end
|
85
|
-
end
|
86
|
-
|
87
86
|
def attribute_map_for_environment
|
88
87
|
attribute_map = YAML.load(File.read("#{Rails.root}/config/attribute-map.yml"))
|
89
88
|
if attribute_map.has_key?(Rails.env)
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module SamlAuthenticatable
|
2
|
+
class SamlMappedAttributes
|
3
|
+
def initialize(attributes, attribute_map)
|
4
|
+
@attributes = attributes
|
5
|
+
@attribute_map = attribute_map
|
6
|
+
@inverted_attribute_map = @attribute_map.invert
|
7
|
+
end
|
8
|
+
|
9
|
+
def saml_attribute_keys
|
10
|
+
@attribute_map.keys
|
11
|
+
end
|
12
|
+
|
13
|
+
def resource_keys
|
14
|
+
@attribute_map.values
|
15
|
+
end
|
16
|
+
|
17
|
+
def value_by_resource_key(key)
|
18
|
+
value_by_saml_attribute_key(@inverted_attribute_map.fetch(String(key)))
|
19
|
+
end
|
20
|
+
|
21
|
+
def value_by_saml_attribute_key(key)
|
22
|
+
@attributes[String(key)]
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'devise_saml_authenticatable/saml_mapped_attributes'
|
2
|
+
|
3
|
+
module SamlAuthenticatable
|
4
|
+
class SamlResponse
|
5
|
+
attr_reader :raw_response, :attributes
|
6
|
+
|
7
|
+
def initialize(saml_response, attribute_map)
|
8
|
+
@attributes = ::SamlAuthenticatable::SamlMappedAttributes.new(saml_response.attributes, attribute_map)
|
9
|
+
@raw_response = saml_response
|
10
|
+
end
|
11
|
+
|
12
|
+
def attribute_value_by_resource_key(key)
|
13
|
+
attributes.value_by_resource_key(key)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -6,7 +6,10 @@ module Devise
|
|
6
6
|
include DeviseSamlAuthenticatable::SamlConfig
|
7
7
|
def valid?
|
8
8
|
if params[:SAMLResponse]
|
9
|
-
OneLogin::RubySaml::Response.new(
|
9
|
+
OneLogin::RubySaml::Response.new(
|
10
|
+
params[:SAMLResponse],
|
11
|
+
allowed_clock_drift: Devise.allowed_clock_drift_in_seconds,
|
12
|
+
)
|
10
13
|
else
|
11
14
|
false
|
12
15
|
end
|
@@ -30,7 +33,11 @@ module Devise
|
|
30
33
|
|
31
34
|
private
|
32
35
|
def parse_saml_response
|
33
|
-
@response = OneLogin::RubySaml::Response.new(
|
36
|
+
@response = OneLogin::RubySaml::Response.new(
|
37
|
+
params[:SAMLResponse],
|
38
|
+
settings: saml_config(get_idp_entity_id(params)),
|
39
|
+
allowed_clock_drift: Devise.allowed_clock_drift_in_seconds,
|
40
|
+
)
|
34
41
|
unless @response.is_valid?
|
35
42
|
failed_auth("Auth errors: #{@response.errors.join(', ')}")
|
36
43
|
end
|
@@ -5,18 +5,48 @@ describe DeviseSamlAuthenticatable::DefaultIdpEntityIdReader do
|
|
5
5
|
context "when there is a SAMLRequest in the params" do
|
6
6
|
let(:params) { {SAMLRequest: "logout request"} }
|
7
7
|
let(:slo_logout_request) { double('slo_logout_request', issuer: 'meow')}
|
8
|
+
before do
|
9
|
+
allow(OneLogin::RubySaml::SloLogoutrequest).to receive(:new).and_return(slo_logout_request)
|
10
|
+
end
|
11
|
+
|
8
12
|
it "uses an OneLogin::RubySaml::SloLogoutrequest to get the idp_entity_id" do
|
9
|
-
expect(OneLogin::RubySaml::SloLogoutrequest).to receive(:new).
|
10
|
-
described_class.entity_id(params)
|
13
|
+
expect(OneLogin::RubySaml::SloLogoutrequest).to receive(:new).with("logout request", hash_including)
|
14
|
+
expect(described_class.entity_id(params)).to eq("meow")
|
15
|
+
end
|
16
|
+
|
17
|
+
context "and allowed_clock_drift is configured" do
|
18
|
+
before do
|
19
|
+
allow(Devise).to receive(:allowed_clock_drift_in_seconds).and_return(30)
|
20
|
+
end
|
21
|
+
|
22
|
+
it "allows the configured clock drift" do
|
23
|
+
expect(OneLogin::RubySaml::SloLogoutrequest).to receive(:new).with("logout request", hash_including(allowed_clock_drift: 30))
|
24
|
+
expect(described_class.entity_id(params)).to eq("meow")
|
25
|
+
end
|
11
26
|
end
|
12
27
|
end
|
13
28
|
|
14
29
|
context "when there is a SAMLResponse in the params" do
|
15
30
|
let(:params) { {SAMLResponse: "auth response"} }
|
16
31
|
let(:response) { double('response', issuers: ['meow'] )}
|
32
|
+
before do
|
33
|
+
allow(OneLogin::RubySaml::Response).to receive(:new).and_return(response)
|
34
|
+
end
|
35
|
+
|
17
36
|
it "uses an OneLogin::RubySaml::Response to get the idp_entity_id" do
|
18
|
-
expect(OneLogin::RubySaml::Response).to receive(:new).
|
19
|
-
described_class.entity_id(params)
|
37
|
+
expect(OneLogin::RubySaml::Response).to receive(:new).with("auth response", hash_including)
|
38
|
+
expect(described_class.entity_id(params)).to eq("meow")
|
39
|
+
end
|
40
|
+
|
41
|
+
context "and allowed_clock_drift is configured" do
|
42
|
+
before do
|
43
|
+
allow(Devise).to receive(:allowed_clock_drift_in_seconds).and_return(30)
|
44
|
+
end
|
45
|
+
|
46
|
+
it "allows the configured clock drift" do
|
47
|
+
expect(OneLogin::RubySaml::Response).to receive(:new).with("auth response", hash_including(allowed_clock_drift: 30))
|
48
|
+
expect(described_class.entity_id(params)).to eq("meow")
|
49
|
+
end
|
20
50
|
end
|
21
51
|
end
|
22
52
|
end
|
@@ -178,4 +178,156 @@ describe Devise::Models::SamlAuthenticatable do
|
|
178
178
|
include_examples "correct downcasing"
|
179
179
|
end
|
180
180
|
end
|
181
|
+
|
182
|
+
context "when configured with a resource validator" do
|
183
|
+
let(:validator_class) { double("validator_class") }
|
184
|
+
let(:validator) { double("validator") }
|
185
|
+
let(:user) { Model.new(new_record: false) }
|
186
|
+
|
187
|
+
before do
|
188
|
+
allow(Devise).to receive(:saml_resource_validator).and_return(validator_class)
|
189
|
+
allow(validator_class).to receive(:new).and_return(validator)
|
190
|
+
end
|
191
|
+
|
192
|
+
context "and sent a valid value" do
|
193
|
+
before do
|
194
|
+
allow(validator).to receive(:validate).with(user, response).and_return(true)
|
195
|
+
end
|
196
|
+
|
197
|
+
it "returns the user" do
|
198
|
+
expect(Model).to receive(:where).with(email: 'user@example.com').and_return([user])
|
199
|
+
expect(Model.authenticate_with_saml(response, nil)).to eq(user)
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
context "and sent an invalid value" do
|
204
|
+
before do
|
205
|
+
allow(validator).to receive(:validate).with(user, response).and_return(false)
|
206
|
+
end
|
207
|
+
|
208
|
+
it "returns nil" do
|
209
|
+
expect(Model).to receive(:where).with(email: 'user@example.com').and_return([user])
|
210
|
+
expect(Model.authenticate_with_saml(response, nil)).to be_nil
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
context "when configured to use a custom update hook" do
|
216
|
+
it "can replicate the default behaviour in a custom hook" do
|
217
|
+
configure_hook do |user, saml_response|
|
218
|
+
Devise.saml_default_update_resource_hook.call(user, saml_response)
|
219
|
+
end
|
220
|
+
|
221
|
+
new_user = Model.authenticate_with_saml(response, nil)
|
222
|
+
|
223
|
+
expect(new_user.name).to eq(attributes['saml-name-format'])
|
224
|
+
expect(new_user.email).to eq(attributes['saml-email-format'])
|
225
|
+
end
|
226
|
+
|
227
|
+
it "can extend the default behaviour with custom transformations" do
|
228
|
+
configure_hook do |user, saml_response|
|
229
|
+
Devise.saml_default_update_resource_hook.call(user, saml_response)
|
230
|
+
|
231
|
+
user.email = "ext+#{user.email}"
|
232
|
+
end
|
233
|
+
|
234
|
+
new_user = Model.authenticate_with_saml(response, nil)
|
235
|
+
|
236
|
+
expect(new_user.name).to eq(attributes['saml-name-format'])
|
237
|
+
expect(new_user.email).to eq("ext+#{attributes['saml-email-format']}")
|
238
|
+
end
|
239
|
+
|
240
|
+
it "can extend the default behaviour using information from the saml response" do
|
241
|
+
configure_hook do |user, saml_response|
|
242
|
+
Devise.saml_default_update_resource_hook.call(user, saml_response)
|
243
|
+
|
244
|
+
name_id = saml_response.raw_response.name_id
|
245
|
+
user.name += "@#{name_id}"
|
246
|
+
end
|
247
|
+
|
248
|
+
new_user = Model.authenticate_with_saml(response, nil)
|
249
|
+
|
250
|
+
expect(new_user.name).to eq("#{attributes['saml-name-format']}@#{response.name_id}")
|
251
|
+
expect(new_user.email).to eq(attributes['saml-email-format'])
|
252
|
+
end
|
253
|
+
|
254
|
+
def configure_hook(&block)
|
255
|
+
allow(Model).to receive(:where).with(email: 'user@example.com').and_return([])
|
256
|
+
allow(Devise).to receive(:saml_default_user_key).and_return(:email)
|
257
|
+
allow(Devise).to receive(:saml_create_user).and_return(true)
|
258
|
+
allow(Devise).to receive(:saml_update_resource_hook).and_return(block)
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
context "when configured to use a custom user locator" do
|
263
|
+
let(:name_id) { 'SomeUsername' }
|
264
|
+
|
265
|
+
it "can replicate the default behaviour for a new user in a custom locator" do
|
266
|
+
allow(Model).to receive(:where).with(email: attributes['saml-email-format']).and_return([])
|
267
|
+
|
268
|
+
configure_hook do |model, saml_response, auth_value|
|
269
|
+
Devise.saml_default_resource_locator.call(model, saml_response, auth_value)
|
270
|
+
end
|
271
|
+
|
272
|
+
new_user = Model.authenticate_with_saml(response, nil)
|
273
|
+
|
274
|
+
expect(new_user.name).to eq(attributes['saml-name-format'])
|
275
|
+
expect(new_user.email).to eq(attributes['saml-email-format'])
|
276
|
+
end
|
277
|
+
|
278
|
+
it "can replicate the default behaviour for an existing user in a custom locator" do
|
279
|
+
user = Model.new(email: attributes['saml-email-format'], name: attributes['saml-name-format'])
|
280
|
+
user.save!
|
281
|
+
|
282
|
+
allow(Model).to receive(:where).with(email: attributes['saml-email-format']).and_return([user])
|
283
|
+
|
284
|
+
configure_hook do |model, saml_response, auth_value|
|
285
|
+
Devise.saml_default_resource_locator.call(model, saml_response, auth_value)
|
286
|
+
end
|
287
|
+
|
288
|
+
new_user = Model.authenticate_with_saml(response, nil)
|
289
|
+
|
290
|
+
expect(new_user).to eq(user)
|
291
|
+
expect(new_user.name).to eq(attributes['saml-name-format'])
|
292
|
+
expect(new_user.email).to eq(attributes['saml-email-format'])
|
293
|
+
end
|
294
|
+
|
295
|
+
it "can change the default behaviour for a new user from the saml response" do
|
296
|
+
allow(Model).to receive(:where).with(foo: attributes['saml-email-format'], bar: name_id).and_return([])
|
297
|
+
|
298
|
+
configure_hook do |model, saml_response, auth_value|
|
299
|
+
name_id = saml_response.raw_response.name_id
|
300
|
+
model.where(foo: auth_value, bar: name_id).first
|
301
|
+
end
|
302
|
+
|
303
|
+
new_user = Model.authenticate_with_saml(response, nil)
|
304
|
+
|
305
|
+
expect(new_user.name).to eq(attributes['saml-name-format'])
|
306
|
+
expect(new_user.email).to eq(attributes['saml-email-format'])
|
307
|
+
end
|
308
|
+
|
309
|
+
it "can change the default behaviour for an existing user from the saml response" do
|
310
|
+
user = Model.new(email: attributes['saml-email-format'], name: attributes['saml-name-format'])
|
311
|
+
user.save!
|
312
|
+
|
313
|
+
allow(Model).to receive(:where).with(foo: attributes['saml-email-format'], bar: name_id).and_return([user])
|
314
|
+
|
315
|
+
configure_hook do |model, saml_response, auth_value|
|
316
|
+
name_id = saml_response.raw_response.name_id
|
317
|
+
model.where(foo: auth_value, bar: name_id).first
|
318
|
+
end
|
319
|
+
|
320
|
+
new_user = Model.authenticate_with_saml(response, nil)
|
321
|
+
|
322
|
+
expect(new_user).to eq(user)
|
323
|
+
expect(new_user.name).to eq(attributes['saml-name-format'])
|
324
|
+
expect(new_user.email).to eq(attributes['saml-email-format'])
|
325
|
+
end
|
326
|
+
|
327
|
+
def configure_hook(&block)
|
328
|
+
allow(Devise).to receive(:saml_default_user_key).and_return(:email)
|
329
|
+
allow(Devise).to receive(:saml_create_user).and_return(true)
|
330
|
+
allow(Devise).to receive(:saml_resource_locator).and_return(block)
|
331
|
+
end
|
332
|
+
end
|
181
333
|
end
|
@@ -134,6 +134,24 @@ describe Devise::Strategies::SamlAuthenticatable do
|
|
134
134
|
strategy.authenticate!
|
135
135
|
end
|
136
136
|
end
|
137
|
+
|
138
|
+
context "when allowed_clock_drift is configured" do
|
139
|
+
before do
|
140
|
+
allow(Devise).to receive(:allowed_clock_drift_in_seconds).and_return(30)
|
141
|
+
end
|
142
|
+
|
143
|
+
it "is valid with the configured clock drift" do
|
144
|
+
expect(OneLogin::RubySaml::Response).to receive(:new).with(params[:SAMLResponse], hash_including(allowed_clock_drift: 30))
|
145
|
+
expect(strategy).to be_valid
|
146
|
+
end
|
147
|
+
|
148
|
+
it "authenticates with the configured clock drift" do
|
149
|
+
expect(OneLogin::RubySaml::Response).to receive(:new).with(params[:SAMLResponse], hash_including(allowed_clock_drift: 30))
|
150
|
+
|
151
|
+
expect(strategy).to receive(:success!).with(user)
|
152
|
+
strategy.authenticate!
|
153
|
+
end
|
154
|
+
end
|
137
155
|
end
|
138
156
|
|
139
157
|
it "is not valid without a SAMLResponse parameter" do
|
data/spec/support/Gemfile.rails4
CHANGED
@@ -0,0 +1,14 @@
|
|
1
|
+
source 'https://rubygems.org'
|
2
|
+
|
3
|
+
# Specify your gem's dependencies in devise_saml_authenticatable.gemspec
|
4
|
+
gemspec path: '../..'
|
5
|
+
|
6
|
+
group :test do
|
7
|
+
gem 'rake'
|
8
|
+
gem 'rspec', '~> 3.0'
|
9
|
+
gem 'rails', '~> 5.0.0'
|
10
|
+
gem 'rspec-rails'
|
11
|
+
gem 'sqlite3'
|
12
|
+
gem 'capybara'
|
13
|
+
gem 'poltergeist'
|
14
|
+
end
|
@@ -3,7 +3,9 @@
|
|
3
3
|
@include_subject_in_attributes = ENV.fetch('INCLUDE_SUBJECT_IN_ATTRIBUTES')
|
4
4
|
@valid_destination = ENV.fetch('VALID_DESTINATION', "true")
|
5
5
|
|
6
|
-
|
6
|
+
gsub_file 'config/secrets.yml', /secret_key_base:.*$/, 'secret_key_base: "34814fd41f91c493b89aa01ac73c44d241a31245b5bc5542fa4b7317525e1dcfa60ba947b3d085e4e229456fdee0d8af6aac6a63cf750d807ea6fe5d853dff4a"'
|
7
|
+
|
8
|
+
gem 'ruby-saml-idp', git: "https://github.com/lawrencepit/ruby-saml-idp.git", ref: "ec715b252e849105c7a96df27b731c6e7f725a51"
|
7
9
|
gem 'thin'
|
8
10
|
|
9
11
|
insert_into_file('Gemfile', after: /\z/) {
|
@@ -11,6 +13,7 @@ insert_into_file('Gemfile', after: /\z/) {
|
|
11
13
|
# Lock down versions of gems for older versions of Ruby
|
12
14
|
if Gem::Version.new(RUBY_VERSION.dup) < Gem::Version.new("2.1")
|
13
15
|
gem 'devise', '~> 3.5'
|
16
|
+
gem 'nokogiri', '~> 1.6.8'
|
14
17
|
end
|
15
18
|
GEMFILE
|
16
19
|
}
|
data/spec/support/rails_app.rb
CHANGED
@@ -1,4 +1,8 @@
|
|
1
1
|
require 'open3'
|
2
|
+
require 'socket'
|
3
|
+
require 'timeout'
|
4
|
+
|
5
|
+
APP_READY_TIMEOUT ||= 30
|
2
6
|
|
3
7
|
def sh!(cmd)
|
4
8
|
unless system(cmd)
|
@@ -7,8 +11,9 @@ def sh!(cmd)
|
|
7
11
|
end
|
8
12
|
|
9
13
|
def app_ready?(pid, port)
|
10
|
-
Process.getpgid(pid) &&
|
11
|
-
|
14
|
+
Process.getpgid(pid) && port_open?(port)
|
15
|
+
rescue Errno::ESRCH
|
16
|
+
false
|
12
17
|
end
|
13
18
|
|
14
19
|
def create_app(name, env = {})
|
@@ -24,16 +29,26 @@ def start_app(name, port, options = {})
|
|
24
29
|
pid = nil
|
25
30
|
Bundler.with_clean_env do
|
26
31
|
Dir.chdir(File.expand_path("../../support/#{name}", __FILE__)) do
|
27
|
-
pid = Process.spawn("bundle exec rails server -p #{port}")
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
+
pid = Process.spawn({"RAILS_ENV" => "production"}, "bundle exec rails server -p #{port} -e production", out: "log/#{name}.log", err: "log/#{name}.err.log")
|
33
|
+
begin
|
34
|
+
Timeout::timeout(APP_READY_TIMEOUT) do
|
35
|
+
sleep 1 until app_ready?(pid, port)
|
36
|
+
end
|
37
|
+
if app_ready?(pid, port)
|
38
|
+
puts "Launched #{name} on port #{port} (pid #{pid})..."
|
39
|
+
else
|
40
|
+
raise "#{name} failed after starting"
|
41
|
+
end
|
42
|
+
rescue Timeout::Error
|
32
43
|
raise "#{name} failed to start"
|
33
44
|
end
|
34
45
|
end
|
35
46
|
end
|
36
47
|
pid
|
48
|
+
rescue RuntimeError => e
|
49
|
+
$stdout.puts "#{File.read(File.expand_path("../../support/#{name}/log/#{name}.log", __FILE__))}"
|
50
|
+
$stderr.puts "#{File.read(File.expand_path("../../support/#{name}/log/#{name}.err.log", __FILE__))}"
|
51
|
+
raise e
|
37
52
|
end
|
38
53
|
|
39
54
|
def stop_app(pid)
|
@@ -42,3 +57,24 @@ def stop_app(pid)
|
|
42
57
|
Process.wait(pid)
|
43
58
|
end
|
44
59
|
end
|
60
|
+
|
61
|
+
def port_open?(port)
|
62
|
+
Timeout::timeout(1) do
|
63
|
+
begin
|
64
|
+
s = TCPSocket.new('localhost', port)
|
65
|
+
s.close
|
66
|
+
return true
|
67
|
+
rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH
|
68
|
+
# try 127.0.0.1
|
69
|
+
end
|
70
|
+
begin
|
71
|
+
s = TCPSocket.new('127.0.0.1', port)
|
72
|
+
s.close
|
73
|
+
return true
|
74
|
+
rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH
|
75
|
+
return false
|
76
|
+
end
|
77
|
+
end
|
78
|
+
rescue Timeout::Error
|
79
|
+
false
|
80
|
+
end
|
@@ -29,14 +29,14 @@ class SamlIdpController < SamlIdp::IdpController
|
|
29
29
|
|
30
30
|
def session_index
|
31
31
|
Rails.cache.fetch('session_key') {
|
32
|
-
|
32
|
+
SecureRandom.uuid
|
33
33
|
}
|
34
34
|
end
|
35
35
|
|
36
36
|
|
37
37
|
def encode_SAMLResponse(nameID, opts = {})
|
38
38
|
now = Time.now.utc
|
39
|
-
response_id =
|
39
|
+
response_id = SecureRandom.uuid
|
40
40
|
audience_uri = opts[:audience_uri] || "#{saml_acs_url[/^(.*?\/\/.*?\/)/, 1]}saml/metadata"
|
41
41
|
issuer_uri = opts[:issuer_uri] || (defined?(request) && request.url) || "http://example.com"
|
42
42
|
|
@@ -72,8 +72,13 @@ class SamlIdpController < SamlIdp::IdpController
|
|
72
72
|
end
|
73
73
|
|
74
74
|
# == SLO functionality, see https://github.com/lawrencepit/ruby-saml-idp/pull/10
|
75
|
+
<% if Rails::VERSION::MAJOR < 5 %>
|
75
76
|
skip_before_filter :validate_saml_request, :only => [:logout, :sp_sign_out]
|
76
77
|
before_filter :validate_saml_slo_request, :only => [:logout]
|
78
|
+
<% else %>
|
79
|
+
skip_before_action :validate_saml_request, :only => [:logout, :sp_sign_out]
|
80
|
+
before_action :validate_saml_slo_request, :only => [:logout]
|
81
|
+
<% end %>
|
77
82
|
|
78
83
|
public
|
79
84
|
|
@@ -139,7 +144,7 @@ class SamlIdpController < SamlIdp::IdpController
|
|
139
144
|
|
140
145
|
def encode_SAML_SLO_Response(nameID, opts = {})
|
141
146
|
now = Time.now.utc
|
142
|
-
response_id =
|
147
|
+
response_id = SecureRandom.uuid
|
143
148
|
audience_uri = opts[:audience_uri] || (@saml_slo_acs_url && @saml_slo_acs_url[/^(.*?\/\/.*?\/)/, 1])
|
144
149
|
issuer_uri = opts[:issuer_uri] || (defined?(request) && request.url.split("?")[0]) || "http://example.com"
|
145
150
|
|
@@ -175,7 +180,7 @@ class SamlIdpController < SamlIdp::IdpController
|
|
175
180
|
|
176
181
|
def encode_SAML_SLO_Request(nameID, opts = {})
|
177
182
|
now = Time.now.utc
|
178
|
-
response_id =
|
183
|
+
response_id = SecureRandom.uuid
|
179
184
|
issuer_uri = opts[:issuer_uri] || (defined?(request) && request.url.split("?")[0]) || "http://example.com"
|
180
185
|
xml = %[<samlp:LogoutRequest
|
181
186
|
xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
|
data/spec/support/sp_template.rb
CHANGED
@@ -8,6 +8,8 @@ idp_settings_adapter = ENV.fetch('IDP_SETTINGS_ADAPTER', "nil")
|
|
8
8
|
idp_entity_id_reader = ENV.fetch('IDP_ENTITY_ID_READER', "DeviseSamlAuthenticatable::DefaultIdpEntityIdReader")
|
9
9
|
saml_failed_callback = ENV.fetch('SAML_FAILED_CALLBACK', "nil")
|
10
10
|
|
11
|
+
gsub_file 'config/secrets.yml', /secret_key_base:.*$/, 'secret_key_base: "8b5889df1fcf03f76c7d66da02d8776bcc85b06bed7d9c592f076d9c8a5455ee6d4beae45986c3c030b40208db5e612f2a6ef8283036a352e3fae83c5eda36be"'
|
12
|
+
|
11
13
|
gem 'devise_saml_authenticatable', path: '../../..'
|
12
14
|
gem 'ruby-saml', OneLogin::RubySaml::VERSION
|
13
15
|
gem 'thin'
|
@@ -17,6 +19,7 @@ insert_into_file('Gemfile', after: /\z/) {
|
|
17
19
|
# Lock down versions of gems for older versions of Ruby
|
18
20
|
if Gem::Version.new(RUBY_VERSION.dup) < Gem::Version.new("2.1")
|
19
21
|
gem 'devise', '~> 3.5'
|
22
|
+
gem 'nokogiri', '~> 1.6.8'
|
20
23
|
end
|
21
24
|
GEMFILE
|
22
25
|
}
|
@@ -75,6 +78,8 @@ after_bundle do
|
|
75
78
|
# Configure for our SAML IdP
|
76
79
|
generate 'devise:install'
|
77
80
|
gsub_file 'config/initializers/devise.rb', /^end$/, <<-CONFIG
|
81
|
+
config.secret_key = 'adc7cd73792f5d20055a0ac749ce8cdddb2e0f0d3ea7fe7855eec3d0f81833b9a4ac31d12e05f232d40ae86ca492826a6fc5a65228c6e16752815316e2d5b38d'
|
82
|
+
|
78
83
|
config.saml_default_user_key = :email
|
79
84
|
config.saml_session_index_key = #{saml_session_index_key}
|
80
85
|
|
@@ -103,13 +108,15 @@ class UsersController < ApplicationController
|
|
103
108
|
skip_before_action :verify_authenticity_token
|
104
109
|
def create
|
105
110
|
User.create!(email: params[:email])
|
106
|
-
|
111
|
+
head 201
|
107
112
|
end
|
108
113
|
end
|
109
114
|
USERS
|
110
115
|
|
111
116
|
rake "db:create"
|
112
117
|
rake "db:migrate"
|
118
|
+
rake "db:create", env: "production"
|
119
|
+
rake "db:migrate", env: "production"
|
113
120
|
end
|
114
121
|
|
115
122
|
create_file 'public/stylesheets/application.css', ''
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: devise_saml_authenticatable
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.3.
|
4
|
+
version: 1.3.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Josef Sauter
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2017-05-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: devise
|
@@ -61,6 +61,8 @@ files:
|
|
61
61
|
- lib/devise_saml_authenticatable/model.rb
|
62
62
|
- lib/devise_saml_authenticatable/routes.rb
|
63
63
|
- lib/devise_saml_authenticatable/saml_config.rb
|
64
|
+
- lib/devise_saml_authenticatable/saml_mapped_attributes.rb
|
65
|
+
- lib/devise_saml_authenticatable/saml_response.rb
|
64
66
|
- lib/devise_saml_authenticatable/strategy.rb
|
65
67
|
- lib/devise_saml_authenticatable/version.rb
|
66
68
|
- rails/init.rb
|
@@ -73,6 +75,7 @@ files:
|
|
73
75
|
- spec/rails_helper.rb
|
74
76
|
- spec/spec_helper.rb
|
75
77
|
- spec/support/Gemfile.rails4
|
78
|
+
- spec/support/Gemfile.rails5
|
76
79
|
- spec/support/Gemfile.ruby-saml-1.3
|
77
80
|
- spec/support/idp_settings_adapter.rb.erb
|
78
81
|
- spec/support/idp_template.rb
|
@@ -114,6 +117,7 @@ test_files:
|
|
114
117
|
- spec/rails_helper.rb
|
115
118
|
- spec/spec_helper.rb
|
116
119
|
- spec/support/Gemfile.rails4
|
120
|
+
- spec/support/Gemfile.rails5
|
117
121
|
- spec/support/Gemfile.ruby-saml-1.3
|
118
122
|
- spec/support/idp_settings_adapter.rb.erb
|
119
123
|
- spec/support/idp_template.rb
|