ruby-saml 0.6.0 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
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
|