omniauth-saml-rstr 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +109 -0
- data/lib/omniauth-saml-rstr.rb +1 -0
- data/lib/omniauth-saml-rstr/version.rb +5 -0
- data/lib/omniauth/strategies/saml-rstr.rb +52 -0
- data/lib/omniauth/strategies/saml-rstr/auth_request.rb +38 -0
- data/lib/omniauth/strategies/saml-rstr/auth_response.rb +128 -0
- data/lib/omniauth/strategies/saml-rstr/validation_error.rb +8 -0
- data/lib/omniauth/strategies/saml-rstr/xml_security.rb +119 -0
- data/spec/omniauth/strategies/saml-rstr/auth_request_spec.rb +75 -0
- data/spec/omniauth/strategies/saml-rstr/auth_response_spec.rb +35 -0
- data/spec/omniauth/strategies/saml-rstr/validation_error_spec.rb +5 -0
- data/spec/omniauth/strategies/saml_rstr_spec.rb +122 -0
- data/spec/shared/validating_method.rb +128 -0
- data/spec/spec_helper.rb +17 -0
- metadata +208 -0
data/README.md
ADDED
@@ -0,0 +1,109 @@
|
|
1
|
+
# OmniAuth SAML-RSTR
|
2
|
+
|
3
|
+
An XML & SAML strategy for OmniAuth integration with ADFS 2.0.
|
4
|
+
|
5
|
+
https://github.com/highgroove/omniauth-saml-rstr
|
6
|
+
|
7
|
+
## Requirements
|
8
|
+
|
9
|
+
* [OmniAuth](http://www.omniauth.org/) 1.0+
|
10
|
+
* nokogiri 1.5
|
11
|
+
* Ruby 1.9.2
|
12
|
+
|
13
|
+
## Usage
|
14
|
+
|
15
|
+
Use the SAML strategy as a middleware in your application:
|
16
|
+
|
17
|
+
```ruby
|
18
|
+
require 'omniauth'
|
19
|
+
use OmniAuth::Strategies::SAML_RSTR,
|
20
|
+
:assertion_consumer_service_url => "consumer_service_url",
|
21
|
+
:issuer => "issuer",
|
22
|
+
:idp_sso_target_url => "idp_sso_target_url",
|
23
|
+
:idp_cert => "-----BEGIN CERTIFICATE-----\n...-----END CERTIFICATE-----",
|
24
|
+
:idp_cert_fingerprint => "E7:91:B2:E1:...",
|
25
|
+
:name_identifier_format => "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
|
26
|
+
```
|
27
|
+
|
28
|
+
or in your Rails application:
|
29
|
+
|
30
|
+
in `Gemfile`:
|
31
|
+
|
32
|
+
```ruby
|
33
|
+
gem 'omniauth-saml-rstr'
|
34
|
+
```
|
35
|
+
|
36
|
+
and in `config/initializers/omniauth.rb`:
|
37
|
+
|
38
|
+
```ruby
|
39
|
+
Rails.application.config.middleware.use OmniAuth::Builder do
|
40
|
+
provider :saml_rstr,
|
41
|
+
:assertion_consumer_service_url => "consumer_service_url",
|
42
|
+
:issuer => "rails-application",
|
43
|
+
:idp_sso_target_url => "idp_sso_target_url",
|
44
|
+
:idp_cert => "-----BEGIN CERTIFICATE-----\n...-----END CERTIFICATE-----",
|
45
|
+
:idp_cert_fingerprint => "E7:91:B2:E1:...",
|
46
|
+
:name_identifier_format => "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
|
47
|
+
end
|
48
|
+
```
|
49
|
+
|
50
|
+
## Options
|
51
|
+
|
52
|
+
* `:assertion_consumer_service_url` - The URL at which the SAML assertion should be
|
53
|
+
received. With OmniAuth this is typically `http://example.com/auth/callback`.
|
54
|
+
**Required**.
|
55
|
+
|
56
|
+
* `:issuer` - The name of your application. Some identity providers might need this
|
57
|
+
to establish the identity of the service provider requesting the login. **Required**.
|
58
|
+
|
59
|
+
* `:idp_sso_target_url` - The URL to which the authentication request should be sent.
|
60
|
+
This would be on the identity provider. **Required**.
|
61
|
+
|
62
|
+
* `:idp_cert` - The identity provider's certificate in PEM format. Takes precedence
|
63
|
+
over the fingerprint option below. This option or `:idp_cert_fingerprint` must
|
64
|
+
be present.
|
65
|
+
|
66
|
+
* `:idp_cert_fingerprint` - The SHA256 fingerprint of the certificate, e.g.
|
67
|
+
"90:CC:16:F0:8D:...". This is provided from the identity provider when setting up
|
68
|
+
the relationship. This option or `:idp_cert` must be present.
|
69
|
+
|
70
|
+
* `:name_identifier_format` - Describes the format of the username required by this
|
71
|
+
application. If you need the email address, use "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress".
|
72
|
+
See http://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf section 8.3 for
|
73
|
+
other options. Note that the identity provider might not support all options.
|
74
|
+
Optional.
|
75
|
+
|
76
|
+
## Authors
|
77
|
+
|
78
|
+
Authored by Josh Skeen [www.joshskeen.com].
|
79
|
+
Based on the work of Raecoo Cao, Todd W Saxton, Ryan Wilcox, Rajiv Aaron Manglani, and Steven Anderson.
|
80
|
+
|
81
|
+
<!-- Maintained by [Rajiv Aaron Manglani](http://www.rajivmanglani.com/). -->
|
82
|
+
|
83
|
+
## License
|
84
|
+
|
85
|
+
Copyright (c) 2012 Apangea [http://www.apangea.com/]
|
86
|
+
Developed at Highgroove Studios [http://www.highgroove.com]
|
87
|
+
|
88
|
+
Copyright (c) 2011-2012 [Practically Green, Inc.](http://www.practicallygreen.com/).
|
89
|
+
All rights reserved. Released under the MIT license.
|
90
|
+
|
91
|
+
Portions Copyright (c) 2007 Sun Microsystems Inc.
|
92
|
+
|
93
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
94
|
+
of this software and associated documentation files (the "Software"), to deal
|
95
|
+
in the Software without restriction, including without limitation the rights
|
96
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
97
|
+
copies of the Software, and to permit persons to whom the Software is
|
98
|
+
furnished to do so, subject to the following conditions:
|
99
|
+
|
100
|
+
The above copyright notice and this permission notice shall be included in
|
101
|
+
all copies or substantial portions of the Software.
|
102
|
+
|
103
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
104
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
105
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
106
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
107
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
108
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
109
|
+
THE SOFTWARE.
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'omniauth/strategies/saml-rstr'
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'omniauth'
|
2
|
+
|
3
|
+
module OmniAuth
|
4
|
+
module Strategies
|
5
|
+
class SAML_RSTR
|
6
|
+
include OmniAuth::Strategy
|
7
|
+
|
8
|
+
autoload :AuthRequest, 'omniauth/strategies/saml-rstr/auth_request'
|
9
|
+
autoload :AuthResponse, 'omniauth/strategies/saml-rstr/auth_response'
|
10
|
+
autoload :ValidationError, 'omniauth/strategies/saml-rstr/validation_error'
|
11
|
+
autoload :XMLSecurity, 'omniauth/strategies/saml-rstr/xml_security'
|
12
|
+
|
13
|
+
option :name_identifier_format, "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
|
14
|
+
|
15
|
+
def request_phase
|
16
|
+
request = OmniAuth::Strategies::SAML_RSTR::AuthRequest.new
|
17
|
+
req = request.create(options)
|
18
|
+
redirect(req)
|
19
|
+
end
|
20
|
+
|
21
|
+
def callback_phase
|
22
|
+
|
23
|
+
begin
|
24
|
+
response = OmniAuth::Strategies::SAML_RSTR::AuthResponse.new(request.params['wresult'])
|
25
|
+
response.settings = options
|
26
|
+
|
27
|
+
@name_id = response.name_id
|
28
|
+
@attributes = response.attributes
|
29
|
+
|
30
|
+
return fail!(:invalid_ticket, OmniAuth::Error.new('Invalid SAML_RSTR Ticket')) if @name_id.nil? || @name_id.empty? || !response.valid?
|
31
|
+
super
|
32
|
+
rescue ArgumentError => e
|
33
|
+
fail!(:invalid_ticket, OmniAuth::Error.new('Invalid SAML_RSTR Response'))
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
uid { @name_id }
|
38
|
+
|
39
|
+
info do
|
40
|
+
{
|
41
|
+
:name => @name_id
|
42
|
+
}
|
43
|
+
end
|
44
|
+
|
45
|
+
extra { { :raw_info => @attributes } }
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# OmniAuth.config.add_camelization 'saml', 'SAML'
|
52
|
+
OmniAuth.config.add_camelization 'saml_rstr', 'SAML_RSTR'
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require "base64"
|
2
|
+
require "uuid"
|
3
|
+
require "zlib"
|
4
|
+
require "cgi"
|
5
|
+
|
6
|
+
module OmniAuth
|
7
|
+
module Strategies
|
8
|
+
class SAML_RSTR
|
9
|
+
class AuthRequest
|
10
|
+
|
11
|
+
def create(settings, params = {})
|
12
|
+
uuid = "_" + UUID.new.generate
|
13
|
+
time = Time.now.utc.strftime("%Y-%m-%dT%H:%M:%SZ")
|
14
|
+
|
15
|
+
request =
|
16
|
+
"<samlp:AuthnRequest xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\" ID=\"#{uuid}\" Version=\"2.0\" IssueInstant=\"#{time}\" ProtocolBinding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST\" AssertionConsumerServiceURL=\"#{settings[:assertion_consumer_service_url]}\">" +
|
17
|
+
"<saml:Issuer xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\">#{settings[:issuer]}</saml:Issuer>\n" +
|
18
|
+
"<samlp:NameIDPolicy xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\" Format=\"#{settings[:name_identifier_format]}\" AllowCreate=\"true\"></samlp:NameIDPolicy>\n" +
|
19
|
+
"<samlp:RequestedAuthnContext xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\" Comparison=\"exact\">" +
|
20
|
+
"<saml:AuthnContextClassRef xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\">urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</saml:AuthnContextClassRef></samlp:RequestedAuthnContext>\n" +
|
21
|
+
"</samlp:AuthnRequest>"
|
22
|
+
|
23
|
+
deflated_request = Zlib::Deflate.deflate(request, 9)[2..-5]
|
24
|
+
base64_request = Base64.encode64(deflated_request)
|
25
|
+
encoded_request = CGI.escape(base64_request)
|
26
|
+
request_params = "?SAMLRequest=" + encoded_request
|
27
|
+
|
28
|
+
params.each_pair do |key, value|
|
29
|
+
request_params << "&#{key}=#{CGI.escape(value.to_s)}"
|
30
|
+
end
|
31
|
+
|
32
|
+
settings[:idp_sso_target_url] + request_params
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
require "time"
|
2
|
+
|
3
|
+
module OmniAuth
|
4
|
+
module Strategies
|
5
|
+
class SAML_RSTR
|
6
|
+
class AuthResponse
|
7
|
+
|
8
|
+
ASSERTION = "urn:oasis:names:tc:SAML:2.0:assertion"
|
9
|
+
PROTOCOL = "urn:oasis:names:tc:SAML:2.0:protocol"
|
10
|
+
DSIG = "http://www.w3.org/2000/09/xmldsig#"
|
11
|
+
|
12
|
+
attr_accessor :options, :response, :security_token_content, :settings
|
13
|
+
|
14
|
+
def initialize(response, options = {})
|
15
|
+
raise ArgumentError.new("Response cannot be nil") if response.nil?
|
16
|
+
self.options = options
|
17
|
+
self.response = response
|
18
|
+
self.security_token_content = OmniAuth::Strategies::SAML_RSTR::XMLSecurity::SecurityTokenResponseContent.new(response)
|
19
|
+
end
|
20
|
+
|
21
|
+
def valid?
|
22
|
+
validate(soft = true)
|
23
|
+
end
|
24
|
+
|
25
|
+
def validate!
|
26
|
+
validate(soft = false)
|
27
|
+
end
|
28
|
+
|
29
|
+
# The value of the user identifier as designated by the initialization request response
|
30
|
+
def name_id
|
31
|
+
@security_token_content.name_identifier
|
32
|
+
end
|
33
|
+
|
34
|
+
# A hash of all the attributes with the response. Assuming there is only one value for each key
|
35
|
+
def attributes
|
36
|
+
{ :userEmailID => @security_token_content.name_identifier}
|
37
|
+
end
|
38
|
+
|
39
|
+
# When this user session should expire at latest
|
40
|
+
def session_expires_at
|
41
|
+
@expires_at ||= begin
|
42
|
+
parse_time(security_token_content.conditions_not_on_or_after)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# Conditions (if any) for the assertion to run
|
47
|
+
def conditions
|
48
|
+
@conditions ||= begin
|
49
|
+
{
|
50
|
+
:before => security_token_content.conditions_before,
|
51
|
+
:not_on_or_after => security_token_content.conditions_not_on_or_after
|
52
|
+
}
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def validation_error(message)
|
59
|
+
raise OmniAuth::Strategies::SAML_RSTR::ValidationError.new(message)
|
60
|
+
end
|
61
|
+
|
62
|
+
def validate(soft = true)
|
63
|
+
status = validate_response_state(soft) && security_token_content.validate(get_fingerprint, soft)
|
64
|
+
return status
|
65
|
+
end
|
66
|
+
|
67
|
+
def validate_response_state(soft = true)
|
68
|
+
if response.empty?
|
69
|
+
return soft ? false : validation_error("Blank response")
|
70
|
+
end
|
71
|
+
|
72
|
+
if settings.nil?
|
73
|
+
return soft ? false : validation_error("No settings on response")
|
74
|
+
end
|
75
|
+
|
76
|
+
if settings.idp_cert_fingerprint.nil? && settings.idp_cert.nil?
|
77
|
+
return soft ? false : validation_error("No fingerprint or certificate on settings")
|
78
|
+
end
|
79
|
+
true
|
80
|
+
end
|
81
|
+
|
82
|
+
def get_fingerprint
|
83
|
+
if settings.idp_cert
|
84
|
+
cert = OpenSSL::X509::Certificate.new(settings.idp_cert.gsub(/^ +/, ''))
|
85
|
+
Digest::SHA1.hexdigest(cert.to_der).upcase.scan(/../).join(":")
|
86
|
+
else
|
87
|
+
settings.idp_cert_fingerprint
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def validate_conditions(soft = true)
|
92
|
+
return true if conditions.nil?
|
93
|
+
return true if options[:skip_conditions]
|
94
|
+
|
95
|
+
if not_before = parse_time(security_token_content.conditions_before)
|
96
|
+
if Time.now.utc < not_before
|
97
|
+
return soft ? false : validation_error("Current time is earlier than NotBefore condition")
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
if not_on_or_after = parse_time(security_token_content.conditions_not_on_or_after)
|
102
|
+
if Time.now.utc >= not_on_or_after
|
103
|
+
return soft ? false : validation_error("Current time is on or after NotOnOrAfter condition")
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
true
|
108
|
+
end
|
109
|
+
|
110
|
+
private
|
111
|
+
|
112
|
+
def parse_time(attribute)
|
113
|
+
Time.parse(attribute)
|
114
|
+
end
|
115
|
+
|
116
|
+
def strip(string)
|
117
|
+
return string unless string
|
118
|
+
string.gsub(/^\s+/, '').gsub(/\s+$/, '')
|
119
|
+
end
|
120
|
+
|
121
|
+
def signed_element_id
|
122
|
+
doc_id = security_token_content.signed_element_id
|
123
|
+
doc_id[1, doc_id.size]
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
# The contents of this file are subject to the terms
|
2
|
+
# of the Common Development and Distribution License
|
3
|
+
# (the License). You may not use this file except in
|
4
|
+
# compliance with the License.
|
5
|
+
#
|
6
|
+
# You can obtain a copy of the License at
|
7
|
+
# https://opensso.dev.java.net/public/CDDLv1.0.html or
|
8
|
+
# opensso/legal/CDDLv1.0.txt
|
9
|
+
# See the License for the specific language governing
|
10
|
+
# permission and limitations under the License.
|
11
|
+
#
|
12
|
+
# When distributing Covered Code, include this CDDL
|
13
|
+
# Header Notice in each file and include the License file
|
14
|
+
# at opensso/legal/CDDLv1.0.txt.
|
15
|
+
# If applicable, add the following below the CDDL Header,
|
16
|
+
# with the fields enclosed by brackets [] replaced by
|
17
|
+
# your own identifying information:
|
18
|
+
# "Portions Copyrighted [year] [name of copyright owner]"
|
19
|
+
#
|
20
|
+
# $Id: xml_sec.rb,v 1.6 2007/10/24 00:28:41 todddd Exp $
|
21
|
+
#
|
22
|
+
# Copyright 2007 Sun Microsystems Inc. All Rights responseerved
|
23
|
+
# Portions Copyrighted 2007 Todd W Saxton.
|
24
|
+
|
25
|
+
require 'rubygems'
|
26
|
+
require "rexml/document"
|
27
|
+
require "rexml/xpath"
|
28
|
+
require "openssl"
|
29
|
+
require "xmlcanonicalizer"
|
30
|
+
require "digest/sha1"
|
31
|
+
require "nokogiri"
|
32
|
+
|
33
|
+
module OmniAuth
|
34
|
+
module Strategies
|
35
|
+
class SAML_RSTR
|
36
|
+
|
37
|
+
module XMLSecurity
|
38
|
+
|
39
|
+
class SecurityTokenResponseContent
|
40
|
+
|
41
|
+
#plugging these namespaces in was required in order to get nokogiri to use them. eg @xml.at_xpath("//ds:SignatureValue", {"ds" => DSIG}).text. Any way to avoid this?
|
42
|
+
DSIG = "http://www.w3.org/2000/09/xmldsig#"
|
43
|
+
SAML = "urn:oasis:names:tc:SAML:1.0:assertion"
|
44
|
+
WSP = "http://schemas.xmlsoap.org/ws/2004/09/policy"
|
45
|
+
WSA = "http://www.w3.org/2005/08/addressing"
|
46
|
+
WSU = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
|
47
|
+
TRUST = "http://schemas.xmlsoap.org/ws/2005/02/trust"
|
48
|
+
|
49
|
+
attr_accessor :name_identifier, :xml, :xml_unnamespaced, :name_identifier_test, :x509_cert, :conditions_not_on_or_after, :conditions_before, :info_element
|
50
|
+
|
51
|
+
def initialize(response)
|
52
|
+
self.xml_unnamespaced = Nokogiri::XML::Document.parse(response).remove_namespaces!()
|
53
|
+
self.xml = Nokogiri::XML::Document.parse(response)
|
54
|
+
end
|
55
|
+
|
56
|
+
def signature
|
57
|
+
@xml.at_xpath("//ds:SignatureValue", {"ds" => DSIG}).text
|
58
|
+
end
|
59
|
+
|
60
|
+
def info_element
|
61
|
+
@xml.at_xpath("//ds:SignedInfo", {"ds" => DSIG})
|
62
|
+
end
|
63
|
+
|
64
|
+
def name_identifier
|
65
|
+
@xml_unnamespaced.css("NameIdentifier").text
|
66
|
+
end
|
67
|
+
|
68
|
+
def conditions_before
|
69
|
+
if !conditions.nil?
|
70
|
+
conditions.attribute("NotBefore").value
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def conditions_not_on_or_after
|
75
|
+
if !conditions.nil?
|
76
|
+
conditions.attribute("NotOnOrAfter").value
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def x509_cert
|
81
|
+
@xml_unnamespaced.css("X509Certificate").text
|
82
|
+
end
|
83
|
+
|
84
|
+
#validate the response fingerprint matches the plugin fingerprint
|
85
|
+
#validate the certificate signature matches the signature generated from signing the certificate's SignedInfo node
|
86
|
+
def validate(idp_cert_fingerprint, soft = true)
|
87
|
+
|
88
|
+
cert_text = Base64.decode64(x509_cert)
|
89
|
+
|
90
|
+
certificate = OpenSSL::X509::Certificate.new(cert_text)
|
91
|
+
fingerprint = Digest::SHA1.hexdigest(certificate.to_der)
|
92
|
+
|
93
|
+
if fingerprint != idp_cert_fingerprint.gsub(/[^a-zA-Z0-9]/,"").downcase
|
94
|
+
raise OmniAuth::Strategies::SAML_RSTR::ValidationError.new("Fingerprint validation error")
|
95
|
+
end
|
96
|
+
|
97
|
+
canon_string = info_element.canonicalize(Nokogiri::XML::XML_C14N_EXCLUSIVE_1_0)
|
98
|
+
sig = Base64.decode64(signature)
|
99
|
+
|
100
|
+
if !certificate.public_key.verify(OpenSSL::Digest::SHA256.new, sig, canon_string)
|
101
|
+
return soft ? false : (raise OmniAuth::Strategies::SAML_RSTR::ValidationError.new("Key validation error"))
|
102
|
+
end
|
103
|
+
|
104
|
+
return true
|
105
|
+
end
|
106
|
+
|
107
|
+
private
|
108
|
+
|
109
|
+
def conditions
|
110
|
+
@xml.at_xpath("//saml:Conditions", {"saml" => SAML})
|
111
|
+
end
|
112
|
+
|
113
|
+
end
|
114
|
+
|
115
|
+
end
|
116
|
+
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe OmniAuth::Strategies::SAML_RSTR::AuthRequest do
|
4
|
+
describe :create do
|
5
|
+
let(:url) do
|
6
|
+
described_class.new.create(
|
7
|
+
{
|
8
|
+
:idp_sso_target_url => 'example.com',
|
9
|
+
:assertion_consumer_service_url => 'http://example.com/auth/saml-rstr/callback',
|
10
|
+
:issuer => 'This is an issuer',
|
11
|
+
:name_identifier_format => 'Some Policy'
|
12
|
+
},
|
13
|
+
{
|
14
|
+
:some_param => 'foo',
|
15
|
+
:some_other => 'bar'
|
16
|
+
}
|
17
|
+
)
|
18
|
+
end
|
19
|
+
let(:saml_request) { url.match(/SAMLRequest=(.*)/)[1] }
|
20
|
+
|
21
|
+
describe "the url" do
|
22
|
+
subject { url }
|
23
|
+
|
24
|
+
it "should contain a SAMLRequest query string param" do
|
25
|
+
subject.should match /^example\.com\?SAMLRequest=/
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should contain any other parameters passed through" do
|
29
|
+
subject.should match /^example\.com\?SAMLRequest=(.*)&some_param=foo&some_other=bar/
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
describe "the saml request" do
|
34
|
+
subject { saml_request }
|
35
|
+
|
36
|
+
let(:decoded) do
|
37
|
+
cgi_unescaped = CGI.unescape(subject)
|
38
|
+
base64_decoded = Base64.decode64(cgi_unescaped)
|
39
|
+
Zlib::Inflate.new(-Zlib::MAX_WBITS).inflate(base64_decoded)
|
40
|
+
end
|
41
|
+
|
42
|
+
let(:xml) { REXML::Document.new(decoded) }
|
43
|
+
let(:root_element) { REXML::XPath.first(xml, '//samlp:AuthnRequest') }
|
44
|
+
|
45
|
+
it "should contain base64 encoded and zlib deflated xml" do
|
46
|
+
decoded.should match /^<samlp:AuthnRequest/
|
47
|
+
end
|
48
|
+
|
49
|
+
it "should contain a uuid with an underscore in front" do
|
50
|
+
UUID.any_instance.stub(:generate).and_return('MY_UUID')
|
51
|
+
|
52
|
+
root_element.attributes['ID'].should == '_MY_UUID'
|
53
|
+
end
|
54
|
+
|
55
|
+
it "should contain the current time as the IssueInstant" do
|
56
|
+
t = Time.now
|
57
|
+
Time.stub(:now).and_return(t)
|
58
|
+
|
59
|
+
root_element.attributes['IssueInstant'].should == t.utc.iso8601
|
60
|
+
end
|
61
|
+
|
62
|
+
it "should contain the callback url in the settings" do
|
63
|
+
root_element.attributes['AssertionConsumerServiceURL'].should == 'http://example.com/auth/saml-rstr/callback'
|
64
|
+
end
|
65
|
+
|
66
|
+
it "should contain the issuer" do
|
67
|
+
REXML::XPath.first(xml, '//saml:Issuer').text.should == 'This is an issuer'
|
68
|
+
end
|
69
|
+
|
70
|
+
it "should contain the name identifier format" do
|
71
|
+
REXML::XPath.first(xml, '//samlp:NameIDPolicy').attributes['Format'].should == 'Some Policy'
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe OmniAuth::Strategies::SAML_RSTR::AuthResponse do
|
4
|
+
let(:xml) { :rstr_response }
|
5
|
+
subject { described_class.new(load_xml(xml)) }
|
6
|
+
|
7
|
+
describe :initialize do
|
8
|
+
context "when the response is nil" do
|
9
|
+
it "should raise an exception" do
|
10
|
+
expect { described_class.new(nil) }.to raise_error ArgumentError
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
# 2012-07-25T21:16:34.271Z
|
15
|
+
describe :session_expires_at do
|
16
|
+
it "should return the SessionNotOnOrAfter as a Ruby date" do
|
17
|
+
subject.session_expires_at.to_i.should == Time.new(2012, 07, 25, 21, 16, 34, 0).to_i
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
describe :name_id do
|
22
|
+
it "should load the name id from the assertion" do
|
23
|
+
subject.name_id.should == 'highgroove@thinkthroughmath.com'
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe :valid? do
|
28
|
+
it_should_behave_like 'a validating method', true
|
29
|
+
end
|
30
|
+
|
31
|
+
describe :validate! do
|
32
|
+
it_should_behave_like 'a validating method', false
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec::Matchers.define :fail_with do |message|
|
4
|
+
match do |actual|
|
5
|
+
actual.redirect? && actual.location.include?("/auth/failure?message")
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
def post_xml(xml=:rstr_response)
|
10
|
+
post "/auth/saml_rstr/callback", {'wresult' => load_xml(xml)}
|
11
|
+
end
|
12
|
+
|
13
|
+
describe OmniAuth::Strategies::SAML_RSTR, :type => :strategy do
|
14
|
+
include OmniAuth::Test::StrategyTestCase
|
15
|
+
let(:invalid_ticket){ OmniAuth::Error.new }
|
16
|
+
let(:auth_hash){ last_request.env['omniauth.auth'] }
|
17
|
+
let(:saml_options) do
|
18
|
+
{
|
19
|
+
:assertion_consumer_service_url => "http://localhost:3000/auth/saml_rstr/callback",
|
20
|
+
:issuer => "https://saml.issuer.url/issuers/29490",
|
21
|
+
:idp_sso_target_url => "https://idp.sso.target_url/signon/29490",
|
22
|
+
:idp_cert_fingerprint => "76:C5:6A:64:E0:D8:81:44:11:24:F2:9C:1B:41:56:27:6E:3B:FB:8C",
|
23
|
+
:name_identifier_format => "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
|
24
|
+
}
|
25
|
+
end
|
26
|
+
let(:strategy) { [OmniAuth::Strategies::SAML_RSTR, saml_options] }
|
27
|
+
|
28
|
+
describe 'GET /auth/saml_rstr' do
|
29
|
+
before do
|
30
|
+
get '/auth/saml_rstr'
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'should get authentication page' do
|
34
|
+
last_response.should be_redirect
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
describe 'POST /auth/saml_rstr/callback' do
|
39
|
+
subject { last_response }
|
40
|
+
let(:xml) { :rstr_response }
|
41
|
+
before :each do
|
42
|
+
Time.stub(:now).and_return(Time.new(2012, 3, 8, 16, 25, 00, 0))
|
43
|
+
end
|
44
|
+
|
45
|
+
context "when the response is valid" do
|
46
|
+
puts "when the response is valid"
|
47
|
+
before :each do
|
48
|
+
post_xml
|
49
|
+
end
|
50
|
+
|
51
|
+
it "should set the uid to the nameID in the SAML response" do
|
52
|
+
auth_hash['uid'].should == 'highgroove@thinkthroughmath.com'
|
53
|
+
end
|
54
|
+
|
55
|
+
it "should set the raw info to all attributes" do
|
56
|
+
auth_hash['extra']['raw_info'].to_hash.should == {
|
57
|
+
'userEmailID' => 'highgroove@thinkthroughmath.com'
|
58
|
+
}
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
context "when there is no wresult parameter" do
|
63
|
+
before :each do
|
64
|
+
post '/auth/saml_rstr/callback'
|
65
|
+
end
|
66
|
+
it { should fail_with(:invalid_ticket) }
|
67
|
+
end
|
68
|
+
|
69
|
+
context "when there is no name id in the XML" do
|
70
|
+
before :each do
|
71
|
+
post_xml :rstr_no_name
|
72
|
+
end
|
73
|
+
|
74
|
+
it { should fail_with(:invalid_ticket) }
|
75
|
+
end
|
76
|
+
|
77
|
+
context "when the fingerprint is invalid" do
|
78
|
+
before :each do
|
79
|
+
saml_options[:idp_cert_fingerprint] = "E6:87:89:FB:F2:5F:CD:B0:31:32:7E:05:44:84:53:B1:EC:4E:3F:gg"
|
80
|
+
post_xml
|
81
|
+
end
|
82
|
+
it { should fail_with(:invalid_ticket) }
|
83
|
+
# it {should raise_error(OmniAuth::Strategies::SAML_RSTR::ValidationError, "Fingerprint validation error")}
|
84
|
+
end
|
85
|
+
|
86
|
+
context "when the digest is invalid" do
|
87
|
+
before :each do
|
88
|
+
post_xml :digest_mismatch
|
89
|
+
end
|
90
|
+
|
91
|
+
it { should fail_with(:invalid_ticket) }
|
92
|
+
end
|
93
|
+
|
94
|
+
context "when the signature is invalid" do
|
95
|
+
before :each do
|
96
|
+
post_xml :rstr_invalid_signature
|
97
|
+
puts "invalid signature"
|
98
|
+
end
|
99
|
+
it { should fail_with(:invalid_ticket) }
|
100
|
+
end
|
101
|
+
|
102
|
+
context "when the time is before the NotBefore date" do
|
103
|
+
before :each do
|
104
|
+
Time.stub(:now).and_return(Time.new(2000, 3, 8, 16, 25, 00, 0))
|
105
|
+
post_xml
|
106
|
+
end
|
107
|
+
|
108
|
+
it { should fail_with(:invalid_ticket) }
|
109
|
+
end
|
110
|
+
|
111
|
+
context "when the time is after the NotOnOrAfter date" do
|
112
|
+
before :each do
|
113
|
+
Time.stub(:now).and_return(Time.new(3000, 3, 8, 16, 25, 00, 0))
|
114
|
+
post_xml
|
115
|
+
end
|
116
|
+
|
117
|
+
it { should fail_with(:invalid_ticket) }
|
118
|
+
end
|
119
|
+
|
120
|
+
|
121
|
+
end
|
122
|
+
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
def assert_is_valid(soft)
|
2
|
+
if soft
|
3
|
+
it { should be_valid }
|
4
|
+
else
|
5
|
+
it "should be valid" do
|
6
|
+
expect { subject.validate! }.not_to raise_error
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def assert_is_not_valid(soft)
|
12
|
+
if soft
|
13
|
+
it { should_not be_valid }
|
14
|
+
else
|
15
|
+
it "should be invalid" do
|
16
|
+
expect { subject.validate! }.to raise_error
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def stub_validate_to_fail(soft)
|
22
|
+
if soft
|
23
|
+
subject.security_token_content.stub(:validate).and_return(false)
|
24
|
+
else
|
25
|
+
subject.security_token_content.stub(:validate).and_raise(Exception)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
shared_examples_for 'a validating method' do |soft|
|
30
|
+
before :each do
|
31
|
+
subject.settings = mock(Object, :idp_cert_fingerprint => 'FINGERPRINT', :idp_cert => nil)
|
32
|
+
subject.security_token_content.stub(:validate).and_return(true)
|
33
|
+
end
|
34
|
+
|
35
|
+
context "when the response is empty" do
|
36
|
+
subject { described_class.new('') }
|
37
|
+
|
38
|
+
assert_is_not_valid(soft)
|
39
|
+
end
|
40
|
+
|
41
|
+
context "when the settings are nil" do
|
42
|
+
before :each do
|
43
|
+
subject.settings = nil
|
44
|
+
end
|
45
|
+
|
46
|
+
assert_is_not_valid(soft)
|
47
|
+
end
|
48
|
+
|
49
|
+
context "when there is no idp_cert_fingerprint and idp_cert" do
|
50
|
+
before :each do
|
51
|
+
subject.settings = mock(Object, :idp_cert_fingerprint => nil, :idp_cert => nil)
|
52
|
+
end
|
53
|
+
|
54
|
+
assert_is_not_valid(soft)
|
55
|
+
end
|
56
|
+
|
57
|
+
context "when conditions are not given" do
|
58
|
+
let(:xml) { :rstr_response_no_conditions }
|
59
|
+
assert_is_valid(soft)
|
60
|
+
end
|
61
|
+
|
62
|
+
context "when the current time is before the NotBefore time" do
|
63
|
+
before :each do
|
64
|
+
Time.stub(:now).and_return(Time.new(2000, 01, 01, 10, 00, 00, 0))
|
65
|
+
end
|
66
|
+
|
67
|
+
assert_is_not_valid(soft)
|
68
|
+
end
|
69
|
+
|
70
|
+
context "when the current time is after the NotOnOrAfter time" do
|
71
|
+
before :each do
|
72
|
+
# We're assuming here that this code will be out of use in 1000 years...
|
73
|
+
Time.stub(:now).and_return(Time.new(3012, 01, 01, 10, 00, 00, 0))
|
74
|
+
end
|
75
|
+
|
76
|
+
assert_is_not_valid(soft)
|
77
|
+
end
|
78
|
+
|
79
|
+
context "when the current time is between the NotBefore and NotOnOrAfter times" do
|
80
|
+
before :each do
|
81
|
+
Time.stub(:now).and_return(Time.new(2012, 3, 8, 16, 25, 00, 0))
|
82
|
+
end
|
83
|
+
|
84
|
+
assert_is_valid(soft)
|
85
|
+
end
|
86
|
+
|
87
|
+
context "when skip_conditions option is given" do
|
88
|
+
before :each do
|
89
|
+
subject.options[:skip_conditions] = true
|
90
|
+
end
|
91
|
+
|
92
|
+
assert_is_valid(soft)
|
93
|
+
end
|
94
|
+
|
95
|
+
context "when the SAML document is valid" do
|
96
|
+
before :each do
|
97
|
+
subject.document.should_receive(:validate).with('FINGERPRINT', soft).and_return(true)
|
98
|
+
subject.options[:skip_conditions] = true
|
99
|
+
end
|
100
|
+
|
101
|
+
assert_is_valid(soft)
|
102
|
+
end
|
103
|
+
|
104
|
+
context "when the SAML document is valid and the idp_cert is given" do
|
105
|
+
let(:cert) do
|
106
|
+
filename = File.expand_path(File.join('..', '..', 'support', "example_cert.pem"), __FILE__)
|
107
|
+
IO.read(filename)
|
108
|
+
end
|
109
|
+
let(:expected) { 'E6:87:89:FB:F2:5F:CD:B0:31:32:7E:05:44:84:53:B1:EC:4E:3F:FA' }
|
110
|
+
|
111
|
+
before :each do
|
112
|
+
subject.settings.stub(:idp_cert).and_return(cert)
|
113
|
+
subject.document.should_receive(:validate).with(expected, soft).and_return(true)
|
114
|
+
subject.options[:skip_conditions] = true
|
115
|
+
end
|
116
|
+
|
117
|
+
assert_is_valid(soft)
|
118
|
+
end
|
119
|
+
|
120
|
+
context "when the SAML document is invalid" do
|
121
|
+
before :each do
|
122
|
+
stub_validate_to_fail(soft)
|
123
|
+
subject.options[:skip_conditions] = true
|
124
|
+
end
|
125
|
+
|
126
|
+
assert_is_not_valid(soft)
|
127
|
+
end
|
128
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'simplecov'
|
2
|
+
SimpleCov.start
|
3
|
+
require 'omniauth-saml-rstr'
|
4
|
+
require 'rack/test'
|
5
|
+
require 'rexml/document'
|
6
|
+
require 'rexml/xpath'
|
7
|
+
require 'base64'
|
8
|
+
require File.expand_path('../shared/validating_method.rb', __FILE__)
|
9
|
+
|
10
|
+
RSpec.configure do |config|
|
11
|
+
config.include Rack::Test::Methods
|
12
|
+
end
|
13
|
+
|
14
|
+
def load_xml(filename=:rstr_response)
|
15
|
+
filename = File.expand_path(File.join('..', 'support', "#{filename.to_s}.xml"), __FILE__)
|
16
|
+
result = IO.read(filename)
|
17
|
+
end
|
metadata
ADDED
@@ -0,0 +1,208 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: omniauth-saml-rstr
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Josh Skeen
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-07-31 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: omniauth
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '1.0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '1.0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: xmlcanonicalizer
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - '='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: 0.1.1
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - '='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: 0.1.1
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: uuid
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ~>
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '2.3'
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '2.3'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: guard
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - '='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: 1.0.1
|
70
|
+
type: :development
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - '='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: 1.0.1
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: guard-rspec
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - '='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: 0.6.0
|
86
|
+
type: :development
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - '='
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: 0.6.0
|
94
|
+
- !ruby/object:Gem::Dependency
|
95
|
+
name: rspec
|
96
|
+
requirement: !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - '='
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '2.8'
|
102
|
+
type: :development
|
103
|
+
prerelease: false
|
104
|
+
version_requirements: !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - '='
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '2.8'
|
110
|
+
- !ruby/object:Gem::Dependency
|
111
|
+
name: simplecov
|
112
|
+
requirement: !ruby/object:Gem::Requirement
|
113
|
+
none: false
|
114
|
+
requirements:
|
115
|
+
- - '='
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: 0.6.1
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
none: false
|
122
|
+
requirements:
|
123
|
+
- - '='
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: 0.6.1
|
126
|
+
- !ruby/object:Gem::Dependency
|
127
|
+
name: rack-test
|
128
|
+
requirement: !ruby/object:Gem::Requirement
|
129
|
+
none: false
|
130
|
+
requirements:
|
131
|
+
- - '='
|
132
|
+
- !ruby/object:Gem::Version
|
133
|
+
version: 0.6.1
|
134
|
+
type: :development
|
135
|
+
prerelease: false
|
136
|
+
version_requirements: !ruby/object:Gem::Requirement
|
137
|
+
none: false
|
138
|
+
requirements:
|
139
|
+
- - '='
|
140
|
+
- !ruby/object:Gem::Version
|
141
|
+
version: 0.6.1
|
142
|
+
- !ruby/object:Gem::Dependency
|
143
|
+
name: nokogiri
|
144
|
+
requirement: !ruby/object:Gem::Requirement
|
145
|
+
none: false
|
146
|
+
requirements:
|
147
|
+
- - '='
|
148
|
+
- !ruby/object:Gem::Version
|
149
|
+
version: 1.5.5
|
150
|
+
type: :development
|
151
|
+
prerelease: false
|
152
|
+
version_requirements: !ruby/object:Gem::Requirement
|
153
|
+
none: false
|
154
|
+
requirements:
|
155
|
+
- - '='
|
156
|
+
- !ruby/object:Gem::Version
|
157
|
+
version: 1.5.5
|
158
|
+
description: A RequestSecurityTokenResponse for ADFS strategy based on https://github.com/PracticallyGreen/omniauth-saml.
|
159
|
+
email: josh@highgroove.com
|
160
|
+
executables: []
|
161
|
+
extensions: []
|
162
|
+
extra_rdoc_files: []
|
163
|
+
files:
|
164
|
+
- README.md
|
165
|
+
- lib/omniauth/strategies/saml-rstr/auth_request.rb
|
166
|
+
- lib/omniauth/strategies/saml-rstr/auth_response.rb
|
167
|
+
- lib/omniauth/strategies/saml-rstr/validation_error.rb
|
168
|
+
- lib/omniauth/strategies/saml-rstr/xml_security.rb
|
169
|
+
- lib/omniauth/strategies/saml-rstr.rb
|
170
|
+
- lib/omniauth-saml-rstr/version.rb
|
171
|
+
- lib/omniauth-saml-rstr.rb
|
172
|
+
- spec/omniauth/strategies/saml-rstr/auth_request_spec.rb
|
173
|
+
- spec/omniauth/strategies/saml-rstr/auth_response_spec.rb
|
174
|
+
- spec/omniauth/strategies/saml-rstr/validation_error_spec.rb
|
175
|
+
- spec/omniauth/strategies/saml_rstr_spec.rb
|
176
|
+
- spec/shared/validating_method.rb
|
177
|
+
- spec/spec_helper.rb
|
178
|
+
homepage: https://github.com/mutexkid/omniauth-saml-rstr
|
179
|
+
licenses: []
|
180
|
+
post_install_message:
|
181
|
+
rdoc_options: []
|
182
|
+
require_paths:
|
183
|
+
- lib
|
184
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
185
|
+
none: false
|
186
|
+
requirements:
|
187
|
+
- - ! '>='
|
188
|
+
- !ruby/object:Gem::Version
|
189
|
+
version: '0'
|
190
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
191
|
+
none: false
|
192
|
+
requirements:
|
193
|
+
- - ! '>='
|
194
|
+
- !ruby/object:Gem::Version
|
195
|
+
version: '0'
|
196
|
+
requirements: []
|
197
|
+
rubyforge_project:
|
198
|
+
rubygems_version: 1.8.24
|
199
|
+
signing_key:
|
200
|
+
specification_version: 3
|
201
|
+
summary: A RequestSecurityTokenResponse for ADFS strategy based on https://github.com/PracticallyGreen/omniauth-saml.
|
202
|
+
test_files:
|
203
|
+
- spec/omniauth/strategies/saml-rstr/auth_request_spec.rb
|
204
|
+
- spec/omniauth/strategies/saml-rstr/auth_response_spec.rb
|
205
|
+
- spec/omniauth/strategies/saml-rstr/validation_error_spec.rb
|
206
|
+
- spec/omniauth/strategies/saml_rstr_spec.rb
|
207
|
+
- spec/shared/validating_method.rb
|
208
|
+
- spec/spec_helper.rb
|