devise_saml_authenticatable 1.3.1 → 1.3.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|