saml2 3.1.2 → 3.1.4

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.
Files changed (85) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +6 -4
  3. data/exe/bulk_verify_responses +94 -0
  4. data/lib/saml2/assertion.rb +7 -7
  5. data/lib/saml2/attribute/x500.rb +31 -28
  6. data/lib/saml2/attribute.rb +53 -49
  7. data/lib/saml2/attribute_consuming_service.rb +29 -31
  8. data/lib/saml2/authn_request.rb +54 -47
  9. data/lib/saml2/authn_statement.rb +31 -20
  10. data/lib/saml2/base.rb +72 -63
  11. data/lib/saml2/bindings/http_post.rb +7 -7
  12. data/lib/saml2/bindings/http_redirect.rb +37 -33
  13. data/lib/saml2/bindings.rb +1 -1
  14. data/lib/saml2/conditions.rb +19 -16
  15. data/lib/saml2/contact.rb +19 -18
  16. data/lib/saml2/endpoint.rb +14 -11
  17. data/lib/saml2/entity.rb +27 -27
  18. data/lib/saml2/identity_provider.rb +13 -10
  19. data/lib/saml2/indexed_object.rb +15 -12
  20. data/lib/saml2/key.rb +43 -34
  21. data/lib/saml2/localized_name.rb +11 -10
  22. data/lib/saml2/logout_request.rb +8 -8
  23. data/lib/saml2/logout_response.rb +4 -4
  24. data/lib/saml2/message.rb +24 -20
  25. data/lib/saml2/name_id.rb +45 -41
  26. data/lib/saml2/namespaces.rb +8 -8
  27. data/lib/saml2/organization.rb +11 -10
  28. data/lib/saml2/organization_and_contacts.rb +5 -5
  29. data/lib/saml2/request.rb +3 -3
  30. data/lib/saml2/requested_authn_context.rb +4 -4
  31. data/lib/saml2/response.rb +45 -33
  32. data/lib/saml2/role.rb +11 -11
  33. data/lib/saml2/schemas.rb +13 -10
  34. data/lib/saml2/service_provider.rb +11 -12
  35. data/lib/saml2/signable.rb +23 -18
  36. data/lib/saml2/sso.rb +5 -5
  37. data/lib/saml2/status.rb +9 -7
  38. data/lib/saml2/status_response.rb +5 -5
  39. data/lib/saml2/subject.rb +28 -28
  40. data/lib/saml2/version.rb +1 -1
  41. data/lib/saml2.rb +7 -7
  42. metadata +78 -122
  43. data/spec/fixtures/FederationMetadata.xml +0 -670
  44. data/spec/fixtures/authnrequest.xml +0 -12
  45. data/spec/fixtures/certificate.pem +0 -24
  46. data/spec/fixtures/entities.xml +0 -13
  47. data/spec/fixtures/external-uri-reference-response.xml +0 -48
  48. data/spec/fixtures/identity_provider.xml +0 -46
  49. data/spec/fixtures/noconditions_response.xml +0 -1
  50. data/spec/fixtures/othercertificate.pem +0 -25
  51. data/spec/fixtures/privatekey.key +0 -27
  52. data/spec/fixtures/response_assertion_signed_reffed_from_response.xml +0 -6
  53. data/spec/fixtures/response_signed.xml +0 -46
  54. data/spec/fixtures/response_tampered_certificate.xml +0 -25
  55. data/spec/fixtures/response_tampered_signature.xml +0 -46
  56. data/spec/fixtures/response_with_attribute_signed.xml +0 -46
  57. data/spec/fixtures/response_with_encrypted_assertion.xml +0 -58
  58. data/spec/fixtures/response_with_rsa_key_value.xml +0 -1
  59. data/spec/fixtures/response_with_signed_assertion_and_encrypted_subject.xml +0 -116
  60. data/spec/fixtures/response_without_keyinfo.xml +0 -1
  61. data/spec/fixtures/service_provider.xml +0 -79
  62. data/spec/fixtures/test3-response.xml +0 -9
  63. data/spec/fixtures/test6-response.xml +0 -10
  64. data/spec/fixtures/test7-response.xml +0 -10
  65. data/spec/fixtures/xml_missigned_assertion.xml +0 -84
  66. data/spec/fixtures/xml_signature_wrapping_attack_duplicate_ids.xml +0 -11
  67. data/spec/fixtures/xml_signature_wrapping_attack_response_attributes.xml +0 -45
  68. data/spec/fixtures/xml_signature_wrapping_attack_response_nameid.xml +0 -44
  69. data/spec/fixtures/xslt-transform-response.xml +0 -57
  70. data/spec/lib/attribute_consuming_service_spec.rb +0 -129
  71. data/spec/lib/attribute_spec.rb +0 -149
  72. data/spec/lib/authn_request_spec.rb +0 -52
  73. data/spec/lib/bindings/http_redirect_spec.rb +0 -183
  74. data/spec/lib/conditions_spec.rb +0 -74
  75. data/spec/lib/entity_spec.rb +0 -58
  76. data/spec/lib/identity_provider_spec.rb +0 -43
  77. data/spec/lib/indexed_object_spec.rb +0 -71
  78. data/spec/lib/key_spec.rb +0 -23
  79. data/spec/lib/logout_request_spec.rb +0 -33
  80. data/spec/lib/logout_response_spec.rb +0 -33
  81. data/spec/lib/message_spec.rb +0 -23
  82. data/spec/lib/response_spec.rb +0 -293
  83. data/spec/lib/service_provider_spec.rb +0 -76
  84. data/spec/lib/signable_spec.rb +0 -15
  85. data/spec/spec_helper.rb +0 -8
@@ -1,17 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'base64'
4
- require 'zlib'
5
-
6
- require 'saml2/attribute_consuming_service'
7
- require 'saml2/bindings/http_redirect'
8
- require 'saml2/endpoint'
9
- require 'saml2/name_id'
10
- require 'saml2/namespaces'
11
- require 'saml2/request'
12
- require 'saml2/requested_authn_context'
13
- require 'saml2/schemas'
14
- require 'saml2/subject'
3
+ require "base64"
4
+ require "zlib"
5
+
6
+ require "saml2/attribute_consuming_service"
7
+ require "saml2/bindings/http_redirect"
8
+ require "saml2/endpoint"
9
+ require "saml2/name_id"
10
+ require "saml2/namespaces"
11
+ require "saml2/request"
12
+ require "saml2/requested_authn_context"
13
+ require "saml2/schemas"
14
+ require "saml2/subject"
15
15
 
16
16
  module SAML2
17
17
  class AuthnRequest < Request
@@ -35,8 +35,8 @@ module SAML2
35
35
  # @param service_provider [ServiceProvider]
36
36
  # @return [AuthnRequest]
37
37
  def self.initiate(issuer, identity_provider = nil,
38
- assertion_consumer_service: nil,
39
- service_provider: nil)
38
+ assertion_consumer_service: nil,
39
+ service_provider: nil)
40
40
  authn_request = new
41
41
  authn_request.issuer = issuer
42
42
  authn_request.destination = identity_provider.single_sign_on_services.first.location if identity_provider
@@ -80,12 +80,16 @@ module SAML2
80
80
  def resolve(service_provider)
81
81
  # TODO: check signature if present
82
82
 
83
- if assertion_consumer_service_url
84
- @assertion_consumer_service = service_provider.assertion_consumer_services.find { |acs| acs.location == assertion_consumer_service_url }
85
- else
86
- @assertion_consumer_service = service_provider.assertion_consumer_services.resolve(assertion_consumer_service_index)
87
- end
88
- @attribute_consuming_service = service_provider.attribute_consuming_services.resolve(attribute_consuming_service_index)
83
+ @assertion_consumer_service =
84
+ if assertion_consumer_service_url
85
+ service_provider.assertion_consumer_services.find do |acs|
86
+ acs.location == assertion_consumer_service_url
87
+ end
88
+ else
89
+ service_provider.assertion_consumer_services.resolve(assertion_consumer_service_index)
90
+ end
91
+ @attribute_consuming_service =
92
+ service_provider.attribute_consuming_services.resolve(attribute_consuming_service_index)
89
93
 
90
94
  return false unless @assertion_consumer_service
91
95
  return false if attribute_consuming_service_index && !@attribute_consuming_service
@@ -96,7 +100,7 @@ module SAML2
96
100
  # @return [NameID::Policy, nil]
97
101
  def name_id_policy
98
102
  if xml && !instance_variable_defined?(:@name_id_policy)
99
- @name_id_policy = NameID::Policy.from_xml(xml.at_xpath('samlp:NameIDPolicy', Namespaces::ALL))
103
+ @name_id_policy = NameID::Policy.from_xml(xml.at_xpath("samlp:NameIDPolicy", Namespaces::ALL))
100
104
  end
101
105
  @name_id_policy
102
106
  end
@@ -111,7 +115,7 @@ module SAML2
111
115
  # @return [Integer, nil]
112
116
  def assertion_consumer_service_index
113
117
  if xml && !instance_variable_defined?(:@assertion_consumer_service_index)
114
- @assertion_consumer_service_index = xml['AssertionConsumerServiceIndex']&.to_i
118
+ @assertion_consumer_service_index = xml["AssertionConsumerServiceIndex"]&.to_i
115
119
  end
116
120
  @assertion_consumer_service_index
117
121
  end
@@ -119,7 +123,7 @@ module SAML2
119
123
  # @return [String, nil]
120
124
  def assertion_consumer_service_url
121
125
  if xml && !instance_variable_defined?(:@assertion_consumer_service_url)
122
- @assertion_consumer_service_url = xml['AssertionConsumerServiceURL']
126
+ @assertion_consumer_service_url = xml["AssertionConsumerServiceURL"]
123
127
  end
124
128
  @assertion_consumer_service_url
125
129
  end
@@ -127,61 +131,64 @@ module SAML2
127
131
  # @return [Integer, nil]
128
132
  def attribute_consuming_service_index
129
133
  if xml && !instance_variable_defined?(:@attribute_consuming_service_index)
130
- @attribute_consuming_service_index = xml['AttributeConsumingServiceIndex']&.to_i
134
+ @attribute_consuming_service_index = xml["AttributeConsumingServiceIndex"]&.to_i
131
135
  end
132
136
  @attribute_consuming_service_index
133
137
  end
134
138
 
135
139
  # @return [true, false, nil]
136
140
  def force_authn?
137
- if xml && !instance_variable_defined?(:@force_authn)
138
- @force_authn = xml['ForceAuthn']&.== 'true'
139
- end
141
+ @force_authn = xml["ForceAuthn"]&.== "true" if xml && !instance_variable_defined?(:@force_authn)
140
142
  @force_authn
141
143
  end
142
144
 
143
145
  # @return [true, false, nil]
144
146
  def passive?
145
- if xml && !instance_variable_defined?(:@passive)
146
- @passive = xml['IsPassive']&.== 'true'
147
- end
147
+ @passive = xml["IsPassive"]&.== "true" if xml && !instance_variable_defined?(:@passive)
148
148
  @passive
149
149
  end
150
150
 
151
151
  # @return [String, nil]
152
152
  def protocol_binding
153
- if xml && !instance_variable_defined?(:@protocol_binding)
154
- @protocol_binding = xml['ProtocolBinding']
155
- end
153
+ @protocol_binding = xml["ProtocolBinding"] if xml && !instance_variable_defined?(:@protocol_binding)
156
154
  @protocol_binding
157
155
  end
158
156
 
159
157
  # @return [Subject, nil]
160
158
  def subject
161
159
  if xml && !instance_variable_defined?(:@subject)
162
- @subject = Subject.from_xml(xml.at_xpath('saml:Subject', Namespaces::ALL))
160
+ @subject = Subject.from_xml(xml.at_xpath("saml:Subject", Namespaces::ALL))
163
161
  end
164
162
  @subject
165
163
  end
166
164
 
167
165
  # (see Base#build)
168
166
  def build(builder)
169
- builder['samlp'].AuthnRequest(
170
- 'xmlns:samlp' => Namespaces::SAMLP,
171
- 'xmlns:saml' => Namespaces::SAML
167
+ builder["samlp"].AuthnRequest(
168
+ "xmlns:samlp" => Namespaces::SAMLP,
169
+ "xmlns:saml" => Namespaces::SAML
172
170
  ) do |authn_request|
173
171
  super(authn_request)
174
172
 
175
- authn_request.parent['AssertionConsumerServiceIndex'] = assertion_consumer_service_index if assertion_consumer_service_index
176
- authn_request.parent['AssertionConsumerServiceURL'] = assertion_consumer_service_url if assertion_consumer_service_url
177
- authn_request.parent['AttributeConsumingServiceIndex'] = attribute_consuming_service_index if attribute_consuming_service_index
178
- authn_request.parent['ForceAuthn'] = force_authn? unless force_authn?.nil?
179
- authn_request.parent['IsPassive'] = passive? unless passive?.nil?
180
- authn_request.parent['ProtocolBinding'] = protocol_binding if protocol_binding
181
-
182
- subject.build(authn_request) if subject
183
- name_id_policy.build(authn_request) if name_id_policy
184
- requested_authn_context.build(authn_request) if requested_authn_context
173
+ if assertion_consumer_service_index
174
+ authn_request.parent["AssertionConsumerServiceIndex"] =
175
+ assertion_consumer_service_index
176
+ end
177
+ if assertion_consumer_service_url
178
+ authn_request.parent["AssertionConsumerServiceURL"] =
179
+ assertion_consumer_service_url
180
+ end
181
+ if attribute_consuming_service_index
182
+ authn_request.parent["AttributeConsumingServiceIndex"] =
183
+ attribute_consuming_service_index
184
+ end
185
+ authn_request.parent["ForceAuthn"] = force_authn? unless force_authn?.nil?
186
+ authn_request.parent["IsPassive"] = passive? unless passive?.nil?
187
+ authn_request.parent["ProtocolBinding"] = protocol_binding if protocol_binding
188
+
189
+ subject&.build(authn_request)
190
+ name_id_policy&.build(authn_request)
191
+ requested_authn_context&.build(authn_request)
185
192
  end
186
193
  end
187
194
  end
@@ -1,20 +1,30 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'saml2/base'
3
+ require "saml2/base"
4
4
 
5
5
  module SAML2
6
6
  class AuthnStatement < Base
7
7
  module Classes
8
- INTERNET_PROTOCOL = "urn:oasis:names:tc:SAML:2.0:ac:classes:InternetProtocol" # IP address
9
- INTERNET_PROTOCOL_PASSWORD = "urn:oasis:names:tc:SAML:2.0:ac:classes:InternetProtocolPassword" # IP address, as well as username/password
10
- KERBEROS = "urn:oasis:names:tc:SAML:2.0:ac:classes:Kerberos"
11
- PASSWORD = "urn:oasis:names:tc:SAML:2.0:ac:classes:Password" # username/password, NOT over SSL
12
- PASSWORD_PROTECTED_TRANSPORT = "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport" # username/password over SSL
13
- PREVIOUS_SESSION = "urn:oasis:names:tc:SAML:2.0:ac:classes:PreviousSession" # remember me
14
- SMARTCARD = "urn:oasis:names:tc:SAML:2.0:ac:classes:Smartcard"
15
- SMARTCARD_PKI = "urn:oasis:names:tc:SAML:2.0:ac:classes:SmartcardPKI" # smartcard with a private key on it
16
- TLS_CLIENT = "urn:oasis:names:tc:SAML:2.0:ac:classes:TLSClient" # SSL client certificate
17
- UNSPECIFIED = "urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified"
8
+ INTERNET_PROTOCOL =
9
+ "urn:oasis:names:tc:SAML:2.0:ac:classes:InternetProtocol" # IP address
10
+ INTERNET_PROTOCOL_PASSWORD =
11
+ "urn:oasis:names:tc:SAML:2.0:ac:classes:InternetProtocolPassword" # IP address, as well as username/password
12
+ KERBEROS =
13
+ "urn:oasis:names:tc:SAML:2.0:ac:classes:Kerberos"
14
+ PASSWORD =
15
+ "urn:oasis:names:tc:SAML:2.0:ac:classes:Password" # username/password, NOT over SSL
16
+ PASSWORD_PROTECTED_TRANSPORT =
17
+ "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport" # username/password over SSL
18
+ PREVIOUS_SESSION =
19
+ "urn:oasis:names:tc:SAML:2.0:ac:classes:PreviousSession" # remember me
20
+ SMARTCARD =
21
+ "urn:oasis:names:tc:SAML:2.0:ac:classes:Smartcard"
22
+ SMARTCARD_PKI =
23
+ "urn:oasis:names:tc:SAML:2.0:ac:classes:SmartcardPKI" # smartcard with a private key on it
24
+ TLS_CLIENT =
25
+ "urn:oasis:names:tc:SAML:2.0:ac:classes:TLSClient" # SSL client certificate
26
+ UNSPECIFIED =
27
+ "urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified"
18
28
  end
19
29
 
20
30
  # @return [Time]
@@ -30,19 +40,20 @@ module SAML2
30
40
  # (see Base#from_xml)
31
41
  def from_xml(node)
32
42
  super
33
- @authn_instant = Time.parse(node['AuthnInstant'])
34
- @session_index = node['SessionIndex']
35
- @session_not_on_or_after = Time.parse(node['SessionNotOnOrAfter']) if node['SessionNotOnOrAfter']
36
- @authn_context_class_ref = node.at_xpath('saml:AuthnContext/saml:AuthnContextClassRef', Namespaces::ALL)&.content&.strip
43
+ @authn_instant = Time.parse(node["AuthnInstant"])
44
+ @session_index = node["SessionIndex"]
45
+ @session_not_on_or_after = Time.parse(node["SessionNotOnOrAfter"]) if node["SessionNotOnOrAfter"]
46
+ @authn_context_class_ref = node.at_xpath("saml:AuthnContext/saml:AuthnContextClassRef",
47
+ Namespaces::ALL)&.content&.strip
37
48
  end
38
49
 
39
50
  # (see Base#build)
40
51
  def build(builder)
41
- builder['saml'].AuthnStatement('AuthnInstant' => authn_instant.iso8601) do |authn_statement|
42
- authn_statement.parent['SessionIndex'] = session_index if session_index
43
- authn_statement.parent['SessionNotOnOrAfter'] = session_not_on_or_after.iso8601 if session_not_on_or_after
44
- authn_statement['saml'].AuthnContext do |authn_context|
45
- authn_context['saml'].AuthnContextClassRef(authn_context_class_ref) if authn_context_class_ref
52
+ builder["saml"].AuthnStatement("AuthnInstant" => authn_instant.iso8601) do |authn_statement|
53
+ authn_statement.parent["SessionIndex"] = session_index if session_index
54
+ authn_statement.parent["SessionNotOnOrAfter"] = session_not_on_or_after.iso8601 if session_not_on_or_after
55
+ authn_statement["saml"].AuthnContext do |authn_context|
56
+ authn_context["saml"].AuthnContextClassRef(authn_context_class_ref) if authn_context_class_ref
46
57
  end
47
58
  end
48
59
  end
data/lib/saml2/base.rb CHANGED
@@ -1,19 +1,61 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'saml2/namespaces'
3
+ require "saml2/namespaces"
4
4
 
5
5
  module SAML2
6
6
  # @abstract
7
7
  class Base
8
- # Create an appropriate object to represent the given XML element.
9
- #
10
- # @param node [Nokogiri::XML::Element, nil]
11
- # @return [Base, nil]
12
- def self.from_xml(node)
13
- return nil unless node
14
- result = new
15
- result.from_xml(node)
16
- result
8
+ class << self
9
+ def lookup_qname(qname, namespaces)
10
+ prefix, local_name = split_qname(qname)
11
+ [lookup_namespace(prefix, namespaces), local_name]
12
+ end
13
+
14
+ # Create an appropriate object to represent the given XML element.
15
+ #
16
+ # @param node [Nokogiri::XML::Element, nil]
17
+ # @return [Base, nil]
18
+ def from_xml(node)
19
+ return nil unless node
20
+
21
+ result = new
22
+ result.from_xml(node)
23
+ result
24
+ end
25
+
26
+ def load_string_array(node, element)
27
+ node.xpath(element, Namespaces::ALL).map do |element_node|
28
+ element_node.content&.strip
29
+ end
30
+ end
31
+
32
+ def load_object_array(node, element, klass = nil)
33
+ node.xpath(element, Namespaces::ALL).map do |element_node|
34
+ if klass.nil?
35
+ SAML2.const_get(element_node.name, false).from_xml(element_node)
36
+ elsif klass.is_a?(Hash)
37
+ klass[element_node.name].from_xml(element_node)
38
+ else
39
+ klass.from_xml(element_node)
40
+ end
41
+ end
42
+ end
43
+
44
+ private
45
+
46
+ def split_qname(qname)
47
+ if qname.include?(":")
48
+ qname.split(":", 2)
49
+ else
50
+ [nil, qname]
51
+ end
52
+ end
53
+
54
+ def lookup_namespace(prefix, namespaces)
55
+ return nil if namespaces.empty?
56
+
57
+ namespaces[prefix.empty? ? "xmlns" : "xmlns:#{prefix}"]
58
+ end
17
59
  end
18
60
 
19
61
  # @return [Nokogiri::XML::Element]
@@ -45,13 +87,15 @@ module SAML2
45
87
  if pretty
46
88
  xml.to_s
47
89
  else
48
- xml.to_xml(save_with: Nokogiri::XML::Node::SaveOptions::AS_XML | Nokogiri::XML::Node::SaveOptions::NO_DECLARATION)
90
+ xml.to_xml(save_with: Nokogiri::XML::Node::SaveOptions::AS_XML |
91
+ Nokogiri::XML::Node::SaveOptions::NO_DECLARATION)
49
92
  end
50
93
  elsif pretty
51
94
  to_xml.to_s
52
95
  else
53
96
  # make sure to not FORMAT it - it breaks signatures!
54
- to_xml.to_xml(save_with: Nokogiri::XML::Node::SaveOptions::AS_XML | Nokogiri::XML::Node::SaveOptions::NO_DECLARATION)
97
+ to_xml.to_xml(save_with: Nokogiri::XML::Node::SaveOptions::AS_XML |
98
+ Nokogiri::XML::Node::SaveOptions::NO_DECLARATION)
55
99
  end
56
100
  end
57
101
 
@@ -62,7 +106,11 @@ module SAML2
62
106
  # not be created until their attribute is accessed.
63
107
  # @return [String]
64
108
  def inspect
65
- "#<#{self.class.name} #{instance_variables.map { |iv| next if iv == :@xml; "#{iv}=#{instance_variable_get(iv).inspect}" }.compact.join(", ") }>"
109
+ "#<#{self.class.name} #{instance_variables.filter_map do |iv|
110
+ next if iv == :@xml
111
+
112
+ "#{iv}=#{instance_variable_get(iv).inspect}"
113
+ end.join(", ")}>"
66
114
  end
67
115
 
68
116
  # Serialize this object to XML
@@ -84,8 +132,7 @@ module SAML2
84
132
  #
85
133
  # @param builder [Nokogiri::XML::Builder] The builder helper object to serialize to.
86
134
  # @return [void]
87
- def build(builder)
88
- end
135
+ def build(builder); end
89
136
 
90
137
  # Decrypt (in-place) encrypted portions of this object
91
138
  #
@@ -107,13 +154,14 @@ module SAML2
107
154
  encrypted_nodes.each do |node|
108
155
  this_nodes_keys = keys
109
156
  if keys.nil?
110
- allowed_certs = node.xpath("dsig:KeyInfo/xenc:EncryptedKey/dsig:KeyInfo/dsig:X509Data", SAML2::Namespaces::ALL).map do |x509data|
157
+ allowed_certs = node.xpath("dsig:KeyInfo/xenc:EncryptedKey/dsig:KeyInfo/dsig:X509Data",
158
+ SAML2::Namespaces::ALL).map do |x509data|
111
159
  if (cert = x509data.at_xpath("dsig:X509Certificate", SAML2::Namespaces::ALL)&.content&.strip)
112
160
  OpenSSL::X509::Certificate.new(Base64.decode64(cert))
113
161
  elsif (issuer_serial = x509data.at_xpath("dsig:X509IssuerSerial", SAML2::Namespaces::ALL))
114
162
  {
115
- issuer: issuer_serial.at_xpath("dsig:X509IssuerName", SAML2::Namespaces::ALL).content.strip,
116
- serial: issuer_serial.at_xpath("dsig:X509SerialNumber", SAML2::Namespaces::ALL).content.strip.to_i,
163
+ issuer: issuer_serial.at_xpath("dsig:X509IssuerName", SAML2::Namespaces::ALL).content.strip,
164
+ serial: issuer_serial.at_xpath("dsig:X509SerialNumber", SAML2::Namespaces::ALL).content.strip.to_i
117
165
  }
118
166
  elsif (subject_name = x509data.at_xpath("dsig:X509SubjectName", SAML2::Namespaces::ALL)&.content&.strip)
119
167
  subject_name
@@ -126,42 +174,16 @@ module SAML2
126
174
 
127
175
  old_node = node.parent
128
176
  this_nodes_keys.each_with_index do |key, i|
129
- begin
130
- old_node.replace(node.decrypt_with(key: key))
131
- rescue XMLSec::DecryptionError
132
- # swallow errors on all but the last key
133
- raise if i - 1 == this_nodes_keys.length
134
- end
177
+ old_node.replace(node.decrypt_with(key: key))
178
+ rescue XMLSec::DecryptionError
179
+ # swallow errors on all but the last key
180
+ raise if i - 1 == this_nodes_keys.length
135
181
  end
136
182
  end
137
183
  !encrypted_nodes.empty?
138
184
  end
139
185
 
140
- def self.load_string_array(node, element)
141
- node.xpath(element, Namespaces::ALL).map do |element_node|
142
- element_node.content&.strip
143
- end
144
- end
145
-
146
-
147
- def self.load_object_array(node, element, klass = nil)
148
- node.xpath(element, Namespaces::ALL).map do |element_node|
149
- if klass.nil?
150
- SAML2.const_get(element_node.name, false).from_xml(element_node)
151
- elsif klass.is_a?(Hash)
152
- klass[element_node.name].from_xml(element_node)
153
- else
154
- klass.from_xml(element_node)
155
- end
156
- end
157
- end
158
-
159
- def self.lookup_qname(qname, namespaces)
160
- prefix, local_name = split_qname(qname)
161
- [lookup_namespace(prefix, namespaces), local_name]
162
- end
163
-
164
- protected
186
+ private
165
187
 
166
188
  def load_string_array(node, element)
167
189
  self.class.load_string_array(node, element)
@@ -172,20 +194,7 @@ module SAML2
172
194
  end
173
195
 
174
196
  def encrypted_nodes
175
- xml.xpath('//xenc:EncryptedData', Namespaces::ALL)
176
- end
177
-
178
- def self.split_qname(qname)
179
- if qname.include?(':')
180
- qname.split(':', 2)
181
- else
182
- [nil, qname]
183
- end
184
- end
185
-
186
- def self.lookup_namespace(prefix, namespaces)
187
- return nil if namespaces.empty?
188
- namespaces[prefix.empty? ? 'xmlns' : "xmlns:#{prefix}"]
197
+ xml.xpath("//xenc:EncryptedData", Namespaces::ALL)
189
198
  end
190
199
  end
191
200
  end
@@ -1,11 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'base64'
3
+ require "base64"
4
4
 
5
5
  module SAML2
6
6
  module Bindings
7
- module HTTP_POST
8
- URN ="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
7
+ module HTTP_POST # rubocop:disable Naming/ClassAndModuleCamelCase
8
+ URN = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
9
9
 
10
10
  class << self
11
11
  # Decode and parse a Base64 encoded SAML message.
@@ -16,7 +16,7 @@ module SAML2
16
16
  # @return [[Message, String]]
17
17
  # The Message and the RelayState.
18
18
  def decode(post_params)
19
- base64 = post_params['SAMLRequest'] || post_params['SAMLResponse']
19
+ base64 = post_params["SAMLRequest"] || post_params["SAMLResponse"]
20
20
  raise MissingMessage unless base64
21
21
 
22
22
  raise MessageTooLarge if base64.bytesize > SAML2.config[:max_message_size]
@@ -28,7 +28,7 @@ module SAML2
28
28
  end
29
29
 
30
30
  message = Message.parse(xml)
31
- [message, post_params['RelayState']]
31
+ [message, post_params["RelayState"]]
32
32
  end
33
33
 
34
34
  # Encode a SAML message into Base64 POST params.
@@ -40,9 +40,9 @@ module SAML2
40
40
  # +SAMLResponse+ chosen appropriately.
41
41
  def encode(message, relay_state: nil)
42
42
  xml = message.to_s(pretty: false)
43
- key = message.is_a?(Request) ? 'SAMLRequest' : 'SAMLResponse'
43
+ key = message.is_a?(Request) ? "SAMLRequest" : "SAMLResponse"
44
44
  post_params = { key => Base64.encode64(xml) }
45
- post_params['RelayState'] = relay_state if relay_state
45
+ post_params["RelayState"] = relay_state if relay_state
46
46
  post_params
47
47
  end
48
48
  end
@@ -1,16 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'base64'
4
- require 'uri'
5
- require 'zlib'
3
+ require "base64"
4
+ require "uri"
5
+ require "zlib"
6
6
 
7
- require 'saml2/bindings'
8
- require 'saml2/message'
7
+ require "saml2/bindings"
8
+ require "saml2/message"
9
9
 
10
10
  module SAML2
11
11
  module Bindings
12
12
  module HTTPRedirect
13
- URN ="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
13
+ URN = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
14
14
 
15
15
  module SigAlgs
16
16
  DSA_SHA1 = "http://www.w3.org/2000/09/xmldsig#dsa-sha1"
@@ -51,18 +51,19 @@ module SAML2
51
51
  end
52
52
 
53
53
  raise MissingMessage unless uri.query
54
+
54
55
  query = URI.decode_www_form(uri.query)
55
- base64 = query.assoc('SAMLRequest')&.last
56
+ base64 = query.assoc("SAMLRequest")&.last
56
57
  if base64
57
- message_param = 'SAMLRequest'
58
+ message_param = "SAMLRequest"
58
59
  else
59
- base64 = query.assoc('SAMLResponse')&.last
60
- message_param = 'SAMLResponse'
60
+ base64 = query.assoc("SAMLResponse")&.last
61
+ message_param = "SAMLResponse"
61
62
  end
62
- encoding = query.assoc('SAMLEncoding')&.last
63
- relay_state = query.assoc('RelayState')&.last
64
- signature = query.assoc('Signature')&.last
65
- sig_alg = query.assoc('SigAlg')&.last
63
+ encoding = query.assoc("SAMLEncoding")&.last
64
+ relay_state = query.assoc("RelayState")&.last
65
+ signature = query.assoc("Signature")&.last
66
+ sig_alg = query.assoc("SigAlg")&.last
66
67
  raise MissingMessage unless base64
67
68
 
68
69
  raise UnsupportedEncoding if encoding && encoding != Encodings::DEFLATE
@@ -76,7 +77,7 @@ module SAML2
76
77
  end
77
78
 
78
79
  zstream = Zlib::Inflate.new(-Zlib::MAX_WBITS)
79
- xml = String.new
80
+ xml = +""
80
81
  begin
81
82
  # do it in 1K slices, so we can protect against bombs
82
83
  (0..deflated.bytesize / 1024).each do |i|
@@ -106,19 +107,19 @@ module SAML2
106
107
  end
107
108
 
108
109
  base_string = find_raw_query_param(uri.query, message_param)
109
- base_string << '&' << find_raw_query_param(uri.query, 'RelayState') if relay_state
110
- base_string << '&' << find_raw_query_param(uri.query, 'SigAlg')
110
+ base_string << "&" << find_raw_query_param(uri.query, "RelayState") if relay_state
111
+ base_string << "&" << find_raw_query_param(uri.query, "SigAlg")
111
112
 
112
113
  valid_signature = false
113
114
  # there could be multiple certificates to try
114
115
  Array(public_key).each do |key|
115
- hash = (sig_alg == SigAlgs::RSA_SHA256 ? OpenSSL::Digest::SHA256 : OpenSSL::Digest::SHA1)
116
- if key.verify(hash.new, signature, base_string)
117
- # notify the caller which certificate was used
118
- public_key_used&.call(key)
119
- valid_signature = true
120
- break
121
- end
116
+ hash = ((sig_alg == SigAlgs::RSA_SHA256) ? OpenSSL::Digest::SHA256 : OpenSSL::Digest::SHA1)
117
+ next unless key.verify(hash.new, signature, base_string)
118
+
119
+ # notify the caller which certificate was used
120
+ public_key_used&.call(key)
121
+ valid_signature = true
122
+ break
122
123
  end
123
124
  raise InvalidSignature unless valid_signature
124
125
  end
@@ -145,8 +146,8 @@ module SAML2
145
146
  original_query = URI.decode_www_form(result.query) if result.query
146
147
  original_query ||= []
147
148
  # remove any SAML protocol parameters
148
- %w{SAMLEncoding SAMLRequest SAMLResponse RelayState SigAlg Signature}.each do |param|
149
- original_query.delete_if { |(k, v)| k == param }
149
+ %w[SAMLEncoding SAMLRequest SAMLResponse RelayState SigAlg Signature].each do |param|
150
+ original_query.delete_if { |(k, _v)| k == param }
150
151
  end
151
152
 
152
153
  xml = message.to_s(pretty: false)
@@ -156,16 +157,19 @@ module SAML2
156
157
  base64 = Base64.strict_encode64(deflated)
157
158
 
158
159
  query = []
159
- query << [message.is_a?(Request) ? 'SAMLRequest' : 'SAMLResponse', base64]
160
- query << ['RelayState', relay_state] if relay_state
160
+ query << [message.is_a?(Request) ? "SAMLRequest" : "SAMLResponse", base64]
161
+ query << ["RelayState", relay_state] if relay_state
161
162
  if private_key
162
- raise ArgumentError, "Unsupported signature algorithm #{sig_alg}" unless SigAlgs::RECOGNIZED.include?(sig_alg)
163
+ unless SigAlgs::RECOGNIZED.include?(sig_alg)
164
+ raise ArgumentError,
165
+ "Unsupported signature algorithm #{sig_alg}"
166
+ end
163
167
 
164
- query << ['SigAlg', sig_alg]
168
+ query << ["SigAlg", sig_alg]
165
169
  base_string = URI.encode_www_form(query)
166
- hash = (sig_alg == SigAlgs::RSA_SHA256 ? OpenSSL::Digest::SHA256 : OpenSSL::Digest::SHA1)
170
+ hash = ((sig_alg == SigAlgs::RSA_SHA256) ? OpenSSL::Digest::SHA256 : OpenSSL::Digest::SHA1)
167
171
  signature = private_key.sign(hash.new, base_string)
168
- query << ['Signature', Base64.strict_encode64(signature)]
172
+ query << ["Signature", Base64.strict_encode64(signature)]
169
173
  end
170
174
 
171
175
  result.query = URI.encode_www_form(original_query + query)
@@ -177,7 +181,7 @@ module SAML2
177
181
  # we need to find the param, and return it still encoded from the URL
178
182
  def find_raw_query_param(query, param)
179
183
  start = query.index(param)
180
- finish = (query.index('&', start + param.length + 1) || 0) - 1
184
+ finish = (query.index("&", start + param.length + 1) || 0) - 1
181
185
  query[start..finish]
182
186
  end
183
187
  end
@@ -3,7 +3,7 @@
3
3
  module SAML2
4
4
  module Bindings
5
5
  module Encodings
6
- DEFLATE = 'urn:oasis:names:tc:SAML:2.0:bindings:URL-Encoding:DEFLATE'
6
+ DEFLATE = "urn:oasis:names:tc:SAML:2.0:bindings:URL-Encoding:DEFLATE"
7
7
  end
8
8
  end
9
9
  end