omniauth-saml 1.6.0 → 1.7.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 +38 -16
- data/README.md +39 -0
- data/lib/omniauth-saml/version.rb +1 -1
- data/lib/omniauth/strategies/saml.rb +133 -33
- data/spec/omniauth/strategies/saml_spec.rb +107 -35
- data/spec/spec_helper.rb +4 -0
- metadata +38 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f00284ea5fdac55a5ea5cd48980d48e31534f07d
|
4
|
+
data.tar.gz: 2b0e57bbc925087f1bc0f2e50d95123b10767536
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e9b49c5336256abad932cd797e884a221424574eb4bdeda60fabef9d9fd97cfde9b4910feb823bfd3f2a89b95cc05a3bc6f8eb19591e32f1b155dfac0c0a474b
|
7
|
+
data.tar.gz: 4cc832476d2f55e103cf138770f14175d9a193b06e9e58816f3ca5ea31e001acb02dfcc5246df07aad291efb67cf8b910cce601666ed258b353b523ba5f1c32b
|
data/CHANGELOG.md
CHANGED
@@ -1,14 +1,25 @@
|
|
1
|
-
|
1
|
+
<a name="v1.7.0"></a>
|
2
|
+
### v1.7.0 (2016-09-18)
|
2
3
|
|
3
|
-
A generic SAML strategy for OmniAuth.
|
4
4
|
|
5
|
-
|
5
|
+
#### Features
|
6
6
|
|
7
|
-
|
7
|
+
* Support for Single Logout ([cd3fc43](/../../commit/cd3fc43))
|
8
|
+
* Add issuer information to the metadata endpoint, to allow IdPs to properly configure themselves. ([7bbbb67](/../../commit/7bbbb67))
|
9
|
+
* Added the response object to the extra['response_object'], so we can use the raw response object if we want to. ([76ed3d6](/../../commit/76ed3d6))
|
10
|
+
|
11
|
+
#### Chores
|
12
|
+
|
13
|
+
* Update `ruby-saml` to 1.4.0 to address security fixes. ([638212](/../../commit/638212))
|
14
|
+
|
15
|
+
|
16
|
+
<a name="v1.6.0"></a>
|
17
|
+
### v1.6.0 (2016-06-27)
|
8
18
|
* Ensure that subclasses of `OmniAuth::Stategies::SAML` are registered with OmniAuth as strategies (https://github.com/omniauth/omniauth-saml/pull/95)
|
9
19
|
* Update ruby-saml to 1.3 to address [CVE-2016-5697](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2016-5697) (Signature wrapping attacks)
|
10
20
|
|
11
|
-
|
21
|
+
<a name="v1.5.0"></a>
|
22
|
+
### v1.5.0 (2016-02-25)
|
12
23
|
|
13
24
|
* Initialize OneLogin::RubySaml::Response instance with settings
|
14
25
|
* Adding "settings" to Response Class at initialization to handle signing verification
|
@@ -18,56 +29,67 @@ https://github.com/omniauth/omniauth-saml
|
|
18
29
|
* Call validation earlier to get real error instead of 'response missing name_id'
|
19
30
|
* Avoid mutation of the options hash during requests and callbacks
|
20
31
|
|
21
|
-
|
32
|
+
<a name="v1.4.2"></a>
|
33
|
+
### v1.4.2 (2016-02-09)
|
22
34
|
|
23
35
|
* update ruby-saml to 1.1
|
24
36
|
|
25
|
-
|
37
|
+
<a name="v1.4.1"></a>
|
38
|
+
### v1.4.1 (2015-08-09)
|
26
39
|
|
27
40
|
* Configurable attribute_consuming_service
|
28
41
|
|
29
|
-
|
42
|
+
<a name="v1.4.0"></a>
|
43
|
+
### v1.4.0 (2015-07-23)
|
30
44
|
|
31
45
|
* update ruby-saml to 1.0.0
|
32
46
|
|
33
|
-
|
47
|
+
<a name="v1.3.1"></a>
|
48
|
+
### v1.3.1 (2015-02-26)
|
34
49
|
|
35
50
|
* Added missing fingerprint key check
|
36
51
|
* Expose fingerprint on the auth_hash
|
37
52
|
|
38
|
-
|
53
|
+
<a name="v1.3.0"></a>
|
54
|
+
### v1.3.0 (2015-01-23)
|
39
55
|
|
40
56
|
* add `idp_cert_fingerprint_validator` option
|
41
57
|
|
42
|
-
|
58
|
+
<a name="v1.2.0"></a>
|
59
|
+
### v1.2.0 (2014-03-19)
|
43
60
|
|
44
61
|
* provide SP metadata at `/auth/saml/metadata`
|
45
62
|
|
46
|
-
|
63
|
+
<a name="v1.1.0"></a>
|
64
|
+
### v1.1.0 (2013-11-07)
|
47
65
|
|
48
66
|
* no longer set a default `name_identifier_format`
|
49
67
|
* pass strategy options to the underlying ruby-saml library
|
50
68
|
* fallback to omniauth callback url if `assertion_consumer_service_url` is not set
|
51
69
|
* add `idp_sso_target_url_runtime_params` option
|
52
70
|
|
53
|
-
|
71
|
+
<a name="v1.0.0"></a>
|
72
|
+
### v1.0.0 (2012-11-12)
|
54
73
|
|
55
74
|
* remove SAML code and port to ruby-saml gem
|
56
75
|
* fix incompatibility with OmniAuth 1.1
|
57
76
|
|
58
|
-
|
77
|
+
<a name="v0.9.2"></a>
|
78
|
+
### v0.9.2 (2012-03-30)
|
59
79
|
|
60
80
|
* validate the SAML response
|
61
81
|
* 100% test coverage
|
62
82
|
* now requires ruby 1.9.2+
|
63
83
|
|
64
|
-
|
84
|
+
<a name="v0.9.1"></a>
|
85
|
+
### v0.9.1 (2012-02-23)
|
65
86
|
|
66
87
|
* return first and last name in the info hash
|
67
88
|
* no longer use LDAP OIDs for name and email selection
|
68
89
|
* return SAML attributes as the omniauth raw_info hash
|
69
90
|
|
70
|
-
|
91
|
+
<a name="v0.9.0"></a>
|
92
|
+
### v0.9.0 (2012-02-14)
|
71
93
|
|
72
94
|
* initial release
|
73
95
|
* extracts commits from omniauth 0-3-stable branch
|
data/README.md
CHANGED
@@ -68,6 +68,8 @@ end
|
|
68
68
|
|
69
69
|
For IdP-initiated SSO, users should directly access the IdP SSO target URL. Set the `href` of your application's login link to the value of `idp_sso_target_url`. For SP-initiated SSO, link to `/auth/saml`.
|
70
70
|
|
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
|
+
|
71
73
|
## Metadata
|
72
74
|
|
73
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.
|
@@ -84,6 +86,14 @@ The service provider metadata used to ease configuration of the SAML SP in the I
|
|
84
86
|
* `:idp_sso_target_url` - The URL to which the authentication request should be sent.
|
85
87
|
This would be on the identity provider. **Required**.
|
86
88
|
|
89
|
+
* `:idp_slo_target_url` - The URL to which the single logout request and response should
|
90
|
+
be sent. This would be on the identity provider. Optional.
|
91
|
+
|
92
|
+
* `:slo_default_relay_state` - The value to use as default `RelayState` for single log outs. The
|
93
|
+
value can be a string, or a `Proc` (or other object responding to `call`). The `request`
|
94
|
+
instance will be passed to this callable if it has an arity of 1. If the value is a string,
|
95
|
+
the string will be returned, when the `RelayState` is called. Optional.
|
96
|
+
|
87
97
|
* `:idp_sso_target_url_runtime_params` - A dynamic mapping of request params that exist
|
88
98
|
during the request phase of OmniAuth that should to be sent to the IdP after a specific
|
89
99
|
mapping. So for example, a param `original_request_param` with value `original_param_value`,
|
@@ -143,6 +153,35 @@ end
|
|
143
153
|
|
144
154
|
Then follow Devise's general [OmniAuth tutorial](https://github.com/plataformatec/devise/wiki/OmniAuth:-Overview), replacing references to `facebook` with `saml`.
|
145
155
|
|
156
|
+
## Single Logout
|
157
|
+
|
158
|
+
Single Logout can be Service Provider initiated or Identity Provider initiated.
|
159
|
+
When using Devise as an authentication solution, the SP initiated flow can be integrated
|
160
|
+
in the `SessionsController#destroy` action.
|
161
|
+
|
162
|
+
For this to work it is important to preserve the `saml_uid` value before Devise
|
163
|
+
clears the session and redirect to the `/spslo` sub-path to initiate the single logout.
|
164
|
+
|
165
|
+
Example `destroy` action in `sessions_controller.rb`:
|
166
|
+
|
167
|
+
```ruby
|
168
|
+
class SessionsController < Devise::SessionsController
|
169
|
+
# ...
|
170
|
+
|
171
|
+
def destroy
|
172
|
+
# Preserve the saml_uid in the session
|
173
|
+
saml_uid = session["saml_uid"]
|
174
|
+
super do
|
175
|
+
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
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
```
|
184
|
+
|
146
185
|
## Authors
|
147
186
|
|
148
187
|
Authored by [Rajiv Aaron Manglani](http://www.rajivmanglani.com/), Raecoo Cao, Todd W Saxton, Ryan Wilcox, Steven Anderson, Nikos Dimitrakopoulos, Rudolf Vriend and [Bruno Pedro](http://brunopedro.com/).
|
@@ -27,15 +27,19 @@ module OmniAuth
|
|
27
27
|
first_name: ["first_name", "firstname", "firstName"],
|
28
28
|
last_name: ["last_name", "lastname", "lastName"]
|
29
29
|
}
|
30
|
+
option :slo_default_relay_state
|
30
31
|
|
31
32
|
def request_phase
|
32
33
|
options[:assertion_consumer_service_url] ||= callback_url
|
33
34
|
runtime_request_parameters = options.delete(:idp_sso_target_url_runtime_params)
|
34
35
|
|
35
36
|
additional_params = {}
|
36
|
-
|
37
|
-
|
38
|
-
|
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
|
39
43
|
|
40
44
|
authn_request = OneLogin::RubySaml::Authrequest.new
|
41
45
|
settings = OneLogin::RubySaml::Settings.new(options)
|
@@ -44,9 +48,7 @@ module OmniAuth
|
|
44
48
|
end
|
45
49
|
|
46
50
|
def callback_phase
|
47
|
-
unless request.params[
|
48
|
-
raise OmniAuth::Strategies::SAML::ValidationError.new("SAML response missing")
|
49
|
-
end
|
51
|
+
raise OmniAuth::Strategies::SAML::ValidationError.new("SAML response missing") unless request.params["SAMLResponse"]
|
50
52
|
|
51
53
|
# Call a fingerprint validation method if there's one
|
52
54
|
if options.idp_cert_fingerprint_validator
|
@@ -59,29 +61,21 @@ module OmniAuth
|
|
59
61
|
end
|
60
62
|
|
61
63
|
settings = OneLogin::RubySaml::Settings.new(options)
|
64
|
+
|
62
65
|
# filter options to select only extra parameters
|
63
66
|
opts = options.select {|k,_| OTHER_REQUEST_OPTIONS.include?(k.to_sym)}
|
67
|
+
|
64
68
|
# symbolize keys without activeSupport/symbolize_keys (ruby-saml use symbols)
|
65
69
|
opts =
|
66
70
|
opts.inject({}) do |new_hash, (key, value)|
|
67
71
|
new_hash[key.to_sym] = value
|
68
72
|
new_hash
|
69
73
|
end
|
70
|
-
response = OneLogin::RubySaml::Response.new(request.params['SAMLResponse'], opts.merge(settings: settings))
|
71
|
-
response.attributes['fingerprint'] = options.idp_cert_fingerprint
|
72
|
-
|
73
|
-
# will raise an error since we are not in soft mode
|
74
|
-
response.soft = false
|
75
|
-
response.is_valid?
|
76
|
-
|
77
|
-
@name_id = response.name_id
|
78
|
-
@attributes = response.attributes
|
79
74
|
|
80
|
-
|
81
|
-
|
75
|
+
handle_response(request.params["SAMLResponse"], opts, settings) do
|
76
|
+
super
|
82
77
|
end
|
83
78
|
|
84
|
-
super
|
85
79
|
rescue OmniAuth::Strategies::SAML::ValidationError
|
86
80
|
fail!(:invalid_ticket, $!)
|
87
81
|
rescue OneLogin::RubySaml::ValidationError
|
@@ -90,7 +84,7 @@ module OmniAuth
|
|
90
84
|
|
91
85
|
# Obtain an idp certificate fingerprint from the response.
|
92
86
|
def response_fingerprint
|
93
|
-
response = request.params[
|
87
|
+
response = request.params["SAMLResponse"]
|
94
88
|
response = (response =~ /^</) ? response : Base64.decode64(response)
|
95
89
|
document = XMLSecurity::SignedDocument::new(response)
|
96
90
|
cert_element = REXML::XPath.first(document, "//ds:X509Certificate", { "ds"=> 'http://www.w3.org/2000/09/xmldsig#' })
|
@@ -100,25 +94,43 @@ module OmniAuth
|
|
100
94
|
Digest::SHA1.hexdigest(cert.to_der).upcase.scan(/../).join(':')
|
101
95
|
end
|
102
96
|
|
103
|
-
def on_metadata_path?
|
104
|
-
on_path?("#{request_path}/metadata")
|
105
|
-
end
|
106
|
-
|
107
97
|
def other_phase
|
108
|
-
if
|
109
|
-
# omniauth does not set the strategy on the other_phase
|
98
|
+
if current_path.start_with?(request_path)
|
110
99
|
@env['omniauth.strategy'] ||= self
|
111
100
|
setup_phase
|
112
|
-
|
113
|
-
response = OneLogin::RubySaml::Metadata.new
|
114
101
|
settings = OneLogin::RubySaml::Settings.new(options)
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
102
|
+
|
103
|
+
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
|
119
114
|
end
|
115
|
+
|
116
|
+
Rack::Response.new(response.generate(settings), 200, { "Content-Type" => "application/xml" }).finish
|
117
|
+
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
|
125
|
+
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
|
131
|
+
else
|
132
|
+
call_app!
|
120
133
|
end
|
121
|
-
Rack::Response.new(response.generate(settings), 200, { "Content-Type" => "application/xml" }).finish
|
122
134
|
else
|
123
135
|
call_app!
|
124
136
|
end
|
@@ -135,7 +147,7 @@ module OmniAuth
|
|
135
147
|
Hash[found_attributes]
|
136
148
|
end
|
137
149
|
|
138
|
-
extra { { :raw_info => @attributes } }
|
150
|
+
extra { { :raw_info => @attributes, :response_object => @response_object } }
|
139
151
|
|
140
152
|
def find_attribute_by(keys)
|
141
153
|
keys.each do |key|
|
@@ -144,6 +156,94 @@ module OmniAuth
|
|
144
156
|
|
145
157
|
nil
|
146
158
|
end
|
159
|
+
|
160
|
+
private
|
161
|
+
|
162
|
+
def on_subpath?(subpath)
|
163
|
+
on_path?("#{request_path}/#{subpath}")
|
164
|
+
end
|
165
|
+
|
166
|
+
def handle_response(raw_response, opts, settings)
|
167
|
+
response = OneLogin::RubySaml::Response.new(raw_response, opts.merge(settings: settings))
|
168
|
+
response.attributes["fingerprint"] = options.idp_cert_fingerprint
|
169
|
+
response.soft = false
|
170
|
+
|
171
|
+
response.is_valid?
|
172
|
+
@name_id = response.name_id
|
173
|
+
@attributes = response.attributes
|
174
|
+
@response_object = response
|
175
|
+
|
176
|
+
if @name_id.nil? || @name_id.empty?
|
177
|
+
raise OmniAuth::Strategies::SAML::ValidationError.new("SAML response missing 'name_id'")
|
178
|
+
end
|
179
|
+
|
180
|
+
session["saml_uid"] = @name_id
|
181
|
+
yield
|
182
|
+
end
|
183
|
+
|
184
|
+
def slo_relay_state
|
185
|
+
if request.params.has_key?("RelayState") && request.params["RelayState"] != ""
|
186
|
+
request.params["RelayState"]
|
187
|
+
else
|
188
|
+
slo_default_relay_state = options.slo_default_relay_state
|
189
|
+
if slo_default_relay_state.respond_to?(:call)
|
190
|
+
if slo_default_relay_state.arity == 1
|
191
|
+
slo_default_relay_state.call(request)
|
192
|
+
else
|
193
|
+
slo_default_relay_state.call
|
194
|
+
end
|
195
|
+
else
|
196
|
+
slo_default_relay_state
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
def handle_logout_response(raw_response, settings)
|
202
|
+
# After sending an SP initiated LogoutRequest to the IdP, we need to accept
|
203
|
+
# the LogoutResponse, verify it, then actually delete our session.
|
204
|
+
|
205
|
+
logout_response = OneLogin::RubySaml::Logoutresponse.new(raw_response, settings, :matches_request_id => session["saml_transaction_id"])
|
206
|
+
logout_response.soft = false
|
207
|
+
logout_response.validate
|
208
|
+
|
209
|
+
session.delete("saml_uid")
|
210
|
+
session.delete("saml_transaction_id")
|
211
|
+
|
212
|
+
redirect(slo_relay_state)
|
213
|
+
end
|
214
|
+
|
215
|
+
def handle_logout_request(raw_request, settings)
|
216
|
+
logout_request = OneLogin::RubySaml::SloLogoutrequest.new(raw_request)
|
217
|
+
|
218
|
+
if logout_request.is_valid? &&
|
219
|
+
logout_request.name_id == session["saml_uid"]
|
220
|
+
|
221
|
+
# Actually log out this session
|
222
|
+
session.clear
|
223
|
+
|
224
|
+
# Generate a response to the IdP.
|
225
|
+
logout_request_id = logout_request.id
|
226
|
+
logout_response = OneLogin::RubySaml::SloLogoutresponse.new.create(settings, logout_request_id, nil, RelayState: slo_relay_state)
|
227
|
+
redirect(logout_response)
|
228
|
+
else
|
229
|
+
raise OmniAuth::Strategies::SAML::ValidationError.new("SAML failed to process LogoutRequest")
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
# Create a SP initiated SLO: https://github.com/onelogin/ruby-saml#single-log-out
|
234
|
+
def generate_logout_request(settings)
|
235
|
+
logout_request = OneLogin::RubySaml::Logoutrequest.new()
|
236
|
+
|
237
|
+
# Since we created a new SAML request, save the transaction_id
|
238
|
+
# to compare it with the response we get back
|
239
|
+
session["saml_transaction_id"] = logout_request.uuid
|
240
|
+
|
241
|
+
if settings.name_identifier_value.nil?
|
242
|
+
settings.name_identifier_value = session["saml_uid"]
|
243
|
+
end
|
244
|
+
|
245
|
+
logout_request.create(settings, RelayState: slo_relay_state)
|
246
|
+
end
|
147
247
|
end
|
148
248
|
end
|
149
249
|
end
|
@@ -6,8 +6,8 @@ RSpec::Matchers.define :fail_with do |message|
|
|
6
6
|
end
|
7
7
|
end
|
8
8
|
|
9
|
-
def post_xml(xml=:example_response)
|
10
|
-
post "/auth/saml/callback", {'SAMLResponse' => load_xml(xml)}
|
9
|
+
def post_xml(xml=:example_response, opts = {})
|
10
|
+
post "/auth/saml/callback", opts.merge({'SAMLResponse' => load_xml(xml)})
|
11
11
|
end
|
12
12
|
|
13
13
|
describe OmniAuth::Strategies::SAML, :type => :strategy do
|
@@ -17,7 +17,9 @@ describe OmniAuth::Strategies::SAML, :type => :strategy do
|
|
17
17
|
let(:saml_options) do
|
18
18
|
{
|
19
19
|
:assertion_consumer_service_url => "http://localhost:9080/auth/saml/callback",
|
20
|
+
:single_logout_service_url => "http://localhost:9080/auth/saml/slo",
|
20
21
|
:idp_sso_target_url => "https://idp.sso.example.com/signon/29490",
|
22
|
+
:idp_slo_target_url => "https://idp.sso.example.com/signoff/29490",
|
21
23
|
:idp_cert_fingerprint => "C1:59:74:2B:E8:0C:6C:A9:41:0F:6E:83:F6:D1:52:25:45:58:89:FB",
|
22
24
|
:idp_sso_target_url_runtime_params => {:original_param_key => :mapped_param_key},
|
23
25
|
:name_identifier_format => "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress",
|
@@ -39,11 +41,11 @@ describe OmniAuth::Strategies::SAML, :type => :strategy do
|
|
39
41
|
end
|
40
42
|
|
41
43
|
it 'should get authentication page' do
|
42
|
-
last_response.
|
43
|
-
last_response.location.
|
44
|
-
last_response.location.
|
45
|
-
last_response.location.
|
46
|
-
last_response.location.
|
44
|
+
expect(last_response).to be_redirect
|
45
|
+
expect(last_response.location).to match /https:\/\/idp.sso.example.com\/signon\/29490/
|
46
|
+
expect(last_response.location).to match /\?SAMLRequest=/
|
47
|
+
expect(last_response.location).not_to match /mapped_param_key/
|
48
|
+
expect(last_response.location).not_to match /original_param_key/
|
47
49
|
end
|
48
50
|
end
|
49
51
|
|
@@ -53,11 +55,11 @@ describe OmniAuth::Strategies::SAML, :type => :strategy do
|
|
53
55
|
end
|
54
56
|
|
55
57
|
it 'should get authentication page' do
|
56
|
-
last_response.
|
57
|
-
last_response.location.
|
58
|
-
last_response.location.
|
59
|
-
last_response.location.
|
60
|
-
last_response.location.
|
58
|
+
expect(last_response).to be_redirect
|
59
|
+
expect(last_response.location).to match /https:\/\/idp.sso.example.com\/signon\/29490/
|
60
|
+
expect(last_response.location).to match /\?SAMLRequest=/
|
61
|
+
expect(last_response.location).to match /\&mapped_param_key=original_param_value/
|
62
|
+
expect(last_response.location).not_to match /original_param_key/
|
61
63
|
end
|
62
64
|
end
|
63
65
|
|
@@ -71,17 +73,17 @@ describe OmniAuth::Strategies::SAML, :type => :strategy do
|
|
71
73
|
%w(foo.example.com bar.example.com).each do |host|
|
72
74
|
get "https://#{host}/auth/saml"
|
73
75
|
|
74
|
-
last_response.
|
76
|
+
expect(last_response).to be_redirect
|
75
77
|
|
76
78
|
location = URI.parse(last_response.location)
|
77
79
|
query = Rack::Utils.parse_query location.query
|
78
|
-
query.
|
80
|
+
expect(query).to have_key('SAMLRequest')
|
79
81
|
|
80
82
|
request = REXML::Document.new(Base64.decode64(query['SAMLRequest']))
|
81
|
-
request.root.
|
83
|
+
expect(request.root).not_to be_nil
|
82
84
|
|
83
85
|
acs = request.root.attributes.get_attribute('AssertionConsumerServiceURL')
|
84
|
-
acs.to_s.
|
86
|
+
expect(acs.to_s).to eq "https://#{host}/auth/saml/callback"
|
85
87
|
end
|
86
88
|
end
|
87
89
|
end
|
@@ -93,7 +95,7 @@ describe OmniAuth::Strategies::SAML, :type => :strategy do
|
|
93
95
|
let(:xml) { :example_response }
|
94
96
|
|
95
97
|
before :each do
|
96
|
-
Time.
|
98
|
+
allow(Time).to receive(:now).and_return(Time.utc(2012, 11, 8, 20, 40, 00))
|
97
99
|
end
|
98
100
|
|
99
101
|
context "when the response is valid" do
|
@@ -102,17 +104,21 @@ describe OmniAuth::Strategies::SAML, :type => :strategy do
|
|
102
104
|
end
|
103
105
|
|
104
106
|
it "should set the uid to the nameID in the SAML response" do
|
105
|
-
auth_hash['uid'].
|
107
|
+
expect(auth_hash['uid']).to eq '_1f6fcf6be5e13b08b1e3610e7ff59f205fbd814f23'
|
106
108
|
end
|
107
109
|
|
108
110
|
it "should set the raw info to all attributes" do
|
109
|
-
auth_hash['extra']['raw_info'].all.to_hash.
|
111
|
+
expect(auth_hash['extra']['raw_info'].all.to_hash).to eq(
|
110
112
|
'first_name' => ['Rajiv'],
|
111
113
|
'last_name' => ['Manglani'],
|
112
114
|
'email' => ['user@example.com'],
|
113
115
|
'company_name' => ['Example Company'],
|
114
116
|
'fingerprint' => saml_options[:idp_cert_fingerprint]
|
115
|
-
|
117
|
+
)
|
118
|
+
end
|
119
|
+
|
120
|
+
it "should set the response_object to the response object from ruby_saml response" do
|
121
|
+
expect(auth_hash['extra']['response_object']).to be_kind_of(OneLogin::RubySaml::Response)
|
116
122
|
end
|
117
123
|
end
|
118
124
|
|
@@ -124,17 +130,17 @@ describe OmniAuth::Strategies::SAML, :type => :strategy do
|
|
124
130
|
end
|
125
131
|
|
126
132
|
it "should set the uid to the nameID in the SAML response" do
|
127
|
-
auth_hash['uid'].
|
133
|
+
expect(auth_hash['uid']).to eq '_1f6fcf6be5e13b08b1e3610e7ff59f205fbd814f23'
|
128
134
|
end
|
129
135
|
|
130
136
|
it "should set the raw info to all attributes" do
|
131
|
-
auth_hash['extra']['raw_info'].all.to_hash.
|
137
|
+
expect(auth_hash['extra']['raw_info'].all.to_hash).to eq(
|
132
138
|
'first_name' => ['Rajiv'],
|
133
139
|
'last_name' => ['Manglani'],
|
134
140
|
'email' => ['user@example.com'],
|
135
141
|
'company_name' => ['Example Company'],
|
136
142
|
'fingerprint' => 'C1:59:74:2B:E8:0C:6C:A9:41:0F:6E:83:F6:D1:52:25:45:58:89:FB'
|
137
|
-
|
143
|
+
)
|
138
144
|
end
|
139
145
|
end
|
140
146
|
|
@@ -148,6 +154,7 @@ describe OmniAuth::Strategies::SAML, :type => :strategy do
|
|
148
154
|
|
149
155
|
context "when there is no name id in the XML" do
|
150
156
|
before :each do
|
157
|
+
allow(Time).to receive(:now).and_return(Time.utc(2012, 11, 8, 23, 55, 00))
|
151
158
|
post_xml :no_name_id
|
152
159
|
end
|
153
160
|
|
@@ -191,38 +198,103 @@ describe OmniAuth::Strategies::SAML, :type => :strategy do
|
|
191
198
|
end
|
192
199
|
|
193
200
|
it "should obey attribute statements mapping" do
|
194
|
-
auth_hash[:info].
|
201
|
+
expect(auth_hash[:info]).to eq(
|
195
202
|
'first_name' => 'Rajiv',
|
196
203
|
'last_name' => 'Manglani',
|
197
204
|
'email' => 'user@example.com',
|
198
205
|
'name' => nil
|
199
|
-
|
206
|
+
)
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
context "when response is a logout response" do
|
211
|
+
before :each do
|
212
|
+
saml_options[:issuer] = "https://idp.sso.example.com/metadata/29490"
|
213
|
+
|
214
|
+
post "/auth/saml/slo", {
|
215
|
+
SAMLResponse: load_xml(:example_logout_response),
|
216
|
+
RelayState: "https://example.com/",
|
217
|
+
}, "rack.session" => {"saml_transaction_id" => "_3fef1069-d0c6-418a-b68d-6f008a4787e9"}
|
218
|
+
end
|
219
|
+
it "should redirect to relaystate" do
|
220
|
+
expect(last_response).to be_redirect
|
221
|
+
expect(last_response.location).to match /https:\/\/example.com\//
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
context "when request is a logout request" do
|
226
|
+
before :each do
|
227
|
+
saml_options[:issuer] = "https://idp.sso.example.com/metadata/29490"
|
228
|
+
post "/auth/saml/slo", {
|
229
|
+
"SAMLRequest" => load_xml(:example_logout_request),
|
230
|
+
"RelayState" => "https://example.com/",
|
231
|
+
}, "rack.session" => {"saml_uid" => "username@example.com"}
|
232
|
+
end
|
233
|
+
|
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/
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
context "when sp initiated SLO" do
|
242
|
+
def test_default_relay_state(static_default_relay_state = nil, &block_default_relay_state)
|
243
|
+
saml_options["slo_default_relay_state"] = static_default_relay_state || block_default_relay_state
|
244
|
+
post "/auth/saml/spslo"
|
245
|
+
|
246
|
+
expect(last_response).to be_redirect
|
247
|
+
expect(last_response.location).to match /https:\/\/idp.sso.example.com\/signoff\/29490/
|
248
|
+
expect(last_response.location).to match /RelayState=https%3A%2F%2Fexample.com%2F/
|
249
|
+
end
|
250
|
+
|
251
|
+
it "should redirect to logout request" do
|
252
|
+
test_default_relay_state("https://example.com/")
|
253
|
+
end
|
254
|
+
|
255
|
+
it "should redirect to logout request with a block" do
|
256
|
+
test_default_relay_state do
|
257
|
+
"https://example.com/"
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
it "should redirect to logout request with a block with a request parameter" do
|
262
|
+
test_default_relay_state do |request|
|
263
|
+
"https://example.com/"
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
it "should give not implemented without an idp_slo_target_url" do
|
268
|
+
saml_options.delete(:idp_slo_target_url)
|
269
|
+
post "/auth/saml/spslo"
|
270
|
+
|
271
|
+
expect(last_response.status).to eq 501
|
272
|
+
expect(last_response.body).to match /Not Implemented/
|
200
273
|
end
|
201
274
|
end
|
202
275
|
end
|
203
276
|
|
204
277
|
describe 'GET /auth/saml/metadata' do
|
205
278
|
before do
|
279
|
+
saml_options[:issuer] = 'http://example.com/SAML'
|
206
280
|
get '/auth/saml/metadata'
|
207
281
|
end
|
208
282
|
|
209
283
|
it 'should get SP metadata page' do
|
210
|
-
last_response.status.
|
211
|
-
last_response.header["Content-Type"].
|
284
|
+
expect(last_response.status).to eq 200
|
285
|
+
expect(last_response.header["Content-Type"]).to eq "application/xml"
|
212
286
|
end
|
213
287
|
|
214
288
|
it 'should configure attributes consuming service' do
|
215
|
-
last_response.body.
|
216
|
-
last_response.body.
|
217
|
-
last_response.body.
|
218
|
-
last_response.body.
|
289
|
+
expect(last_response.body).to match /AttributeConsumingService/
|
290
|
+
expect(last_response.body).to match /first_name/
|
291
|
+
expect(last_response.body).to match /last_name/
|
292
|
+
expect(last_response.body).to match /Required attributes/
|
293
|
+
expect(last_response.body).to match /entityID/
|
294
|
+
expect(last_response.body).to match /http:\/\/example.com\/SAML/
|
219
295
|
end
|
220
296
|
end
|
221
297
|
|
222
|
-
it 'implements #on_metadata_path?' do
|
223
|
-
expect(described_class.new(nil)).to respond_to(:on_metadata_path?)
|
224
|
-
end
|
225
|
-
|
226
298
|
describe 'subclass behavior' do
|
227
299
|
it 'registers subclasses in OmniAuth.strategies' do
|
228
300
|
subclass = Class.new(described_class)
|
data/spec/spec_helper.rb
CHANGED
@@ -15,6 +15,10 @@ require 'rexml/document'
|
|
15
15
|
require 'rexml/xpath'
|
16
16
|
require 'base64'
|
17
17
|
|
18
|
+
TEST_LOGGER = Logger.new(StringIO.new)
|
19
|
+
OneLogin::RubySaml::Logging.logger = TEST_LOGGER
|
20
|
+
OmniAuth.config.logger = TEST_LOGGER
|
21
|
+
|
18
22
|
RSpec.configure do |config|
|
19
23
|
config.include Rack::Test::Methods
|
20
24
|
end
|
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.7.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-
|
17
|
+
date: 2016-10-19 00:00:00.000000000 Z
|
18
18
|
dependencies:
|
19
19
|
- !ruby/object:Gem::Dependency
|
20
20
|
name: omniauth
|
@@ -36,14 +36,34 @@ dependencies:
|
|
36
36
|
requirements:
|
37
37
|
- - "~>"
|
38
38
|
- !ruby/object:Gem::Version
|
39
|
-
version: '1.
|
39
|
+
version: '1.4'
|
40
40
|
type: :runtime
|
41
41
|
prerelease: false
|
42
42
|
version_requirements: !ruby/object:Gem::Requirement
|
43
43
|
requirements:
|
44
44
|
- - "~>"
|
45
45
|
- !ruby/object:Gem::Version
|
46
|
-
version: '1.
|
46
|
+
version: '1.4'
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: rake
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '10'
|
54
|
+
- - "<"
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: '12'
|
57
|
+
type: :development
|
58
|
+
prerelease: false
|
59
|
+
version_requirements: !ruby/object:Gem::Requirement
|
60
|
+
requirements:
|
61
|
+
- - ">="
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: '10'
|
64
|
+
- - "<"
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: '12'
|
47
67
|
- !ruby/object:Gem::Dependency
|
48
68
|
name: rspec
|
49
69
|
requirement: !ruby/object:Gem::Requirement
|
@@ -92,6 +112,20 @@ dependencies:
|
|
92
112
|
- - ">="
|
93
113
|
- !ruby/object:Gem::Version
|
94
114
|
version: 0.6.3
|
115
|
+
- !ruby/object:Gem::Dependency
|
116
|
+
name: conventional-changelog
|
117
|
+
requirement: !ruby/object:Gem::Requirement
|
118
|
+
requirements:
|
119
|
+
- - "~>"
|
120
|
+
- !ruby/object:Gem::Version
|
121
|
+
version: '1.2'
|
122
|
+
type: :development
|
123
|
+
prerelease: false
|
124
|
+
version_requirements: !ruby/object:Gem::Requirement
|
125
|
+
requirements:
|
126
|
+
- - "~>"
|
127
|
+
- !ruby/object:Gem::Version
|
128
|
+
version: '1.2'
|
95
129
|
description: A generic SAML strategy for OmniAuth.
|
96
130
|
email: rajiv@alum.mit.edu
|
97
131
|
executables: []
|