saml-kit 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +12 -0
- data/.rspec +2 -0
- data/.travis.yml +5 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +21 -0
- data/README.md +39 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/exe/saml-kit-decode-http-redirect +7 -0
- data/lib/saml/kit.rb +60 -0
- data/lib/saml/kit/authentication_request.rb +78 -0
- data/lib/saml/kit/binding.rb +40 -0
- data/lib/saml/kit/configuration.rb +36 -0
- data/lib/saml/kit/default_registry.rb +49 -0
- data/lib/saml/kit/document.rb +96 -0
- data/lib/saml/kit/fingerprint.rb +40 -0
- data/lib/saml/kit/http_post_binding.rb +27 -0
- data/lib/saml/kit/http_redirect_binding.rb +58 -0
- data/lib/saml/kit/identity_provider_metadata.rb +122 -0
- data/lib/saml/kit/invalid_document.rb +13 -0
- data/lib/saml/kit/locales/en.yml +25 -0
- data/lib/saml/kit/logout_request.rb +78 -0
- data/lib/saml/kit/logout_response.rb +63 -0
- data/lib/saml/kit/metadata.rb +171 -0
- data/lib/saml/kit/namespaces.rb +47 -0
- data/lib/saml/kit/requestable.rb +14 -0
- data/lib/saml/kit/respondable.rb +35 -0
- data/lib/saml/kit/response.rb +197 -0
- data/lib/saml/kit/self_signed_certificate.rb +30 -0
- data/lib/saml/kit/serializable.rb +31 -0
- data/lib/saml/kit/service_provider_metadata.rb +99 -0
- data/lib/saml/kit/signature.rb +75 -0
- data/lib/saml/kit/trustable.rb +62 -0
- data/lib/saml/kit/url_builder.rb +38 -0
- data/lib/saml/kit/version.rb +5 -0
- data/lib/saml/kit/xml.rb +57 -0
- data/lib/saml/kit/xsd/MetadataExchange.xsd +95 -0
- data/lib/saml/kit/xsd/oasis-200401-wss-wssecurity-secext-1.0.xsd +196 -0
- data/lib/saml/kit/xsd/oasis-200401-wss-wssecurity-utility-1.0.xsd +95 -0
- data/lib/saml/kit/xsd/saml-schema-assertion-2.0.xsd +283 -0
- data/lib/saml/kit/xsd/saml-schema-authn-context-2.0.xsd +23 -0
- data/lib/saml/kit/xsd/saml-schema-authn-context-types-2.0.xsd +821 -0
- data/lib/saml/kit/xsd/saml-schema-metadata-2.0.xsd +335 -0
- data/lib/saml/kit/xsd/saml-schema-protocol-2.0.xsd +302 -0
- data/lib/saml/kit/xsd/sstc-metadata-attr.xsd +35 -0
- data/lib/saml/kit/xsd/sstc-saml-attribute-ext.xsd +25 -0
- data/lib/saml/kit/xsd/sstc-saml-metadata-algsupport-v1.0.xsd +41 -0
- data/lib/saml/kit/xsd/sstc-saml-metadata-ui-v1.0.xsd +89 -0
- data/lib/saml/kit/xsd/ws-addr.xsd +120 -0
- data/lib/saml/kit/xsd/ws-authorization.xsd +145 -0
- data/lib/saml/kit/xsd/ws-federation.xsd +471 -0
- data/lib/saml/kit/xsd/ws-securitypolicy-1.2.xsd +900 -0
- data/lib/saml/kit/xsd/xenc-schema.xsd +136 -0
- data/lib/saml/kit/xsd/xml.xsd +287 -0
- data/lib/saml/kit/xsd/xmldsig-core-schema.xsd +309 -0
- data/lib/saml/kit/xsd_validatable.rb +19 -0
- data/saml-kit.gemspec +35 -0
- metadata +243 -0
@@ -0,0 +1,40 @@
|
|
1
|
+
module Saml
|
2
|
+
module Kit
|
3
|
+
class Fingerprint
|
4
|
+
attr_reader :x509
|
5
|
+
|
6
|
+
def initialize(raw_certificate)
|
7
|
+
@x509 = OpenSSL::X509::Certificate.new(raw_certificate)
|
8
|
+
rescue OpenSSL::X509::CertificateError => error
|
9
|
+
Saml::Kit.logger.warn(error)
|
10
|
+
@x509 = OpenSSL::X509::Certificate.new(Base64.decode64(raw_certificate))
|
11
|
+
end
|
12
|
+
|
13
|
+
def algorithm(algorithm)
|
14
|
+
pretty_fingerprint(algorithm.new.hexdigest(x509.to_der))
|
15
|
+
end
|
16
|
+
|
17
|
+
def ==(other)
|
18
|
+
self.to_s == other.to_s
|
19
|
+
end
|
20
|
+
|
21
|
+
def eql?(other)
|
22
|
+
self == other
|
23
|
+
end
|
24
|
+
|
25
|
+
def hash
|
26
|
+
to_s.hash
|
27
|
+
end
|
28
|
+
|
29
|
+
def to_s
|
30
|
+
algorithm(OpenSSL::Digest::SHA256)
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def pretty_fingerprint(fingerprint)
|
36
|
+
fingerprint.upcase.scan(/../).join(":")
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Saml
|
2
|
+
module Kit
|
3
|
+
class HttpPostBinding < Binding
|
4
|
+
include Serializable
|
5
|
+
|
6
|
+
def initialize(location:)
|
7
|
+
super(binding: Saml::Kit::Namespaces::HTTP_POST, location: location)
|
8
|
+
end
|
9
|
+
|
10
|
+
def serialize(builder, relay_state: nil)
|
11
|
+
builder.sign = true
|
12
|
+
builder.destination = location
|
13
|
+
document = builder.build
|
14
|
+
saml_params = {
|
15
|
+
document.query_string_parameter => Base64.strict_encode64(document.to_xml),
|
16
|
+
}
|
17
|
+
saml_params['RelayState'] = relay_state if relay_state.present?
|
18
|
+
[location, saml_params]
|
19
|
+
end
|
20
|
+
|
21
|
+
def deserialize(params)
|
22
|
+
xml = decode(saml_param_from(params))
|
23
|
+
Saml::Kit::Document.to_saml_document(xml)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module Saml
|
2
|
+
module Kit
|
3
|
+
class HttpRedirectBinding < Binding
|
4
|
+
include Serializable
|
5
|
+
|
6
|
+
def initialize(location:)
|
7
|
+
super(binding: Saml::Kit::Namespaces::HTTP_REDIRECT, location: location)
|
8
|
+
end
|
9
|
+
|
10
|
+
def serialize(builder, relay_state: nil)
|
11
|
+
builder.sign = false
|
12
|
+
builder.destination = location
|
13
|
+
document = builder.build
|
14
|
+
[UrlBuilder.new.build(document, relay_state: relay_state), {}]
|
15
|
+
end
|
16
|
+
|
17
|
+
def deserialize(params)
|
18
|
+
document = deserialize_document_from!(params)
|
19
|
+
ensure_valid_signature!(params, document)
|
20
|
+
document
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def deserialize_document_from!(params)
|
26
|
+
xml = inflate(decode(unescape(saml_param_from(params))))
|
27
|
+
Saml::Kit.logger.debug(xml)
|
28
|
+
Saml::Kit::Document.to_saml_document(xml)
|
29
|
+
end
|
30
|
+
|
31
|
+
def ensure_valid_signature!(params, document)
|
32
|
+
return if params['Signature'].blank? || params['SigAlg'].blank?
|
33
|
+
|
34
|
+
signature = decode(params['Signature'])
|
35
|
+
canonical_form = ['SAMLRequest', 'SAMLResponse', 'RelayState', 'SigAlg'].map do |key|
|
36
|
+
value = params[key]
|
37
|
+
value.present? ? "#{key}=#{value}" : nil
|
38
|
+
end.compact.join('&')
|
39
|
+
|
40
|
+
valid = document.provider.verify(algorithm_for(params['SigAlg']), signature, canonical_form)
|
41
|
+
raise ArgumentError.new("Invalid Signature") unless valid
|
42
|
+
end
|
43
|
+
|
44
|
+
def algorithm_for(algorithm)
|
45
|
+
case algorithm =~ /(rsa-)?sha(.*?)$/i && $2.to_i
|
46
|
+
when 256
|
47
|
+
OpenSSL::Digest::SHA256.new
|
48
|
+
when 384
|
49
|
+
OpenSSL::Digest::SHA384.new
|
50
|
+
when 512
|
51
|
+
OpenSSL::Digest::SHA512.new
|
52
|
+
else
|
53
|
+
OpenSSL::Digest::SHA1.new
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
module Saml
|
2
|
+
module Kit
|
3
|
+
class IdentityProviderMetadata < Metadata
|
4
|
+
def initialize(xml)
|
5
|
+
super("IDPSSODescriptor", xml)
|
6
|
+
end
|
7
|
+
|
8
|
+
def want_authn_requests_signed
|
9
|
+
xpath = "/md:EntityDescriptor/md:#{name}"
|
10
|
+
attribute = find_by(xpath).attribute("WantAuthnRequestsSigned")
|
11
|
+
return true if attribute.nil?
|
12
|
+
attribute.text.downcase == "true"
|
13
|
+
end
|
14
|
+
|
15
|
+
def single_sign_on_services
|
16
|
+
services('SingleSignOnService')
|
17
|
+
end
|
18
|
+
|
19
|
+
def single_sign_on_service_for(binding:)
|
20
|
+
service_for(binding: binding, type: 'SingleSignOnService')
|
21
|
+
end
|
22
|
+
|
23
|
+
def attributes
|
24
|
+
find_all("/md:EntityDescriptor/md:#{name}/saml:Attribute").map do |item|
|
25
|
+
{
|
26
|
+
format: item.attribute("NameFormat").try(:value),
|
27
|
+
name: item.attribute("Name").value,
|
28
|
+
}
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
class Builder
|
35
|
+
attr_accessor :id, :organization_name, :organization_url, :contact_email, :entity_id, :attributes, :name_id_formats
|
36
|
+
attr_accessor :want_authn_requests_signed, :sign
|
37
|
+
attr_reader :logout_urls, :single_sign_on_urls
|
38
|
+
|
39
|
+
def initialize(configuration = Saml::Kit.configuration)
|
40
|
+
@id = SecureRandom.uuid
|
41
|
+
@entity_id = configuration.issuer
|
42
|
+
@attributes = []
|
43
|
+
@name_id_formats = [Namespaces::PERSISTENT]
|
44
|
+
@single_sign_on_urls = []
|
45
|
+
@logout_urls = []
|
46
|
+
@configuration = configuration
|
47
|
+
@sign = true
|
48
|
+
@want_authn_requests_signed = true
|
49
|
+
end
|
50
|
+
|
51
|
+
def add_single_sign_on_service(url, binding: :post)
|
52
|
+
@single_sign_on_urls.push(location: url, binding: Namespaces.binding_for(binding))
|
53
|
+
end
|
54
|
+
|
55
|
+
def add_single_logout_service(url, binding: :post)
|
56
|
+
@logout_urls.push(location: url, binding: Namespaces.binding_for(binding))
|
57
|
+
end
|
58
|
+
|
59
|
+
def to_xml
|
60
|
+
Signature.sign(id, sign: sign) do |xml, signature|
|
61
|
+
xml.instruct!
|
62
|
+
xml.EntityDescriptor entity_descriptor_options do
|
63
|
+
signature.template(xml)
|
64
|
+
xml.IDPSSODescriptor idp_sso_descriptor_options do
|
65
|
+
xml.KeyDescriptor use: "signing" do
|
66
|
+
xml.KeyInfo "xmlns": Namespaces::XMLDSIG do
|
67
|
+
xml.X509Data do
|
68
|
+
xml.X509Certificate @configuration.stripped_signing_certificate
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
logout_urls.each do |item|
|
73
|
+
xml.SingleLogoutService Binding: item[:binding], Location: item[:location]
|
74
|
+
end
|
75
|
+
name_id_formats.each do |format|
|
76
|
+
xml.NameIDFormat format
|
77
|
+
end
|
78
|
+
single_sign_on_urls.each do |item|
|
79
|
+
xml.SingleSignOnService Binding: item[:binding], Location: item[:location]
|
80
|
+
end
|
81
|
+
attributes.each do |attribute|
|
82
|
+
xml.tag! 'saml:Attribute', Name: attribute
|
83
|
+
end
|
84
|
+
end
|
85
|
+
xml.Organization do
|
86
|
+
xml.OrganizationName organization_name, 'xml:lang': "en"
|
87
|
+
xml.OrganizationDisplayName organization_name, 'xml:lang': "en"
|
88
|
+
xml.OrganizationURL organization_url, 'xml:lang': "en"
|
89
|
+
end
|
90
|
+
xml.ContactPerson contactType: "technical" do
|
91
|
+
xml.Company "mailto:#{contact_email}"
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def build
|
98
|
+
IdentityProviderMetadata.new(to_xml)
|
99
|
+
end
|
100
|
+
|
101
|
+
private
|
102
|
+
|
103
|
+
def entity_descriptor_options
|
104
|
+
{
|
105
|
+
'xmlns': Namespaces::METADATA,
|
106
|
+
'xmlns:ds': Namespaces::XMLDSIG,
|
107
|
+
'xmlns:saml': Namespaces::ASSERTION,
|
108
|
+
ID: "_#{id}",
|
109
|
+
entityID: entity_id,
|
110
|
+
}
|
111
|
+
end
|
112
|
+
|
113
|
+
def idp_sso_descriptor_options
|
114
|
+
{
|
115
|
+
protocolSupportEnumeration: Namespaces::PROTOCOL,
|
116
|
+
WantAuthnRequestsSigned: want_authn_requests_signed
|
117
|
+
}
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
---
|
2
|
+
en:
|
3
|
+
saml/kit:
|
4
|
+
errors:
|
5
|
+
AuthnRequest:
|
6
|
+
invalid: "must contain AuthnRequest."
|
7
|
+
invalid_fingerprint: "does not match."
|
8
|
+
unregistered: "is unregistered."
|
9
|
+
IDPSSODescriptor:
|
10
|
+
invalid: "must contain IDPSSODescriptor."
|
11
|
+
invalid_signature: "invalid signature."
|
12
|
+
InvalidDocument:
|
13
|
+
invalid: "must contain valid SAMLRequest"
|
14
|
+
LogoutResponse:
|
15
|
+
unregistered: "is unregistered."
|
16
|
+
Response:
|
17
|
+
invalid: "must contain Response."
|
18
|
+
unregistered: "must originate from registered identity provider."
|
19
|
+
expired: "must not be expired."
|
20
|
+
invalid_version: "must be 2.0."
|
21
|
+
invalid_response_to: "must match request id."
|
22
|
+
must_match_issuer: "must match entityId."
|
23
|
+
SPSSODescriptor:
|
24
|
+
invalid: "must contain SPSSODescriptor."
|
25
|
+
invalid_signature: "invalid signature."
|
@@ -0,0 +1,78 @@
|
|
1
|
+
module Saml
|
2
|
+
module Kit
|
3
|
+
class LogoutRequest < Document
|
4
|
+
include Requestable
|
5
|
+
validates_presence_of :single_logout_service, if: :expected_type?
|
6
|
+
|
7
|
+
def initialize(xml)
|
8
|
+
super(xml, name: "LogoutRequest")
|
9
|
+
end
|
10
|
+
|
11
|
+
def name_id
|
12
|
+
to_h[name]['NameID']
|
13
|
+
end
|
14
|
+
|
15
|
+
def single_logout_service
|
16
|
+
return if provider.nil?
|
17
|
+
urls = provider.single_logout_services
|
18
|
+
urls.first
|
19
|
+
end
|
20
|
+
|
21
|
+
def response_for(user)
|
22
|
+
LogoutResponse::Builder.new(user, self)
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
class Builder
|
28
|
+
attr_accessor :id, :destination, :issuer, :name_id_format, :now
|
29
|
+
attr_accessor :sign, :version
|
30
|
+
attr_reader :user
|
31
|
+
|
32
|
+
def initialize(user, configuration: Saml::Kit.configuration, sign: true)
|
33
|
+
@user = user
|
34
|
+
@id = SecureRandom.uuid
|
35
|
+
@issuer = configuration.issuer
|
36
|
+
@name_id_format = Saml::Kit::Namespaces::PERSISTENT
|
37
|
+
@now = Time.now.utc
|
38
|
+
@version = "2.0"
|
39
|
+
@sign = sign
|
40
|
+
end
|
41
|
+
|
42
|
+
def to_xml
|
43
|
+
Signature.sign(id, sign: sign) do |xml, signature|
|
44
|
+
xml.instruct!
|
45
|
+
xml.LogoutRequest logout_request_options do
|
46
|
+
xml.Issuer({ xmlns: Namespaces::ASSERTION }, issuer)
|
47
|
+
signature.template(xml)
|
48
|
+
xml.NameID name_id_options, user.name_id_for(name_id_format)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def build
|
54
|
+
Saml::Kit::LogoutRequest.new(to_xml)
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
def logout_request_options
|
60
|
+
{
|
61
|
+
ID: "_#{id}",
|
62
|
+
Version: version,
|
63
|
+
IssueInstant: now.utc.iso8601,
|
64
|
+
Destination: destination,
|
65
|
+
xmlns: Namespaces::PROTOCOL,
|
66
|
+
}
|
67
|
+
end
|
68
|
+
|
69
|
+
def name_id_options
|
70
|
+
{
|
71
|
+
Format: name_id_format,
|
72
|
+
xmlns: Namespaces::ASSERTION,
|
73
|
+
}
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module Saml
|
2
|
+
module Kit
|
3
|
+
class LogoutResponse < Document
|
4
|
+
include Respondable
|
5
|
+
|
6
|
+
def initialize(xml, request_id: nil)
|
7
|
+
@request_id = request_id
|
8
|
+
super(xml, name: "LogoutResponse")
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
class Builder
|
14
|
+
attr_accessor :id, :issuer, :version, :status_code, :sign, :now, :destination
|
15
|
+
attr_reader :request
|
16
|
+
|
17
|
+
def initialize(user, request, configuration: Saml::Kit.configuration, sign: true)
|
18
|
+
@user = user
|
19
|
+
@now = Time.now.utc
|
20
|
+
@request = request
|
21
|
+
@id = SecureRandom.uuid
|
22
|
+
@version = "2.0"
|
23
|
+
@status_code = Namespaces::SUCCESS
|
24
|
+
@sign = sign
|
25
|
+
@issuer = configuration.issuer
|
26
|
+
provider = configuration.registry.metadata_for(@issuer)
|
27
|
+
if provider
|
28
|
+
@destination = provider.single_logout_service_for(binding: :post).try(:location)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def to_xml
|
33
|
+
Signature.sign(id, sign: sign) do |xml, signature|
|
34
|
+
xml.LogoutResponse logout_response_options do
|
35
|
+
xml.Issuer(issuer, xmlns: Namespaces::ASSERTION)
|
36
|
+
signature.template(xml)
|
37
|
+
xml.Status do
|
38
|
+
xml.StatusCode Value: status_code
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def build
|
45
|
+
LogoutResponse.new(to_xml, request_id: request.id)
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def logout_response_options
|
51
|
+
{
|
52
|
+
xmlns: Namespaces::PROTOCOL,
|
53
|
+
ID: "_#{id}",
|
54
|
+
Version: version,
|
55
|
+
IssueInstant: now.utc.iso8601,
|
56
|
+
Destination: destination,
|
57
|
+
InResponseTo: request.id,
|
58
|
+
}
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,171 @@
|
|
1
|
+
module Saml
|
2
|
+
module Kit
|
3
|
+
class Metadata
|
4
|
+
include ActiveModel::Validations
|
5
|
+
include XsdValidatable
|
6
|
+
|
7
|
+
METADATA_XSD = File.expand_path("./xsd/saml-schema-metadata-2.0.xsd", File.dirname(__FILE__)).freeze
|
8
|
+
NAMESPACES = {
|
9
|
+
"NameFormat": Namespaces::ATTR_SPLAT,
|
10
|
+
"ds": Namespaces::XMLDSIG,
|
11
|
+
"md": Namespaces::METADATA,
|
12
|
+
"saml": Namespaces::ASSERTION,
|
13
|
+
}.freeze
|
14
|
+
|
15
|
+
validates_presence_of :metadata
|
16
|
+
validate :must_contain_descriptor
|
17
|
+
validate :must_match_xsd
|
18
|
+
validate :must_have_valid_signature
|
19
|
+
|
20
|
+
attr_reader :xml, :name
|
21
|
+
attr_accessor :hash_algorithm
|
22
|
+
|
23
|
+
def initialize(name, xml)
|
24
|
+
@name = name
|
25
|
+
@xml = xml
|
26
|
+
@hash_algorithm = OpenSSL::Digest::SHA256
|
27
|
+
end
|
28
|
+
|
29
|
+
def entity_id
|
30
|
+
find_by("/md:EntityDescriptor/@entityID").value
|
31
|
+
end
|
32
|
+
|
33
|
+
def name_id_formats
|
34
|
+
find_all("/md:EntityDescriptor/md:#{name}/md:NameIDFormat").map(&:text)
|
35
|
+
end
|
36
|
+
|
37
|
+
def certificates
|
38
|
+
@certificates ||= find_all("/md:EntityDescriptor/md:#{name}/md:KeyDescriptor").map do |item|
|
39
|
+
cert = item.at_xpath("./ds:KeyInfo/ds:X509Data/ds:X509Certificate", NAMESPACES).text
|
40
|
+
{
|
41
|
+
text: cert,
|
42
|
+
fingerprint: Fingerprint.new(cert).algorithm(hash_algorithm),
|
43
|
+
use: item.attribute('use').value.to_sym,
|
44
|
+
}
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def encryption_certificates
|
49
|
+
certificates.find_all { |x| x[:use] == :encryption }
|
50
|
+
end
|
51
|
+
|
52
|
+
def signing_certificates
|
53
|
+
certificates.find_all { |x| x[:use] == :signing }
|
54
|
+
end
|
55
|
+
|
56
|
+
def services(type)
|
57
|
+
find_all("/md:EntityDescriptor/md:#{name}/md:#{type}").map do |item|
|
58
|
+
binding = item.attribute("Binding").value
|
59
|
+
location = item.attribute("Location").value
|
60
|
+
binding_for(binding, location)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def service_for(binding:, type:)
|
65
|
+
binding = Saml::Kit::Namespaces.binding_for(binding)
|
66
|
+
services(type).find { |x| x.binding?(binding) }
|
67
|
+
end
|
68
|
+
|
69
|
+
def single_logout_services
|
70
|
+
services('SingleLogoutService')
|
71
|
+
end
|
72
|
+
|
73
|
+
def single_logout_service_for(binding:)
|
74
|
+
service_for(binding: binding, type: 'SingleLogoutService')
|
75
|
+
end
|
76
|
+
|
77
|
+
def matches?(fingerprint, use: :signing)
|
78
|
+
if :signing == use.to_sym
|
79
|
+
hash_value = fingerprint.algorithm(hash_algorithm)
|
80
|
+
signing_certificates.find do |signing_certificate|
|
81
|
+
hash_value == signing_certificate[:fingerprint]
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def to_h
|
87
|
+
@xml_hash ||= Hash.from_xml(to_xml)
|
88
|
+
end
|
89
|
+
|
90
|
+
def to_xml
|
91
|
+
@xml
|
92
|
+
end
|
93
|
+
|
94
|
+
def to_s
|
95
|
+
to_xml
|
96
|
+
end
|
97
|
+
|
98
|
+
def verify(algorithm, signature, data)
|
99
|
+
signing_certificates.find do |cert|
|
100
|
+
x509 = OpenSSL::X509::Certificate.new(Base64.decode64(cert[:text]))
|
101
|
+
public_key = x509.public_key
|
102
|
+
public_key.verify(algorithm, signature, data)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def self.from(content)
|
107
|
+
hash = Hash.from_xml(content)
|
108
|
+
entity_descriptor = hash["EntityDescriptor"]
|
109
|
+
if entity_descriptor.keys.include?("SPSSODescriptor")
|
110
|
+
Saml::Kit::ServiceProviderMetadata.new(content)
|
111
|
+
elsif entity_descriptor.keys.include?("IDPSSODescriptor")
|
112
|
+
Saml::Kit::IdentityProviderMetadata.new(content)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
private
|
117
|
+
|
118
|
+
def document
|
119
|
+
@document ||= Nokogiri::XML(@xml)
|
120
|
+
end
|
121
|
+
|
122
|
+
def find_by(xpath)
|
123
|
+
document.at_xpath(xpath, NAMESPACES)
|
124
|
+
end
|
125
|
+
|
126
|
+
def find_all(xpath)
|
127
|
+
document.search(xpath, NAMESPACES)
|
128
|
+
end
|
129
|
+
|
130
|
+
def metadata
|
131
|
+
find_by("/md:EntityDescriptor/md:#{name}").present?
|
132
|
+
end
|
133
|
+
|
134
|
+
def must_contain_descriptor
|
135
|
+
errors[:base] << error_message(:invalid) unless metadata
|
136
|
+
end
|
137
|
+
|
138
|
+
def must_match_xsd
|
139
|
+
matches_xsd?(METADATA_XSD)
|
140
|
+
end
|
141
|
+
|
142
|
+
def must_have_valid_signature
|
143
|
+
return if to_xml.blank?
|
144
|
+
|
145
|
+
unless valid_signature?
|
146
|
+
errors[:base] << error_message(:invalid_signature)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
def valid_signature?
|
151
|
+
xml = Saml::Kit::Xml.new(to_xml)
|
152
|
+
result = xml.valid?
|
153
|
+
xml.errors.each do |error|
|
154
|
+
errors[:base] << error
|
155
|
+
end
|
156
|
+
result
|
157
|
+
end
|
158
|
+
|
159
|
+
def binding_for(binding, location)
|
160
|
+
case binding
|
161
|
+
when Namespaces::HTTP_REDIRECT
|
162
|
+
Saml::Kit::HttpRedirectBinding.new(location: location)
|
163
|
+
when Namespaces::POST
|
164
|
+
Saml::Kit::HttpPostBinding.new(location: location)
|
165
|
+
else
|
166
|
+
Saml::Kit::Binding.new(binding: binding, location: location)
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|