saml2 3.1.1 → 3.1.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (100) 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 -137
  43. data/schemas/MetadataExchange.xsd +0 -112
  44. data/schemas/metadata_combined.xsd +0 -13
  45. data/schemas/oasis-200401-wss-wssecurity-secext-1.0.xsd +0 -195
  46. data/schemas/oasis-200401-wss-wssecurity-utility-1.0.xsd +0 -108
  47. data/schemas/saml-schema-assertion-2.0.xsd +0 -283
  48. data/schemas/saml-schema-metadata-2.0.xsd +0 -339
  49. data/schemas/saml-schema-protocol-2.0.xsd +0 -302
  50. data/schemas/sstc-saml-metadata-ext-query.xsd +0 -66
  51. data/schemas/ws-addr.xsd +0 -137
  52. data/schemas/ws-authorization.xsd +0 -145
  53. data/schemas/ws-federation.xsd +0 -471
  54. data/schemas/ws-securitypolicy-1.2.xsd +0 -1205
  55. data/schemas/xenc-schema.xsd +0 -136
  56. data/schemas/xml.xsd +0 -287
  57. data/schemas/xmldsig-core-schema.xsd +0 -309
  58. data/spec/fixtures/FederationMetadata.xml +0 -670
  59. data/spec/fixtures/authnrequest.xml +0 -12
  60. data/spec/fixtures/certificate.pem +0 -24
  61. data/spec/fixtures/entities.xml +0 -13
  62. data/spec/fixtures/external-uri-reference-response.xml +0 -48
  63. data/spec/fixtures/identity_provider.xml +0 -46
  64. data/spec/fixtures/noconditions_response.xml +0 -1
  65. data/spec/fixtures/othercertificate.pem +0 -25
  66. data/spec/fixtures/privatekey.key +0 -27
  67. data/spec/fixtures/response_assertion_signed_reffed_from_response.xml +0 -6
  68. data/spec/fixtures/response_signed.xml +0 -46
  69. data/spec/fixtures/response_tampered_certificate.xml +0 -25
  70. data/spec/fixtures/response_tampered_signature.xml +0 -46
  71. data/spec/fixtures/response_with_attribute_signed.xml +0 -46
  72. data/spec/fixtures/response_with_encrypted_assertion.xml +0 -58
  73. data/spec/fixtures/response_with_rsa_key_value.xml +0 -1
  74. data/spec/fixtures/response_with_signed_assertion_and_encrypted_subject.xml +0 -116
  75. data/spec/fixtures/response_without_keyinfo.xml +0 -1
  76. data/spec/fixtures/service_provider.xml +0 -79
  77. data/spec/fixtures/test3-response.xml +0 -9
  78. data/spec/fixtures/test6-response.xml +0 -10
  79. data/spec/fixtures/test7-response.xml +0 -10
  80. data/spec/fixtures/xml_missigned_assertion.xml +0 -84
  81. data/spec/fixtures/xml_signature_wrapping_attack_duplicate_ids.xml +0 -11
  82. data/spec/fixtures/xml_signature_wrapping_attack_response_attributes.xml +0 -45
  83. data/spec/fixtures/xml_signature_wrapping_attack_response_nameid.xml +0 -44
  84. data/spec/fixtures/xslt-transform-response.xml +0 -57
  85. data/spec/lib/attribute_consuming_service_spec.rb +0 -129
  86. data/spec/lib/attribute_spec.rb +0 -149
  87. data/spec/lib/authn_request_spec.rb +0 -52
  88. data/spec/lib/bindings/http_redirect_spec.rb +0 -183
  89. data/spec/lib/conditions_spec.rb +0 -74
  90. data/spec/lib/entity_spec.rb +0 -58
  91. data/spec/lib/identity_provider_spec.rb +0 -43
  92. data/spec/lib/indexed_object_spec.rb +0 -71
  93. data/spec/lib/key_spec.rb +0 -23
  94. data/spec/lib/logout_request_spec.rb +0 -33
  95. data/spec/lib/logout_response_spec.rb +0 -33
  96. data/spec/lib/message_spec.rb +0 -23
  97. data/spec/lib/response_spec.rb +0 -293
  98. data/spec/lib/service_provider_spec.rb +0 -76
  99. data/spec/lib/signable_spec.rb +0 -15
  100. 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