ruby-saml 0.8.18 → 0.9
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.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.travis.yml +1 -6
- data/Gemfile +2 -12
- data/README.md +363 -35
- data/Rakefile +14 -0
- data/changelog.md +22 -9
- data/lib/onelogin/ruby-saml/attribute_service.rb +34 -0
- data/lib/onelogin/ruby-saml/attributes.rb +26 -64
- data/lib/onelogin/ruby-saml/authrequest.rb +47 -93
- data/lib/onelogin/ruby-saml/idp_metadata_parser.rb +87 -0
- data/lib/onelogin/ruby-saml/logoutrequest.rb +36 -100
- data/lib/onelogin/ruby-saml/logoutresponse.rb +25 -35
- data/lib/onelogin/ruby-saml/metadata.rb +46 -16
- data/lib/onelogin/ruby-saml/response.rb +63 -373
- data/lib/onelogin/ruby-saml/saml_message.rb +78 -0
- data/lib/onelogin/ruby-saml/settings.rb +54 -122
- data/lib/onelogin/ruby-saml/slo_logoutrequest.rb +25 -71
- data/lib/onelogin/ruby-saml/slo_logoutresponse.rb +37 -102
- data/lib/onelogin/ruby-saml/utils.rb +32 -199
- data/lib/onelogin/ruby-saml/version.rb +1 -1
- data/lib/ruby-saml.rb +5 -2
- data/lib/schemas/{saml20assertion_schema.xsd → saml-schema-assertion-2.0.xsd} +283 -283
- data/lib/schemas/saml-schema-authn-context-2.0.xsd +23 -0
- data/lib/schemas/saml-schema-authn-context-types-2.0.xsd +821 -0
- data/lib/schemas/saml-schema-metadata-2.0.xsd +339 -0
- data/lib/schemas/{saml20protocol_schema.xsd → saml-schema-protocol-2.0.xsd} +302 -302
- data/lib/schemas/sstc-metadata-attr.xsd +35 -0
- data/lib/schemas/sstc-saml-attribute-ext.xsd +25 -0
- data/lib/schemas/sstc-saml-metadata-algsupport-v1.0.xsd +41 -0
- data/lib/schemas/sstc-saml-metadata-ui-v1.0.xsd +89 -0
- data/lib/schemas/{xenc_schema.xsd → xenc-schema.xsd} +1 -11
- data/lib/schemas/xml.xsd +287 -0
- data/lib/schemas/{xmldsig_schema.xsd → xmldsig-core-schema.xsd} +0 -9
- data/lib/xml_security.rb +83 -235
- data/ruby-saml.gemspec +1 -0
- data/test/idp_metadata_parser_test.rb +54 -0
- data/test/logoutrequest_test.rb +68 -155
- data/test/logoutresponse_test.rb +43 -32
- data/test/metadata_test.rb +87 -0
- data/test/request_test.rb +102 -99
- data/test/response_test.rb +181 -495
- data/test/responses/idp_descriptor.xml +3 -0
- data/test/responses/logoutresponse_fixtures.rb +7 -8
- data/test/responses/response_no_cert_and_encrypted_attrs.xml +29 -0
- data/test/responses/response_with_multiple_attribute_values.xml +1 -1
- data/test/responses/slo_request.xml +4 -0
- data/test/settings_test.rb +25 -112
- data/test/slo_logoutrequest_test.rb +40 -50
- data/test/slo_logoutresponse_test.rb +86 -185
- data/test/test_helper.rb +27 -102
- data/test/xml_security_test.rb +114 -337
- metadata +30 -81
- data/lib/onelogin/ruby-saml/setting_error.rb +0 -6
- data/test/certificates/certificate.der +0 -0
- data/test/certificates/formatted_certificate +0 -14
- data/test/certificates/formatted_chained_certificate +0 -42
- data/test/certificates/formatted_private_key +0 -12
- data/test/certificates/formatted_rsa_private_key +0 -12
- data/test/certificates/invalid_certificate1 +0 -1
- data/test/certificates/invalid_certificate2 +0 -1
- data/test/certificates/invalid_certificate3 +0 -12
- data/test/certificates/invalid_chained_certificate1 +0 -1
- data/test/certificates/invalid_private_key1 +0 -1
- data/test/certificates/invalid_private_key2 +0 -1
- data/test/certificates/invalid_private_key3 +0 -10
- data/test/certificates/invalid_rsa_private_key1 +0 -1
- data/test/certificates/invalid_rsa_private_key2 +0 -1
- data/test/certificates/invalid_rsa_private_key3 +0 -10
- data/test/certificates/ruby-saml-2.crt +0 -15
- data/test/requests/logoutrequest_fixtures.rb +0 -47
- data/test/responses/encrypted_new_attack.xml.base64 +0 -1
- data/test/responses/invalids/invalid_issuer_assertion.xml.base64 +0 -1
- data/test/responses/invalids/invalid_issuer_message.xml.base64 +0 -1
- data/test/responses/invalids/multiple_signed.xml.base64 +0 -1
- data/test/responses/invalids/no_signature.xml.base64 +0 -1
- data/test/responses/invalids/response_with_concealed_signed_assertion.xml +0 -51
- data/test/responses/invalids/response_with_doubled_signed_assertion.xml +0 -49
- data/test/responses/invalids/signature_wrapping_attack.xml.base64 +0 -1
- data/test/responses/response_node_text_attack.xml.base64 +0 -1
- data/test/responses/response_with_concealed_signed_assertion.xml +0 -51
- data/test/responses/response_with_doubled_signed_assertion.xml +0 -49
- data/test/responses/response_with_multiple_attribute_statements.xml +0 -72
- data/test/responses/response_with_signed_assertion_3.xml +0 -30
- data/test/responses/response_with_signed_message_and_assertion.xml +0 -34
- data/test/responses/response_with_undefined_recipient.xml.base64 +0 -1
- data/test/responses/response_wrapped.xml.base64 +0 -150
- data/test/responses/valid_response.xml.base64 +0 -1
- data/test/responses/valid_response_without_x509certificate.xml.base64 +0 -1
- data/test/utils_test.rb +0 -231
|
@@ -3,11 +3,7 @@ require "time"
|
|
|
3
3
|
|
|
4
4
|
module OneLogin
|
|
5
5
|
module RubySaml
|
|
6
|
-
class Logoutresponse
|
|
7
|
-
|
|
8
|
-
ASSERTION = "urn:oasis:names:tc:SAML:2.0:assertion"
|
|
9
|
-
PROTOCOL = "urn:oasis:names:tc:SAML:2.0:protocol"
|
|
10
|
-
|
|
6
|
+
class Logoutresponse < SamlMessage
|
|
11
7
|
# For API compability, this is mutable.
|
|
12
8
|
attr_accessor :settings
|
|
13
9
|
|
|
@@ -28,27 +24,16 @@ module OneLogin
|
|
|
28
24
|
self.settings = settings
|
|
29
25
|
|
|
30
26
|
@options = options
|
|
31
|
-
@response =
|
|
27
|
+
@response = decode_raw_saml(response)
|
|
32
28
|
@document = XMLSecurity::SignedDocument.new(@response)
|
|
33
29
|
end
|
|
34
30
|
|
|
35
|
-
def response_id
|
|
36
|
-
@response_id ||= begin
|
|
37
|
-
node = REXML::XPath.first(
|
|
38
|
-
document,
|
|
39
|
-
"/p:LogoutResponse",
|
|
40
|
-
{ "p" => PROTOCOL }
|
|
41
|
-
)
|
|
42
|
-
node.nil? ? nil : node.attributes['ID']
|
|
43
|
-
end
|
|
44
|
-
end
|
|
45
|
-
|
|
46
31
|
def validate!
|
|
47
32
|
validate(false)
|
|
48
33
|
end
|
|
49
34
|
|
|
50
35
|
def validate(soft = true)
|
|
51
|
-
return false unless
|
|
36
|
+
return false unless valid_saml?(document, soft) && valid_state?(soft)
|
|
52
37
|
|
|
53
38
|
valid_in_response_to?(soft) && valid_issuer?(soft) && success?(soft)
|
|
54
39
|
end
|
|
@@ -62,7 +47,7 @@ module OneLogin
|
|
|
62
47
|
|
|
63
48
|
def in_response_to
|
|
64
49
|
@in_response_to ||= begin
|
|
65
|
-
node = REXML::XPath.first(document, "/p:LogoutResponse", { "p" => PROTOCOL })
|
|
50
|
+
node = REXML::XPath.first(document, "/p:LogoutResponse", { "p" => PROTOCOL, "a" => ASSERTION })
|
|
66
51
|
node.nil? ? nil : node.attributes['InResponseTo']
|
|
67
52
|
end
|
|
68
53
|
end
|
|
@@ -70,7 +55,8 @@ module OneLogin
|
|
|
70
55
|
def issuer
|
|
71
56
|
@issuer ||= begin
|
|
72
57
|
node = REXML::XPath.first(document, "/p:LogoutResponse/a:Issuer", { "p" => PROTOCOL, "a" => ASSERTION })
|
|
73
|
-
|
|
58
|
+
node ||= REXML::XPath.first(document, "/p:LogoutResponse/a:Assertion/a:Issuer", { "p" => PROTOCOL, "a" => ASSERTION })
|
|
59
|
+
node.nil? ? nil : node.text
|
|
74
60
|
end
|
|
75
61
|
end
|
|
76
62
|
|
|
@@ -83,16 +69,24 @@ module OneLogin
|
|
|
83
69
|
|
|
84
70
|
private
|
|
85
71
|
|
|
86
|
-
def
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
72
|
+
def valid_state?(soft = true)
|
|
73
|
+
if response.empty?
|
|
74
|
+
return soft ? false : validation_error("Blank response")
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
if settings.nil?
|
|
78
|
+
return soft ? false : validation_error("No settings on response")
|
|
90
79
|
end
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
80
|
+
|
|
81
|
+
if settings.issuer.nil?
|
|
82
|
+
return soft ? false : validation_error("No issuer in settings")
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
if settings.idp_cert_fingerprint.nil? && settings.idp_cert.nil?
|
|
86
|
+
return soft ? false : validation_error("No fingerprint or certificate on settings")
|
|
95
87
|
end
|
|
88
|
+
|
|
89
|
+
true
|
|
96
90
|
end
|
|
97
91
|
|
|
98
92
|
def valid_in_response_to?(soft = true)
|
|
@@ -106,17 +100,13 @@ module OneLogin
|
|
|
106
100
|
end
|
|
107
101
|
|
|
108
102
|
def valid_issuer?(soft = true)
|
|
109
|
-
return true if
|
|
103
|
+
return true if self.settings.idp_entity_id.nil? or self.issuer.nil?
|
|
110
104
|
|
|
111
|
-
unless
|
|
112
|
-
return soft ? false : validation_error("Doesn't match the issuer, expected: <#{self.settings.
|
|
105
|
+
unless URI.parse(self.issuer) == URI.parse(self.settings.idp_entity_id)
|
|
106
|
+
return soft ? false : validation_error("Doesn't match the issuer, expected: <#{self.settings.issuer}>, but was: <#{issuer}>")
|
|
113
107
|
end
|
|
114
108
|
true
|
|
115
109
|
end
|
|
116
|
-
|
|
117
|
-
def validation_error(message)
|
|
118
|
-
raise ValidationError.new(message)
|
|
119
|
-
end
|
|
120
110
|
end
|
|
121
111
|
end
|
|
122
112
|
end
|
|
@@ -2,6 +2,8 @@ require "rexml/document"
|
|
|
2
2
|
require "rexml/xpath"
|
|
3
3
|
require "uri"
|
|
4
4
|
|
|
5
|
+
require "onelogin/ruby-saml/logging"
|
|
6
|
+
|
|
5
7
|
# Class to return SP metadata based on the settings requested.
|
|
6
8
|
# Return this XML in a controller, then give that URL to the the
|
|
7
9
|
# IdP administrator. The IdP will poll the URL and your settings
|
|
@@ -17,49 +19,77 @@ module OneLogin
|
|
|
17
19
|
}
|
|
18
20
|
sp_sso = root.add_element "md:SPSSODescriptor", {
|
|
19
21
|
"protocolSupportEnumeration" => "urn:oasis:names:tc:SAML:2.0:protocol",
|
|
20
|
-
|
|
21
|
-
"AuthnRequestsSigned" => false,
|
|
22
|
+
"AuthnRequestsSigned" => settings.security[:authn_requests_signed],
|
|
22
23
|
# However we would like assertions signed if idp_cert_fingerprint or idp_cert is set
|
|
23
|
-
"WantAssertionsSigned" => (
|
|
24
|
+
"WantAssertionsSigned" => !!(settings.idp_cert_fingerprint || settings.idp_cert)
|
|
24
25
|
}
|
|
25
|
-
if settings.
|
|
26
|
-
root.attributes["entityID"] = settings.
|
|
26
|
+
if settings.issuer
|
|
27
|
+
root.attributes["entityID"] = settings.issuer
|
|
27
28
|
end
|
|
28
|
-
if settings.single_logout_service_url
|
|
29
|
+
if settings.single_logout_service_url
|
|
29
30
|
sp_sso.add_element "md:SingleLogoutService", {
|
|
30
|
-
|
|
31
|
-
"Binding" => "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect",
|
|
31
|
+
"Binding" => settings.single_logout_service_binding,
|
|
32
32
|
"Location" => settings.single_logout_service_url,
|
|
33
33
|
"ResponseLocation" => settings.single_logout_service_url,
|
|
34
34
|
"isDefault" => true,
|
|
35
35
|
"index" => 0
|
|
36
36
|
}
|
|
37
37
|
end
|
|
38
|
-
if settings.name_identifier_format
|
|
38
|
+
if settings.name_identifier_format
|
|
39
39
|
name_id = sp_sso.add_element "md:NameIDFormat"
|
|
40
40
|
name_id.text = settings.name_identifier_format
|
|
41
41
|
end
|
|
42
|
-
if settings.assertion_consumer_service_url
|
|
42
|
+
if settings.assertion_consumer_service_url
|
|
43
43
|
sp_sso.add_element "md:AssertionConsumerService", {
|
|
44
|
-
|
|
45
|
-
"Binding" => "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST",
|
|
44
|
+
"Binding" => settings.assertion_consumer_service_binding,
|
|
46
45
|
"Location" => settings.assertion_consumer_service_url,
|
|
47
46
|
"isDefault" => true,
|
|
48
47
|
"index" => 0
|
|
49
48
|
}
|
|
50
49
|
end
|
|
50
|
+
|
|
51
|
+
# Add KeyDescriptor if messages will be signed
|
|
52
|
+
cert = settings.get_sp_cert()
|
|
53
|
+
if cert
|
|
54
|
+
kd = sp_sso.add_element "md:KeyDescriptor", { "use" => "signing" }
|
|
55
|
+
ki = kd.add_element "ds:KeyInfo", {"xmlns:ds" => "http://www.w3.org/2000/09/xmldsig#"}
|
|
56
|
+
xd = ki.add_element "ds:X509Data"
|
|
57
|
+
xc = xd.add_element "ds:X509Certificate"
|
|
58
|
+
xc.text = Base64.encode64(cert.to_der).gsub("\n", '')
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
if settings.attribute_consuming_service.configured?
|
|
62
|
+
sp_acs = sp_sso.add_element "md:AttributeConsumingService", {
|
|
63
|
+
"isDefault" => "true",
|
|
64
|
+
"index" => settings.attribute_consuming_service.index
|
|
65
|
+
}
|
|
66
|
+
srv_name = sp_acs.add_element "md:ServiceName", {
|
|
67
|
+
"xml:lang" => "en"
|
|
68
|
+
}
|
|
69
|
+
srv_name.text = settings.attribute_consuming_service.name
|
|
70
|
+
settings.attribute_consuming_service.attributes.each do |attribute|
|
|
71
|
+
sp_req_attr = sp_acs.add_element "md:RequestedAttribute", {
|
|
72
|
+
"NameFormat" => attribute[:name_format],
|
|
73
|
+
"Name" => attribute[:name],
|
|
74
|
+
"FriendlyName" => attribute[:friendly_name]
|
|
75
|
+
}
|
|
76
|
+
unless attribute[:attribute_value].nil?
|
|
77
|
+
sp_attr_val = sp_req_attr.add_element "md:AttributeValue"
|
|
78
|
+
sp_attr_val.text = attribute[:attribute_value]
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
51
83
|
# With OpenSSO, it might be required to also include
|
|
52
84
|
# <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
85
|
# <md:XACMLAuthzDecisionQueryDescriptor WantAssertionsSigned="false" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol"/>
|
|
54
86
|
|
|
55
|
-
meta_doc << REXML::XMLDecl.new
|
|
87
|
+
meta_doc << REXML::XMLDecl.new("1.0", "UTF-8")
|
|
56
88
|
ret = ""
|
|
57
89
|
# pretty print the XML so IdP administrators can easily see what the SP supports
|
|
58
90
|
meta_doc.write(ret, 1)
|
|
59
91
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
ret
|
|
92
|
+
return ret
|
|
63
93
|
end
|
|
64
94
|
end
|
|
65
95
|
end
|