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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: f00284ea5fdac55a5ea5cd48980d48e31534f07d
4
- data.tar.gz: 2b0e57bbc925087f1bc0f2e50d95123b10767536
3
+ metadata.gz: 27f7eb61023b2810a4d90cad719720226d018107
4
+ data.tar.gz: 2e44a8f6da13660d0a32ddf08b1418f7c4122f1f
5
5
  SHA512:
6
- metadata.gz: e9b49c5336256abad932cd797e884a221424574eb4bdeda60fabef9d9fd97cfde9b4910feb823bfd3f2a89b95cc05a3bc6f8eb19591e32f1b155dfac0c0a474b
7
- data.tar.gz: 4cc832476d2f55e103cf138770f14175d9a193b06e9e58816f3ca5ea31e001acb02dfcc5246df07aad291efb67cf8b910cce601666ed258b353b523ba5f1c32b
6
+ metadata.gz: fa98d4ddc896effb77f572ab5222f4bb1db9f1c1dc03891a718db4a18b1144a25130ac9c1e1aee6772137b98fe117c3e38a71065f417743c5a050109f823f0c0
7
+ data.tar.gz: 692fb62fb8a14d99b700c9152ba02a7dcc17d8157c0f06e1b36565c41f69d265df87ff3603a302026e340755e9886a1358cb9e55acdbe87bab83682c09aa59b1
@@ -1,6 +1,16 @@
1
- <a name="v1.7.0"></a>
2
- ### v1.7.0 (2016-09-18)
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 1.9.x or Ruby 2.1.x+
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
 
@@ -1,5 +1,5 @@
1
1
  module OmniAuth
2
2
  module SAML
3
- VERSION = '1.7.0'
3
+ VERSION = '1.8.0'
4
4
  end
5
5
  end
@@ -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
- redirect(authn_request.create(settings, additional_params))
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
- # Call a fingerprint validation method if there's one
54
- if options.idp_cert_fingerprint_validator
55
- fingerprint_exists = options.idp_cert_fingerprint_validator[response_fingerprint]
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
- # symbolize keys without activeSupport/symbolize_keys (ruby-saml use symbols)
69
- opts =
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
- # omniauth does not set the strategy on the other_phase
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
- if request.params["SAMLResponse"]
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
- if options.idp_slo_target_url
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 { @name_id }
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"] = options.idp_cert_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.clear
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] = lambda { |fingerprint| "C1:59:74:2B:E8:0C:6C:A9:41:0F:6E:83:F6:D1:52:25:45:58:89:FB" }
129
- post_xml
128
+ saml_options[:idp_cert_fingerprint_validator] = fingerprint_validator
130
129
  end
131
130
 
132
- it "should set the uid to the nameID in the SAML response" do
133
- expect(auth_hash['uid']).to eq '_1f6fcf6be5e13b08b1e3610e7ff59f205fbd814f23'
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
- it "should set the raw info to all attributes" do
137
- expect(auth_hash['extra']['raw_info'].all.to_hash).to eq(
138
- 'first_name' => ['Rajiv'],
139
- 'last_name' => ['Manglani'],
140
- 'email' => ['user@example.com'],
141
- 'company_name' => ['Example Company'],
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 { should fail_with(:invalid_ticket) }
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 { should fail_with(:invalid_ticket) }
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 { should fail_with(:invalid_ticket) }
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 { should fail_with(:invalid_ticket) }
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 { should fail_with(:invalid_ticket) }
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
- post "/auth/saml/slo", {
289
+ end
290
+
291
+ let(:params) do
292
+ {
229
293
  "SAMLRequest" => load_xml(:example_logout_request),
230
294
  "RelayState" => "https://example.com/",
231
- }, "rack.session" => {"saml_uid" => "username@example.com"}
295
+ }
232
296
  end
233
297
 
234
- it "should redirect to logout response" do
235
- expect(last_response).to be_redirect
236
- expect(last_response.location).to match /https:\/\/idp.sso.example.com\/signoff\/29490/
237
- expect(last_response.location).to match /RelayState=https%3A%2F%2Fexample.com%2F/
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)
@@ -1,14 +1,12 @@
1
- if RUBY_VERSION >= '1.9'
2
- require 'simplecov'
1
+ require 'simplecov'
3
2
 
4
- if ENV['TRAVIS']
5
- require 'coveralls'
6
- Coveralls.wear!
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.7.0
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: 2016-10-19 00:00:00.000000000 Z
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: '0'
162
+ version: '2.1'
157
163
  required_rubygems_version: !ruby/object:Gem::Requirement
158
164
  requirements:
159
165
  - - ">="