saml-kit 1.0.6 → 1.0.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitlab-ci.yml +5 -5
- data/.rubocop.yml +92 -0
- data/.rubocop_todo.yml +45 -0
- data/.travis.yml +7 -3
- data/Gemfile +2 -2
- data/Rakefile +5 -3
- data/bin/cibuild +23 -0
- data/bin/console +3 -3
- data/bin/lint +13 -0
- data/bin/setup +1 -1
- data/bin/test +19 -0
- data/exe/saml-kit-create-self-signed-certificate +6 -6
- data/exe/saml-kit-decode-http-redirect +6 -2
- data/lib/saml/kit.rb +42 -39
- data/lib/saml/kit/assertion.rb +67 -25
- data/lib/saml/kit/authentication_request.rb +1 -1
- data/lib/saml/kit/bindings.rb +8 -8
- data/lib/saml/kit/bindings/binding.rb +5 -5
- data/lib/saml/kit/bindings/http_redirect.rb +12 -7
- data/lib/saml/kit/bindings/url_builder.rb +2 -2
- data/lib/saml/kit/buildable.rb +3 -3
- data/lib/saml/kit/builders/assertion.rb +4 -0
- data/lib/saml/kit/builders/authentication_request.rb +3 -3
- data/lib/saml/kit/builders/logout_request.rb +1 -1
- data/lib/saml/kit/builders/logout_response.rb +1 -1
- data/lib/saml/kit/builders/response.rb +2 -8
- data/lib/saml/kit/builders/templates/assertion.builder +1 -1
- data/lib/saml/kit/builders/templates/metadata.builder +4 -4
- data/lib/saml/kit/builders/templates/service_provider_metadata.builder +1 -1
- data/lib/saml/kit/composite_metadata.rb +9 -5
- data/lib/saml/kit/configuration.rb +7 -7
- data/lib/saml/kit/default_registry.rb +1 -1
- data/lib/saml/kit/document.rb +39 -23
- data/lib/saml/kit/identity_provider_metadata.rb +6 -6
- data/lib/saml/kit/invalid_document.rb +2 -2
- data/lib/saml/kit/locales/en.yml +12 -3
- data/lib/saml/kit/logout_request.rb +1 -1
- data/lib/saml/kit/logout_response.rb +1 -1
- data/lib/saml/kit/metadata.rb +43 -41
- data/lib/saml/kit/namespaces.rb +25 -25
- data/lib/saml/kit/null_assertion.rb +17 -0
- data/lib/saml/kit/respondable.rb +2 -3
- data/lib/saml/kit/response.rb +23 -4
- data/lib/saml/kit/rspec/have_query_param.rb +1 -1
- data/lib/saml/kit/service_provider_metadata.rb +3 -3
- data/lib/saml/kit/signature.rb +74 -4
- data/lib/saml/kit/translatable.rb +3 -2
- data/lib/saml/kit/trustable.rb +4 -11
- data/lib/saml/kit/version.rb +1 -1
- data/lib/saml/kit/xml_templatable.rb +10 -5
- data/saml-kit.gemspec +25 -22
- metadata +54 -6
@@ -32,15 +32,15 @@ module Saml
|
|
32
32
|
# {include:file:spec/examples/identity_provider_metadata_spec.rb}
|
33
33
|
class IdentityProviderMetadata < Metadata
|
34
34
|
def initialize(xml)
|
35
|
-
super(
|
35
|
+
super('IDPSSODescriptor', xml)
|
36
36
|
end
|
37
37
|
|
38
38
|
# Returns the IDPSSODescriptor/@WantAuthnRequestsSigned attribute.
|
39
39
|
def want_authn_requests_signed
|
40
40
|
xpath = "/md:EntityDescriptor/md:#{name}"
|
41
|
-
attribute = document.find_by(xpath).attribute(
|
41
|
+
attribute = document.find_by(xpath).attribute('WantAuthnRequestsSigned')
|
42
42
|
return true if attribute.nil?
|
43
|
-
attribute.text.
|
43
|
+
attribute.text.casecmp('true').zero?
|
44
44
|
end
|
45
45
|
|
46
46
|
# Returns each of the SingleSignOnService elements.
|
@@ -59,8 +59,8 @@ module Saml
|
|
59
59
|
def attributes
|
60
60
|
document.find_all("/md:EntityDescriptor/md:#{name}/saml:Attribute").map do |item|
|
61
61
|
{
|
62
|
-
format: item.attribute(
|
63
|
-
name: item.attribute(
|
62
|
+
format: item.attribute('NameFormat').try(:value),
|
63
|
+
name: item.attribute('Name').value,
|
64
64
|
}
|
65
65
|
end
|
66
66
|
end
|
@@ -71,7 +71,7 @@ module Saml
|
|
71
71
|
# @param relay_state [Object] The RelayState to include the returned SAML params.
|
72
72
|
# @param configuration [Saml::Kit::Configuration] the configuration to use for generating the request.
|
73
73
|
# @return [Array] The url and saml params encoded using the rules for the specified binding.
|
74
|
-
def login_request_for(binding:, relay_state: nil, configuration: Saml::Kit.configuration)
|
74
|
+
def login_request_for(binding:, relay_state: nil, configuration: Saml::Kit.configuration)
|
75
75
|
builder = Saml::Kit::AuthenticationRequest.builder(configuration: configuration) do |x|
|
76
76
|
x.embed_signature = want_authn_requests_signed
|
77
77
|
yield x if block_given?
|
data/lib/saml/kit/locales/en.yml
CHANGED
@@ -3,12 +3,13 @@ en:
|
|
3
3
|
saml/kit:
|
4
4
|
errors:
|
5
5
|
Assertion:
|
6
|
+
cannot_decrypt: "cannot be decrypted."
|
6
7
|
expired: "must not be expired."
|
7
8
|
must_match_issuer: "must match entityId."
|
8
9
|
must_contain_single_assertion: "must contain single Assertion."
|
9
10
|
AuthnRequest:
|
10
11
|
invalid: "must contain AuthnRequest."
|
11
|
-
invalid_fingerprint: "
|
12
|
+
invalid_fingerprint: "is not registered."
|
12
13
|
unregistered: "is unregistered."
|
13
14
|
IDPSSODescriptor:
|
14
15
|
invalid: "must contain IDPSSODescriptor."
|
@@ -16,16 +17,24 @@ en:
|
|
16
17
|
InvalidDocument:
|
17
18
|
invalid: "must contain valid SAMLRequest"
|
18
19
|
LogoutRequest:
|
19
|
-
invalid_fingerprint: "
|
20
|
+
invalid_fingerprint: "is not registered."
|
20
21
|
unregistered: "is unregistered."
|
21
22
|
LogoutResponse:
|
22
23
|
unregistered: "is unregistered."
|
24
|
+
NullAssertion:
|
25
|
+
invalid: "is missing."
|
23
26
|
Response:
|
24
27
|
invalid: "must contain Response."
|
25
|
-
invalid_fingerprint: "
|
28
|
+
invalid_fingerprint: "is not registered."
|
26
29
|
invalid_response_to: "must match request id."
|
27
30
|
invalid_version: "must be 2.0."
|
28
31
|
unregistered: "must originate from registered identity provider."
|
32
|
+
must_contain_single_assertion: "must contain a single Assertion."
|
33
|
+
Signature:
|
34
|
+
certificate: "Not valid before %{not_before}. Not valid after %{not_after}."
|
35
|
+
digest_value: "is invalid."
|
36
|
+
empty: "is missing."
|
37
|
+
signature: "is invalid."
|
29
38
|
SPSSODescriptor:
|
30
39
|
invalid: "must contain SPSSODescriptor."
|
31
40
|
invalid_signature: "invalid signature."
|
@@ -31,7 +31,7 @@ module Saml
|
|
31
31
|
# @param xml [String] The raw xml string.
|
32
32
|
# @param configuration [Saml::Kit::Configuration] the configuration to use.
|
33
33
|
def initialize(xml, configuration: Saml::Kit.configuration)
|
34
|
-
super(xml, name:
|
34
|
+
super(xml, name: 'LogoutRequest', configuration: configuration)
|
35
35
|
end
|
36
36
|
|
37
37
|
# Returns the NameID value.
|
@@ -10,7 +10,7 @@ module Saml
|
|
10
10
|
|
11
11
|
def initialize(xml, request_id: nil, configuration: Saml::Kit.configuration)
|
12
12
|
@request_id = request_id
|
13
|
-
super(xml, name:
|
13
|
+
super(xml, name: 'LogoutResponse', configuration: configuration)
|
14
14
|
end
|
15
15
|
end
|
16
16
|
end
|
data/lib/saml/kit/metadata.rb
CHANGED
@@ -23,7 +23,11 @@ module Saml
|
|
23
23
|
# for a list of options that can be specified.
|
24
24
|
# {include:file:spec/examples/metadata_spec.rb}
|
25
25
|
class Metadata
|
26
|
-
|
26
|
+
include ActiveModel::Validations
|
27
|
+
include XsdValidatable
|
28
|
+
include Translatable
|
29
|
+
include Buildable
|
30
|
+
METADATA_XSD = File.expand_path('./xsd/saml-schema-metadata-2.0.xsd', File.dirname(__FILE__)).freeze
|
27
31
|
NAMESPACES = {
|
28
32
|
"NameFormat": Namespaces::ATTR_SPLAT,
|
29
33
|
"ds": ::Xml::Kit::Namespaces::XMLDSIG,
|
@@ -31,10 +35,6 @@ module Saml
|
|
31
35
|
"saml": Namespaces::ASSERTION,
|
32
36
|
"samlp": Namespaces::PROTOCOL,
|
33
37
|
}.freeze
|
34
|
-
include ActiveModel::Validations
|
35
|
-
include XsdValidatable
|
36
|
-
include Translatable
|
37
|
-
include Buildable
|
38
38
|
|
39
39
|
validates_presence_of :metadata
|
40
40
|
validate :must_contain_descriptor
|
@@ -50,7 +50,7 @@ module Saml
|
|
50
50
|
|
51
51
|
# Returns the /EntityDescriptor/@entityID
|
52
52
|
def entity_id
|
53
|
-
document.find_by(
|
53
|
+
document.find_by('/md:EntityDescriptor/@entityID').value
|
54
54
|
end
|
55
55
|
|
56
56
|
# Returns the supported NameIDFormats.
|
@@ -60,23 +60,23 @@ module Saml
|
|
60
60
|
|
61
61
|
# Returns the Organization Name
|
62
62
|
def organization_name
|
63
|
-
document.find_by(
|
63
|
+
document.find_by('/md:EntityDescriptor/md:Organization/md:OrganizationName').try(:text)
|
64
64
|
end
|
65
65
|
|
66
66
|
# Returns the Organization URL
|
67
67
|
def organization_url
|
68
|
-
document.find_by(
|
68
|
+
document.find_by('/md:EntityDescriptor/md:Organization/md:OrganizationURL').try(:text)
|
69
69
|
end
|
70
70
|
|
71
71
|
# Returns the Company
|
72
72
|
def contact_person_company
|
73
|
-
document.find_by(
|
73
|
+
document.find_by('/md:EntityDescriptor/md:ContactPerson/md:Company').try(:text)
|
74
74
|
end
|
75
75
|
|
76
76
|
# Returns each of the X509 certificates.
|
77
77
|
def certificates
|
78
78
|
@certificates ||= document.find_all("/md:EntityDescriptor/md:#{name}/md:KeyDescriptor").map do |item|
|
79
|
-
cert = item.at_xpath(
|
79
|
+
cert = item.at_xpath('./ds:KeyInfo/ds:X509Data/ds:X509Certificate', NAMESPACES).text
|
80
80
|
attribute = item.attribute('use')
|
81
81
|
use = attribute.nil? ? nil : item.attribute('use').value
|
82
82
|
::Xml::Kit::Certificate.new(cert, use: use)
|
@@ -98,8 +98,8 @@ module Saml
|
|
98
98
|
# @param type [String] the type of service. .E.g. `AssertionConsumerServiceURL`
|
99
99
|
def services(type)
|
100
100
|
document.find_all("/md:EntityDescriptor/md:#{name}/md:#{type}").map do |item|
|
101
|
-
binding = item.attribute(
|
102
|
-
location = item.attribute(
|
101
|
+
binding = item.attribute('Binding').value
|
102
|
+
location = item.attribute('Location').value
|
103
103
|
Saml::Kit::Bindings.create_for(binding, location)
|
104
104
|
end
|
105
105
|
end
|
@@ -179,25 +179,31 @@ module Saml
|
|
179
179
|
end
|
180
180
|
end
|
181
181
|
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
182
|
+
def signature
|
183
|
+
@signature ||= Signature.new(at_xpath('/md:EntityDescriptor/ds:Signature'))
|
184
|
+
end
|
185
|
+
|
186
|
+
class << self
|
187
|
+
# Creates a `{Saml::Kit::Metadata}` object from a raw XML [String].
|
188
|
+
#
|
189
|
+
# @param content [String] the raw metadata XML.
|
190
|
+
# @return [Saml::Kit::Metadata] the metadata document or subclass.
|
191
|
+
def from(content)
|
192
|
+
hash = Hash.from_xml(content)
|
193
|
+
entity_descriptor = hash['EntityDescriptor']
|
194
|
+
if entity_descriptor.key?('SPSSODescriptor') && entity_descriptor.key?('IDPSSODescriptor')
|
195
|
+
Saml::Kit::CompositeMetadata.new(content)
|
196
|
+
elsif entity_descriptor.keys.include?('SPSSODescriptor')
|
197
|
+
Saml::Kit::ServiceProviderMetadata.new(content)
|
198
|
+
elsif entity_descriptor.keys.include?('IDPSSODescriptor')
|
199
|
+
Saml::Kit::IdentityProviderMetadata.new(content)
|
200
|
+
end
|
195
201
|
end
|
196
|
-
end
|
197
202
|
|
198
|
-
|
199
|
-
|
200
|
-
|
203
|
+
# @!visibility private
|
204
|
+
def builder_class
|
205
|
+
Saml::Kit::Builders::Metadata
|
206
|
+
end
|
201
207
|
end
|
202
208
|
|
203
209
|
private
|
@@ -208,6 +214,10 @@ module Saml
|
|
208
214
|
@document ||= ::Xml::Kit::Document.new(xml, namespaces: NAMESPACES)
|
209
215
|
end
|
210
216
|
|
217
|
+
def at_xpath(xpath)
|
218
|
+
document.find_by(xpath)
|
219
|
+
end
|
220
|
+
|
211
221
|
def metadata
|
212
222
|
document.find_by("/md:EntityDescriptor/md:#{name}").present?
|
213
223
|
end
|
@@ -221,20 +231,12 @@ module Saml
|
|
221
231
|
end
|
222
232
|
|
223
233
|
def must_have_valid_signature
|
224
|
-
return
|
225
|
-
|
226
|
-
unless valid_signature?
|
227
|
-
errors[:base] << error_message(:invalid_signature)
|
228
|
-
end
|
229
|
-
end
|
234
|
+
return unless signature.present?
|
230
235
|
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
xml.errors.each do |error|
|
235
|
-
errors[:base] << error
|
236
|
+
signature.valid?
|
237
|
+
signature.errors.each do |attribute, error|
|
238
|
+
errors[attribute] << error
|
236
239
|
end
|
237
|
-
result
|
238
240
|
end
|
239
241
|
end
|
240
242
|
end
|
data/lib/saml/kit/namespaces.rb
CHANGED
@@ -1,32 +1,32 @@
|
|
1
1
|
module Saml
|
2
2
|
module Kit
|
3
3
|
module Namespaces
|
4
|
-
SAML_2_0 =
|
5
|
-
SAML_1_1 =
|
6
|
-
ATTR_NAME_FORMAT = "#{SAML_2_0}:attrname-format"
|
7
|
-
NAME_ID_FORMAT_1_1 = "#{SAML_1_1}:nameid-format"
|
8
|
-
NAME_ID_FORMAT_2_0 = "#{SAML_2_0}:nameid-format"
|
9
|
-
STATUS = "#{SAML_2_0}:status"
|
4
|
+
SAML_2_0 = 'urn:oasis:names:tc:SAML:2.0'.freeze
|
5
|
+
SAML_1_1 = 'urn:oasis:names:tc:SAML:1.1'.freeze
|
6
|
+
ATTR_NAME_FORMAT = "#{SAML_2_0}:attrname-format".freeze
|
7
|
+
NAME_ID_FORMAT_1_1 = "#{SAML_1_1}:nameid-format".freeze
|
8
|
+
NAME_ID_FORMAT_2_0 = "#{SAML_2_0}:nameid-format".freeze
|
9
|
+
STATUS = "#{SAML_2_0}:status".freeze
|
10
10
|
|
11
|
-
ASSERTION = "#{SAML_2_0}:assertion"
|
12
|
-
ATTR_SPLAT = "#{ATTR_NAME_FORMAT}:*"
|
13
|
-
BASIC = "#{ATTR_NAME_FORMAT}:basic"
|
14
|
-
BEARER = "#{SAML_2_0}:cm:bearer"
|
15
|
-
EMAIL_ADDRESS = "#{NAME_ID_FORMAT_1_1}:emailAddress"
|
16
|
-
INVALID_NAME_ID_POLICY = "#{STATUS}:InvalidNameIDPolicy"
|
17
|
-
METADATA = "#{SAML_2_0}:metadata"
|
18
|
-
PASSWORD = "#{SAML_2_0}:ac:classes:Password"
|
19
|
-
PASSWORD_PROTECTED = "#{SAML_2_0}:ac:classes:PasswordProtectedTransport"
|
20
|
-
PERSISTENT = "#{NAME_ID_FORMAT_2_0}:persistent"
|
21
|
-
PROTOCOL = "#{SAML_2_0}:protocol"
|
22
|
-
REQUESTER_ERROR = "#{STATUS}:Requester"
|
23
|
-
RESPONDER_ERROR = "#{STATUS}:Responder"
|
24
|
-
SUCCESS = "#{STATUS}:Success"
|
25
|
-
TRANSIENT = "#{NAME_ID_FORMAT_2_0}:transient"
|
26
|
-
UNSPECIFIED = "#{SAML_2_0}:consent:unspecified"
|
27
|
-
UNSPECIFIED_NAMEID = "#{NAME_ID_FORMAT_1_1}:unspecified"
|
28
|
-
URI = "#{ATTR_NAME_FORMAT}:uri"
|
29
|
-
VERSION_MISMATCH_ERROR = "#{STATUS}:VersionMismatch"
|
11
|
+
ASSERTION = "#{SAML_2_0}:assertion".freeze
|
12
|
+
ATTR_SPLAT = "#{ATTR_NAME_FORMAT}:*".freeze
|
13
|
+
BASIC = "#{ATTR_NAME_FORMAT}:basic".freeze
|
14
|
+
BEARER = "#{SAML_2_0}:cm:bearer".freeze
|
15
|
+
EMAIL_ADDRESS = "#{NAME_ID_FORMAT_1_1}:emailAddress".freeze
|
16
|
+
INVALID_NAME_ID_POLICY = "#{STATUS}:InvalidNameIDPolicy".freeze
|
17
|
+
METADATA = "#{SAML_2_0}:metadata".freeze
|
18
|
+
PASSWORD = "#{SAML_2_0}:ac:classes:Password".freeze
|
19
|
+
PASSWORD_PROTECTED = "#{SAML_2_0}:ac:classes:PasswordProtectedTransport".freeze
|
20
|
+
PERSISTENT = "#{NAME_ID_FORMAT_2_0}:persistent".freeze
|
21
|
+
PROTOCOL = "#{SAML_2_0}:protocol".freeze
|
22
|
+
REQUESTER_ERROR = "#{STATUS}:Requester".freeze
|
23
|
+
RESPONDER_ERROR = "#{STATUS}:Responder".freeze
|
24
|
+
SUCCESS = "#{STATUS}:Success".freeze
|
25
|
+
TRANSIENT = "#{NAME_ID_FORMAT_2_0}:transient".freeze
|
26
|
+
UNSPECIFIED = "#{SAML_2_0}:consent:unspecified".freeze
|
27
|
+
UNSPECIFIED_NAMEID = "#{NAME_ID_FORMAT_1_1}:unspecified".freeze
|
28
|
+
URI = "#{ATTR_NAME_FORMAT}:uri".freeze
|
29
|
+
VERSION_MISMATCH_ERROR = "#{STATUS}:VersionMismatch".freeze
|
30
30
|
end
|
31
31
|
end
|
32
32
|
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Saml
|
2
|
+
module Kit
|
3
|
+
class NullAssertion
|
4
|
+
include ActiveModel::Validations
|
5
|
+
include Translatable
|
6
|
+
validate :invalid
|
7
|
+
|
8
|
+
def invalid
|
9
|
+
errors[:assertion].push(error_message(:invalid))
|
10
|
+
end
|
11
|
+
|
12
|
+
def name
|
13
|
+
'NullAssertion'
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/lib/saml/kit/respondable.rb
CHANGED
@@ -33,10 +33,9 @@ module Saml
|
|
33
33
|
|
34
34
|
def must_match_request_id
|
35
35
|
return if request_id.nil?
|
36
|
+
return if in_response_to == request_id
|
36
37
|
|
37
|
-
|
38
|
-
errors[:in_response_to] << error_message(:invalid_response_to)
|
39
|
-
end
|
38
|
+
errors[:in_response_to] << error_message(:invalid_response_to)
|
40
39
|
end
|
41
40
|
end
|
42
41
|
end
|
data/lib/saml/kit/response.rb
CHANGED
@@ -8,14 +8,23 @@ module Saml
|
|
8
8
|
def_delegators :assertion, :name_id, :[], :attributes
|
9
9
|
|
10
10
|
validate :must_be_valid_assertion
|
11
|
+
validate :must_contain_single_assertion
|
11
12
|
|
12
13
|
def initialize(xml, request_id: nil, configuration: Saml::Kit.configuration)
|
13
14
|
@request_id = request_id
|
14
|
-
super(xml, name:
|
15
|
+
super(xml, name: 'Response', configuration: configuration)
|
15
16
|
end
|
16
17
|
|
17
|
-
def assertion
|
18
|
-
@assertion ||=
|
18
|
+
def assertion(private_keys = configuration.private_keys(use: :encryption))
|
19
|
+
@assertion ||=
|
20
|
+
begin
|
21
|
+
node = assertion_nodes.last
|
22
|
+
if node.nil?
|
23
|
+
Saml::Kit::NullAssertion.new
|
24
|
+
else
|
25
|
+
Saml::Kit::Assertion.new(node, configuration: @configuration, private_keys: private_keys)
|
26
|
+
end
|
27
|
+
end
|
19
28
|
end
|
20
29
|
|
21
30
|
private
|
@@ -23,9 +32,19 @@ module Saml
|
|
23
32
|
def must_be_valid_assertion
|
24
33
|
assertion.valid?
|
25
34
|
assertion.errors.each do |attribute, error|
|
26
|
-
|
35
|
+
attribute = :assertion if attribute == :base
|
36
|
+
errors[attribute] << error
|
27
37
|
end
|
28
38
|
end
|
39
|
+
|
40
|
+
def must_contain_single_assertion
|
41
|
+
return if assertion_nodes.count <= 1
|
42
|
+
errors[:base] << error_message(:must_contain_single_assertion)
|
43
|
+
end
|
44
|
+
|
45
|
+
def assertion_nodes
|
46
|
+
search(Saml::Kit::Assertion::XPATH)
|
47
|
+
end
|
29
48
|
end
|
30
49
|
end
|
31
50
|
end
|
@@ -3,7 +3,7 @@ module Saml
|
|
3
3
|
# {include:file:spec/examples/service_provider_metadata_spec.rb}
|
4
4
|
class ServiceProviderMetadata < Metadata
|
5
5
|
def initialize(xml)
|
6
|
-
super(
|
6
|
+
super('SPSSODescriptor', xml)
|
7
7
|
end
|
8
8
|
|
9
9
|
# Returns each of the AssertionConsumerService bindings.
|
@@ -20,9 +20,9 @@ module Saml
|
|
20
20
|
|
21
21
|
# Returns true when the metadata demands that Assertions must be signed.
|
22
22
|
def want_assertions_signed
|
23
|
-
attribute = document.find_by("/md:EntityDescriptor/md:#{name}").attribute(
|
23
|
+
attribute = document.find_by("/md:EntityDescriptor/md:#{name}").attribute('WantAssertionsSigned')
|
24
24
|
return true if attribute.nil?
|
25
|
-
attribute.text.
|
25
|
+
attribute.text.casecmp('true').zero?
|
26
26
|
end
|
27
27
|
|
28
28
|
# @!visibility private
|
data/lib/saml/kit/signature.rb
CHANGED
@@ -2,14 +2,21 @@ module Saml
|
|
2
2
|
module Kit
|
3
3
|
class Signature
|
4
4
|
include ActiveModel::Validations
|
5
|
+
include Translatable
|
5
6
|
|
6
|
-
|
7
|
-
|
7
|
+
validate :validate_signature
|
8
|
+
validate :validate_certificate
|
9
|
+
|
10
|
+
attr_reader :name
|
11
|
+
|
12
|
+
def initialize(node)
|
13
|
+
@name = 'Signature'
|
14
|
+
@node = node
|
8
15
|
end
|
9
16
|
|
10
17
|
# Returns the embedded X509 Certificate
|
11
18
|
def certificate
|
12
|
-
value =
|
19
|
+
value = at_xpath('./ds:KeyInfo/ds:X509Data/ds:X509Certificate').try(:text)
|
13
20
|
return if value.nil?
|
14
21
|
::Xml::Kit::Certificate.new(value, use: :signing)
|
15
22
|
end
|
@@ -20,9 +27,72 @@ module Saml
|
|
20
27
|
metadata.matches?(certificate.fingerprint, use: :signing)
|
21
28
|
end
|
22
29
|
|
30
|
+
def digest_value
|
31
|
+
at_xpath('./ds:SignedInfo/ds:Reference/ds:DigestValue').try(:text)
|
32
|
+
end
|
33
|
+
|
34
|
+
def digest_method
|
35
|
+
at_xpath('./ds:SignedInfo/ds:Reference/ds:DigestMethod/@Algorithm').try(:value)
|
36
|
+
end
|
37
|
+
|
38
|
+
def signature_value
|
39
|
+
at_xpath('./ds:SignatureValue').try(:text)
|
40
|
+
end
|
41
|
+
|
42
|
+
def signature_method
|
43
|
+
at_xpath('./ds:SignedInfo/ds:SignatureMethod/@Algorithm').try(:value)
|
44
|
+
end
|
45
|
+
|
46
|
+
def canonicalization_method
|
47
|
+
at_xpath('./ds:SignedInfo/ds:CanonicalizationMethod/@Algorithm').try(:value)
|
48
|
+
end
|
49
|
+
|
50
|
+
def transforms
|
51
|
+
node.search('./ds:SignedInfo/ds:Reference/ds:Transforms/ds:Transform/@Algorithm', Saml::Kit::Document::NAMESPACES).try(:map, &:value)
|
52
|
+
end
|
53
|
+
|
23
54
|
# Returns the XML Hash.
|
24
55
|
def to_h
|
25
|
-
@xml_hash
|
56
|
+
@xml_hash ||= present? ? Hash.from_xml(to_xml)['Signature'] : {}
|
57
|
+
end
|
58
|
+
|
59
|
+
def present?
|
60
|
+
node
|
61
|
+
end
|
62
|
+
|
63
|
+
def to_xml
|
64
|
+
node.to_s
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
attr_reader :node
|
70
|
+
|
71
|
+
def validate_signature
|
72
|
+
return errors[:base].push(error_message(:empty)) if certificate.nil?
|
73
|
+
|
74
|
+
signature = Xmldsig::Signature.new(@node, 'ID=$uri or @Id')
|
75
|
+
return if signature.valid?(certificate.x509)
|
76
|
+
signature.errors.each do |attribute|
|
77
|
+
errors.add(attribute, error_message(attribute))
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def validate_certificate(now = Time.now.utc)
|
82
|
+
return unless certificate.present?
|
83
|
+
return if certificate.active?(now)
|
84
|
+
|
85
|
+
message = error_message(
|
86
|
+
:certificate,
|
87
|
+
not_before: certificate.not_before,
|
88
|
+
not_after: certificate.not_after
|
89
|
+
)
|
90
|
+
errors.add(:certificate, message)
|
91
|
+
end
|
92
|
+
|
93
|
+
def at_xpath(xpath)
|
94
|
+
return nil unless node
|
95
|
+
node.at_xpath(xpath, Saml::Kit::Document::NAMESPACES)
|
26
96
|
end
|
27
97
|
end
|
28
98
|
end
|