ruby-saml 0.8.16 → 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 +5 -5
- 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 -89
- data/lib/onelogin/ruby-saml/idp_metadata_parser.rb +87 -0
- data/lib/onelogin/ruby-saml/logoutrequest.rb +34 -93
- data/lib/onelogin/ruby-saml/logoutresponse.rb +25 -24
- data/lib/onelogin/ruby-saml/metadata.rb +46 -16
- data/lib/onelogin/ruby-saml/response.rb +62 -322
- data/lib/onelogin/ruby-saml/saml_message.rb +78 -0
- data/lib/onelogin/ruby-saml/settings.rb +54 -121
- data/lib/onelogin/ruby-saml/slo_logoutrequest.rb +26 -61
- data/lib/onelogin/ruby-saml/slo_logoutresponse.rb +27 -84
- 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 -144
- data/test/logoutresponse_test.rb +43 -25
- data/test/metadata_test.rb +87 -0
- data/test/request_test.rb +103 -90
- data/test/response_test.rb +181 -471
- data/test/responses/idp_descriptor.xml +3 -0
- data/test/responses/logoutresponse_fixtures.rb +5 -5
- 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 +41 -44
- data/test/slo_logoutresponse_test.rb +87 -167
- data/test/test_helper.rb +27 -102
- data/test/xml_security_test.rb +114 -337
- metadata +34 -84
- 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
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
require "base64"
|
|
2
|
+
require "uuid"
|
|
3
|
+
require "zlib"
|
|
4
|
+
require "cgi"
|
|
5
|
+
require "rexml/document"
|
|
6
|
+
require "rexml/xpath"
|
|
7
|
+
|
|
8
|
+
module OneLogin
|
|
9
|
+
module RubySaml
|
|
10
|
+
include REXML
|
|
11
|
+
|
|
12
|
+
class IdpMetadataParser
|
|
13
|
+
|
|
14
|
+
METADATA = "urn:oasis:names:tc:SAML:2.0:metadata"
|
|
15
|
+
DSIG = "http://www.w3.org/2000/09/xmldsig#"
|
|
16
|
+
|
|
17
|
+
attr_reader :document
|
|
18
|
+
|
|
19
|
+
def parse_remote(url, validate_cert = true)
|
|
20
|
+
idp_metadata = get_idp_metadata(url, validate_cert)
|
|
21
|
+
parse(idp_metadata)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def parse(idp_metadata)
|
|
25
|
+
@document = REXML::Document.new(idp_metadata)
|
|
26
|
+
|
|
27
|
+
OneLogin::RubySaml::Settings.new.tap do |settings|
|
|
28
|
+
|
|
29
|
+
settings.idp_sso_target_url = single_signon_service_url
|
|
30
|
+
settings.idp_slo_target_url = single_logout_service_url
|
|
31
|
+
settings.idp_cert_fingerprint = fingerprint
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
# Retrieve the remote IdP metadata from the URL or a cached copy
|
|
38
|
+
# # returns a REXML document of the metadata
|
|
39
|
+
def get_idp_metadata(url, validate_cert)
|
|
40
|
+
uri = URI.parse(url)
|
|
41
|
+
if uri.scheme == "http"
|
|
42
|
+
response = Net::HTTP.get_response(uri)
|
|
43
|
+
meta_text = response.body
|
|
44
|
+
elsif uri.scheme == "https"
|
|
45
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
46
|
+
http.use_ssl = true
|
|
47
|
+
# Most IdPs will probably use self signed certs
|
|
48
|
+
if validate_cert
|
|
49
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
|
50
|
+
else
|
|
51
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
|
52
|
+
end
|
|
53
|
+
get = Net::HTTP::Get.new(uri.request_uri)
|
|
54
|
+
response = http.request(get)
|
|
55
|
+
meta_text = response.body
|
|
56
|
+
end
|
|
57
|
+
meta_text
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def single_signon_service_url
|
|
61
|
+
node = REXML::XPath.first(document, "/md:EntityDescriptor/md:IDPSSODescriptor/md:SingleSignOnService/@Location", { "md" => METADATA })
|
|
62
|
+
node.value if node
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def single_logout_service_url
|
|
66
|
+
node = REXML::XPath.first(document, "/md:EntityDescriptor/md:IDPSSODescriptor/md:SingleLogoutService/@Location", { "md" => METADATA })
|
|
67
|
+
node.value if node
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def certificate
|
|
71
|
+
@certificate ||= begin
|
|
72
|
+
node = REXML::XPath.first(document, "/md:EntityDescriptor/md:IDPSSODescriptor/md:KeyDescriptor[@use='signing']/ds:KeyInfo/ds:X509Data/ds:X509Certificate", { "md" => METADATA, "ds" => DSIG })
|
|
73
|
+
Base64.decode64(node.text) if node
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def fingerprint
|
|
78
|
+
@fingerprint ||= begin
|
|
79
|
+
if certificate
|
|
80
|
+
cert = OpenSSL::X509::Certificate.new(certificate)
|
|
81
|
+
Digest::SHA1.hexdigest(cert.to_der).upcase.scan(/../).join(":")
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
@@ -1,19 +1,15 @@
|
|
|
1
|
-
require "
|
|
2
|
-
|
|
3
|
-
require "
|
|
4
|
-
require 'rexml/document'
|
|
5
|
-
require "onelogin/ruby-saml/utils"
|
|
6
|
-
require "onelogin/ruby-saml/setting_error"
|
|
1
|
+
require "uuid"
|
|
2
|
+
|
|
3
|
+
require "onelogin/ruby-saml/logging"
|
|
7
4
|
|
|
8
5
|
module OneLogin
|
|
9
6
|
module RubySaml
|
|
10
|
-
|
|
11
|
-
class Logoutrequest
|
|
7
|
+
class Logoutrequest < SamlMessage
|
|
12
8
|
|
|
13
9
|
attr_reader :uuid # Can be obtained if neccessary
|
|
14
10
|
|
|
15
11
|
def initialize
|
|
16
|
-
@uuid =
|
|
12
|
+
@uuid = "_" + UUID.new.generate
|
|
17
13
|
end
|
|
18
14
|
|
|
19
15
|
def create(settings, params={})
|
|
@@ -24,25 +20,11 @@ module OneLogin
|
|
|
24
20
|
params.each_pair do |key, value|
|
|
25
21
|
request_params << "&#{key.to_s}=#{CGI.escape(value.to_s)}"
|
|
26
22
|
end
|
|
27
|
-
raise SettingError.new "Invalid settings, idp_slo_target_url is not set!" if settings.idp_slo_target_url.nil? or settings.idp_slo_target_url.empty?
|
|
28
23
|
@logout_url = settings.idp_slo_target_url + request_params
|
|
29
24
|
end
|
|
30
25
|
|
|
31
|
-
# Creates the Get parameters for the logout request.
|
|
32
|
-
# @param settings [OneLogin::RubySaml::Settings|nil] Toolkit settings
|
|
33
|
-
# @param params [Hash] Some extra parameters to be added in the GET for example the RelayState
|
|
34
|
-
# @return [Hash] Parameters
|
|
35
|
-
#
|
|
36
26
|
def create_params(settings, params={})
|
|
37
|
-
|
|
38
|
-
# Based on the HashWithIndifferentAccess value in Rails we could experience
|
|
39
|
-
# conflicts so this line will solve them.
|
|
40
|
-
relay_state = params[:RelayState] || params['RelayState']
|
|
41
|
-
|
|
42
|
-
if relay_state.nil?
|
|
43
|
-
params.delete(:RelayState)
|
|
44
|
-
params.delete('RelayState')
|
|
45
|
-
end
|
|
27
|
+
params = {} if params.nil?
|
|
46
28
|
|
|
47
29
|
request_doc = create_logout_request_xml_doc(settings)
|
|
48
30
|
request_doc.context[:attribute_quote] = :quote if settings.double_quote_xml_attribute_values
|
|
@@ -52,29 +34,18 @@ module OneLogin
|
|
|
52
34
|
|
|
53
35
|
Logging.debug "Created SLO Logout Request: #{request}"
|
|
54
36
|
|
|
55
|
-
request =
|
|
56
|
-
|
|
57
|
-
base64_request = Base64.strict_encode64(request)
|
|
58
|
-
else
|
|
59
|
-
base64_request = Base64.encode64(request).gsub(/\n/, "")
|
|
60
|
-
end
|
|
37
|
+
request = deflate(request) if settings.compress_request
|
|
38
|
+
base64_request = encode(request)
|
|
61
39
|
request_params = {"SAMLRequest" => base64_request}
|
|
62
40
|
|
|
63
41
|
if settings.security[:logout_requests_signed] && !settings.security[:embed_sign] && settings.private_key
|
|
64
|
-
params['SigAlg'] =
|
|
65
|
-
url_string
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
)
|
|
71
|
-
sign_algorithm = XMLSecurity::BaseDocument.new.algorithm(settings.security[:signature_method])
|
|
72
|
-
signature = settings.get_sp_key.sign(sign_algorithm.new, url_string)
|
|
73
|
-
if Base64.respond_to?('strict_encode64')
|
|
74
|
-
params['Signature'] = Base64.strict_encode64(signature)
|
|
75
|
-
else
|
|
76
|
-
params['Signature'] = Base64.encode64(signature).gsub(/\n/, "")
|
|
77
|
-
end
|
|
42
|
+
params['SigAlg'] = XMLSecurity::Document::SHA1
|
|
43
|
+
url_string = "SAMLRequest=#{CGI.escape(base64_request)}"
|
|
44
|
+
url_string += "&RelayState=#{CGI.escape(params['RelayState'])}" if params['RelayState']
|
|
45
|
+
url_string += "&SigAlg=#{CGI.escape(params['SigAlg'])}"
|
|
46
|
+
private_key = settings.get_sp_key()
|
|
47
|
+
signature = private_key.sign(XMLSecurity::BaseDocument.new.algorithm(settings.security[:signature_method]).new, url_string)
|
|
48
|
+
params['Signature'] = encode(signature)
|
|
78
49
|
end
|
|
79
50
|
|
|
80
51
|
params.each_pair do |key, value|
|
|
@@ -84,77 +55,47 @@ module OneLogin
|
|
|
84
55
|
request_params
|
|
85
56
|
end
|
|
86
57
|
|
|
87
|
-
# Creates the SAMLRequest String.
|
|
88
|
-
# @param settings [OneLogin::RubySaml::Settings|nil] Toolkit settings
|
|
89
|
-
# @return [String] The SAMLRequest String.
|
|
90
|
-
#
|
|
91
58
|
def create_logout_request_xml_doc(settings)
|
|
92
|
-
|
|
93
|
-
sign_document(document, settings)
|
|
94
|
-
end
|
|
59
|
+
time = Time.now.utc.strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
95
60
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
if request_doc.nil?
|
|
100
|
-
request_doc = XMLSecurity::Document.new
|
|
101
|
-
request_doc.uuid = uuid
|
|
102
|
-
end
|
|
61
|
+
request_doc = XMLSecurity::Document.new
|
|
62
|
+
request_doc.uuid = uuid
|
|
103
63
|
|
|
104
|
-
root = request_doc.add_element "samlp:LogoutRequest", { "xmlns:samlp" => "urn:oasis:names:tc:SAML:2.0:protocol" }
|
|
64
|
+
root = request_doc.add_element "samlp:LogoutRequest", { "xmlns:samlp" => "urn:oasis:names:tc:SAML:2.0:protocol", "xmlns:saml" => "urn:oasis:names:tc:SAML:2.0:assertion" }
|
|
105
65
|
root.attributes['ID'] = uuid
|
|
106
66
|
root.attributes['IssueInstant'] = time
|
|
107
67
|
root.attributes['Version'] = "2.0"
|
|
108
|
-
root.attributes['Destination'] = settings.idp_slo_target_url unless settings.idp_slo_target_url.nil?
|
|
68
|
+
root.attributes['Destination'] = settings.idp_slo_target_url unless settings.idp_slo_target_url.nil?
|
|
109
69
|
|
|
110
|
-
if settings.
|
|
111
|
-
issuer = root.add_element "saml:Issuer"
|
|
112
|
-
issuer.text = settings.
|
|
70
|
+
if settings.issuer
|
|
71
|
+
issuer = root.add_element "saml:Issuer"
|
|
72
|
+
issuer.text = settings.issuer
|
|
113
73
|
end
|
|
114
74
|
|
|
75
|
+
name_id = root.add_element "saml:NameID"
|
|
115
76
|
if settings.name_identifier_value
|
|
116
|
-
name_id = root.add_element "saml:NameID", { "xmlns:saml" => "urn:oasis:names:tc:SAML:2.0:assertion" }
|
|
117
77
|
name_id.attributes['NameQualifier'] = settings.sp_name_qualifier if settings.sp_name_qualifier
|
|
118
78
|
name_id.attributes['Format'] = settings.name_identifier_format if settings.name_identifier_format
|
|
119
79
|
name_id.text = settings.name_identifier_value
|
|
80
|
+
else
|
|
81
|
+
# If no NameID is present in the settings we generate one
|
|
82
|
+
name_id.text = "_" + UUID.new.generate
|
|
83
|
+
name_id.attributes['Format'] = 'urn:oasis:names:tc:SAML:2.0:nameid-format:transient'
|
|
120
84
|
end
|
|
121
85
|
|
|
122
86
|
if settings.sessionindex
|
|
123
|
-
sessionindex = root.add_element "samlp:SessionIndex"
|
|
87
|
+
sessionindex = root.add_element "samlp:SessionIndex"
|
|
124
88
|
sessionindex.text = settings.sessionindex
|
|
125
89
|
end
|
|
126
90
|
|
|
127
|
-
#
|
|
128
|
-
# match required for authentication to succeed. If this is not defined,
|
|
129
|
-
# the IdP will choose default rules for authentication. (Shibboleth IdP)
|
|
130
|
-
if settings.authn_context != nil
|
|
131
|
-
requested_context = root.add_element "samlp:RequestedAuthnContext", {
|
|
132
|
-
"xmlns:samlp" => "urn:oasis:names:tc:SAML:2.0:protocol",
|
|
133
|
-
"Comparison" => "exact",
|
|
134
|
-
}
|
|
135
|
-
class_ref = requested_context.add_element "saml:AuthnContextClassRef", {
|
|
136
|
-
"xmlns:saml" => "urn:oasis:names:tc:SAML:2.0:assertion",
|
|
137
|
-
}
|
|
138
|
-
class_ref.text = settings.authn_context
|
|
139
|
-
end
|
|
140
|
-
request_doc
|
|
141
|
-
end
|
|
142
|
-
|
|
143
|
-
def sign_document(document, settings)
|
|
144
|
-
# embed signature
|
|
91
|
+
# embebed sign
|
|
145
92
|
if settings.security[:logout_requests_signed] && settings.private_key && settings.certificate && settings.security[:embed_sign]
|
|
146
|
-
private_key = settings.get_sp_key
|
|
147
|
-
cert = settings.get_sp_cert
|
|
148
|
-
|
|
93
|
+
private_key = settings.get_sp_key()
|
|
94
|
+
cert = settings.get_sp_cert()
|
|
95
|
+
request_doc.sign_document(private_key, cert, settings.security[:signature_method], settings.security[:digest_method])
|
|
149
96
|
end
|
|
150
97
|
|
|
151
|
-
|
|
152
|
-
end
|
|
153
|
-
|
|
154
|
-
# Leave due compatibility
|
|
155
|
-
def create_unauth_xml_doc(settings, params)
|
|
156
|
-
request_doc = ReXML::Document.new
|
|
157
|
-
create_xml_document(settings, request_doc)
|
|
98
|
+
request_doc
|
|
158
99
|
end
|
|
159
100
|
end
|
|
160
101
|
end
|
|
@@ -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,7 +24,7 @@ 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
|
|
|
@@ -37,7 +33,7 @@ module OneLogin
|
|
|
37
33
|
end
|
|
38
34
|
|
|
39
35
|
def validate(soft = true)
|
|
40
|
-
return false unless
|
|
36
|
+
return false unless valid_saml?(document, soft) && valid_state?(soft)
|
|
41
37
|
|
|
42
38
|
valid_in_response_to?(soft) && valid_issuer?(soft) && success?(soft)
|
|
43
39
|
end
|
|
@@ -51,7 +47,7 @@ module OneLogin
|
|
|
51
47
|
|
|
52
48
|
def in_response_to
|
|
53
49
|
@in_response_to ||= begin
|
|
54
|
-
node = REXML::XPath.first(document, "/p:LogoutResponse", { "p" => PROTOCOL })
|
|
50
|
+
node = REXML::XPath.first(document, "/p:LogoutResponse", { "p" => PROTOCOL, "a" => ASSERTION })
|
|
55
51
|
node.nil? ? nil : node.attributes['InResponseTo']
|
|
56
52
|
end
|
|
57
53
|
end
|
|
@@ -59,7 +55,8 @@ module OneLogin
|
|
|
59
55
|
def issuer
|
|
60
56
|
@issuer ||= begin
|
|
61
57
|
node = REXML::XPath.first(document, "/p:LogoutResponse/a:Issuer", { "p" => PROTOCOL, "a" => ASSERTION })
|
|
62
|
-
|
|
58
|
+
node ||= REXML::XPath.first(document, "/p:LogoutResponse/a:Assertion/a:Issuer", { "p" => PROTOCOL, "a" => ASSERTION })
|
|
59
|
+
node.nil? ? nil : node.text
|
|
63
60
|
end
|
|
64
61
|
end
|
|
65
62
|
|
|
@@ -72,16 +69,24 @@ module OneLogin
|
|
|
72
69
|
|
|
73
70
|
private
|
|
74
71
|
|
|
75
|
-
def
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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")
|
|
79
79
|
end
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
@schema.validate(@xml).map{ |error| validation_error("#{error.message}\n\n#{@xml.to_s}") }
|
|
80
|
+
|
|
81
|
+
if settings.issuer.nil?
|
|
82
|
+
return soft ? false : validation_error("No issuer in settings")
|
|
84
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")
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
true
|
|
85
90
|
end
|
|
86
91
|
|
|
87
92
|
def valid_in_response_to?(soft = true)
|
|
@@ -95,17 +100,13 @@ module OneLogin
|
|
|
95
100
|
end
|
|
96
101
|
|
|
97
102
|
def valid_issuer?(soft = true)
|
|
98
|
-
return true if
|
|
103
|
+
return true if self.settings.idp_entity_id.nil? or self.issuer.nil?
|
|
99
104
|
|
|
100
|
-
unless
|
|
101
|
-
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}>")
|
|
102
107
|
end
|
|
103
108
|
true
|
|
104
109
|
end
|
|
105
|
-
|
|
106
|
-
def validation_error(message)
|
|
107
|
-
raise ValidationError.new(message)
|
|
108
|
-
end
|
|
109
110
|
end
|
|
110
111
|
end
|
|
111
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
|