ruby-saml 0.6.0 → 0.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 ruby-saml might be problematic. Click here for more details.
- data/.gitignore +1 -0
- data/lib/onelogin/ruby-saml/authrequest.rb +5 -3
- data/lib/onelogin/ruby-saml/logoutrequest.rb +3 -1
- data/lib/onelogin/ruby-saml/logoutresponse.rb +154 -0
- data/lib/onelogin/ruby-saml/metadata.rb +26 -7
- data/lib/onelogin/ruby-saml/response.rb +19 -15
- data/lib/onelogin/ruby-saml/settings.rb +8 -1
- data/lib/onelogin/ruby-saml/version.rb +1 -1
- data/lib/ruby-saml.rb +1 -0
- data/lib/xml_security.rb +2 -2
- data/test/logoutrequest_test.rb +13 -0
- data/test/logoutresponse_test.rb +116 -0
- data/test/request_test.rb +12 -0
- data/test/responses/logoutresponse_fixtures.rb +67 -0
- data/test/responses/starfield_response.xml.base64 +1 -0
- data/test/settings_test.rb +2 -1
- data/test/xml_security_test.rb +11 -1
- metadata +10 -3
data/.gitignore
CHANGED
@@ -10,6 +10,8 @@ module Onelogin
|
|
10
10
|
include REXML
|
11
11
|
class Authrequest
|
12
12
|
def create(settings, params = {})
|
13
|
+
params = {} if params.nil?
|
14
|
+
|
13
15
|
request_doc = create_authentication_xml_doc(settings)
|
14
16
|
|
15
17
|
request = ""
|
@@ -17,14 +19,14 @@ module Onelogin
|
|
17
19
|
|
18
20
|
Logging.debug "Created AuthnRequest: #{request}"
|
19
21
|
|
20
|
-
|
21
|
-
base64_request = Base64.encode64(
|
22
|
+
request = Zlib::Deflate.deflate(request, 9)[2..-5] if settings.compress_request
|
23
|
+
base64_request = Base64.encode64(request)
|
22
24
|
encoded_request = CGI.escape(base64_request)
|
23
25
|
params_prefix = (settings.idp_sso_target_url =~ /\?/) ? '&' : '?'
|
24
26
|
request_params = "#{params_prefix}SAMLRequest=#{encoded_request}"
|
25
27
|
|
26
28
|
params.each_pair do |key, value|
|
27
|
-
request_params << "&#{key}=#{CGI.escape(value.to_s)}"
|
29
|
+
request_params << "&#{key.to_s}=#{CGI.escape(value.to_s)}"
|
28
30
|
end
|
29
31
|
|
30
32
|
settings.idp_sso_target_url + request_params
|
@@ -35,7 +35,7 @@ module Onelogin
|
|
35
35
|
|
36
36
|
def create_unauth_xml_doc(settings, params)
|
37
37
|
|
38
|
-
time = Time.new().strftime("%Y-%m-%dT%H:%M:%
|
38
|
+
time = Time.new().strftime("%Y-%m-%dT%H:%M:%S")
|
39
39
|
|
40
40
|
request_doc = REXML::Document.new
|
41
41
|
root = request_doc.add_element "samlp:LogoutRequest", { "xmlns:samlp" => "urn:oasis:names:tc:SAML:2.0:protocol" }
|
@@ -53,6 +53,8 @@ module Onelogin
|
|
53
53
|
name_id.attributes['NameQualifier'] = settings.sp_name_qualifier if settings.sp_name_qualifier
|
54
54
|
name_id.attributes['Format'] = settings.name_identifier_format if settings.name_identifier_format
|
55
55
|
name_id.text = settings.name_identifier_value
|
56
|
+
else
|
57
|
+
raise ValidationError.new("Missing required name identifier")
|
56
58
|
end
|
57
59
|
|
58
60
|
if settings.sessionindex
|
@@ -0,0 +1,154 @@
|
|
1
|
+
require "xml_security"
|
2
|
+
require "time"
|
3
|
+
require "base64"
|
4
|
+
require "zlib"
|
5
|
+
require "open-uri"
|
6
|
+
|
7
|
+
module Onelogin
|
8
|
+
module Saml
|
9
|
+
class Logoutresponse
|
10
|
+
|
11
|
+
ASSERTION = "urn:oasis:names:tc:SAML:2.0:assertion"
|
12
|
+
PROTOCOL = "urn:oasis:names:tc:SAML:2.0:protocol"
|
13
|
+
|
14
|
+
# For API compability, this is mutable.
|
15
|
+
attr_accessor :settings
|
16
|
+
|
17
|
+
attr_reader :document
|
18
|
+
attr_reader :response
|
19
|
+
attr_reader :options
|
20
|
+
|
21
|
+
#
|
22
|
+
# In order to validate that the response matches a given request, append
|
23
|
+
# the option:
|
24
|
+
# :matches_request_id => REQUEST_ID
|
25
|
+
#
|
26
|
+
# It will validate that the logout response matches the ID of the request.
|
27
|
+
# You can also do this yourself through the in_response_to accessor.
|
28
|
+
#
|
29
|
+
def initialize(response, settings = nil, options = {})
|
30
|
+
raise ArgumentError.new("Logoutresponse cannot be nil") if response.nil?
|
31
|
+
self.settings = settings
|
32
|
+
|
33
|
+
@options = options
|
34
|
+
@response = decode_raw_response(response)
|
35
|
+
@document = XMLSecurity::SignedDocument.new(response)
|
36
|
+
end
|
37
|
+
|
38
|
+
def validate!
|
39
|
+
validate(false)
|
40
|
+
end
|
41
|
+
|
42
|
+
def validate(soft = true)
|
43
|
+
return false unless valid_saml?(soft) && valid_state?(soft)
|
44
|
+
|
45
|
+
valid_in_response_to?(soft) && valid_issuer?(soft) && success?(soft)
|
46
|
+
end
|
47
|
+
|
48
|
+
def success?(soft = true)
|
49
|
+
unless status_code == "urn:oasis:names:tc:SAML:2.0:status:Success"
|
50
|
+
return soft ? false : validation_error("Bad status code. Expected <urn:oasis:names:tc:SAML:2.0:status:Success>, but was: <#@status_code> ")
|
51
|
+
end
|
52
|
+
true
|
53
|
+
end
|
54
|
+
|
55
|
+
def in_response_to
|
56
|
+
@in_response_to ||= begin
|
57
|
+
node = REXML::XPath.first(document, "/p:LogoutResponse", { "p" => PROTOCOL, "a" => ASSERTION })
|
58
|
+
node.nil? ? nil : node.attributes['InResponseTo']
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def issuer
|
63
|
+
@issuer ||= begin
|
64
|
+
node = REXML::XPath.first(document, "/p:LogoutResponse/a:Issuer", { "p" => PROTOCOL, "a" => ASSERTION })
|
65
|
+
node ||= REXML::XPath.first(document, "/p:LogoutResponse/a:Assertion/a:Issuer", { "p" => PROTOCOL, "a" => ASSERTION })
|
66
|
+
node.nil? ? nil : node.text
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def status_code
|
71
|
+
@status_code ||= begin
|
72
|
+
node = REXML::XPath.first(document, "/p:LogoutResponse/p:Status/p:StatusCode", { "p" => PROTOCOL, "a" => ASSERTION })
|
73
|
+
node.nil? ? nil : node.attributes["Value"]
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
|
79
|
+
def decode(encoded)
|
80
|
+
Base64.decode64(encoded)
|
81
|
+
end
|
82
|
+
|
83
|
+
def inflate(deflated)
|
84
|
+
zlib = Zlib::Inflate.new(-Zlib::MAX_WBITS)
|
85
|
+
zlib.inflate(deflated)
|
86
|
+
end
|
87
|
+
|
88
|
+
def decode_raw_response(response)
|
89
|
+
if response =~ /^</
|
90
|
+
return response
|
91
|
+
elsif (decoded = decode(response)) =~ /^</
|
92
|
+
return decoded
|
93
|
+
elsif (inflated = inflate(decoded)) =~ /^</
|
94
|
+
return inflated
|
95
|
+
end
|
96
|
+
|
97
|
+
raise "Couldn't decode SAMLResponse"
|
98
|
+
end
|
99
|
+
|
100
|
+
def valid_saml?(soft = true)
|
101
|
+
Dir.chdir(File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'schemas'))) do
|
102
|
+
@schema = Nokogiri::XML::Schema(IO.read('saml20protocol_schema.xsd'))
|
103
|
+
@xml = Nokogiri::XML(self.document.to_s)
|
104
|
+
end
|
105
|
+
if soft
|
106
|
+
@schema.validate(@xml).map{ return false }
|
107
|
+
else
|
108
|
+
@schema.validate(@xml).map{ |error| raise(Exception.new("#{error.message}\n\n#{@xml.to_s}")) }
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def valid_state?(soft = true)
|
113
|
+
if response.empty?
|
114
|
+
return soft ? false : validation_error("Blank response")
|
115
|
+
end
|
116
|
+
|
117
|
+
if settings.nil?
|
118
|
+
return soft ? false : validation_error("No settings on response")
|
119
|
+
end
|
120
|
+
|
121
|
+
if settings.issuer.nil?
|
122
|
+
return soft ? false : validation_error("No issuer in settings")
|
123
|
+
end
|
124
|
+
|
125
|
+
if settings.idp_cert_fingerprint.nil? && settings.idp_cert.nil?
|
126
|
+
return soft ? false : validation_error("No fingerprint or certificate on settings")
|
127
|
+
end
|
128
|
+
|
129
|
+
true
|
130
|
+
end
|
131
|
+
|
132
|
+
def valid_in_response_to?(soft = true)
|
133
|
+
return true unless self.options.has_key? :matches_request_id
|
134
|
+
|
135
|
+
unless self.options[:matches_request_id] == in_response_to
|
136
|
+
return soft ? false : validation_error("Response does not match the request ID, expected: <#{self.options[:matches_request_id]}>, but was: <#{in_response_to}>")
|
137
|
+
end
|
138
|
+
|
139
|
+
true
|
140
|
+
end
|
141
|
+
|
142
|
+
def valid_issuer?(soft = true)
|
143
|
+
unless URI.parse(issuer) == URI.parse(self.settings.issuer)
|
144
|
+
return soft ? false : validation_error("Doesn't match the issuer, expected: <#{self.settings.issuer}>, but was: <#{issuer}>")
|
145
|
+
end
|
146
|
+
true
|
147
|
+
end
|
148
|
+
|
149
|
+
def validation_error(message)
|
150
|
+
raise ValidationError.new(message)
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
@@ -12,15 +12,29 @@ module Onelogin
|
|
12
12
|
class Metadata
|
13
13
|
def generate(settings)
|
14
14
|
meta_doc = REXML::Document.new
|
15
|
-
root = meta_doc.add_element "md:EntityDescriptor", {
|
16
|
-
"xmlns:md" => "urn:oasis:names:tc:SAML:2.0:metadata"
|
15
|
+
root = meta_doc.add_element "md:EntityDescriptor", {
|
16
|
+
"xmlns:md" => "urn:oasis:names:tc:SAML:2.0:metadata"
|
17
17
|
}
|
18
|
-
sp_sso = root.add_element "md:SPSSODescriptor", {
|
19
|
-
"protocolSupportEnumeration" => "urn:oasis:names:tc:SAML:2.0:protocol"
|
18
|
+
sp_sso = root.add_element "md:SPSSODescriptor", {
|
19
|
+
"protocolSupportEnumeration" => "urn:oasis:names:tc:SAML:2.0:protocol",
|
20
|
+
# Metadata request need not be signed (as we don't publish our cert)
|
21
|
+
"AuthnRequestsSigned" => false,
|
22
|
+
# However we would like assertions signed if idp_cert_fingerprint or idp_cert is set
|
23
|
+
"WantAssertionsSigned" => (!settings.idp_cert_fingerprint.nil? || !settings.idp_cert.nil?)
|
20
24
|
}
|
21
25
|
if settings.issuer != nil
|
22
26
|
root.attributes["entityID"] = settings.issuer
|
23
27
|
end
|
28
|
+
if settings.assertion_consumer_logout_service_url != nil
|
29
|
+
sp_sso.add_element "md:SingleLogoutService", {
|
30
|
+
# Add this as a setting to create different bindings?
|
31
|
+
"Binding" => "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect",
|
32
|
+
"Location" => settings.assertion_consumer_logout_service_url,
|
33
|
+
"ResponseLocation" => settings.assertion_consumer_logout_service_url,
|
34
|
+
"isDefault" => true,
|
35
|
+
"index" => 0
|
36
|
+
}
|
37
|
+
end
|
24
38
|
if settings.name_identifier_format != nil
|
25
39
|
name_id = sp_sso.add_element "md:NameIDFormat"
|
26
40
|
name_id.text = settings.name_identifier_format
|
@@ -29,9 +43,15 @@ module Onelogin
|
|
29
43
|
sp_sso.add_element "md:AssertionConsumerService", {
|
30
44
|
# Add this as a setting to create different bindings?
|
31
45
|
"Binding" => "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST",
|
32
|
-
"Location" => settings.assertion_consumer_service_url
|
46
|
+
"Location" => settings.assertion_consumer_service_url,
|
47
|
+
"isDefault" => true,
|
48
|
+
"index" => 0
|
33
49
|
}
|
34
50
|
end
|
51
|
+
# With OpenSSO, it might be required to also include
|
52
|
+
# <md:RoleDescriptor xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:query="urn:oasis:names:tc:SAML:metadata:ext:query" xsi:type="query:AttributeQueryDescriptorType" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol"/>
|
53
|
+
# <md:XACMLAuthzDecisionQueryDescriptor WantAssertionsSigned="false" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol"/>
|
54
|
+
|
35
55
|
meta_doc << REXML::XMLDecl.new
|
36
56
|
ret = ""
|
37
57
|
# pretty print the XML so IdP administrators can easily see what the SP supports
|
@@ -39,8 +59,7 @@ module Onelogin
|
|
39
59
|
|
40
60
|
Logging.debug "Generated metadata:\n#{ret}"
|
41
61
|
|
42
|
-
|
43
|
-
|
62
|
+
ret
|
44
63
|
end
|
45
64
|
end
|
46
65
|
end
|
@@ -11,22 +11,18 @@ module Onelogin
|
|
11
11
|
PROTOCOL = "urn:oasis:names:tc:SAML:2.0:protocol"
|
12
12
|
DSIG = "http://www.w3.org/2000/09/xmldsig#"
|
13
13
|
|
14
|
-
|
14
|
+
# TODO: This should probably be ctor initialized too... WDYT?
|
15
|
+
attr_accessor :settings
|
16
|
+
|
17
|
+
attr_reader :options
|
18
|
+
attr_reader :response
|
19
|
+
attr_reader :document
|
15
20
|
|
16
21
|
def initialize(response, options = {})
|
17
22
|
raise ArgumentError.new("Response cannot be nil") if response.nil?
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
begin
|
22
|
-
self.document = XMLSecurity::SignedDocument.new(Base64.decode64(response))
|
23
|
-
rescue REXML::ParseException => e
|
24
|
-
if response =~ /</
|
25
|
-
self.document = XMLSecurity::SignedDocument.new(response)
|
26
|
-
else
|
27
|
-
raise e
|
28
|
-
end
|
29
|
-
end
|
23
|
+
@options = options
|
24
|
+
@response = (response =~ /^</) ? response : Base64.decode64(response)
|
25
|
+
@document = XMLSecurity::SignedDocument.new(@response)
|
30
26
|
end
|
31
27
|
|
32
28
|
def is_valid?
|
@@ -46,6 +42,14 @@ module Onelogin
|
|
46
42
|
end
|
47
43
|
end
|
48
44
|
|
45
|
+
def sessionindex
|
46
|
+
@sessionindex ||= begin
|
47
|
+
node = REXML::XPath.first(document, "/p:Response/a:Assertion[@ID='#{document.signed_element_id}']/a:AuthnStatement", { "p" => PROTOCOL, "a" => ASSERTION })
|
48
|
+
node ||= REXML::XPath.first(document, "/p:Response[@ID='#{document.signed_element_id}']/a:Assertion/a:AuthnStatement", { "p" => PROTOCOL, "a" => ASSERTION })
|
49
|
+
node.nil? ? nil : node.attributes['SessionIndex']
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
49
53
|
# A hash of alle the attributes with the response. Assuming there is only one value for each key
|
50
54
|
def attributes
|
51
55
|
@attr_statements ||= begin
|
@@ -155,13 +159,13 @@ module Onelogin
|
|
155
159
|
return true if conditions.nil?
|
156
160
|
return true if options[:skip_conditions]
|
157
161
|
|
158
|
-
if not_before = parse_time(conditions, "NotBefore")
|
162
|
+
if (not_before = parse_time(conditions, "NotBefore"))
|
159
163
|
if Time.now.utc < not_before
|
160
164
|
return soft ? false : validation_error("Current time is earlier than NotBefore condition")
|
161
165
|
end
|
162
166
|
end
|
163
167
|
|
164
|
-
if not_on_or_after = parse_time(conditions, "NotOnOrAfter")
|
168
|
+
if (not_on_or_after = parse_time(conditions, "NotOnOrAfter"))
|
165
169
|
if Time.now.utc >= not_on_or_after
|
166
170
|
return soft ? false : validation_error("Current time is on or after NotOnOrAfter condition")
|
167
171
|
end
|
@@ -1,7 +1,8 @@
|
|
1
1
|
module Onelogin
|
2
2
|
module Saml
|
3
3
|
class Settings
|
4
|
-
def initialize(
|
4
|
+
def initialize(overrides = {})
|
5
|
+
config = DEFAULTS.merge(overrides)
|
5
6
|
config.each do |k,v|
|
6
7
|
acc = "#{k.to_s}=".to_sym
|
7
8
|
self.send(acc, v) if self.respond_to? acc
|
@@ -13,6 +14,12 @@ module Onelogin
|
|
13
14
|
attr_accessor :idp_slo_target_url
|
14
15
|
attr_accessor :name_identifier_value
|
15
16
|
attr_accessor :sessionindex
|
17
|
+
attr_accessor :assertion_consumer_logout_service_url
|
18
|
+
attr_accessor :compress_request
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
DEFAULTS = {:compress_request => true}
|
16
23
|
end
|
17
24
|
end
|
18
25
|
end
|
data/lib/ruby-saml.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'onelogin/ruby-saml/logging'
|
2
2
|
require 'onelogin/ruby-saml/authrequest'
|
3
3
|
require 'onelogin/ruby-saml/logoutrequest'
|
4
|
+
require 'onelogin/ruby-saml/logoutresponse'
|
4
5
|
require 'onelogin/ruby-saml/response'
|
5
6
|
require 'onelogin/ruby-saml/settings'
|
6
7
|
require 'onelogin/ruby-saml/validation_error'
|
data/lib/xml_security.rb
CHANGED
@@ -80,7 +80,7 @@ module XMLSecurity
|
|
80
80
|
signed_info_element = REXML::XPath.first(sig_element, "//ds:SignedInfo", {"ds"=>DSIG})
|
81
81
|
self.noko_sig_element ||= document.at_xpath('//ds:Signature', 'ds' => DSIG)
|
82
82
|
noko_signed_info_element = noko_sig_element.at_xpath('./ds:SignedInfo', 'ds' => DSIG)
|
83
|
-
canon_algorithm = canon_algorithm REXML::XPath.first(sig_element, '//ds:CanonicalizationMethod')
|
83
|
+
canon_algorithm = canon_algorithm REXML::XPath.first(sig_element, '//ds:CanonicalizationMethod', 'ds' => DSIG)
|
84
84
|
canon_string = noko_signed_info_element.canonicalize(canon_algorithm)
|
85
85
|
noko_sig_element.remove
|
86
86
|
|
@@ -89,7 +89,7 @@ module XMLSecurity
|
|
89
89
|
uri = ref.attributes.get_attribute("URI").value
|
90
90
|
|
91
91
|
hashed_element = document.at_xpath("//*[@ID='#{uri[1..-1]}']")
|
92
|
-
canon_algorithm = canon_algorithm REXML::XPath.first(ref, '//ds:CanonicalizationMethod')
|
92
|
+
canon_algorithm = canon_algorithm REXML::XPath.first(ref, '//ds:CanonicalizationMethod', 'ds' => DSIG)
|
93
93
|
canon_hashed_element = hashed_element.canonicalize(canon_algorithm, inclusive_namespaces).gsub('&','&')
|
94
94
|
|
95
95
|
digest_algorithm = algorithm(REXML::XPath.first(ref, "//ds:DigestMethod"))
|
data/test/logoutrequest_test.rb
CHANGED
@@ -7,6 +7,8 @@ class RequestTest < Test::Unit::TestCase
|
|
7
7
|
|
8
8
|
should "create the deflated SAMLRequest URL parameter" do
|
9
9
|
settings.idp_slo_target_url = "http://unauth.com/logout"
|
10
|
+
settings.name_identifier_value = "f00f00"
|
11
|
+
|
10
12
|
unauth_url = Onelogin::Saml::Logoutrequest.new.create(settings)
|
11
13
|
assert unauth_url =~ /^http:\/\/unauth\.com\/logout\?SAMLRequest=/
|
12
14
|
|
@@ -50,10 +52,19 @@ class RequestTest < Test::Unit::TestCase
|
|
50
52
|
assert_match %r(#{name_identifier_value}</saml:NameID>), inflated
|
51
53
|
end
|
52
54
|
|
55
|
+
should "require name_identifier_value" do
|
56
|
+
settings = Onelogin::Saml::Settings.new
|
57
|
+
settings.idp_slo_target_url = "http://example.com"
|
58
|
+
settings.name_identifier_format = nil
|
59
|
+
|
60
|
+
assert_raises(Onelogin::Saml::ValidationError) { Onelogin::Saml::Logoutrequest.new.create(settings) }
|
61
|
+
end
|
62
|
+
|
53
63
|
context "when the target url doesn't contain a query string" do
|
54
64
|
should "create the SAMLRequest parameter correctly" do
|
55
65
|
settings = Onelogin::Saml::Settings.new
|
56
66
|
settings.idp_slo_target_url = "http://example.com"
|
67
|
+
settings.name_identifier_value = "f00f00"
|
57
68
|
|
58
69
|
unauth_url = Onelogin::Saml::Logoutrequest.new.create(settings)
|
59
70
|
assert unauth_url =~ /^http:\/\/example.com\?SAMLRequest/
|
@@ -64,6 +75,7 @@ class RequestTest < Test::Unit::TestCase
|
|
64
75
|
should "create the SAMLRequest parameter correctly" do
|
65
76
|
settings = Onelogin::Saml::Settings.new
|
66
77
|
settings.idp_slo_target_url = "http://example.com?field=value"
|
78
|
+
settings.name_identifier_value = "f00f00"
|
67
79
|
|
68
80
|
unauth_url = Onelogin::Saml::Logoutrequest.new.create(settings)
|
69
81
|
assert unauth_url =~ /^http:\/\/example.com\?field=value&SAMLRequest/
|
@@ -74,6 +86,7 @@ class RequestTest < Test::Unit::TestCase
|
|
74
86
|
should "have access to the request uuid" do
|
75
87
|
settings = Onelogin::Saml::Settings.new
|
76
88
|
settings.idp_slo_target_url = "http://example.com?field=value"
|
89
|
+
settings.name_identifier_value = "f00f00"
|
77
90
|
|
78
91
|
unauth_req = Onelogin::Saml::Logoutrequest.new
|
79
92
|
unauth_url = unauth_req.create(settings)
|
@@ -0,0 +1,116 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), "test_helper"))
|
2
|
+
require 'rexml/document'
|
3
|
+
require 'responses/logoutresponse_fixtures'
|
4
|
+
class RubySamlTest < Test::Unit::TestCase
|
5
|
+
|
6
|
+
context "Logoutresponse" do
|
7
|
+
context "#new" do
|
8
|
+
should "raise an exception when response is initialized with nil" do
|
9
|
+
assert_raises(ArgumentError) { Onelogin::Saml::Logoutresponse.new(nil) }
|
10
|
+
end
|
11
|
+
should "default to empty settings" do
|
12
|
+
logoutresponse = Onelogin::Saml::Logoutresponse.new( valid_response)
|
13
|
+
assert logoutresponse.settings.nil?
|
14
|
+
end
|
15
|
+
should "accept constructor-injected settings" do
|
16
|
+
logoutresponse = Onelogin::Saml::Logoutresponse.new(valid_response, settings)
|
17
|
+
assert !logoutresponse.settings.nil?
|
18
|
+
end
|
19
|
+
should "accept constructor-injected options" do
|
20
|
+
logoutresponse = Onelogin::Saml::Logoutresponse.new(valid_response, nil, { :foo => :bar} )
|
21
|
+
assert !logoutresponse.options.empty?
|
22
|
+
end
|
23
|
+
should "support base64 encoded responses" do
|
24
|
+
expected_response = valid_response
|
25
|
+
logoutresponse = Onelogin::Saml::Logoutresponse.new(Base64.encode64(expected_response), settings)
|
26
|
+
|
27
|
+
assert_equal expected_response, logoutresponse.response
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
context "#validate" do
|
32
|
+
should "validate the response" do
|
33
|
+
in_relation_to_request_id = random_id
|
34
|
+
|
35
|
+
logoutresponse = Onelogin::Saml::Logoutresponse.new(valid_response({:uuid => in_relation_to_request_id}), settings)
|
36
|
+
|
37
|
+
assert logoutresponse.validate
|
38
|
+
|
39
|
+
assert_equal settings.issuer, logoutresponse.issuer
|
40
|
+
assert_equal in_relation_to_request_id, logoutresponse.in_response_to
|
41
|
+
|
42
|
+
assert logoutresponse.success?
|
43
|
+
end
|
44
|
+
|
45
|
+
should "invalidate responses with wrong id when given option :matches_uuid" do
|
46
|
+
|
47
|
+
expected_request_id = "_some_other_expected_uuid"
|
48
|
+
opts = { :matches_request_id => expected_request_id}
|
49
|
+
|
50
|
+
logoutresponse = Onelogin::Saml::Logoutresponse.new(valid_response, settings, opts)
|
51
|
+
|
52
|
+
assert !logoutresponse.validate
|
53
|
+
assert_not_equal expected_request_id, logoutresponse.in_response_to
|
54
|
+
end
|
55
|
+
|
56
|
+
should "invalidate responses with wrong request status" do
|
57
|
+
logoutresponse = Onelogin::Saml::Logoutresponse.new(unsuccessful_response, settings)
|
58
|
+
|
59
|
+
assert !logoutresponse.validate
|
60
|
+
assert !logoutresponse.success?
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
context "#validate!" do
|
65
|
+
should "validates good responses" do
|
66
|
+
in_relation_to_request_id = random_id
|
67
|
+
|
68
|
+
logoutresponse = Onelogin::Saml::Logoutresponse.new(valid_response({:uuid => in_relation_to_request_id}), settings)
|
69
|
+
|
70
|
+
logoutresponse.validate!
|
71
|
+
end
|
72
|
+
|
73
|
+
should "raises validation error when matching for wrong request id" do
|
74
|
+
|
75
|
+
expected_request_id = "_some_other_expected_id"
|
76
|
+
opts = { :matches_request_id => expected_request_id}
|
77
|
+
|
78
|
+
logoutresponse = Onelogin::Saml::Logoutresponse.new(valid_response, settings, opts)
|
79
|
+
|
80
|
+
assert_raises(Onelogin::Saml::ValidationError) { logoutresponse.validate! }
|
81
|
+
end
|
82
|
+
|
83
|
+
should "raise validation error for wrong request status" do
|
84
|
+
logoutresponse = Onelogin::Saml::Logoutresponse.new(unsuccessful_response, settings)
|
85
|
+
|
86
|
+
assert_raises(Onelogin::Saml::ValidationError) { logoutresponse.validate! }
|
87
|
+
end
|
88
|
+
|
89
|
+
should "raise validation error when in bad state" do
|
90
|
+
# no settings
|
91
|
+
logoutresponse = Onelogin::Saml::Logoutresponse.new(unsuccessful_response)
|
92
|
+
assert_raises(Onelogin::Saml::ValidationError) { logoutresponse.validate! }
|
93
|
+
end
|
94
|
+
|
95
|
+
should "raise validation error when in lack of issuer setting" do
|
96
|
+
bad_settings = settings
|
97
|
+
bad_settings.issuer = nil
|
98
|
+
logoutresponse = Onelogin::Saml::Logoutresponse.new(unsuccessful_response, bad_settings)
|
99
|
+
assert_raises(Onelogin::Saml::ValidationError) { logoutresponse.validate! }
|
100
|
+
end
|
101
|
+
|
102
|
+
should "raise error for invalid xml" do
|
103
|
+
logoutresponse = Onelogin::Saml::Logoutresponse.new(invalid_xml_response, settings)
|
104
|
+
|
105
|
+
assert_raises(Exception) { logoutresponse.validate! }
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
end
|
110
|
+
|
111
|
+
# logoutresponse fixtures
|
112
|
+
def random_id
|
113
|
+
"_#{UUID.new.generate}"
|
114
|
+
end
|
115
|
+
|
116
|
+
end
|
data/test/request_test.rb
CHANGED
@@ -19,6 +19,18 @@ class RequestTest < Test::Unit::TestCase
|
|
19
19
|
assert_match /^<samlp:AuthnRequest/, inflated
|
20
20
|
end
|
21
21
|
|
22
|
+
should "create the SAMLRequest URL parameter without deflating" do
|
23
|
+
settings = Onelogin::Saml::Settings.new
|
24
|
+
settings.compress_request = false
|
25
|
+
settings.idp_sso_target_url = "http://example.com"
|
26
|
+
auth_url = Onelogin::Saml::Authrequest.new.create(settings)
|
27
|
+
assert auth_url =~ /^http:\/\/example\.com\?SAMLRequest=/
|
28
|
+
payload = CGI.unescape(auth_url.split("=").last)
|
29
|
+
decoded = Base64.decode64(payload)
|
30
|
+
|
31
|
+
assert_match /^<samlp:AuthnRequest/, decoded
|
32
|
+
end
|
33
|
+
|
22
34
|
should "accept extra parameters" do
|
23
35
|
settings = Onelogin::Saml::Settings.new
|
24
36
|
settings.idp_sso_target_url = "http://example.com"
|
@@ -0,0 +1,67 @@
|
|
1
|
+
#encoding: utf-8
|
2
|
+
|
3
|
+
def default_response_opts
|
4
|
+
{
|
5
|
+
:uuid => "_28024690-000e-0130-b6d2-38f6b112be8b",
|
6
|
+
:issue_instant => Time.now.strftime('%Y-%m-%dT%H:%M:%SZ'),
|
7
|
+
:settings => settings
|
8
|
+
}
|
9
|
+
end
|
10
|
+
|
11
|
+
def valid_response(opts = {})
|
12
|
+
opts = default_response_opts.merge!(opts)
|
13
|
+
|
14
|
+
"<samlp:LogoutResponse
|
15
|
+
xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\"
|
16
|
+
ID=\"#{random_id}\" Version=\"2.0\"
|
17
|
+
IssueInstant=\"#{opts[:issue_instant]}\"
|
18
|
+
Destination=\"#{opts[:settings].assertion_consumer_logout_service_url}\"
|
19
|
+
InResponseTo=\"#{opts[:uuid]}\">
|
20
|
+
<saml:Issuer xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\">#{opts[:settings].issuer}</saml:Issuer>
|
21
|
+
<samlp:Status xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\">
|
22
|
+
<samlp:StatusCode xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\"
|
23
|
+
Value=\"urn:oasis:names:tc:SAML:2.0:status:Success\">
|
24
|
+
</samlp:StatusCode>
|
25
|
+
</samlp:Status>
|
26
|
+
</samlp:LogoutResponse>"
|
27
|
+
end
|
28
|
+
|
29
|
+
def unsuccessful_response(opts = {})
|
30
|
+
opts = default_response_opts.merge!(opts)
|
31
|
+
|
32
|
+
"<samlp:LogoutResponse
|
33
|
+
xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\"
|
34
|
+
ID=\"#{random_id}\" Version=\"2.0\"
|
35
|
+
IssueInstant=\"#{opts[:issue_instant]}\"
|
36
|
+
Destination=\"#{opts[:settings].assertion_consumer_logout_service_url}\"
|
37
|
+
InResponseTo=\"#{opts[:uuid]}\">
|
38
|
+
<saml:Issuer xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\">#{opts[:settings].issuer}</saml:Issuer>
|
39
|
+
<samlp:Status xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\">
|
40
|
+
<samlp:StatusCode xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\"
|
41
|
+
Value=\"urn:oasis:names:tc:SAML:2.0:status:Requester\">
|
42
|
+
</samlp:StatusCode>
|
43
|
+
</samlp:Status>
|
44
|
+
</samlp:LogoutResponse>"
|
45
|
+
end
|
46
|
+
|
47
|
+
def invalid_xml_response
|
48
|
+
"<samlp:SomethingAwful
|
49
|
+
xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\"
|
50
|
+
ID=\"#{random_id}\" Version=\"2.0\">
|
51
|
+
</samlp:SomethingAwful>"
|
52
|
+
end
|
53
|
+
|
54
|
+
def settings
|
55
|
+
@settings ||= Onelogin::Saml::Settings.new(
|
56
|
+
{
|
57
|
+
:assertion_consumer_service_url => "http://app.muda.no/sso/consume",
|
58
|
+
:assertion_consumer_logout_service_url => "http://app.muda.no/sso/consume_logout",
|
59
|
+
:issuer => "http://app.muda.no",
|
60
|
+
:sp_name_qualifier => "http://sso.muda.no",
|
61
|
+
:idp_sso_target_url => "http://sso.muda.no/sso",
|
62
|
+
:idp_slo_target_url => "http://sso.muda.no/slo",
|
63
|
+
:idp_cert_fingerprint => "00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00",
|
64
|
+
:name_identifier_format => "urn:oasis:names:tc:SAML:2.0:nameid-format:transient",
|
65
|
+
}
|
66
|
+
)
|
67
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
PHNhbWxwOlJlc3BvbnNlIElEPSJCZWViMzkyYjc1Ny02ZGM3LTRlYjktYmI1Yy03NmU1MTFmZDZiZWIiIElzc3VlSW5zdGFudD0iMjAxMi0xMS0yOFQxODoxMzo0NVoiIFZlcnNpb249IjIuMCIgeG1sbnM9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphc3NlcnRpb24iIHhtbG5zOnNhbWxwPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6cHJvdG9jb2wiIHhtbG5zOnhlbmM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMDQveG1sZW5jIyI+PFNpZ25hdHVyZSB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnIyI+PFNpZ25lZEluZm8+PENhbm9uaWNhbGl6YXRpb25NZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy9UUi8yMDAxL1JFQy14bWwtYzE0bi0yMDAxMDMxNSIgLz48U2lnbmF0dXJlTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnI3JzYS1zaGExIiAvPjxSZWZlcmVuY2UgVVJJPSIjQmVlYjM5MmI3NTctNmRjNy00ZWI5LWJiNWMtNzZlNTExZmQ2YmViIj48VHJhbnNmb3Jtcz48VHJhbnNmb3JtIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnI2VudmVsb3BlZC1zaWduYXR1cmUiIC8+PC9UcmFuc2Zvcm1zPjxEaWdlc3RNZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjc2hhMSIgLz48RGlnZXN0VmFsdWU+S0RqNHlxK0RDaVVJZVpDZUcyRFhFZWxWVURNPTwvRGlnZXN0VmFsdWU+PC9SZWZlcmVuY2U+PC9TaWduZWRJbmZvPjxTaWduYXR1cmVWYWx1ZT5zTDZjakVJTkt0U2l3THpRamk3RzYvRXVXMytZVXUzOHNUa3A1WnJva2pmUUxxZlZacVE0MDltYXowSU9neDVwWW44TmxrdWpIZUtTQ1N6ME9uRjlLRUxUNEpINi82Sklob0JuSjdiY1JFMG0vSFdEbFhCRGU2V3hkd2w4M1Y2aENKMTZtM25tTHhLTldDRThPU3BuaEdqK3d3UFRxRU56NVRTdGgrbkU1WVcyLzNBU3ZYK3ZENjFBUFNUeWkxWm5CR0huWWRiZVQ5Yk9LUUFVck1kQ1ZwWFlYZlVrK1I5Qkh6U3grR1VaL3RWU01mUjQ0ZE01dlpjTXFnSHhtd1c0eUQrQUVYNVNNQlZEYkdRd1o5dUFnQk14YWtQYjU4VHM1YlVtVUVFUzFDbFV6Zm95S3lvdHR3WmIwSVVsQVh4bSs3RHNpT2tkQ1BTQjhyUDRnMXIvNHc9PTwvU2lnbmF0dXJlVmFsdWU+PEtleUluZm8+PFg1MDlEYXRhPjxYNTA5Q2VydGlmaWNhdGU+TUlJQ1dUQ0NBZ2VnQXdJQkFnSVF5K0lPeGE5UkZZcE1mZjlPYkJMNThqQUpCZ1VyRGdNQ0hRVUFNQll4RkRBU0JnTlZCQU1UQzFKdmIzUWdRV2RsYm1ONU1CNFhEVEV3TURFeU1URTFNREl4TUZvWERUTTVNVEl6TVRJek5UazFPVm93T0RFMk1EUUdBMVVFQXhNdFUwRk5URlJsYzNRdE5qSTVOalF4T0dRdFlUWmpOeTAwTVRoa0xUZzBaV0V0WmpSak1EUmlPV1JrTVdJMk1JSUJJakFOQmdrcWhraUc5dzBCQVFFRkFBT0NBUThBTUlJQkNnS0NBUUVBM1JtMW9oaEcwdE5XdVRIdnpNYWJaWUgwclJSeUFGQ012Wk9EczBBNVArVjJSYVU2b3IwcmNpREhLQUlhaG5FVGRNcUl0RzhzbEtxeUhRRVAzbXFVTk1adjMrd3ByMVBlV0I2REJZMnBBUmoxM2pQaTUyTjVHSWF2UUtCS0E1OEJBcmtJK0pzQjZmMmpXWXM4c0YyekZ5TUg5aEEvZDJVRm50L1BDWjNBLytUU1FJY3lqTldiUVByZUNDdW1hRUJDVWkvbGRhQ2lZSXM2SUJ5aGdFeXRKYWhTejdPTWVPdWNKanFmUGRHbWlHVzZ1dzRmZmZaR3l1U0k0TC9memFKQTZGdU9XZlZCTUdLb3NoZzQxUnB3U3V1dDRXWU05dXlLZzdYQ1dZbndSL1F3YTJDQmgzdGZHd3E3ME5uMzcyWVdqYkszdU1OU2JzOHlhUGtqb0psLzJRSURBUUFCbzBzd1NUQkhCZ05WSFFFRVFEQStnQkFTNUFrdEJoMGRUd0NOWVNIY0ZtUmpvUmd3RmpFVU1CSUdBMVVFQXhNTFVtOXZkQ0JCWjJWdVkzbUNFQVkzYkFDcUFHU0tFYys0MUtwY05mUXdDUVlGS3c0REFoMEZBQU5CQUM1K2RFaElTbFB4bEZLeDIvQTVXcU9WTWZVVG96N2F6c0k3VDVmMXJkdWhrWXlDVnlvc3RUb3BSQWQrL3JSdzhxYmVGVXhYTVZHK3VHaG5RVlR3N0ljPTwvWDUwOUNlcnRpZmljYXRlPjwvWDUwOURhdGE+PC9LZXlJbmZvPjwvU2lnbmF0dXJlPjxzYW1scDpTdGF0dXM+PHNhbWxwOlN0YXR1c0NvZGUgVmFsdWU9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpzdGF0dXM6U3VjY2VzcyIgLz48L3NhbWxwOlN0YXR1cz48QXNzZXJ0aW9uIElEPSJCZWVhYjUwOTk1My01ZDE0LTQwMDctYTY0NC1mOWFjMmRlOWNlMjIiIElzc3VlSW5zdGFudD0iMjAwMy0wNC0xN1QwMDo0NjowMloiIFZlcnNpb249IjIuMCIgeG1sbnM9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphc3NlcnRpb24iPjxJc3N1ZXI+QmVlbGluZS5jb20NCiAgICAgICAgICAgICAgICA8L0lzc3Vlcj48U3ViamVjdD48TmFtZUlEIEZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOm5hbWVpZC1mb3JtYXQ6ZW1haWxBZGRyZXNzIj4NCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdHJldm9yLmxpdHRsZUBzdGFyZmllbGR0bXMuY29tDQogICAgICAgICAgICAgICAgICAgICAgICA8L05hbWVJRD48U3ViamVjdENvbmZpcm1hdGlvbiBNZXRob2Q9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpjbTpiZWFyZXIiIC8+PC9TdWJqZWN0PjxDb25kaXRpb25zIE5vdEJlZm9yZT0iMjAxMi0xMS0yOFQxNzo1Mzo0NVoiIE5vdE9uT3JBZnRlcj0iMjAxMi0xMS0yOFQxODozMzo0NVoiPjwvQ29uZGl0aW9ucz48QXV0aG5TdGF0ZW1lbnQgQXV0aG5JbnN0YW50PSIyMDEyLTExLTI4VDE4OjEzOjQ1WiI+PEF1dGhuQ29udGV4dD48QXV0aG5Db250ZXh0Q2xhc3NSZWY+DQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFjOmNsYXNzZXM6UGFzc3dvcmQNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC9BdXRobkNvbnRleHRDbGFzc1JlZj48L0F1dGhuQ29udGV4dD48L0F1dGhuU3RhdGVtZW50PjwvQXNzZXJ0aW9uPjwvc2FtbHA6UmVzcG9uc2U+
|
data/test/settings_test.rb
CHANGED
@@ -10,7 +10,8 @@ class SettingsTest < Test::Unit::TestCase
|
|
10
10
|
accessors = [
|
11
11
|
:assertion_consumer_service_url, :issuer, :sp_name_qualifier,
|
12
12
|
:idp_sso_target_url, :idp_cert_fingerprint, :name_identifier_format,
|
13
|
-
:idp_slo_target_url, :name_identifier_value, :sessionindex
|
13
|
+
:idp_slo_target_url, :name_identifier_value, :sessionindex,
|
14
|
+
:assertion_consumer_logout_service_url
|
14
15
|
]
|
15
16
|
|
16
17
|
accessors.each do |accessor|
|
data/test/xml_security_test.rb
CHANGED
@@ -117,7 +117,17 @@ class XmlSecurityTest < Test::Unit::TestCase
|
|
117
117
|
assert inclusive_namespaces.empty?
|
118
118
|
end
|
119
119
|
end
|
120
|
-
|
120
|
+
|
121
|
+
context "StarfieldTMS" do
|
122
|
+
should "be able to validate a response" do
|
123
|
+
response = Onelogin::Saml::Response.new(fixture(:starfield_response))
|
124
|
+
response.settings = Onelogin::Saml::Settings.new(
|
125
|
+
:idp_cert_fingerprint => "8D:BA:53:8E:A3:B6:F9:F1:69:6C:BB:D9:D8:BD:41:B3:AC:4F:9D:4D"
|
126
|
+
)
|
127
|
+
assert response.validate!
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
121
131
|
end
|
122
132
|
|
123
133
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ruby-saml
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.7.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2013-01-23 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: canonix
|
@@ -77,6 +77,7 @@ files:
|
|
77
77
|
- lib/onelogin/ruby-saml/authrequest.rb
|
78
78
|
- lib/onelogin/ruby-saml/logging.rb
|
79
79
|
- lib/onelogin/ruby-saml/logoutrequest.rb
|
80
|
+
- lib/onelogin/ruby-saml/logoutresponse.rb
|
80
81
|
- lib/onelogin/ruby-saml/metadata.rb
|
81
82
|
- lib/onelogin/ruby-saml/response.rb
|
82
83
|
- lib/onelogin/ruby-saml/settings.rb
|
@@ -91,12 +92,14 @@ files:
|
|
91
92
|
- ruby-saml.gemspec
|
92
93
|
- test/certificates/certificate1
|
93
94
|
- test/logoutrequest_test.rb
|
95
|
+
- test/logoutresponse_test.rb
|
94
96
|
- test/request_test.rb
|
95
97
|
- test/response_test.rb
|
96
98
|
- test/responses/adfs_response_sha1.xml
|
97
99
|
- test/responses/adfs_response_sha256.xml
|
98
100
|
- test/responses/adfs_response_sha384.xml
|
99
101
|
- test/responses/adfs_response_sha512.xml
|
102
|
+
- test/responses/logoutresponse_fixtures.rb
|
100
103
|
- test/responses/no_signature_ns.xml
|
101
104
|
- test/responses/open_saml_response.xml
|
102
105
|
- test/responses/response1.xml.base64
|
@@ -107,6 +110,7 @@ files:
|
|
107
110
|
- test/responses/response_with_ampersands.xml
|
108
111
|
- test/responses/response_with_ampersands.xml.base64
|
109
112
|
- test/responses/simple_saml_php.xml
|
113
|
+
- test/responses/starfield_response.xml.base64
|
110
114
|
- test/responses/wrapped_response_2.xml.base64
|
111
115
|
- test/settings_test.rb
|
112
116
|
- test/test_helper.rb
|
@@ -132,19 +136,21 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
132
136
|
version: '0'
|
133
137
|
requirements: []
|
134
138
|
rubyforge_project: http://www.rubygems.org/gems/ruby-saml
|
135
|
-
rubygems_version: 1.8.
|
139
|
+
rubygems_version: 1.8.23
|
136
140
|
signing_key:
|
137
141
|
specification_version: 3
|
138
142
|
summary: SAML Ruby Tookit
|
139
143
|
test_files:
|
140
144
|
- test/certificates/certificate1
|
141
145
|
- test/logoutrequest_test.rb
|
146
|
+
- test/logoutresponse_test.rb
|
142
147
|
- test/request_test.rb
|
143
148
|
- test/response_test.rb
|
144
149
|
- test/responses/adfs_response_sha1.xml
|
145
150
|
- test/responses/adfs_response_sha256.xml
|
146
151
|
- test/responses/adfs_response_sha384.xml
|
147
152
|
- test/responses/adfs_response_sha512.xml
|
153
|
+
- test/responses/logoutresponse_fixtures.rb
|
148
154
|
- test/responses/no_signature_ns.xml
|
149
155
|
- test/responses/open_saml_response.xml
|
150
156
|
- test/responses/response1.xml.base64
|
@@ -155,6 +161,7 @@ test_files:
|
|
155
161
|
- test/responses/response_with_ampersands.xml
|
156
162
|
- test/responses/response_with_ampersands.xml.base64
|
157
163
|
- test/responses/simple_saml_php.xml
|
164
|
+
- test/responses/starfield_response.xml.base64
|
158
165
|
- test/responses/wrapped_response_2.xml.base64
|
159
166
|
- test/settings_test.rb
|
160
167
|
- test/test_helper.rb
|