ruby-saml 0.9.1 → 0.9.2
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.
- checksums.yaml +4 -4
- data/.travis.yml +10 -0
- data/Gemfile +1 -1
- data/README.md +31 -7
- data/changelog.md +18 -0
- data/gemfiles/nokogiri-1.5.gemfile +5 -0
- data/lib/onelogin/ruby-saml.rb +16 -0
- data/lib/onelogin/ruby-saml/authrequest.rb +10 -5
- data/lib/onelogin/ruby-saml/idp_metadata_parser.rb +20 -1
- data/lib/onelogin/ruby-saml/logoutrequest.rb +9 -5
- data/lib/onelogin/ruby-saml/logoutresponse.rb +2 -0
- data/lib/onelogin/ruby-saml/metadata.rb +16 -6
- data/lib/onelogin/ruby-saml/response.rb +17 -15
- data/lib/onelogin/ruby-saml/saml_message.rb +44 -30
- data/lib/onelogin/ruby-saml/settings.rb +17 -7
- data/lib/onelogin/ruby-saml/slo_logoutrequest.rb +2 -0
- data/lib/onelogin/ruby-saml/slo_logoutresponse.rb +9 -5
- data/lib/onelogin/ruby-saml/version.rb +1 -1
- data/lib/ruby-saml.rb +1 -16
- data/lib/xml_security.rb +39 -17
- data/ruby-saml.gemspec +5 -6
- data/test/idp_metadata_parser_test.rb +6 -2
- data/test/logoutrequest_test.rb +50 -22
- data/test/logoutresponse_test.rb +2 -1
- data/test/metadata_test.rb +100 -55
- data/test/request_test.rb +49 -24
- data/test/response_test.rb +41 -8
- data/test/responses/test_sign.xml +43 -0
- data/test/settings_test.rb +16 -0
- data/test/slo_logoutrequest_test.rb +2 -0
- data/test/slo_logoutresponse_test.rb +51 -21
- data/test/test_helper.rb +0 -1
- data/test/xml_security_test.rb +41 -4
- metadata +10 -6
@@ -1,3 +1,7 @@
|
|
1
|
+
require "xml_security"
|
2
|
+
require "onelogin/ruby-saml/attribute_service"
|
3
|
+
require "onelogin/ruby-saml/utils"
|
4
|
+
|
1
5
|
module OneLogin
|
2
6
|
module RubySaml
|
3
7
|
class Settings
|
@@ -5,7 +9,10 @@ module OneLogin
|
|
5
9
|
config = DEFAULTS.merge(overrides)
|
6
10
|
config.each do |k,v|
|
7
11
|
acc = "#{k.to_s}=".to_sym
|
8
|
-
|
12
|
+
if self.respond_to? acc
|
13
|
+
value = v.is_a?(Hash) ? v.dup : v
|
14
|
+
self.send(acc, value)
|
15
|
+
end
|
9
16
|
end
|
10
17
|
@attribute_consuming_service = AttributeService.new
|
11
18
|
end
|
@@ -16,6 +23,7 @@ module OneLogin
|
|
16
23
|
attr_accessor :idp_slo_target_url
|
17
24
|
attr_accessor :idp_cert
|
18
25
|
attr_accessor :idp_cert_fingerprint
|
26
|
+
attr_accessor :idp_cert_fingerprint_algorithm
|
19
27
|
# SP Data
|
20
28
|
attr_accessor :issuer
|
21
29
|
attr_accessor :assertion_consumer_service_url
|
@@ -97,20 +105,22 @@ module OneLogin
|
|
97
105
|
private
|
98
106
|
|
99
107
|
DEFAULTS = {
|
100
|
-
:assertion_consumer_service_binding => "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST",
|
101
|
-
:single_logout_service_binding => "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect",
|
108
|
+
:assertion_consumer_service_binding => "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST".freeze,
|
109
|
+
:single_logout_service_binding => "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect".freeze,
|
110
|
+
:idp_cert_fingerprint_algorithm => XMLSecurity::Document::SHA1,
|
102
111
|
:compress_request => true,
|
103
112
|
:compress_response => true,
|
104
113
|
:security => {
|
105
114
|
:authn_requests_signed => false,
|
106
115
|
:logout_requests_signed => false,
|
107
|
-
:logout_responses_signed
|
116
|
+
:logout_responses_signed => false,
|
117
|
+
:metadata_signed => false,
|
108
118
|
:embed_sign => false,
|
109
119
|
:digest_method => XMLSecurity::Document::SHA1,
|
110
|
-
:signature_method => XMLSecurity::Document::
|
111
|
-
},
|
120
|
+
:signature_method => XMLSecurity::Document::RSA_SHA1
|
121
|
+
}.freeze,
|
112
122
|
:double_quote_xml_attribute_values => false,
|
113
|
-
}
|
123
|
+
}.freeze
|
114
124
|
end
|
115
125
|
end
|
116
126
|
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
require "uuid"
|
2
2
|
|
3
3
|
require "onelogin/ruby-saml/logging"
|
4
|
+
require "onelogin/ruby-saml/saml_message"
|
4
5
|
|
5
6
|
module OneLogin
|
6
7
|
module RubySaml
|
@@ -25,7 +26,10 @@ module OneLogin
|
|
25
26
|
end
|
26
27
|
|
27
28
|
def create_params(settings, request_id = nil, logout_message = nil, params = {})
|
28
|
-
|
29
|
+
# The method expects :RelayState but sometimes we get 'RelayState' instead.
|
30
|
+
# Based on the HashWithIndifferentAccess value in Rails we could experience
|
31
|
+
# conflicts so this line will solve them.
|
32
|
+
relay_state = params[:RelayState] || params['RelayState']
|
29
33
|
|
30
34
|
response_doc = create_logout_response_xml_doc(settings, request_id, logout_message)
|
31
35
|
response_doc.context[:attribute_quote] = :quote if settings.double_quote_xml_attribute_values
|
@@ -40,10 +44,10 @@ module OneLogin
|
|
40
44
|
response_params = {"SAMLResponse" => base64_response}
|
41
45
|
|
42
46
|
if settings.security[:logout_responses_signed] && !settings.security[:embed_sign] && settings.private_key
|
43
|
-
params['SigAlg'] =
|
47
|
+
params['SigAlg'] = settings.security[:signature_method]
|
44
48
|
url_string = "SAMLResponse=#{CGI.escape(base64_response)}"
|
45
|
-
url_string
|
46
|
-
url_string
|
49
|
+
url_string << "&RelayState=#{CGI.escape(relay_state)}" if relay_state
|
50
|
+
url_string << "&SigAlg=#{CGI.escape(params['SigAlg'])}"
|
47
51
|
private_key = settings.get_sp_key()
|
48
52
|
signature = private_key.sign(XMLSecurity::BaseDocument.new.algorithm(settings.security[:signature_method]).new, url_string)
|
49
53
|
params['Signature'] = encode(signature)
|
@@ -86,7 +90,7 @@ module OneLogin
|
|
86
90
|
issuer.text = settings.issuer
|
87
91
|
end
|
88
92
|
|
89
|
-
#
|
93
|
+
# embed signature
|
90
94
|
if settings.security[:logout_responses_signed] && settings.private_key && settings.certificate && settings.security[:embed_sign]
|
91
95
|
private_key = settings.get_sp_key()
|
92
96
|
cert = settings.get_sp_cert()
|
data/lib/ruby-saml.rb
CHANGED
@@ -1,16 +1 @@
|
|
1
|
-
require 'onelogin/ruby-saml
|
2
|
-
require 'onelogin/ruby-saml/saml_message'
|
3
|
-
require 'onelogin/ruby-saml/authrequest'
|
4
|
-
require 'onelogin/ruby-saml/logoutrequest'
|
5
|
-
require 'onelogin/ruby-saml/logoutresponse'
|
6
|
-
require 'onelogin/ruby-saml/attributes'
|
7
|
-
require 'onelogin/ruby-saml/slo_logoutrequest'
|
8
|
-
require 'onelogin/ruby-saml/slo_logoutresponse'
|
9
|
-
require 'onelogin/ruby-saml/response'
|
10
|
-
require 'onelogin/ruby-saml/settings'
|
11
|
-
require 'onelogin/ruby-saml/attribute_service'
|
12
|
-
require 'onelogin/ruby-saml/validation_error'
|
13
|
-
require 'onelogin/ruby-saml/metadata'
|
14
|
-
require 'onelogin/ruby-saml/idp_metadata_parser'
|
15
|
-
require 'onelogin/ruby-saml/utils'
|
16
|
-
require 'onelogin/ruby-saml/version'
|
1
|
+
require 'onelogin/ruby-saml'
|
data/lib/xml_security.rb
CHANGED
@@ -56,9 +56,10 @@ module XMLSecurity
|
|
56
56
|
algorithm = element
|
57
57
|
if algorithm.is_a?(REXML::Element)
|
58
58
|
algorithm = element.attribute("Algorithm").value
|
59
|
-
algorithm = algorithm && algorithm =~ /sha(.*?)$/i && $1.to_i
|
60
59
|
end
|
61
60
|
|
61
|
+
algorithm = algorithm && algorithm =~ /(rsa-)?sha(.*?)$/i && $2.to_i
|
62
|
+
|
62
63
|
case algorithm
|
63
64
|
when 256 then OpenSSL::Digest::SHA256
|
64
65
|
when 384 then OpenSSL::Digest::SHA384
|
@@ -71,15 +72,25 @@ module XMLSecurity
|
|
71
72
|
end
|
72
73
|
|
73
74
|
class Document < BaseDocument
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
75
|
+
RSA_SHA1 = "http://www.w3.org/2000/09/xmldsig#rsa-sha1"
|
76
|
+
RSA_SHA256 = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"
|
77
|
+
RSA_SHA384 = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha384"
|
78
|
+
RSA_SHA512 = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha512"
|
79
|
+
SHA1 = "http://www.w3.org/2000/09/xmldsig#sha1"
|
80
|
+
SHA256 = "http://www.w3.org/2001/04/xmldsig-more#sha256"
|
81
|
+
SHA384 = "http://www.w3.org/2001/04/xmldsig-more#sha384"
|
82
|
+
SHA512 = "http://www.w3.org/2001/04/xmldsig-more#sha512"
|
78
83
|
ENVELOPED_SIG = "http://www.w3.org/2000/09/xmldsig#enveloped-signature"
|
79
|
-
INC_PREFIX_LIST = "#default samlp saml ds xs xsi"
|
84
|
+
INC_PREFIX_LIST = "#default samlp saml ds xs xsi md"
|
80
85
|
|
81
86
|
attr_accessor :uuid
|
82
87
|
|
88
|
+
def uuid
|
89
|
+
@uuid ||= begin
|
90
|
+
document.root.nil? ? nil : document.root.attributes['ID']
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
83
94
|
#<Signature>
|
84
95
|
#<SignedInfo>
|
85
96
|
#<CanonicalizationMethod />
|
@@ -95,9 +106,8 @@ module XMLSecurity
|
|
95
106
|
#<KeyInfo />
|
96
107
|
#<Object />
|
97
108
|
#</Signature>
|
98
|
-
def sign_document(private_key, certificate, signature_method =
|
109
|
+
def sign_document(private_key, certificate, signature_method = RSA_SHA1, digest_method = SHA1)
|
99
110
|
noko = Nokogiri.parse(self.to_s)
|
100
|
-
canon_doc = noko.canonicalize(canon_algorithm(C14N))
|
101
111
|
|
102
112
|
signature_element = REXML::Element.new("ds:Signature").add_namespace('ds', DSIG)
|
103
113
|
signed_info_element = signature_element.add_element("ds:SignedInfo")
|
@@ -110,16 +120,19 @@ module XMLSecurity
|
|
110
120
|
# Add Transforms
|
111
121
|
transforms_element = reference_element.add_element("ds:Transforms")
|
112
122
|
transforms_element.add_element("ds:Transform", {"Algorithm" => ENVELOPED_SIG})
|
113
|
-
transforms_element.add_element("ds:Transform", {"Algorithm" => C14N})
|
114
|
-
|
123
|
+
c14element = transforms_element.add_element("ds:Transform", {"Algorithm" => C14N})
|
124
|
+
c14element.add_element("ec:InclusiveNamespaces", {"xmlns:ec" => C14N, "PrefixList" => INC_PREFIX_LIST})
|
115
125
|
|
116
126
|
digest_method_element = reference_element.add_element("ds:DigestMethod", {"Algorithm" => digest_method})
|
127
|
+
inclusive_namespaces = INC_PREFIX_LIST.split(" ")
|
128
|
+
canon_doc = noko.canonicalize(canon_algorithm(C14N), inclusive_namespaces)
|
117
129
|
reference_element.add_element("ds:DigestValue").text = compute_digest(canon_doc, algorithm(digest_method_element))
|
118
130
|
|
119
131
|
# add SignatureValue
|
120
132
|
noko_sig_element = Nokogiri.parse(signature_element.to_s)
|
121
133
|
noko_signed_info_element = noko_sig_element.at_xpath('//ds:Signature/ds:SignedInfo', 'ds' => DSIG)
|
122
134
|
canon_string = noko_signed_info_element.canonicalize(canon_algorithm(C14N))
|
135
|
+
|
123
136
|
signature = compute_signature(private_key, algorithm(signature_method).new, canon_string)
|
124
137
|
signature_element.add_element("ds:SignatureValue").text = signature
|
125
138
|
|
@@ -137,7 +150,11 @@ module XMLSecurity
|
|
137
150
|
if issuer_element
|
138
151
|
self.root.insert_after issuer_element, signature_element
|
139
152
|
else
|
140
|
-
self.
|
153
|
+
if sp_sso_descriptor = self.elements["/md:EntityDescriptor"]
|
154
|
+
self.root.insert_before sp_sso_descriptor, signature_element
|
155
|
+
else
|
156
|
+
self.root.add_element(signature_element)
|
157
|
+
end
|
141
158
|
end
|
142
159
|
end
|
143
160
|
|
@@ -165,7 +182,7 @@ module XMLSecurity
|
|
165
182
|
extract_signed_element_id
|
166
183
|
end
|
167
184
|
|
168
|
-
def validate_document(idp_cert_fingerprint, soft = true)
|
185
|
+
def validate_document(idp_cert_fingerprint, soft = true, options = {})
|
169
186
|
# get cert from response
|
170
187
|
cert_element = REXML::XPath.first(self, "//ds:X509Certificate", { "ds"=>DSIG })
|
171
188
|
unless cert_element
|
@@ -175,13 +192,18 @@ module XMLSecurity
|
|
175
192
|
raise OneLogin::RubySaml::ValidationError.new("Certificate element missing in response (ds:X509Certificate)")
|
176
193
|
end
|
177
194
|
end
|
178
|
-
base64_cert
|
179
|
-
cert_text
|
180
|
-
cert
|
195
|
+
base64_cert = cert_element.text
|
196
|
+
cert_text = Base64.decode64(base64_cert)
|
197
|
+
cert = OpenSSL::X509::Certificate.new(cert_text)
|
181
198
|
|
182
|
-
|
183
|
-
|
199
|
+
if options[:fingerprint_alg]
|
200
|
+
fingerprint_alg = XMLSecurity::BaseDocument.new.algorithm(options[:fingerprint_alg]).new
|
201
|
+
else
|
202
|
+
fingerprint_alg = OpenSSL::Digest::SHA1.new
|
203
|
+
end
|
204
|
+
fingerprint = fingerprint_alg.hexdigest(cert.to_der)
|
184
205
|
|
206
|
+
# check cert matches registered idp cert
|
185
207
|
if fingerprint != idp_cert_fingerprint.gsub(/[^a-zA-Z0-9]/,"").downcase
|
186
208
|
@errors << "Fingerprint mismatch"
|
187
209
|
return soft ? false : (raise OneLogin::RubySaml::ValidationError.new("Fingerprint mismatch"))
|
data/ruby-saml.gemspec
CHANGED
@@ -26,12 +26,11 @@ Gem::Specification.new do |s|
|
|
26
26
|
s.test_files = `git ls-files test/*`.split("\n")
|
27
27
|
|
28
28
|
s.add_runtime_dependency('uuid', '~> 2.3')
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
end
|
29
|
+
|
30
|
+
# Because runtime dependencies are determined at build time, we cannot make
|
31
|
+
# Nokogiri's version dependent on the Ruby version, even though we would
|
32
|
+
# have liked to constrain Ruby 1.8.7 to install only the 1.5.x versions.
|
33
|
+
s.add_runtime_dependency('nokogiri', '>= 1.5.10')
|
35
34
|
|
36
35
|
s.add_development_dependency('minitest', '~> 5.5')
|
37
36
|
s.add_development_dependency('mocha', '~> 0.14')
|
@@ -1,6 +1,6 @@
|
|
1
1
|
require File.expand_path(File.join(File.dirname(__FILE__), "test_helper"))
|
2
|
-
|
3
|
-
require '
|
2
|
+
|
3
|
+
require 'onelogin/ruby-saml/idp_metadata_parser'
|
4
4
|
|
5
5
|
class IdpMetadataParserTest < Minitest::Test
|
6
6
|
|
@@ -14,9 +14,11 @@ class IdpMetadataParserTest < Minitest::Test
|
|
14
14
|
|
15
15
|
settings = idp_metadata_parser.parse(idp_metadata)
|
16
16
|
|
17
|
+
assert_equal "https://example.hello.com/access/saml/idp.xml", settings.idp_entity_id
|
17
18
|
assert_equal "https://example.hello.com/access/saml/login", settings.idp_sso_target_url
|
18
19
|
assert_equal "F1:3C:6B:80:90:5A:03:0E:6C:91:3E:5D:15:FA:DD:B0:16:45:48:72", settings.idp_cert_fingerprint
|
19
20
|
assert_equal "https://example.hello.com/access/saml/logout", settings.idp_slo_target_url
|
21
|
+
assert_equal "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified", settings.name_identifier_format
|
20
22
|
end
|
21
23
|
end
|
22
24
|
|
@@ -37,9 +39,11 @@ class IdpMetadataParserTest < Minitest::Test
|
|
37
39
|
idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new
|
38
40
|
settings = idp_metadata_parser.parse_remote(@url)
|
39
41
|
|
42
|
+
assert_equal "https://example.hello.com/access/saml/idp.xml", settings.idp_entity_id
|
40
43
|
assert_equal "https://example.hello.com/access/saml/login", settings.idp_sso_target_url
|
41
44
|
assert_equal "F1:3C:6B:80:90:5A:03:0E:6C:91:3E:5D:15:FA:DD:B0:16:45:48:72", settings.idp_cert_fingerprint
|
42
45
|
assert_equal "https://example.hello.com/access/saml/logout", settings.idp_slo_target_url
|
46
|
+
assert_equal "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified", settings.name_identifier_format
|
43
47
|
assert_equal OpenSSL::SSL::VERIFY_PEER, @http.verify_mode
|
44
48
|
end
|
45
49
|
|
data/test/logoutrequest_test.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
require File.expand_path(File.join(File.dirname(__FILE__), "test_helper"))
|
2
2
|
|
3
|
+
require 'onelogin/ruby-saml/logoutrequest'
|
4
|
+
|
3
5
|
class RequestTest < Minitest::Test
|
4
6
|
|
5
7
|
describe "Logoutrequest" do
|
@@ -88,7 +90,7 @@ class RequestTest < Minitest::Test
|
|
88
90
|
end
|
89
91
|
end
|
90
92
|
|
91
|
-
describe "when the settings indicate to sign (
|
93
|
+
describe "when the settings indicate to sign (embedded) logout request" do
|
92
94
|
it "created a signed logout request" do
|
93
95
|
settings = OneLogin::RubySaml::Settings.new
|
94
96
|
settings.idp_slo_target_url = "http://example.com?field=value"
|
@@ -104,6 +106,8 @@ class RequestTest < Minitest::Test
|
|
104
106
|
|
105
107
|
inflated = decode_saml_request_payload(unauth_url)
|
106
108
|
assert_match %r[<ds:SignatureValue>([a-zA-Z0-9/+=]+)</ds:SignatureValue>], inflated
|
109
|
+
assert_match %r[<ds:SignatureMethod Algorithm='http://www.w3.org/2000/09/xmldsig#rsa-sha1'/>], inflated
|
110
|
+
assert_match %r[<ds:DigestMethod Algorithm='http://www.w3.org/2000/09/xmldsig#sha1'/>], inflated
|
107
111
|
end
|
108
112
|
|
109
113
|
it "create a signed logout request with 256 digest and signature methods" do
|
@@ -114,7 +118,7 @@ class RequestTest < Minitest::Test
|
|
114
118
|
# sign the logout request
|
115
119
|
settings.security[:logout_requests_signed] = true
|
116
120
|
settings.security[:embed_sign] = true
|
117
|
-
settings.security[:signature_method] = XMLSecurity::Document::
|
121
|
+
settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA256
|
118
122
|
settings.security[:digest_method] = XMLSecurity::Document::SHA512
|
119
123
|
settings.certificate = ruby_saml_cert_text
|
120
124
|
settings.private_key = ruby_saml_key_text
|
@@ -123,34 +127,58 @@ class RequestTest < Minitest::Test
|
|
123
127
|
request_xml = Base64.decode64(params["SAMLRequest"])
|
124
128
|
|
125
129
|
assert_match %r[<ds:SignatureValue>([a-zA-Z0-9/+=]+)</ds:SignatureValue>], request_xml
|
126
|
-
|
127
|
-
|
130
|
+
assert_match %r[<ds:SignatureMethod Algorithm='http://www.w3.org/2001/04/xmldsig-more#rsa-sha256'/>], request_xml
|
131
|
+
assert_match %r[<ds:DigestMethod Algorithm='http://www.w3.org/2001/04/xmldsig-more#sha512'/>], request_xml
|
128
132
|
end
|
129
133
|
end
|
130
134
|
|
131
|
-
describe "when the settings indicate to sign the logout request" do
|
132
|
-
|
133
|
-
settings = OneLogin::RubySaml::Settings.new
|
134
|
-
settings.compress_request = false
|
135
|
-
settings.
|
136
|
-
settings.name_identifier_value = "f00f00"
|
137
|
-
settings.security[:logout_requests_signed] = true
|
138
|
-
settings.security[:embed_sign] = false
|
139
|
-
settings.
|
140
|
-
settings.
|
141
|
-
|
135
|
+
describe "#create_params when the settings indicate to sign the logout request" do
|
136
|
+
def setup
|
137
|
+
@settings = OneLogin::RubySaml::Settings.new
|
138
|
+
@settings.compress_request = false
|
139
|
+
@settings.idp_sso_target_url = "http://example.com?field=value"
|
140
|
+
@settings.name_identifier_value = "f00f00"
|
141
|
+
@settings.security[:logout_requests_signed] = true
|
142
|
+
@settings.security[:embed_sign] = false
|
143
|
+
@settings.certificate = ruby_saml_cert_text
|
144
|
+
@settings.private_key = ruby_saml_key_text
|
145
|
+
@cert = OpenSSL::X509::Certificate.new(ruby_saml_cert_text)
|
146
|
+
end
|
142
147
|
|
143
|
-
|
148
|
+
it "create a signature parameter with RSA_SHA1 and validate it" do
|
149
|
+
settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA1
|
150
|
+
|
151
|
+
params = OneLogin::RubySaml::Logoutrequest.new.create_params(@settings, :RelayState => 'http://example.com')
|
152
|
+
assert params['SAMLRequest']
|
153
|
+
assert params[:RelayState]
|
144
154
|
assert params['Signature']
|
145
|
-
|
155
|
+
assert_equal params['SigAlg'], XMLSecurity::Document::RSA_SHA1
|
146
156
|
|
147
|
-
|
148
|
-
|
149
|
-
|
157
|
+
query_string = "SAMLRequest=#{CGI.escape(params['SAMLRequest'])}"
|
158
|
+
query_string << "&RelayState=#{CGI.escape(params[:RelayState])}"
|
159
|
+
query_string << "&SigAlg=#{CGI.escape(params['SigAlg'])}"
|
160
|
+
|
161
|
+
signature_algorithm = XMLSecurity::BaseDocument.new.algorithm(params['SigAlg'])
|
162
|
+
assert_equal signature_algorithm, OpenSSL::Digest::SHA1
|
163
|
+
assert @cert.public_key.verify(signature_algorithm.new, Base64.decode64(params['Signature']), query_string)
|
164
|
+
end
|
165
|
+
|
166
|
+
it "create a signature parameter with RSA_SHA256 and validate it" do
|
167
|
+
@settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA256
|
168
|
+
|
169
|
+
params = OneLogin::RubySaml::Logoutrequest.new.create_params(@settings, :RelayState => 'http://example.com')
|
150
170
|
assert params['Signature']
|
151
|
-
|
171
|
+
assert_equal params['SigAlg'], XMLSecurity::Document::RSA_SHA256
|
172
|
+
|
173
|
+
query_string = "SAMLRequest=#{CGI.escape(params['SAMLRequest'])}"
|
174
|
+
query_string << "&RelayState=#{CGI.escape(params[:RelayState])}"
|
175
|
+
query_string << "&SigAlg=#{CGI.escape(params['SigAlg'])}"
|
176
|
+
|
177
|
+
signature_algorithm = XMLSecurity::BaseDocument.new.algorithm(params['SigAlg'])
|
178
|
+
assert_equal signature_algorithm, OpenSSL::Digest::SHA256
|
179
|
+
assert @cert.public_key.verify(signature_algorithm.new, Base64.decode64(params['Signature']), query_string)
|
152
180
|
end
|
153
|
-
end
|
154
181
|
|
182
|
+
end
|
155
183
|
end
|
156
184
|
end
|
data/test/logoutresponse_test.rb
CHANGED
data/test/metadata_test.rb
CHANGED
@@ -1,88 +1,133 @@
|
|
1
1
|
require File.expand_path(File.join(File.dirname(__FILE__), "test_helper"))
|
2
2
|
|
3
|
+
require 'onelogin/ruby-saml/metadata'
|
4
|
+
|
3
5
|
class MetadataTest < Minitest::Test
|
4
6
|
|
5
7
|
describe 'Metadata' do
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
8
|
+
let(:settings) { OneLogin::RubySaml::Settings.new }
|
9
|
+
let(:xml_text) { OneLogin::RubySaml::Metadata.new.generate(settings, false) }
|
10
|
+
let(:xml_doc) { REXML::Document.new(xml_text) }
|
11
|
+
let(:spsso_descriptor) { REXML::XPath.first(xml_doc, "//md:SPSSODescriptor") }
|
12
|
+
let(:acs) { REXML::XPath.first(xml_doc, "//md:AssertionConsumerService") }
|
13
|
+
|
14
|
+
before do
|
15
|
+
settings.issuer = "https://example.com"
|
16
|
+
settings.name_identifier_format = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
|
17
|
+
settings.assertion_consumer_service_url = "https://foo.example/saml/consume"
|
12
18
|
end
|
13
19
|
|
14
|
-
it "generates Service Provider Metadata
|
15
|
-
|
16
|
-
|
20
|
+
it "generates Pretty Print Service Provider Metadata" do
|
21
|
+
xml_text = OneLogin::RubySaml::Metadata.new.generate(settings, true)
|
22
|
+
# assert correct xml declaration
|
23
|
+
start = "<?xml version='1.0' encoding='UTF-8'?>\n<md:EntityDescriptor"
|
24
|
+
assert_equal xml_text[0..start.length-1],start
|
17
25
|
|
18
|
-
|
26
|
+
assert_equal "https://example.com", REXML::XPath.first(xml_doc, "//md:EntityDescriptor").attribute("entityID").value
|
19
27
|
|
20
|
-
|
21
|
-
|
28
|
+
assert_equal "urn:oasis:names:tc:SAML:2.0:protocol", spsso_descriptor.attribute("protocolSupportEnumeration").value
|
29
|
+
assert_equal "false", spsso_descriptor.attribute("AuthnRequestsSigned").value
|
30
|
+
assert_equal "false", spsso_descriptor.attribute("WantAssertionsSigned").value
|
22
31
|
|
23
|
-
|
24
|
-
assert_equal "true", spsso_descriptor.attribute("AuthnRequestsSigned").value
|
32
|
+
assert_equal "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", REXML::XPath.first(xml_doc, "//md:NameIDFormat").text.strip
|
25
33
|
|
26
|
-
|
27
|
-
|
28
|
-
"ds" => "http://www.w3.org/2000/09/xmldsig#"
|
29
|
-
})
|
30
|
-
cert_text = cert_node.text
|
31
|
-
cert = OpenSSL::X509::Certificate.new(Base64.decode64(cert_text))
|
32
|
-
assert_equal ruby_saml_cert.to_der, cert.to_der
|
34
|
+
assert_equal "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST", acs.attribute("Binding").value
|
35
|
+
assert_equal "https://foo.example/saml/consume", acs.attribute("Location").value
|
33
36
|
end
|
34
37
|
|
35
38
|
it "generates Service Provider Metadata" do
|
36
|
-
settings = OneLogin::RubySaml::Settings.new
|
37
|
-
settings.issuer = "https://example.com"
|
38
|
-
settings.name_identifier_format = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
|
39
|
-
settings.assertion_consumer_service_url = "https://foo.example/saml/consume"
|
40
|
-
settings.security[:authn_requests_signed] = false
|
41
|
-
|
42
|
-
xml_text = OneLogin::RubySaml::Metadata.new.generate(settings)
|
43
|
-
|
44
39
|
# assert correct xml declaration
|
45
|
-
start = "<?xml version='1.0' encoding='UTF-8'
|
46
|
-
|
47
|
-
|
48
|
-
# assert xml_text can be parsed into an xml doc
|
49
|
-
xml_doc = REXML::Document.new(xml_text)
|
40
|
+
start = "<?xml version='1.0' encoding='UTF-8'?><md:EntityDescriptor"
|
41
|
+
assert_equal xml_text[0..start.length-1], start
|
50
42
|
|
51
43
|
assert_equal "https://example.com", REXML::XPath.first(xml_doc, "//md:EntityDescriptor").attribute("entityID").value
|
52
44
|
|
53
|
-
spsso_descriptor = REXML::XPath.first(xml_doc, "//md:SPSSODescriptor")
|
54
45
|
assert_equal "urn:oasis:names:tc:SAML:2.0:protocol", spsso_descriptor.attribute("protocolSupportEnumeration").value
|
55
46
|
assert_equal "false", spsso_descriptor.attribute("AuthnRequestsSigned").value
|
56
47
|
assert_equal "false", spsso_descriptor.attribute("WantAssertionsSigned").value
|
57
48
|
|
58
49
|
assert_equal "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", REXML::XPath.first(xml_doc, "//md:NameIDFormat").text.strip
|
59
50
|
|
60
|
-
acs = REXML::XPath.first(xml_doc, "//md:AssertionConsumerService")
|
61
51
|
assert_equal "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST", acs.attribute("Binding").value
|
62
52
|
assert_equal "https://foo.example/saml/consume", acs.attribute("Location").value
|
63
53
|
end
|
64
54
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
55
|
+
describe "when auth requests are signed" do
|
56
|
+
let(:cert_node) do
|
57
|
+
REXML::XPath.first(
|
58
|
+
xml_doc,
|
59
|
+
"//md:KeyDescriptor/ds:KeyInfo/ds:X509Data/ds:X509Certificate",
|
60
|
+
"md" => "urn:oasis:names:tc:SAML:2.0:metadata",
|
61
|
+
"ds" => "http://www.w3.org/2000/09/xmldsig#"
|
62
|
+
)
|
63
|
+
end
|
64
|
+
let(:cert) { OpenSSL::X509::Certificate.new(Base64.decode64(cert_node.text)) }
|
65
|
+
|
66
|
+
before do
|
67
|
+
settings.security[:authn_requests_signed] = true
|
68
|
+
settings.certificate = ruby_saml_cert_text
|
73
69
|
end
|
74
70
|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
71
|
+
it "generates Service Provider Metadata with X509Certificate" do
|
72
|
+
assert_equal "true", spsso_descriptor.attribute("AuthnRequestsSigned").value
|
73
|
+
assert_equal ruby_saml_cert.to_der, cert.to_der
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
describe "when attribute service is configured" do
|
78
|
+
let(:attr_svc) { REXML::XPath.first(xml_doc, "//md:AttributeConsumingService") }
|
79
|
+
let(:req_attr) { REXML::XPath.first(xml_doc, "//md:RequestedAttribute") }
|
80
|
+
|
81
|
+
before do
|
82
|
+
settings.attribute_consuming_service.configure do
|
83
|
+
service_name "Test Service"
|
84
|
+
add_attribute(:name => "Name", :name_format => "Name Format", :friendly_name => "Friendly Name", :attribute_value => "Attribute Value")
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
it "generates attribute service" do
|
89
|
+
assert_equal "true", attr_svc.attribute("isDefault").value
|
90
|
+
assert_equal "1", attr_svc.attribute("index").value
|
91
|
+
assert_equal REXML::XPath.first(xml_doc, "//md:ServiceName").text.strip, "Test Service"
|
92
|
+
|
93
|
+
assert_equal "Name", req_attr.attribute("Name").value
|
94
|
+
assert_equal "Name Format", req_attr.attribute("NameFormat").value
|
95
|
+
assert_equal "Friendly Name", req_attr.attribute("FriendlyName").value
|
96
|
+
assert_equal "Attribute Value", REXML::XPath.first(xml_doc, "//md:AttributeValue").text.strip
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
describe "when the settings indicate to sign (embedded) metadata" do
|
101
|
+
before do
|
102
|
+
settings.security[:metadata_signed] = true
|
103
|
+
settings.certificate = ruby_saml_cert_text
|
104
|
+
settings.private_key = ruby_saml_key_text
|
105
|
+
end
|
106
|
+
|
107
|
+
it "creates a signed metadata" do
|
108
|
+
assert_match %r[<ds:SignatureValue>([a-zA-Z0-9/+=]+)</ds:SignatureValue>]m, xml_text
|
109
|
+
assert_match %r[<ds:SignatureMethod Algorithm='http://www.w3.org/2000/09/xmldsig#rsa-sha1'/>], xml_text
|
110
|
+
assert_match %r[<ds:DigestMethod Algorithm='http://www.w3.org/2000/09/xmldsig#sha1'/>], xml_text
|
111
|
+
signed_metadata = XMLSecurity::SignedDocument.new(xml_text)
|
112
|
+
assert signed_metadata.validate_document(ruby_saml_cert_fingerprint, false)
|
113
|
+
end
|
114
|
+
|
115
|
+
describe "when digest and signature methods are specified" do
|
116
|
+
before do
|
117
|
+
settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA256
|
118
|
+
settings.security[:digest_method] = XMLSecurity::Document::SHA512
|
119
|
+
end
|
120
|
+
|
121
|
+
it "creates a signed metadata with specified digest and signature methods" do
|
122
|
+
assert_match %r[<ds:SignatureValue>([a-zA-Z0-9/+=]+)</ds:SignatureValue>]m, xml_text
|
123
|
+
assert_match %r[<ds:SignatureMethod Algorithm='http://www.w3.org/2001/04/xmldsig-more#rsa-sha256'/>], xml_text
|
124
|
+
assert_match %r[<ds:DigestMethod Algorithm='http://www.w3.org/2001/04/xmldsig-more#sha512'/>], xml_text
|
125
|
+
|
126
|
+
signed_metadata_2 = XMLSecurity::SignedDocument.new(xml_text)
|
127
|
+
|
128
|
+
assert signed_metadata_2.validate_document(ruby_saml_cert_fingerprint, false)
|
129
|
+
end
|
130
|
+
end
|
86
131
|
end
|
87
132
|
end
|
88
133
|
end
|