omniauth-saml-rstr 0.1.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.
- 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
|