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 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
  - - ">="