omniauth-saml 1.7.0 → 1.8.0
Sign up to get free protection for your applications and to get access to all the features.
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
|
- - ">="
|