ruby-saml 1.11.0 → 1.13.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.
Files changed (160) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test.yml +25 -0
  3. data/{changelog.md → CHANGELOG.md} +44 -1
  4. data/README.md +333 -217
  5. data/UPGRADING.md +149 -0
  6. data/lib/onelogin/ruby-saml/attributes.rb +24 -1
  7. data/lib/onelogin/ruby-saml/authrequest.rb +11 -7
  8. data/lib/onelogin/ruby-saml/idp_metadata_parser.rb +154 -83
  9. data/lib/onelogin/ruby-saml/logoutrequest.rb +12 -6
  10. data/lib/onelogin/ruby-saml/logoutresponse.rb +5 -1
  11. data/lib/onelogin/ruby-saml/metadata.rb +62 -17
  12. data/lib/onelogin/ruby-saml/response.rb +51 -31
  13. data/lib/onelogin/ruby-saml/saml_message.rb +8 -3
  14. data/lib/onelogin/ruby-saml/setting_error.rb +6 -0
  15. data/lib/onelogin/ruby-saml/settings.rb +89 -49
  16. data/lib/onelogin/ruby-saml/slo_logoutrequest.rb +16 -4
  17. data/lib/onelogin/ruby-saml/slo_logoutresponse.rb +31 -17
  18. data/lib/onelogin/ruby-saml/utils.rb +63 -2
  19. data/lib/onelogin/ruby-saml/version.rb +1 -1
  20. data/lib/xml_security.rb +39 -13
  21. data/ruby-saml.gemspec +8 -4
  22. metadata +24 -282
  23. data/.travis.yml +0 -46
  24. data/test/certificates/certificate.der +0 -0
  25. data/test/certificates/certificate1 +0 -12
  26. data/test/certificates/certificate_without_head_foot +0 -1
  27. data/test/certificates/formatted_certificate +0 -14
  28. data/test/certificates/formatted_chained_certificate +0 -42
  29. data/test/certificates/formatted_private_key +0 -12
  30. data/test/certificates/formatted_rsa_private_key +0 -12
  31. data/test/certificates/invalid_certificate1 +0 -1
  32. data/test/certificates/invalid_certificate2 +0 -1
  33. data/test/certificates/invalid_certificate3 +0 -12
  34. data/test/certificates/invalid_chained_certificate1 +0 -1
  35. data/test/certificates/invalid_private_key1 +0 -1
  36. data/test/certificates/invalid_private_key2 +0 -1
  37. data/test/certificates/invalid_private_key3 +0 -10
  38. data/test/certificates/invalid_rsa_private_key1 +0 -1
  39. data/test/certificates/invalid_rsa_private_key2 +0 -1
  40. data/test/certificates/invalid_rsa_private_key3 +0 -10
  41. data/test/certificates/ruby-saml-2.crt +0 -15
  42. data/test/certificates/ruby-saml.crt +0 -14
  43. data/test/certificates/ruby-saml.key +0 -15
  44. data/test/idp_metadata_parser_test.rb +0 -594
  45. data/test/logging_test.rb +0 -62
  46. data/test/logout_requests/invalid_slo_request.xml +0 -6
  47. data/test/logout_requests/slo_request.xml +0 -4
  48. data/test/logout_requests/slo_request.xml.base64 +0 -1
  49. data/test/logout_requests/slo_request_deflated.xml.base64 +0 -1
  50. data/test/logout_requests/slo_request_with_name_id_format.xml +0 -4
  51. data/test/logout_requests/slo_request_with_session_index.xml +0 -5
  52. data/test/logout_responses/logoutresponse_fixtures.rb +0 -86
  53. data/test/logoutrequest_test.rb +0 -260
  54. data/test/logoutresponse_test.rb +0 -427
  55. data/test/metadata/idp_descriptor.xml +0 -26
  56. data/test/metadata/idp_descriptor_2.xml +0 -56
  57. data/test/metadata/idp_descriptor_3.xml +0 -14
  58. data/test/metadata/idp_descriptor_4.xml +0 -72
  59. data/test/metadata/idp_metadata_different_sign_and_encrypt_cert.xml +0 -72
  60. data/test/metadata/idp_metadata_multi_certs.xml +0 -75
  61. data/test/metadata/idp_metadata_multi_signing_certs.xml +0 -52
  62. data/test/metadata/idp_metadata_same_sign_and_encrypt_cert.xml +0 -71
  63. data/test/metadata/idp_multiple_descriptors.xml +0 -59
  64. data/test/metadata/idp_multiple_descriptors_2.xml +0 -59
  65. data/test/metadata/no_idp_descriptor.xml +0 -21
  66. data/test/metadata_test.rb +0 -331
  67. data/test/request_test.rb +0 -340
  68. data/test/response_test.rb +0 -1629
  69. data/test/responses/adfs_response_sha1.xml +0 -46
  70. data/test/responses/adfs_response_sha256.xml +0 -46
  71. data/test/responses/adfs_response_sha384.xml +0 -46
  72. data/test/responses/adfs_response_sha512.xml +0 -46
  73. data/test/responses/adfs_response_xmlns.xml +0 -45
  74. data/test/responses/attackxee.xml +0 -13
  75. data/test/responses/invalids/duplicated_attributes.xml.base64 +0 -1
  76. data/test/responses/invalids/empty_destination.xml.base64 +0 -1
  77. data/test/responses/invalids/empty_nameid.xml.base64 +0 -1
  78. data/test/responses/invalids/encrypted_new_attack.xml.base64 +0 -1
  79. data/test/responses/invalids/invalid_audience.xml.base64 +0 -1
  80. data/test/responses/invalids/invalid_issuer_assertion.xml.base64 +0 -1
  81. data/test/responses/invalids/invalid_issuer_message.xml.base64 +0 -1
  82. data/test/responses/invalids/invalid_signature_position.xml.base64 +0 -1
  83. data/test/responses/invalids/invalid_subjectconfirmation_inresponse.xml.base64 +0 -1
  84. data/test/responses/invalids/invalid_subjectconfirmation_nb.xml.base64 +0 -1
  85. data/test/responses/invalids/invalid_subjectconfirmation_noa.xml.base64 +0 -1
  86. data/test/responses/invalids/invalid_subjectconfirmation_recipient.xml.base64 +0 -1
  87. data/test/responses/invalids/multiple_assertions.xml.base64 +0 -2
  88. data/test/responses/invalids/multiple_signed.xml.base64 +0 -1
  89. data/test/responses/invalids/no_authnstatement.xml.base64 +0 -1
  90. data/test/responses/invalids/no_conditions.xml.base64 +0 -1
  91. data/test/responses/invalids/no_id.xml.base64 +0 -1
  92. data/test/responses/invalids/no_issuer_assertion.xml.base64 +0 -1
  93. data/test/responses/invalids/no_issuer_response.xml.base64 +0 -1
  94. data/test/responses/invalids/no_nameid.xml.base64 +0 -1
  95. data/test/responses/invalids/no_saml2.xml.base64 +0 -1
  96. data/test/responses/invalids/no_signature.xml.base64 +0 -1
  97. data/test/responses/invalids/no_status.xml.base64 +0 -1
  98. data/test/responses/invalids/no_status_code.xml.base64 +0 -1
  99. data/test/responses/invalids/no_subjectconfirmation_data.xml.base64 +0 -1
  100. data/test/responses/invalids/no_subjectconfirmation_method.xml.base64 +0 -1
  101. data/test/responses/invalids/response_invalid_signed_element.xml.base64 +0 -1
  102. data/test/responses/invalids/response_with_concealed_signed_assertion.xml +0 -51
  103. data/test/responses/invalids/response_with_doubled_signed_assertion.xml +0 -49
  104. data/test/responses/invalids/signature_wrapping_attack.xml.base64 +0 -1
  105. data/test/responses/invalids/status_code_responder.xml.base64 +0 -1
  106. data/test/responses/invalids/status_code_responer_and_msg.xml.base64 +0 -1
  107. data/test/responses/invalids/wrong_spnamequalifier.xml.base64 +0 -1
  108. data/test/responses/no_signature_ns.xml +0 -48
  109. data/test/responses/open_saml_response.xml +0 -56
  110. data/test/responses/response_assertion_wrapped.xml.base64 +0 -93
  111. data/test/responses/response_audience_self_closed_tag.xml.base64 +0 -1
  112. data/test/responses/response_double_status_code.xml.base64 +0 -1
  113. data/test/responses/response_encrypted_attrs.xml.base64 +0 -1
  114. data/test/responses/response_encrypted_nameid.xml.base64 +0 -1
  115. data/test/responses/response_eval.xml +0 -7
  116. data/test/responses/response_no_cert_and_encrypted_attrs.xml +0 -29
  117. data/test/responses/response_node_text_attack.xml.base64 +0 -1
  118. data/test/responses/response_node_text_attack2.xml.base64 +0 -1
  119. data/test/responses/response_node_text_attack3.xml.base64 +0 -1
  120. data/test/responses/response_unsigned_xml_base64 +0 -1
  121. data/test/responses/response_with_ampersands.xml +0 -139
  122. data/test/responses/response_with_ampersands.xml.base64 +0 -93
  123. data/test/responses/response_with_ds_namespace_at_the_root.xml.base64 +0 -1
  124. data/test/responses/response_with_multiple_attribute_statements.xml +0 -72
  125. data/test/responses/response_with_multiple_attribute_values.xml +0 -67
  126. data/test/responses/response_with_retrieval_method.xml +0 -26
  127. data/test/responses/response_with_saml2_namespace.xml.base64 +0 -102
  128. data/test/responses/response_with_signed_assertion.xml.base64 +0 -66
  129. data/test/responses/response_with_signed_assertion_2.xml.base64 +0 -1
  130. data/test/responses/response_with_signed_assertion_3.xml +0 -30
  131. data/test/responses/response_with_signed_message_and_assertion.xml +0 -34
  132. data/test/responses/response_with_undefined_recipient.xml.base64 +0 -1
  133. data/test/responses/response_without_attributes.xml.base64 +0 -79
  134. data/test/responses/response_without_reference_uri.xml.base64 +0 -1
  135. data/test/responses/response_wrapped.xml.base64 +0 -150
  136. data/test/responses/signed_message_encrypted_signed_assertion.xml.base64 +0 -1
  137. data/test/responses/signed_message_encrypted_unsigned_assertion.xml.base64 +0 -1
  138. data/test/responses/signed_nameid_in_atts.xml +0 -47
  139. data/test/responses/signed_unqual_nameid_in_atts.xml +0 -47
  140. data/test/responses/simple_saml_php.xml +0 -71
  141. data/test/responses/starfield_response.xml.base64 +0 -1
  142. data/test/responses/test_sign.xml +0 -43
  143. data/test/responses/unsigned_encrypted_adfs.xml +0 -23
  144. data/test/responses/unsigned_message_aes128_encrypted_signed_assertion.xml.base64 +0 -1
  145. data/test/responses/unsigned_message_aes192_encrypted_signed_assertion.xml.base64 +0 -1
  146. data/test/responses/unsigned_message_aes256_encrypted_signed_assertion.xml.base64 +0 -1
  147. data/test/responses/unsigned_message_des192_encrypted_signed_assertion.xml.base64 +0 -1
  148. data/test/responses/unsigned_message_encrypted_assertion_without_saml_namespace.xml.base64 +0 -1
  149. data/test/responses/unsigned_message_encrypted_signed_assertion.xml.base64 +0 -1
  150. data/test/responses/unsigned_message_encrypted_unsigned_assertion.xml.base64 +0 -1
  151. data/test/responses/valid_response.xml.base64 +0 -1
  152. data/test/responses/valid_response_with_formatted_x509certificate.xml.base64 +0 -1
  153. data/test/responses/valid_response_without_x509certificate.xml.base64 +0 -1
  154. data/test/saml_message_test.rb +0 -56
  155. data/test/settings_test.rb +0 -338
  156. data/test/slo_logoutrequest_test.rb +0 -467
  157. data/test/slo_logoutresponse_test.rb +0 -233
  158. data/test/test_helper.rb +0 -333
  159. data/test/utils_test.rb +0 -259
  160. data/test/xml_security_test.rb +0 -421
@@ -1,6 +1,7 @@
1
1
  require "onelogin/ruby-saml/logging"
2
2
  require "onelogin/ruby-saml/saml_message"
3
3
  require "onelogin/ruby-saml/utils"
4
+ require "onelogin/ruby-saml/setting_error"
4
5
 
5
6
  # Only supports SAML 2.0
6
7
  module OneLogin
@@ -20,6 +21,10 @@ module OneLogin
20
21
  @uuid = OneLogin::RubySaml::Utils.uuid
21
22
  end
22
23
 
24
+ def request_id
25
+ @uuid
26
+ end
27
+
23
28
  # Creates the Logout Request string.
24
29
  # @param settings [OneLogin::RubySaml::Settings|nil] Toolkit settings
25
30
  # @param params [Hash] Some extra parameters to be added in the GET for example the RelayState
@@ -27,13 +32,14 @@ module OneLogin
27
32
  #
28
33
  def create(settings, params={})
29
34
  params = create_params(settings, params)
30
- params_prefix = (settings.idp_slo_target_url =~ /\?/) ? '&' : '?'
35
+ params_prefix = (settings.idp_slo_service_url =~ /\?/) ? '&' : '?'
31
36
  saml_request = CGI.escape(params.delete("SAMLRequest"))
32
37
  request_params = "#{params_prefix}SAMLRequest=#{saml_request}"
33
38
  params.each_pair do |key, value|
34
39
  request_params << "&#{key.to_s}=#{CGI.escape(value.to_s)}"
35
40
  end
36
- @logout_url = settings.idp_slo_target_url + request_params
41
+ raise SettingError.new "Invalid settings, idp_slo_service_url is not set!" if settings.idp_slo_service_url.nil? or settings.idp_slo_service_url.empty?
42
+ @logout_url = settings.idp_slo_service_url + request_params
37
43
  end
38
44
 
39
45
  # Creates the Get parameters for the logout request.
@@ -64,8 +70,8 @@ module OneLogin
64
70
  base64_request = encode(request)
65
71
  request_params = {"SAMLRequest" => base64_request}
66
72
 
67
- if settings.security[:logout_requests_signed] && !settings.security[:embed_sign] && settings.private_key
68
- params['SigAlg'] = settings.security[:signature_method]
73
+ if settings.idp_slo_service_binding == Utils::BINDINGS[:redirect] && settings.security[:logout_requests_signed] && settings.private_key
74
+ params['SigAlg'] = settings.security[:signature_method]
69
75
  url_string = OneLogin::RubySaml::Utils.build_query(
70
76
  :type => 'SAMLRequest',
71
77
  :data => base64_request,
@@ -103,7 +109,7 @@ module OneLogin
103
109
  root.attributes['ID'] = uuid
104
110
  root.attributes['IssueInstant'] = time
105
111
  root.attributes['Version'] = "2.0"
106
- root.attributes['Destination'] = settings.idp_slo_target_url unless settings.idp_slo_target_url.nil?
112
+ root.attributes['Destination'] = settings.idp_slo_service_url unless settings.idp_slo_service_url.nil? or settings.idp_slo_service_url.empty?
107
113
 
108
114
  if settings.sp_entity_id
109
115
  issuer = root.add_element "saml:Issuer"
@@ -132,7 +138,7 @@ module OneLogin
132
138
 
133
139
  def sign_document(document, settings)
134
140
  # embed signature
135
- if settings.security[:logout_requests_signed] && settings.private_key && settings.certificate && settings.security[:embed_sign]
141
+ if settings.idp_slo_service_binding == Utils::BINDINGS[:post] && settings.security[:logout_requests_signed] && settings.private_key && settings.certificate
136
142
  private_key = settings.get_sp_key
137
143
  cert = settings.get_sp_cert
138
144
  document.sign_document(private_key, cert, settings.security[:signature_method], settings.security[:digest_method])
@@ -43,10 +43,14 @@ module OneLogin
43
43
  end
44
44
 
45
45
  @options = options
46
- @response = decode_raw_saml(response)
46
+ @response = decode_raw_saml(response, settings)
47
47
  @document = XMLSecurity::SignedDocument.new(@response)
48
48
  end
49
49
 
50
+ def response_id
51
+ id(document)
52
+ end
53
+
50
54
  # Checks if the Status has the "Success" code
51
55
  # @return [Boolean] True if the StatusCode is Sucess
52
56
  # @raise [ValidationError] if soft == false and validation fails
@@ -15,25 +15,56 @@ module OneLogin
15
15
  # @param settings [OneLogin::RubySaml::Settings|nil] Toolkit settings
16
16
  # @param pretty_print [Boolean] Pretty print or not the response
17
17
  # (No pretty print if you gonna validate the signature)
18
+ # @param valid_until [DateTime] Metadata's valid time
19
+ # @param cache_duration [Integer] Duration of the cache in seconds
18
20
  # @return [String] XML Metadata of the Service Provider
19
21
  #
20
- def generate(settings, pretty_print=false)
22
+ def generate(settings, pretty_print=false, valid_until=nil, cache_duration=nil)
21
23
  meta_doc = XMLSecurity::Document.new
24
+ add_xml_declaration(meta_doc)
25
+ root = add_root_element(meta_doc, settings, valid_until, cache_duration)
26
+ sp_sso = add_sp_sso_element(root, settings)
27
+ add_sp_certificates(sp_sso, settings)
28
+ add_sp_service_elements(sp_sso, settings)
29
+ add_extras(root, settings)
30
+ embed_signature(meta_doc, settings)
31
+ output_xml(meta_doc, pretty_print)
32
+ end
33
+
34
+ protected
35
+
36
+ def add_xml_declaration(meta_doc)
37
+ meta_doc << REXML::XMLDecl.new('1.0', 'UTF-8')
38
+ end
39
+
40
+ def add_root_element(meta_doc, settings, valid_until, cache_duration)
22
41
  namespaces = {
23
42
  "xmlns:md" => "urn:oasis:names:tc:SAML:2.0:metadata"
24
43
  }
44
+
25
45
  if settings.attribute_consuming_service.configured?
26
46
  namespaces["xmlns:saml"] = "urn:oasis:names:tc:SAML:2.0:assertion"
27
47
  end
28
- root = meta_doc.add_element "md:EntityDescriptor", namespaces
29
- sp_sso = root.add_element "md:SPSSODescriptor", {
48
+
49
+ root = meta_doc.add_element("md:EntityDescriptor", namespaces)
50
+ root.attributes["ID"] = OneLogin::RubySaml::Utils.uuid
51
+ root.attributes["entityID"] = settings.sp_entity_id if settings.sp_entity_id
52
+ root.attributes["validUntil"] = valid_until.strftime('%Y-%m-%dT%H:%M:%S%z') if valid_until
53
+ root.attributes["cacheDuration"] = "PT" + cache_duration.to_s + "S" if cache_duration
54
+ root
55
+ end
56
+
57
+ def add_sp_sso_element(root, settings)
58
+ root.add_element "md:SPSSODescriptor", {
30
59
  "protocolSupportEnumeration" => "urn:oasis:names:tc:SAML:2.0:protocol",
31
60
  "AuthnRequestsSigned" => settings.security[:authn_requests_signed],
32
61
  "WantAssertionsSigned" => settings.security[:want_assertions_signed],
33
62
  }
63
+ end
34
64
 
35
- # Add KeyDescriptor if messages will be signed / encrypted
36
- # with SP certificate, and new SP certificate if any
65
+ # Add KeyDescriptor if messages will be signed / encrypted
66
+ # with SP certificate, and new SP certificate if any
67
+ def add_sp_certificates(sp_sso, settings)
37
68
  cert = settings.get_sp_cert
38
69
  cert_new = settings.get_sp_cert_new
39
70
 
@@ -56,10 +87,10 @@ module OneLogin
56
87
  end
57
88
  end
58
89
 
59
- root.attributes["ID"] = OneLogin::RubySaml::Utils.uuid
60
- if settings.sp_entity_id
61
- root.attributes["entityID"] = settings.sp_entity_id
62
- end
90
+ sp_sso
91
+ end
92
+
93
+ def add_sp_service_elements(sp_sso, settings)
63
94
  if settings.single_logout_service_url
64
95
  sp_sso.add_element "md:SingleLogoutService", {
65
96
  "Binding" => settings.single_logout_service_binding,
@@ -67,10 +98,12 @@ module OneLogin
67
98
  "ResponseLocation" => settings.single_logout_service_url
68
99
  }
69
100
  end
101
+
70
102
  if settings.name_identifier_format
71
103
  nameid = sp_sso.add_element "md:NameIDFormat"
72
104
  nameid.text = settings.name_identifier_format
73
105
  end
106
+
74
107
  if settings.assertion_consumer_service_url
75
108
  sp_sso.add_element "md:AssertionConsumerService", {
76
109
  "Binding" => settings.assertion_consumer_service_binding,
@@ -109,15 +142,27 @@ module OneLogin
109
142
  # <md:RoleDescriptor xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:query="urn:oasis:names:tc:SAML:metadata:ext:query" xsi:type="query:AttributeQueryDescriptorType" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol"/>
110
143
  # <md:XACMLAuthzDecisionQueryDescriptor WantAssertionsSigned="false" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol"/>
111
144
 
112
- meta_doc << REXML::XMLDecl.new("1.0", "UTF-8")
145
+ sp_sso
146
+ end
113
147
 
114
- # embed signature
115
- if settings.security[:metadata_signed] && settings.private_key && settings.certificate
116
- private_key = settings.get_sp_key
117
- meta_doc.sign_document(private_key, cert, settings.security[:signature_method], settings.security[:digest_method])
118
- end
148
+ # can be overridden in subclass
149
+ def add_extras(root, _settings)
150
+ root
151
+ end
152
+
153
+ def embed_signature(meta_doc, settings)
154
+ return unless settings.security[:metadata_signed]
155
+
156
+ private_key = settings.get_sp_key
157
+ cert = settings.get_sp_cert
158
+ return unless private_key && cert
159
+
160
+ meta_doc.sign_document(private_key, cert, settings.security[:signature_method], settings.security[:digest_method])
161
+ end
162
+
163
+ def output_xml(meta_doc, pretty_print)
164
+ ret = ''
119
165
 
120
- ret = ""
121
166
  # pretty print the XML so IdP administrators can easily see what the SP supports
122
167
  if pretty_print
123
168
  meta_doc.write(ret, 1)
@@ -125,7 +170,7 @@ module OneLogin
125
170
  ret = meta_doc.to_s
126
171
  end
127
172
 
128
- return ret
173
+ ret
129
174
  end
130
175
  end
131
176
  end
@@ -34,7 +34,7 @@ module OneLogin
34
34
  # This is not a whitelist to allow people extending OneLogin::RubySaml:Response
35
35
  # and pass custom options
36
36
  AVAILABLE_OPTIONS = [
37
- :allowed_clock_drift, :check_duplicated_attributes, :matches_request_id, :settings, :skip_authnstatement, :skip_conditions,
37
+ :allowed_clock_drift, :check_duplicated_attributes, :matches_request_id, :settings, :skip_audience, :skip_authnstatement, :skip_conditions,
38
38
  :skip_destination, :skip_recipient_check, :skip_subject_confirmation
39
39
  ]
40
40
  # TODO: Update the comment on initialize to describe every option
@@ -47,6 +47,8 @@ module OneLogin
47
47
  # or :matches_request_id that will validate that the response matches the ID of the request,
48
48
  # or skip the subject confirmation validation with the :skip_subject_confirmation option
49
49
  # or skip the recipient validation of the subject confirmation element with :skip_recipient_check option
50
+ # or skip the audience validation with :skip_audience option
51
+ #
50
52
  def initialize(response, options = {})
51
53
  raise ArgumentError.new("Response cannot be nil") if response.nil?
52
54
 
@@ -61,7 +63,7 @@ module OneLogin
61
63
  end
62
64
  end
63
65
 
64
- @response = decode_raw_saml(response)
66
+ @response = decode_raw_saml(response, settings)
65
67
  @document = XMLSecurity::SignedDocument.new(@response, @errors)
66
68
 
67
69
  if assertion_encrypted?
@@ -225,11 +227,10 @@ module OneLogin
225
227
  statuses = nodes.collect do |inner_node|
226
228
  inner_node.attributes["Value"]
227
229
  end
228
- extra_code = statuses.join(" | ")
229
- if extra_code
230
- code = "#{code} | #{extra_code}"
231
- end
230
+
231
+ code = [code, statuses].flatten.join(" | ")
232
232
  end
233
+
233
234
  code
234
235
  end
235
236
  end
@@ -336,9 +337,31 @@ module OneLogin
336
337
  end
337
338
 
338
339
  # returns the allowed clock drift on timing validation
339
- # @return [Integer]
340
+ # @return [Float]
340
341
  def allowed_clock_drift
341
- return options[:allowed_clock_drift].to_f
342
+ options[:allowed_clock_drift].to_f.abs + Float::EPSILON
343
+ end
344
+
345
+ # Checks if the SAML Response contains or not an EncryptedAssertion element
346
+ # @return [Boolean] True if the SAML Response contains an EncryptedAssertion element
347
+ #
348
+ def assertion_encrypted?
349
+ ! REXML::XPath.first(
350
+ document,
351
+ "(/p:Response/EncryptedAssertion/)|(/p:Response/a:EncryptedAssertion/)",
352
+ { "p" => PROTOCOL, "a" => ASSERTION }
353
+ ).nil?
354
+ end
355
+
356
+ def response_id
357
+ id(document)
358
+ end
359
+
360
+ def assertion_id
361
+ @assertion_id ||= begin
362
+ node = xpath_first_from_signed_assertion("")
363
+ node.nil? ? nil : node.attributes['ID']
364
+ end
342
365
  end
343
366
 
344
367
  private
@@ -353,7 +376,6 @@ module OneLogin
353
376
  return false unless validate_response_state
354
377
 
355
378
  validations = [
356
- :validate_response_state,
357
379
  :validate_version,
358
380
  :validate_id,
359
381
  :validate_success_status,
@@ -435,7 +457,7 @@ module OneLogin
435
457
  # @return [Boolean] True if the SAML Response contains an ID, otherwise returns False
436
458
  #
437
459
  def validate_id
438
- unless id(document)
460
+ unless response_id
439
461
  return append_error("Missing ID attribute on SAML Response")
440
462
  end
441
463
 
@@ -584,11 +606,13 @@ module OneLogin
584
606
  end
585
607
 
586
608
  # Validates the Audience, (If the Audience match the Service Provider EntityID)
609
+ # If the response was initialized with the :skip_audience option, this validation is skipped,
587
610
  # If fails, the error is added to the errors array
588
611
  # @return [Boolean] True if there is an Audience Element that match the Service Provider EntityID, otherwise False if soft=True
589
612
  # @raise [ValidationError] if soft == false and validation fails
590
613
  #
591
614
  def validate_audience
615
+ return true if options[:skip_audience]
592
616
  return true if audiences.empty? || settings.sp_entity_id.nil? || settings.sp_entity_id.empty?
593
617
 
594
618
  unless audiences.include? settings.sp_entity_id
@@ -668,13 +692,13 @@ module OneLogin
668
692
 
669
693
  now = Time.now.utc
670
694
 
671
- if not_before && (now_with_drift = now + allowed_clock_drift) < not_before
672
- error_msg = "Current time is earlier than NotBefore condition (#{now_with_drift} < #{not_before})"
695
+ if not_before && now < (not_before - allowed_clock_drift)
696
+ error_msg = "Current time is earlier than NotBefore condition (#{now} < #{not_before}#{" - #{allowed_clock_drift.ceil}s" if allowed_clock_drift > 0})"
673
697
  return append_error(error_msg)
674
698
  end
675
699
 
676
- if not_on_or_after && now >= (not_on_or_after_with_drift = not_on_or_after + allowed_clock_drift)
677
- error_msg = "Current time is on or after NotOnOrAfter condition (#{now} >= #{not_on_or_after_with_drift})"
700
+ if not_on_or_after && now >= (not_on_or_after + allowed_clock_drift)
701
+ error_msg = "Current time is on or after NotOnOrAfter condition (#{now} >= #{not_on_or_after}#{" + #{allowed_clock_drift.ceil}s" if allowed_clock_drift > 0})"
678
702
  return append_error(error_msg)
679
703
  end
680
704
 
@@ -716,7 +740,7 @@ module OneLogin
716
740
  return true if session_expires_at.nil?
717
741
 
718
742
  now = Time.now.utc
719
- unless (session_expires_at + allowed_clock_drift) > now
743
+ unless now < (session_expires_at + allowed_clock_drift)
720
744
  error_msg = "The attributes have expired, based on the SessionNotOnOrAfter of the AuthnStatement of this Response"
721
745
  return append_error(error_msg)
722
746
  end
@@ -754,8 +778,8 @@ module OneLogin
754
778
 
755
779
  attrs = confirmation_data_node.attributes
756
780
  next if (attrs.include? "InResponseTo" and attrs['InResponseTo'] != in_response_to) ||
757
- (attrs.include? "NotOnOrAfter" and (parse_time(confirmation_data_node, "NotOnOrAfter") + allowed_clock_drift) <= now) ||
758
- (attrs.include? "NotBefore" and parse_time(confirmation_data_node, "NotBefore") > (now + allowed_clock_drift)) ||
781
+ (attrs.include? "NotBefore" and now < (parse_time(confirmation_data_node, "NotBefore") - allowed_clock_drift)) ||
782
+ (attrs.include? "NotOnOrAfter" and now >= (parse_time(confirmation_data_node, "NotOnOrAfter") + allowed_clock_drift)) ||
759
783
  (attrs.include? "Recipient" and !options[:skip_recipient_check] and settings and attrs['Recipient'] != settings.assertion_consumer_service_url)
760
784
 
761
785
  valid_subject_confirmation = true
@@ -802,7 +826,7 @@ module OneLogin
802
826
  # otherwise, review if the decrypted assertion contains a signature
803
827
  sig_elements = REXML::XPath.match(
804
828
  document,
805
- "/p:Response[@ID=$id]/ds:Signature]",
829
+ "/p:Response[@ID=$id]/ds:Signature",
806
830
  { "p" => PROTOCOL, "ds" => DSIG },
807
831
  { 'id' => document.signed_element_id }
808
832
  )
@@ -821,7 +845,7 @@ module OneLogin
821
845
  end
822
846
 
823
847
  if sig_elements.size != 1
824
- if sig_elements.size == 0
848
+ if sig_elements.size == 0
825
849
  append_error("Signed element id ##{doc.signed_element_id} is not found")
826
850
  else
827
851
  append_error("Signed element id ##{doc.signed_element_id} is found more than once")
@@ -829,6 +853,7 @@ module OneLogin
829
853
  return append_error(error_msg)
830
854
  end
831
855
 
856
+ old_errors = @errors.clone
832
857
 
833
858
  idp_certs = settings.get_idp_cert_multi
834
859
  if idp_certs.nil? || idp_certs[:signing].empty?
@@ -852,21 +877,27 @@ module OneLogin
852
877
  valid = false
853
878
  expired = false
854
879
  idp_certs[:signing].each do |idp_cert|
855
- valid = doc.validate_document_with_cert(idp_cert)
880
+ valid = doc.validate_document_with_cert(idp_cert, true)
856
881
  if valid
857
882
  if settings.security[:check_idp_cert_expiration]
858
883
  if OneLogin::RubySaml::Utils.is_cert_expired(idp_cert)
859
884
  expired = true
860
885
  end
861
886
  end
887
+
888
+ # At least one certificate is valid, restore the old accumulated errors
889
+ @errors = old_errors
862
890
  break
863
891
  end
892
+
864
893
  end
865
894
  if expired
866
895
  error_msg = "IdP x509 certificate expired"
867
896
  return append_error(error_msg)
868
897
  end
869
898
  unless valid
899
+ # Remove duplicated errors
900
+ @errors = @errors.uniq
870
901
  return append_error(error_msg)
871
902
  end
872
903
  end
@@ -967,17 +998,6 @@ module OneLogin
967
998
  XMLSecurity::SignedDocument.new(response_node.to_s)
968
999
  end
969
1000
 
970
- # Checks if the SAML Response contains or not an EncryptedAssertion element
971
- # @return [Boolean] True if the SAML Response contains an EncryptedAssertion element
972
- #
973
- def assertion_encrypted?
974
- ! REXML::XPath.first(
975
- document,
976
- "(/p:Response/EncryptedAssertion/)|(/p:Response/a:EncryptedAssertion/)",
977
- { "p" => PROTOCOL, "a" => ASSERTION }
978
- ).nil?
979
- end
980
-
981
1001
  # Decrypts an EncryptedAssertion element
982
1002
  # @param encrypted_assertion_node [REXML::Element] The EncryptedAssertion element
983
1003
  # @return [REXML::Document] The decrypted EncryptedAssertion element
@@ -16,8 +16,8 @@ module OneLogin
16
16
  class SamlMessage
17
17
  include REXML
18
18
 
19
- ASSERTION = "urn:oasis:names:tc:SAML:2.0:assertion"
20
- PROTOCOL = "urn:oasis:names:tc:SAML:2.0:protocol"
19
+ ASSERTION = "urn:oasis:names:tc:SAML:2.0:assertion".freeze
20
+ PROTOCOL = "urn:oasis:names:tc:SAML:2.0:protocol".freeze
21
21
 
22
22
  BASE64_FORMAT = %r(\A([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?\Z)
23
23
  @@mutex = Mutex.new
@@ -86,9 +86,14 @@ module OneLogin
86
86
  # @param saml [String] The deflated and encoded SAML Message
87
87
  # @return [String] The plain SAML Message
88
88
  #
89
- def decode_raw_saml(saml)
89
+ def decode_raw_saml(saml, settings = nil)
90
90
  return saml unless base64_encoded?(saml)
91
91
 
92
+ settings = OneLogin::RubySaml::Settings.new if settings.nil?
93
+ if saml.bytesize > settings.message_max_bytesize
94
+ raise ValidationError.new("Encoded SAML Message exceeds " + settings.message_max_bytesize.to_s + " bytes, so was rejected")
95
+ end
96
+
92
97
  decoded = decode(saml)
93
98
  begin
94
99
  inflate(decoded)