omniauth-saml 1.7.0 → 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of omniauth-saml might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +12 -3
- data/README.md +59 -6
- data/lib/omniauth-saml/version.rb +1 -1
- data/lib/omniauth/strategies/saml.rb +114 -68
- data/spec/omniauth/strategies/saml_spec.rb +125 -23
- data/spec/spec_helper.rb +6 -8
- metadata +9 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 27f7eb61023b2810a4d90cad719720226d018107
|
4
|
+
data.tar.gz: 2e44a8f6da13660d0a32ddf08b1418f7c4122f1f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fa98d4ddc896effb77f572ab5222f4bb1db9f1c1dc03891a718db4a18b1144a25130ac9c1e1aee6772137b98fe117c3e38a71065f417743c5a050109f823f0c0
|
7
|
+
data.tar.gz: 692fb62fb8a14d99b700c9152ba02a7dcc17d8157c0f06e1b36565c41f69d265df87ff3603a302026e340755e9886a1358cb9e55acdbe87bab83682c09aa59b1
|
data/CHANGELOG.md
CHANGED
@@ -1,6 +1,16 @@
|
|
1
|
-
<a name="v1.
|
2
|
-
### v1.
|
1
|
+
<a name="v1.8.0"></a>
|
2
|
+
### v1.8.0 (2017-06-07)
|
3
|
+
|
4
|
+
|
5
|
+
#### Features
|
3
6
|
|
7
|
+
* include SessionIndex in logout requests ([fb6ad86](/../../commit/fb6ad86))
|
8
|
+
* Support for configurable IdP SLO session destruction ([586bf89](/../../commit/586bf89))
|
9
|
+
* Add `uid_attribute` option to control the attribute used for the user id. ([eacc536](/../../commit/eacc536))
|
10
|
+
|
11
|
+
|
12
|
+
<a name="v1.7.0"></a>
|
13
|
+
### v1.7.0 (2016-10-19)
|
4
14
|
|
5
15
|
#### Features
|
6
16
|
|
@@ -12,7 +22,6 @@
|
|
12
22
|
|
13
23
|
* Update `ruby-saml` to 1.4.0 to address security fixes. ([638212](/../../commit/638212))
|
14
24
|
|
15
|
-
|
16
25
|
<a name="v1.6.0"></a>
|
17
26
|
### v1.6.0 (2016-06-27)
|
18
27
|
* Ensure that subclasses of `OmniAuth::Stategies::SAML` are registered with OmniAuth as strategies (https://github.com/omniauth/omniauth-saml/pull/95)
|
data/README.md
CHANGED
@@ -19,7 +19,7 @@ https://github.com/omniauth/omniauth-saml
|
|
19
19
|
## Requirements
|
20
20
|
|
21
21
|
* [OmniAuth](http://www.omniauth.org/) 1.3+
|
22
|
-
* Ruby
|
22
|
+
* Ruby 2.1.x+
|
23
23
|
|
24
24
|
## Versioning
|
25
25
|
|
@@ -70,10 +70,12 @@ For IdP-initiated SSO, users should directly access the IdP SSO target URL. Set
|
|
70
70
|
|
71
71
|
A `OneLogin::RubySaml::Response` object is added to the `env['omniauth.auth']` extra attribute, so we can use it in the controller via `env['omniauth.auth'].extra.response_object`
|
72
72
|
|
73
|
-
## Metadata
|
73
|
+
## SP Metadata
|
74
74
|
|
75
75
|
The service provider metadata used to ease configuration of the SAML SP in the IdP can be retrieved from `http://example.com/auth/saml/metadata`. Send this URL to the administrator of the IdP.
|
76
76
|
|
77
|
+
Note that when [integrating with Devise](#devise-integration), the URL path will be scoped according to the name of the Devise resource. For example, if the app's user model calls `devise_for :users`, the metadata URL will be `http://example.com/users/auth/saml/metadata`.
|
78
|
+
|
77
79
|
## Options
|
78
80
|
|
79
81
|
* `:assertion_consumer_service_url` - The URL at which the SAML assertion should be
|
@@ -89,6 +91,10 @@ The service provider metadata used to ease configuration of the SAML SP in the I
|
|
89
91
|
* `:idp_slo_target_url` - The URL to which the single logout request and response should
|
90
92
|
be sent. This would be on the identity provider. Optional.
|
91
93
|
|
94
|
+
* `:idp_slo_session_destroy` - A proc that accepts up to two parameters (the rack environment, and the session),
|
95
|
+
and performs whatever tasks are necessary to log out the current user from your application.
|
96
|
+
See the example listed under "Single Logout." Defaults to calling `#clear` on the session. Optional.
|
97
|
+
|
92
98
|
* `:slo_default_relay_state` - The value to use as default `RelayState` for single log outs. The
|
93
99
|
value can be a string, or a `Proc` (or other object responding to `call`). The `request`
|
94
100
|
instance will be passed to this callable if it has an arity of 1. If the value is a string,
|
@@ -135,8 +141,31 @@ The service provider metadata used to ease configuration of the SAML SP in the I
|
|
135
141
|
*Note*: All attributes can also be found in an array under `auth_hash[:extra][:raw_info]`,
|
136
142
|
so this setting should only be used to map attributes that are part of the OmniAuth info hash schema.
|
137
143
|
|
144
|
+
* `:uid_attribute` - Attribute that uniquely identifies the user. If unset, the name identifier returned by the IdP is used.
|
145
|
+
|
138
146
|
* See the `OneLogin::RubySaml::Settings` class in the [Ruby SAML gem](https://github.com/onelogin/ruby-saml) for additional supported options.
|
139
147
|
|
148
|
+
## IdP Metadata
|
149
|
+
|
150
|
+
You can use the `OneLogin::RubySaml::IdpMetadataParser` to configure some options:
|
151
|
+
|
152
|
+
```ruby
|
153
|
+
require 'omniauth'
|
154
|
+
idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new
|
155
|
+
idp_metadata = idp_metadata_parser.parse_remote_to_hash("http://idp.example.com/saml/metadata")
|
156
|
+
|
157
|
+
# or, if you have the metadata in a String:
|
158
|
+
# idp_metadata = idp_metadata_parser.parse_to_hash(idp_metadata_xml)
|
159
|
+
|
160
|
+
use OmniAuth::Strategies::SAML,
|
161
|
+
idp_metadata.merge(
|
162
|
+
:assertion_consumer_service_url => "consumer_service_url",
|
163
|
+
:issuer => "issuer"
|
164
|
+
)
|
165
|
+
```
|
166
|
+
|
167
|
+
See the [Ruby SAML gem's README](https://github.com/onelogin/ruby-saml#metadata-based-configuration) for more details.
|
168
|
+
|
140
169
|
## Devise Integration
|
141
170
|
|
142
171
|
Straightforward integration with [Devise](https://github.com/plataformatec/devise), the widely-used authentication solution for Rails.
|
@@ -156,6 +185,12 @@ Then follow Devise's general [OmniAuth tutorial](https://github.com/plataformate
|
|
156
185
|
## Single Logout
|
157
186
|
|
158
187
|
Single Logout can be Service Provider initiated or Identity Provider initiated.
|
188
|
+
|
189
|
+
For SP initiated logout, the `idp_slo_target_url` option must be set to the logout url on the IdP,
|
190
|
+
and users directed to `user_saml_omniauth_authorize_path + '/spslo'` after logging out locally. For
|
191
|
+
IdP initiated logout, logout requests from the IdP should go to `/auth/saml/slo` (this can be
|
192
|
+
advertised in metadata by setting the `single_logout_service_url` config option).
|
193
|
+
|
159
194
|
When using Devise as an authentication solution, the SP initiated flow can be integrated
|
160
195
|
in the `SessionsController#destroy` action.
|
161
196
|
|
@@ -173,12 +208,30 @@ class SessionsController < Devise::SessionsController
|
|
173
208
|
saml_uid = session["saml_uid"]
|
174
209
|
super do
|
175
210
|
session["saml_uid"] = saml_uid
|
176
|
-
if SAML_SETTINGS.idp_slo_target_url
|
177
|
-
spslo_url = user_omniauth_authorize_url(:saml) + "/spslo"
|
178
|
-
redirect_to(spslo_url)
|
179
|
-
end
|
180
211
|
end
|
181
212
|
end
|
213
|
+
|
214
|
+
# ...
|
215
|
+
|
216
|
+
def after_sign_out_path_for(_)
|
217
|
+
if session['saml_uid'] && SAML_SETTINGS.idp_slo_target_url
|
218
|
+
user_saml_omniauth_authorize_path + "/spslo"
|
219
|
+
else
|
220
|
+
super
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|
224
|
+
```
|
225
|
+
|
226
|
+
By default, omniauth-saml attempts to log the current user out of your application by clearing the session.
|
227
|
+
This may not be enough for some authentication solutions (e.g. [Clearance](https://github.com/thoughtbot/clearance/)).
|
228
|
+
Instead, you may set the `:idp_slo_session_destroy` option to a proc that performs the necessary logout tasks.
|
229
|
+
|
230
|
+
Example `:idp_slo_session_destroy` setting for Clearance compatibility:
|
231
|
+
|
232
|
+
```ruby
|
233
|
+
Rails.application.config.middleware.use OmniAuth::Builder do
|
234
|
+
provider :saml, idp_slo_session_destroy: proc { |env, _session| env[:clearance].sign_out }, ...
|
182
235
|
end
|
183
236
|
```
|
184
237
|
|
@@ -28,54 +28,30 @@ module OmniAuth
|
|
28
28
|
last_name: ["last_name", "lastname", "lastName"]
|
29
29
|
}
|
30
30
|
option :slo_default_relay_state
|
31
|
+
option :uid_attribute
|
32
|
+
option :idp_slo_session_destroy, proc { |_env, session| session.clear }
|
31
33
|
|
32
34
|
def request_phase
|
33
35
|
options[:assertion_consumer_service_url] ||= callback_url
|
34
|
-
runtime_request_parameters = options.delete(:idp_sso_target_url_runtime_params)
|
35
|
-
|
36
|
-
additional_params = {}
|
37
|
-
|
38
|
-
if runtime_request_parameters
|
39
|
-
runtime_request_parameters.each_pair do |request_param_key, mapped_param_key|
|
40
|
-
additional_params[mapped_param_key] = request.params[request_param_key.to_s] if request.params.has_key?(request_param_key.to_s)
|
41
|
-
end
|
42
|
-
end
|
43
36
|
|
44
37
|
authn_request = OneLogin::RubySaml::Authrequest.new
|
45
|
-
settings = OneLogin::RubySaml::Settings.new(options)
|
46
38
|
|
47
|
-
|
39
|
+
with_settings do |settings|
|
40
|
+
redirect(authn_request.create(settings, additional_params_for_authn_request))
|
41
|
+
end
|
48
42
|
end
|
49
43
|
|
50
44
|
def callback_phase
|
51
45
|
raise OmniAuth::Strategies::SAML::ValidationError.new("SAML response missing") unless request.params["SAMLResponse"]
|
52
46
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
unless fingerprint_exists
|
57
|
-
raise OmniAuth::Strategies::SAML::ValidationError.new("Non-existent fingerprint")
|
58
|
-
end
|
59
|
-
# id_cert_fingerprint becomes the given fingerprint if it exists
|
60
|
-
options.idp_cert_fingerprint = fingerprint_exists
|
61
|
-
end
|
62
|
-
|
63
|
-
settings = OneLogin::RubySaml::Settings.new(options)
|
64
|
-
|
65
|
-
# filter options to select only extra parameters
|
66
|
-
opts = options.select {|k,_| OTHER_REQUEST_OPTIONS.include?(k.to_sym)}
|
47
|
+
with_settings do |settings|
|
48
|
+
# Call a fingerprint validation method if there's one
|
49
|
+
validate_fingerprint(settings) if options.idp_cert_fingerprint_validator
|
67
50
|
|
68
|
-
|
69
|
-
|
70
|
-
opts.inject({}) do |new_hash, (key, value)|
|
71
|
-
new_hash[key.to_sym] = value
|
72
|
-
new_hash
|
51
|
+
handle_response(request.params["SAMLResponse"], options_for_response_object, settings) do
|
52
|
+
super
|
73
53
|
end
|
74
|
-
|
75
|
-
handle_response(request.params["SAMLResponse"], opts, settings) do
|
76
|
-
super
|
77
54
|
end
|
78
|
-
|
79
55
|
rescue OmniAuth::Strategies::SAML::ValidationError
|
80
56
|
fail!(:invalid_ticket, $!)
|
81
57
|
rescue OneLogin::RubySaml::ValidationError
|
@@ -98,36 +74,13 @@ module OmniAuth
|
|
98
74
|
if current_path.start_with?(request_path)
|
99
75
|
@env['omniauth.strategy'] ||= self
|
100
76
|
setup_phase
|
101
|
-
settings = OneLogin::RubySaml::Settings.new(options)
|
102
77
|
|
103
78
|
if on_subpath?(:metadata)
|
104
|
-
|
105
|
-
response = OneLogin::RubySaml::Metadata.new
|
106
|
-
|
107
|
-
if options.request_attributes.length > 0
|
108
|
-
settings.attribute_consuming_service.service_name options.attribute_service_name
|
109
|
-
settings.issuer = options.issuer
|
110
|
-
|
111
|
-
options.request_attributes.each do |attribute|
|
112
|
-
settings.attribute_consuming_service.add_attribute attribute
|
113
|
-
end
|
114
|
-
end
|
115
|
-
|
116
|
-
Rack::Response.new(response.generate(settings), 200, { "Content-Type" => "application/xml" }).finish
|
79
|
+
other_phase_for_metadata
|
117
80
|
elsif on_subpath?(:slo)
|
118
|
-
|
119
|
-
handle_logout_response(request.params["SAMLResponse"], settings)
|
120
|
-
elsif request.params["SAMLRequest"]
|
121
|
-
handle_logout_request(request.params["SAMLRequest"], settings)
|
122
|
-
else
|
123
|
-
raise OmniAuth::Strategies::SAML::ValidationError.new("SAML logout response/request missing")
|
124
|
-
end
|
81
|
+
other_phase_for_slo
|
125
82
|
elsif on_subpath?(:spslo)
|
126
|
-
|
127
|
-
redirect(generate_logout_request(settings))
|
128
|
-
else
|
129
|
-
Rack::Response.new("Not Implemented", 501, { "Content-Type" => "text/html" }).finish
|
130
|
-
end
|
83
|
+
other_phase_for_spslo
|
131
84
|
else
|
132
85
|
call_app!
|
133
86
|
end
|
@@ -136,7 +89,17 @@ module OmniAuth
|
|
136
89
|
end
|
137
90
|
end
|
138
91
|
|
139
|
-
uid
|
92
|
+
uid do
|
93
|
+
if options.uid_attribute
|
94
|
+
ret = find_attribute_by([options.uid_attribute])
|
95
|
+
if ret.nil?
|
96
|
+
raise OmniAuth::Strategies::SAML::ValidationError.new("SAML response missing '#{options.uid_attribute}' attribute")
|
97
|
+
end
|
98
|
+
ret
|
99
|
+
else
|
100
|
+
@name_id
|
101
|
+
end
|
102
|
+
end
|
140
103
|
|
141
104
|
info do
|
142
105
|
found_attributes = options.attribute_statements.map do |key, values|
|
@@ -147,7 +110,7 @@ module OmniAuth
|
|
147
110
|
Hash[found_attributes]
|
148
111
|
end
|
149
112
|
|
150
|
-
extra { { :raw_info => @attributes, :response_object => @response_object } }
|
113
|
+
extra { { :raw_info => @attributes, :session_index => @session_index, :response_object => @response_object } }
|
151
114
|
|
152
115
|
def find_attribute_by(keys)
|
153
116
|
keys.each do |key|
|
@@ -165,19 +128,17 @@ module OmniAuth
|
|
165
128
|
|
166
129
|
def handle_response(raw_response, opts, settings)
|
167
130
|
response = OneLogin::RubySaml::Response.new(raw_response, opts.merge(settings: settings))
|
168
|
-
response.attributes["fingerprint"] =
|
131
|
+
response.attributes["fingerprint"] = settings.idp_cert_fingerprint
|
169
132
|
response.soft = false
|
170
133
|
|
171
134
|
response.is_valid?
|
172
135
|
@name_id = response.name_id
|
136
|
+
@session_index = response.sessionindex
|
173
137
|
@attributes = response.attributes
|
174
138
|
@response_object = response
|
175
139
|
|
176
|
-
if @name_id.nil? || @name_id.empty?
|
177
|
-
raise OmniAuth::Strategies::SAML::ValidationError.new("SAML response missing 'name_id'")
|
178
|
-
end
|
179
|
-
|
180
140
|
session["saml_uid"] = @name_id
|
141
|
+
session["saml_session_index"] = @session_index
|
181
142
|
yield
|
182
143
|
end
|
183
144
|
|
@@ -208,6 +169,7 @@ module OmniAuth
|
|
208
169
|
|
209
170
|
session.delete("saml_uid")
|
210
171
|
session.delete("saml_transaction_id")
|
172
|
+
session.delete("saml_session_index")
|
211
173
|
|
212
174
|
redirect(slo_relay_state)
|
213
175
|
end
|
@@ -219,7 +181,7 @@ module OmniAuth
|
|
219
181
|
logout_request.name_id == session["saml_uid"]
|
220
182
|
|
221
183
|
# Actually log out this session
|
222
|
-
session
|
184
|
+
options[:idp_slo_session_destroy].call @env, session
|
223
185
|
|
224
186
|
# Generate a response to the IdP.
|
225
187
|
logout_request_id = logout_request.id
|
@@ -242,8 +204,92 @@ module OmniAuth
|
|
242
204
|
settings.name_identifier_value = session["saml_uid"]
|
243
205
|
end
|
244
206
|
|
207
|
+
if settings.sessionindex.nil?
|
208
|
+
settings.sessionindex = session["saml_session_index"]
|
209
|
+
end
|
210
|
+
|
245
211
|
logout_request.create(settings, RelayState: slo_relay_state)
|
246
212
|
end
|
213
|
+
|
214
|
+
def with_settings
|
215
|
+
yield OneLogin::RubySaml::Settings.new(options)
|
216
|
+
end
|
217
|
+
|
218
|
+
def validate_fingerprint(settings)
|
219
|
+
fingerprint_exists = options.idp_cert_fingerprint_validator[response_fingerprint]
|
220
|
+
|
221
|
+
unless fingerprint_exists
|
222
|
+
raise OmniAuth::Strategies::SAML::ValidationError.new("Non-existent fingerprint")
|
223
|
+
end
|
224
|
+
|
225
|
+
# id_cert_fingerprint becomes the given fingerprint if it exists
|
226
|
+
settings.idp_cert_fingerprint = fingerprint_exists
|
227
|
+
end
|
228
|
+
|
229
|
+
def options_for_response_object
|
230
|
+
# filter options to select only extra parameters
|
231
|
+
opts = options.select {|k,_| OTHER_REQUEST_OPTIONS.include?(k.to_sym)}
|
232
|
+
|
233
|
+
# symbolize keys without activeSupport/symbolize_keys (ruby-saml use symbols)
|
234
|
+
opts.inject({}) do |new_hash, (key, value)|
|
235
|
+
new_hash[key.to_sym] = value
|
236
|
+
new_hash
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
def other_phase_for_metadata
|
241
|
+
with_settings do |settings|
|
242
|
+
# omniauth does not set the strategy on the other_phase
|
243
|
+
response = OneLogin::RubySaml::Metadata.new
|
244
|
+
|
245
|
+
add_request_attributes_to(settings) if options.request_attributes.length > 0
|
246
|
+
|
247
|
+
Rack::Response.new(response.generate(settings), 200, { "Content-Type" => "application/xml" }).finish
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
def other_phase_for_slo
|
252
|
+
with_settings do |settings|
|
253
|
+
if request.params["SAMLResponse"]
|
254
|
+
handle_logout_response(request.params["SAMLResponse"], settings)
|
255
|
+
elsif request.params["SAMLRequest"]
|
256
|
+
handle_logout_request(request.params["SAMLRequest"], settings)
|
257
|
+
else
|
258
|
+
raise OmniAuth::Strategies::SAML::ValidationError.new("SAML logout response/request missing")
|
259
|
+
end
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
def other_phase_for_spslo
|
264
|
+
if options.idp_slo_target_url
|
265
|
+
with_settings do |settings|
|
266
|
+
redirect(generate_logout_request(settings))
|
267
|
+
end
|
268
|
+
else
|
269
|
+
Rack::Response.new("Not Implemented", 501, { "Content-Type" => "text/html" }).finish
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
def add_request_attributes_to(settings)
|
274
|
+
settings.attribute_consuming_service.service_name options.attribute_service_name
|
275
|
+
settings.issuer = options.issuer
|
276
|
+
|
277
|
+
options.request_attributes.each do |attribute|
|
278
|
+
settings.attribute_consuming_service.add_attribute attribute
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
def additional_params_for_authn_request
|
283
|
+
{}.tap do |additional_params|
|
284
|
+
runtime_request_parameters = options.delete(:idp_sso_target_url_runtime_params)
|
285
|
+
|
286
|
+
if runtime_request_parameters
|
287
|
+
runtime_request_parameters.each_pair do |request_param_key, mapped_param_key|
|
288
|
+
additional_params[mapped_param_key] = request.params[request_param_key.to_s] if request.params.has_key?(request_param_key.to_s)
|
289
|
+
end
|
290
|
+
end
|
291
|
+
end
|
292
|
+
end
|
247
293
|
end
|
248
294
|
end
|
249
295
|
end
|
@@ -125,22 +125,35 @@ describe OmniAuth::Strategies::SAML, :type => :strategy do
|
|
125
125
|
context "when fingerprint is empty and there's a fingerprint validator" do
|
126
126
|
before :each do
|
127
127
|
saml_options.delete(:idp_cert_fingerprint)
|
128
|
-
saml_options[:idp_cert_fingerprint_validator] =
|
129
|
-
post_xml
|
128
|
+
saml_options[:idp_cert_fingerprint_validator] = fingerprint_validator
|
130
129
|
end
|
131
130
|
|
132
|
-
|
133
|
-
|
131
|
+
let(:fingerprint_validator) { lambda { |_| "C1:59:74:2B:E8:0C:6C:A9:41:0F:6E:83:F6:D1:52:25:45:58:89:FB" } }
|
132
|
+
|
133
|
+
context "when the fingerprint validator returns a truthy value" do
|
134
|
+
before { post_xml }
|
135
|
+
|
136
|
+
it "should set the uid to the nameID in the SAML response" do
|
137
|
+
expect(auth_hash['uid']).to eq '_1f6fcf6be5e13b08b1e3610e7ff59f205fbd814f23'
|
138
|
+
end
|
139
|
+
|
140
|
+
it "should set the raw info to all attributes" do
|
141
|
+
expect(auth_hash['extra']['raw_info'].all.to_hash).to eq(
|
142
|
+
'first_name' => ['Rajiv'],
|
143
|
+
'last_name' => ['Manglani'],
|
144
|
+
'email' => ['user@example.com'],
|
145
|
+
'company_name' => ['Example Company'],
|
146
|
+
'fingerprint' => 'C1:59:74:2B:E8:0C:6C:A9:41:0F:6E:83:F6:D1:52:25:45:58:89:FB'
|
147
|
+
)
|
148
|
+
end
|
134
149
|
end
|
135
150
|
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
'fingerprint' => 'C1:59:74:2B:E8:0C:6C:A9:41:0F:6E:83:F6:D1:52:25:45:58:89:FB'
|
143
|
-
)
|
151
|
+
context "when the fingerprint validator returns false" do
|
152
|
+
let(:fingerprint_validator) { lambda { |_| false } }
|
153
|
+
|
154
|
+
before { post_xml }
|
155
|
+
|
156
|
+
it { is_expected.to fail_with(:invalid_ticket) }
|
144
157
|
end
|
145
158
|
end
|
146
159
|
|
@@ -149,7 +162,7 @@ describe OmniAuth::Strategies::SAML, :type => :strategy do
|
|
149
162
|
post '/auth/saml/callback'
|
150
163
|
end
|
151
164
|
|
152
|
-
it {
|
165
|
+
it { is_expected.to fail_with(:invalid_ticket) }
|
153
166
|
end
|
154
167
|
|
155
168
|
context "when there is no name id in the XML" do
|
@@ -158,7 +171,7 @@ describe OmniAuth::Strategies::SAML, :type => :strategy do
|
|
158
171
|
post_xml :no_name_id
|
159
172
|
end
|
160
173
|
|
161
|
-
it {
|
174
|
+
it { is_expected.to fail_with(:invalid_ticket) }
|
162
175
|
end
|
163
176
|
|
164
177
|
context "when the fingerprint is invalid" do
|
@@ -167,7 +180,7 @@ describe OmniAuth::Strategies::SAML, :type => :strategy do
|
|
167
180
|
post_xml
|
168
181
|
end
|
169
182
|
|
170
|
-
it {
|
183
|
+
it { is_expected.to fail_with(:invalid_ticket) }
|
171
184
|
end
|
172
185
|
|
173
186
|
context "when the digest is invalid" do
|
@@ -175,7 +188,7 @@ describe OmniAuth::Strategies::SAML, :type => :strategy do
|
|
175
188
|
post_xml :digest_mismatch
|
176
189
|
end
|
177
190
|
|
178
|
-
it {
|
191
|
+
it { is_expected.to fail_with(:invalid_ticket) }
|
179
192
|
end
|
180
193
|
|
181
194
|
context "when the signature is invalid" do
|
@@ -183,7 +196,28 @@ describe OmniAuth::Strategies::SAML, :type => :strategy do
|
|
183
196
|
post_xml :invalid_signature
|
184
197
|
end
|
185
198
|
|
186
|
-
it {
|
199
|
+
it { is_expected.to fail_with(:invalid_ticket) }
|
200
|
+
end
|
201
|
+
|
202
|
+
context "when the response is stale" do
|
203
|
+
before :each do
|
204
|
+
allow(Time).to receive(:now).and_return(Time.utc(2012, 11, 8, 20, 45, 00))
|
205
|
+
end
|
206
|
+
|
207
|
+
context "without :allowed_clock_drift option" do
|
208
|
+
before { post_xml :example_response }
|
209
|
+
|
210
|
+
it { is_expected.to fail_with(:invalid_ticket) }
|
211
|
+
end
|
212
|
+
|
213
|
+
context "with :allowed_clock_drift option" do
|
214
|
+
before :each do
|
215
|
+
saml_options[:allowed_clock_drift] = 60
|
216
|
+
post_xml :example_response
|
217
|
+
end
|
218
|
+
|
219
|
+
it { is_expected.to_not fail_with(:invalid_ticket) }
|
220
|
+
end
|
187
221
|
end
|
188
222
|
|
189
223
|
context "when response has custom attributes" do
|
@@ -207,6 +241,31 @@ describe OmniAuth::Strategies::SAML, :type => :strategy do
|
|
207
241
|
end
|
208
242
|
end
|
209
243
|
|
244
|
+
context "when using custom user id attribute" do
|
245
|
+
before :each do
|
246
|
+
saml_options[:idp_cert_fingerprint] = "3B:82:F1:F5:54:FC:A8:FF:12:B8:4B:B8:16:61:1D:E4:8E:9B:E2:3C"
|
247
|
+
saml_options[:uid_attribute] = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"
|
248
|
+
post_xml :custom_attributes
|
249
|
+
end
|
250
|
+
|
251
|
+
it "should return user id attribute" do
|
252
|
+
expect(auth_hash[:uid]).to eq("user@example.com")
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
context "when using custom user id attribute, but it is missing" do
|
257
|
+
before :each do
|
258
|
+
saml_options[:uid_attribute] = "missing_attribute"
|
259
|
+
post_xml
|
260
|
+
end
|
261
|
+
|
262
|
+
it "should fail to authenticate" do
|
263
|
+
should fail_with(:invalid_ticket)
|
264
|
+
expect(last_request.env['omniauth.error']).to be_instance_of(OmniAuth::Strategies::SAML::ValidationError)
|
265
|
+
expect(last_request.env['omniauth.error'].message).to eq("SAML response missing 'missing_attribute' attribute")
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
210
269
|
context "when response is a logout response" do
|
211
270
|
before :each do
|
212
271
|
saml_options[:issuer] = "https://idp.sso.example.com/metadata/29490"
|
@@ -223,18 +282,49 @@ describe OmniAuth::Strategies::SAML, :type => :strategy do
|
|
223
282
|
end
|
224
283
|
|
225
284
|
context "when request is a logout request" do
|
285
|
+
subject { post "/auth/saml/slo", params, "rack.session" => { "saml_uid" => "username@example.com" } }
|
286
|
+
|
226
287
|
before :each do
|
227
288
|
saml_options[:issuer] = "https://idp.sso.example.com/metadata/29490"
|
228
|
-
|
289
|
+
end
|
290
|
+
|
291
|
+
let(:params) do
|
292
|
+
{
|
229
293
|
"SAMLRequest" => load_xml(:example_logout_request),
|
230
294
|
"RelayState" => "https://example.com/",
|
231
|
-
}
|
295
|
+
}
|
232
296
|
end
|
233
297
|
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
298
|
+
context "when logout request is valid" do
|
299
|
+
before { subject }
|
300
|
+
|
301
|
+
it "should redirect to logout response" do
|
302
|
+
expect(last_response).to be_redirect
|
303
|
+
expect(last_response.location).to match /https:\/\/idp.sso.example.com\/signoff\/29490/
|
304
|
+
expect(last_response.location).to match /RelayState=https%3A%2F%2Fexample.com%2F/
|
305
|
+
end
|
306
|
+
end
|
307
|
+
|
308
|
+
context "when request is an invalid logout request" do
|
309
|
+
before :each do
|
310
|
+
allow_any_instance_of(OneLogin::RubySaml::SloLogoutrequest).to receive(:is_valid?).and_return(false)
|
311
|
+
end
|
312
|
+
|
313
|
+
# TODO: Maybe this should not raise an exception, but return some 4xx error instead?
|
314
|
+
it "should raise an exception" do
|
315
|
+
expect { subject }.
|
316
|
+
to raise_error(OmniAuth::Strategies::SAML::ValidationError, 'SAML failed to process LogoutRequest')
|
317
|
+
end
|
318
|
+
end
|
319
|
+
|
320
|
+
context "when request is a logout request but the request param is missing" do
|
321
|
+
let(:params) { {} }
|
322
|
+
|
323
|
+
# TODO: Maybe this should not raise an exception, but return a 422 error instead?
|
324
|
+
it 'should raise an exception' do
|
325
|
+
expect { subject }.
|
326
|
+
to raise_error(OmniAuth::Strategies::SAML::ValidationError, 'SAML logout response/request missing')
|
327
|
+
end
|
238
328
|
end
|
239
329
|
end
|
240
330
|
|
@@ -295,6 +385,18 @@ describe OmniAuth::Strategies::SAML, :type => :strategy do
|
|
295
385
|
end
|
296
386
|
end
|
297
387
|
|
388
|
+
context 'when hitting an unknown route in our sub path' do
|
389
|
+
before { get '/auth/saml/unknown' }
|
390
|
+
|
391
|
+
specify { expect(last_response.status).to eql 404 }
|
392
|
+
end
|
393
|
+
|
394
|
+
context 'when hitting a completely unknown route' do
|
395
|
+
before { get '/unknown' }
|
396
|
+
|
397
|
+
specify { expect(last_response.status).to eql 404 }
|
398
|
+
end
|
399
|
+
|
298
400
|
describe 'subclass behavior' do
|
299
401
|
it 'registers subclasses in OmniAuth.strategies' do
|
300
402
|
subclass = Class.new(described_class)
|
data/spec/spec_helper.rb
CHANGED
@@ -1,14 +1,12 @@
|
|
1
|
-
|
2
|
-
require 'simplecov'
|
1
|
+
require 'simplecov'
|
3
2
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
end
|
8
|
-
|
9
|
-
SimpleCov.start
|
3
|
+
if ENV['TRAVIS']
|
4
|
+
require 'coveralls'
|
5
|
+
Coveralls.wear!
|
10
6
|
end
|
11
7
|
|
8
|
+
SimpleCov.start
|
9
|
+
|
12
10
|
require 'omniauth-saml'
|
13
11
|
require 'rack/test'
|
14
12
|
require 'rexml/document'
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: omniauth-saml
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.8.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Raecoo Cao
|
@@ -14,7 +14,7 @@ authors:
|
|
14
14
|
autorequire:
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
|
-
date:
|
17
|
+
date: 2017-06-07 00:00:00.000000000 Z
|
18
18
|
dependencies:
|
19
19
|
- !ruby/object:Gem::Dependency
|
20
20
|
name: omniauth
|
@@ -37,6 +37,9 @@ dependencies:
|
|
37
37
|
- - "~>"
|
38
38
|
- !ruby/object:Gem::Version
|
39
39
|
version: '1.4'
|
40
|
+
- - ">="
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: 1.4.3
|
40
43
|
type: :runtime
|
41
44
|
prerelease: false
|
42
45
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -44,6 +47,9 @@ dependencies:
|
|
44
47
|
- - "~>"
|
45
48
|
- !ruby/object:Gem::Version
|
46
49
|
version: '1.4'
|
50
|
+
- - ">="
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: 1.4.3
|
47
53
|
- !ruby/object:Gem::Dependency
|
48
54
|
name: rake
|
49
55
|
requirement: !ruby/object:Gem::Requirement
|
@@ -153,7 +159,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
153
159
|
requirements:
|
154
160
|
- - ">="
|
155
161
|
- !ruby/object:Gem::Version
|
156
|
-
version: '
|
162
|
+
version: '2.1'
|
157
163
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
158
164
|
requirements:
|
159
165
|
- - ">="
|