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
data/UPGRADING.md ADDED
@@ -0,0 +1,149 @@
1
+ # Ruby SAML Migration Guide
2
+
3
+ ## Updating from 1.12.x to 1.13.0 (NOT YET RELEASED)
4
+
5
+ Version `1.13.0` adds `settings.idp_sso_service_binding` and `settings.idp_slo_service_binding`, and
6
+ deprecates `settings.security[:embed_sign]`. If specified, new binding parameters will be used in place of `:embed_sign`
7
+ to determine how to handle SAML message signing (`HTTP-POST` embeds signature and `HTTP-Redirect` does not.)
8
+
9
+ In addition, the `IdpMetadataParser#parse`, `#parse_to_hash` and `#parse_to_array` methods now retrieve
10
+ `idp_sso_service_binding` and `idp_slo_service_binding`.
11
+
12
+ Lastly, for convenience you may now use the Symbol aliases `:post` and `:redirect` for any `settings.*_binding` parameter.
13
+
14
+ ## Upgrading from 1.11.x to 1.12.0
15
+
16
+ Version `1.12.0` adds support for gcm algorithm and
17
+ change/adds specific error messages for signature validations
18
+
19
+ `idp_sso_target_url` and `idp_slo_target_url` attributes of the Settings class deprecated
20
+ in favor of `idp_sso_service_url` and `idp_slo_service_url`. The `IdpMetadataParser#parse`,
21
+ `#parse_to_hash` and `#parse_to_array` methods now retrieve SSO URL and SLO URL endpoints with
22
+ `idp_sso_service_url` and `idp_slo_service_url` (previously `idp_sso_target_url` and
23
+ `idp_slo_target_url` respectively).
24
+
25
+ ## Upgrading from 1.10.x to 1.11.0
26
+
27
+ Version `1.11.0` deprecates the use of `settings.issuer` in favour of `settings.sp_entity_id`.
28
+ There are two new security settings: `settings.security[:check_idp_cert_expiration]` and
29
+ `settings.security[:check_sp_cert_expiration]` (both false by default) that check if the
30
+ IdP or SP X.509 certificate has expired, respectively.
31
+
32
+ Version `1.10.2` includes the `valid_until` attribute in parsed IdP metadata.
33
+
34
+ Version `1.10.1` improves Ruby 1.8.7 support.
35
+
36
+ ## Upgrading from 1.9.0 to 1.10.0
37
+
38
+ Version `1.10.0` improves IdpMetadataParser to allow parse multiple IDPSSODescriptor,
39
+ Add Subject support on AuthNRequest to allow SPs provide info to the IdP about the user
40
+ to be authenticated and updates the format_cert method to accept certs with /\x0d/
41
+
42
+ ## Upgrading from 1.8.0 to 1.9.0
43
+
44
+ Version `1.9.0` better supports Ruby 2.4+ and JRuby 9.2.0.0. `Settings` initialization
45
+ now has a second parameter, `keep_security_settings` (default: false), which saves security
46
+ settings attributes that are not explicitly overridden, if set to true.
47
+
48
+ ## Upgrading from 1.7.x to 1.8.0
49
+
50
+ On Version `1.8.0`, creating AuthRequests/LogoutRequests/LogoutResponses with nil RelayState
51
+ param will not generate a URL with an empty RelayState parameter anymore. It also changes
52
+ the invalid audience error message.
53
+
54
+ ## Upgrading from 1.6.0 to 1.7.0
55
+
56
+ Version `1.7.0` is a recommended update for all Ruby SAML users as it includes a fix for
57
+ the [CVE-2017-11428](https://www.cvedetails.com/cve/CVE-2017-11428/) vulnerability.
58
+
59
+ ## Upgrading from 1.5.0 to 1.6.0
60
+
61
+ Version `1.6.0` changes the preferred way to construct instances of `Logoutresponse` and
62
+ `SloLogoutrequest`. Previously the _SAMLResponse_, _RelayState_, and _SigAlg_ parameters
63
+ of these message types were provided via the constructor's `options[:get_params]` parameter.
64
+ Unfortunately this can result in incompatibility with other SAML implementations; signatures
65
+ are specified to be computed based on the _sender's_ URI-encoding of the message, which can
66
+ differ from that of Ruby SAML. In particular, Ruby SAML's URI-encoding does not match that
67
+ of Microsoft ADFS, so messages from ADFS can fail signature validation.
68
+
69
+ The new preferred way to provide _SAMLResponse_, _RelayState_, and _SigAlg_ is via the
70
+ `options[:raw_get_params]` parameter. For example:
71
+
72
+ ```ruby
73
+ # In this example `query_params` is assumed to contain decoded query parameters,
74
+ # and `raw_query_params` is assumed to contain encoded query parameters as sent by the IDP.
75
+ settings = {
76
+ settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA1
77
+ settings.soft = false
78
+ }
79
+ options = {
80
+ get_params: {
81
+ "Signature" => query_params["Signature"],
82
+ },
83
+ raw_get_params: {
84
+ "SAMLRequest" => raw_query_params["SAMLRequest"],
85
+ "SigAlg" => raw_query_params["SigAlg"],
86
+ "RelayState" => raw_query_params["RelayState"],
87
+ },
88
+ }
89
+ slo_logout_request = OneLogin::RubySaml::SloLogoutrequest.new(query_params["SAMLRequest"], settings, options)
90
+ raise "Invalid Logout Request" unless slo_logout_request.is_valid?
91
+ ```
92
+
93
+ The old form is still supported for backward compatibility, but all Ruby SAML users
94
+ should prefer `options[:raw_get_params]` where possible to ensure compatibility with
95
+ other SAML implementations.
96
+
97
+ ## Upgrading from 1.4.2 to 1.4.3
98
+
99
+ Version `1.4.3` introduces Recipient validation of SubjectConfirmation elements.
100
+ The 'Recipient' value is compared with the settings.assertion_consumer_service_url
101
+ value.
102
+
103
+ If you want to skip that validation, add the :skip_recipient_check option to the
104
+ initialize method of the Response object.
105
+
106
+ Parsing metadata that contains more than one certificate will propagate the
107
+ idp_cert_multi property rather than idp_cert. See [signature validation
108
+ section](#signature-validation) for details.
109
+
110
+ ## Upgrading from 1.3.x to 1.4.x
111
+
112
+ Version `1.4.0` is a recommended update for all Ruby SAML users as it includes security improvements.
113
+
114
+ ## Upgrading from 1.2.x to 1.3.x
115
+
116
+ Version `1.3.0` is a recommended update for all Ruby SAML users as it includes security fixes.
117
+ It adds security improvements in order to prevent Signature wrapping attacks.
118
+ [CVE-2016-5697](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2016-5697)
119
+
120
+ ## Upgrading from 1.1.x to 1.2.x
121
+
122
+ Version `1.2` adds IDP metadata parsing improvements, uuid deprecation in favour of SecureRandom,
123
+ refactor error handling and some minor improvements.
124
+
125
+ There is no compatibility issue detected.
126
+
127
+ For more details, please review [CHANGELOG.md](CHANGELOG.md).
128
+
129
+ ## Upgrading from 1.0.x to 1.1.x
130
+
131
+ Version `1.1` adds some improvements on signature validation and solves some namespace conflicts.
132
+
133
+ ## Upgrading from 0.9.x to 1.0.x
134
+
135
+ Version `1.0` is a recommended update for all Ruby SAML users as it includes security fixes.
136
+
137
+ Version `1.0` adds security improvements like entity expansion limitation, more SAML message validations, and other important improvements like decrypt support.
138
+
139
+ ### Important Changes
140
+
141
+ Please note the `get_idp_metadata` method raises an exception when it is not able to fetch the idp metadata, so review your integration if you are using this functionality.
142
+
143
+ ## Upgrading from 0.8.x to 0.9.x
144
+
145
+ Version `0.9` adds many new features and improvements.
146
+
147
+ ## Upgrading from 0.7.x to 0.8.x
148
+
149
+ Version `0.8.x` changes the namespace of the gem from `OneLogin::Saml` to `OneLogin::RubySaml`. Please update your implementations of the gem accordingly.
@@ -79,7 +79,7 @@ module OneLogin
79
79
  self.class.single_value_compatibility ? single(canonize_name(name)) : multi(canonize_name(name))
80
80
  end
81
81
 
82
- # @return [Array] Return all attributes as an array
82
+ # @return [Hash] Return all attributes as a hash
83
83
  #
84
84
  def all
85
85
  attributes
@@ -113,6 +113,29 @@ module OneLogin
113
113
  end
114
114
  end
115
115
 
116
+ # Fetch attribute value using name or regex
117
+ # @param name [String|Regexp] The attribute name
118
+ # @return [String|Array] Depending on the single value compatibility status this returns:
119
+ # - First value if single_value_compatibility = true
120
+ # response.attributes['mail'] # => 'user@example.com'
121
+ # - All values if single_value_compatibility = false
122
+ # response.attributes['mail'] # => ['user@example.com','user@example.net']
123
+ #
124
+ def fetch(name)
125
+ attributes.each_key do |attribute_key|
126
+ if name.is_a?(Regexp)
127
+ if name.respond_to? :match?
128
+ return self[attribute_key] if name.match?(attribute_key)
129
+ else
130
+ return self[attribute_key] if name.match(attribute_key)
131
+ end
132
+ elsif canonize_name(name) == canonize_name(attribute_key)
133
+ return self[attribute_key]
134
+ end
135
+ end
136
+ nil
137
+ end
138
+
116
139
  protected
117
140
 
118
141
  # stringifies all names so both 'email' and :email return the same result
@@ -3,6 +3,7 @@ require "rexml/document"
3
3
  require "onelogin/ruby-saml/logging"
4
4
  require "onelogin/ruby-saml/saml_message"
5
5
  require "onelogin/ruby-saml/utils"
6
+ require "onelogin/ruby-saml/setting_error"
6
7
 
7
8
  # Only supports SAML 2.0
8
9
  module OneLogin
@@ -23,6 +24,10 @@ module OneLogin
23
24
  @uuid = OneLogin::RubySaml::Utils.uuid
24
25
  end
25
26
 
27
+ def request_id
28
+ @uuid
29
+ end
30
+
26
31
  # Creates the AuthNRequest string.
27
32
  # @param settings [OneLogin::RubySaml::Settings|nil] Toolkit settings
28
33
  # @param params [Hash] Some extra parameters to be added in the GET for example the RelayState
@@ -30,14 +35,14 @@ module OneLogin
30
35
  #
31
36
  def create(settings, params = {})
32
37
  params = create_params(settings, params)
33
- params_prefix = (settings.idp_sso_target_url =~ /\?/) ? '&' : '?'
38
+ params_prefix = (settings.idp_sso_service_url =~ /\?/) ? '&' : '?'
34
39
  saml_request = CGI.escape(params.delete("SAMLRequest"))
35
40
  request_params = "#{params_prefix}SAMLRequest=#{saml_request}"
36
41
  params.each_pair do |key, value|
37
42
  request_params << "&#{key.to_s}=#{CGI.escape(value.to_s)}"
38
43
  end
39
- raise "Invalid settings, idp_sso_target_url is not set!" if settings.idp_sso_target_url.nil?
40
- @login_url = settings.idp_sso_target_url + request_params
44
+ raise SettingError.new "Invalid settings, idp_sso_service_url is not set!" if settings.idp_sso_service_url.nil? or settings.idp_sso_service_url.empty?
45
+ @login_url = settings.idp_sso_service_url + request_params
41
46
  end
42
47
 
43
48
  # Creates the Get parameters for the request.
@@ -68,7 +73,7 @@ module OneLogin
68
73
  base64_request = encode(request)
69
74
  request_params = {"SAMLRequest" => base64_request}
70
75
 
71
- if settings.security[:authn_requests_signed] && !settings.security[:embed_sign] && settings.private_key
76
+ if settings.idp_sso_service_binding == Utils::BINDINGS[:redirect] && settings.security[:authn_requests_signed] && settings.private_key
72
77
  params['SigAlg'] = settings.security[:signature_method]
73
78
  url_string = OneLogin::RubySaml::Utils.build_query(
74
79
  :type => 'SAMLRequest',
@@ -107,7 +112,7 @@ module OneLogin
107
112
  root.attributes['ID'] = uuid
108
113
  root.attributes['IssueInstant'] = time
109
114
  root.attributes['Version'] = "2.0"
110
- root.attributes['Destination'] = settings.idp_sso_target_url unless settings.idp_sso_target_url.nil?
115
+ root.attributes['Destination'] = settings.idp_sso_service_url unless settings.idp_sso_service_url.nil? or settings.idp_sso_service_url.empty?
111
116
  root.attributes['IsPassive'] = settings.passive unless settings.passive.nil?
112
117
  root.attributes['ProtocolBinding'] = settings.protocol_binding unless settings.protocol_binding.nil?
113
118
  root.attributes["AttributeConsumingServiceIndex"] = settings.attributes_index unless settings.attributes_index.nil?
@@ -174,8 +179,7 @@ module OneLogin
174
179
  end
175
180
 
176
181
  def sign_document(document, settings)
177
- # embed signature
178
- if settings.security[:authn_requests_signed] && settings.private_key && settings.certificate && settings.security[:embed_sign]
182
+ if settings.idp_sso_service_binding == Utils::BINDINGS[:post] && settings.security[:authn_requests_signed] && settings.private_key && settings.certificate
179
183
  private_key = settings.get_sp_key
180
184
  cert = settings.get_sp_cert
181
185
  document.sign_document(private_key, cert, settings.security[:signature_method], settings.security[:digest_method])
@@ -11,14 +11,18 @@ module OneLogin
11
11
 
12
12
  # Auxiliary class to retrieve and parse the Identity Provider Metadata
13
13
  #
14
+ # This class does not validate in any way the URL that is introduced,
15
+ # make sure to validate it properly before use it in a parse_remote method.
16
+ # Read the `Security warning` section of the README.md file to get more info
17
+ #
14
18
  class IdpMetadataParser
15
19
 
16
20
  module SamlMetadata
17
21
  module Vocabulary
18
- METADATA = "urn:oasis:names:tc:SAML:2.0:metadata"
19
- DSIG = "http://www.w3.org/2000/09/xmldsig#"
20
- NAME_FORMAT = "urn:oasis:names:tc:SAML:2.0:attrname-format:*"
21
- SAML_ASSERTION = "urn:oasis:names:tc:SAML:2.0:assertion"
22
+ METADATA = "urn:oasis:names:tc:SAML:2.0:metadata".freeze
23
+ DSIG = "http://www.w3.org/2000/09/xmldsig#".freeze
24
+ NAME_FORMAT = "urn:oasis:names:tc:SAML:2.0:attrname-format:*".freeze
25
+ SAML_ASSERTION = "urn:oasis:names:tc:SAML:2.0:assertion".freeze
22
26
  end
23
27
 
24
28
  NAMESPACE = {
@@ -26,7 +30,7 @@ module OneLogin
26
30
  "NameFormat" => Vocabulary::NAME_FORMAT,
27
31
  "saml" => Vocabulary::SAML_ASSERTION,
28
32
  "ds" => Vocabulary::DSIG
29
- }
33
+ }.freeze
30
34
  end
31
35
 
32
36
  include SamlMetadata::Vocabulary
@@ -34,6 +38,16 @@ module OneLogin
34
38
  attr_reader :response
35
39
  attr_reader :options
36
40
 
41
+ # fetch IdP descriptors from a metadata document
42
+ def self.get_idps(metadata_document, only_entity_id=nil)
43
+ path = "//md:EntityDescriptor#{only_entity_id && '[@entityID="' + only_entity_id + '"]'}/md:IDPSSODescriptor"
44
+ REXML::XPath.match(
45
+ metadata_document,
46
+ path,
47
+ SamlMetadata::NAMESPACE
48
+ )
49
+ end
50
+
37
51
  # Parse the Identity Provider metadata and update the settings with the
38
52
  # IdP values
39
53
  #
@@ -42,9 +56,10 @@ module OneLogin
42
56
  #
43
57
  # @param options [Hash] options used for parsing the metadata and the returned Settings instance
44
58
  # @option options [OneLogin::RubySaml::Settings, Hash] :settings the OneLogin::RubySaml::Settings object which gets the parsed metadata merged into or an hash for Settings overrides.
45
- # @option options [Array<String>, nil] :sso_binding an ordered list of bindings to detect the single signon URL. The first binding in the list that is included in the metadata will be used.
46
- # @option options [Array<String>, nil] :slo_binding an ordered list of bindings to detect the single logout URL. The first binding in the list that is included in the metadata will be used.
47
- # @option options [String, nil] :entity_id when this is given, the entity descriptor for this ID is used. When ommitted, the first entity descriptor is used.
59
+ # @option options [String, nil] :entity_id when this is given, the entity descriptor for this ID is used. When omitted, the first entity descriptor is used.
60
+ # @option options [String, Array<String>, nil] :sso_binding an ordered list of bindings to detect the single signon URL. The first binding in the list that is included in the metadata will be used.
61
+ # @option options [String, Array<String>, nil] :slo_binding an ordered list of bindings to detect the single logout URL. The first binding in the list that is included in the metadata will be used.
62
+ # @option options [String, Array<String>, nil] :name_id_format an ordered list of NameIDFormats to detect a desired value. The first NameIDFormat in the list that is included in the metadata will be used.
48
63
  #
49
64
  # @return [OneLogin::RubySaml::Settings]
50
65
  #
@@ -60,9 +75,10 @@ module OneLogin
60
75
  # @param validate_cert [Boolean] If true and the URL is HTTPs, the cert of the domain is checked.
61
76
  #
62
77
  # @param options [Hash] options used for parsing the metadata
63
- # @option options [Array<String>, nil] :sso_binding an ordered list of bindings to detect the single signon URL. The first binding in the list that is included in the metadata will be used.
64
- # @option options [Array<String>, nil] :slo_binding an ordered list of bindings to detect the single logout URL. The first binding in the list that is included in the metadata will be used.
65
- # @option options [String, nil] :entity_id when this is given, the entity descriptor for this ID is used. When ommitted, the first entity descriptor is used.
78
+ # @option options [String, nil] :entity_id when this is given, the entity descriptor for this ID is used. When omitted, the first entity descriptor is used.
79
+ # @option options [String, Array<String>, nil] :sso_binding an ordered list of bindings to detect the single signon URL. The first binding in the list that is included in the metadata will be used.
80
+ # @option options [String, Array<String>, nil] :slo_binding an ordered list of bindings to detect the single logout URL. The first binding in the list that is included in the metadata will be used.
81
+ # @option options [String, Array<String>, nil] :name_id_format an ordered list of NameIDFormats to detect a desired value. The first NameIDFormat in the list that is included in the metadata will be used.
66
82
  #
67
83
  # @return [Hash]
68
84
  #
@@ -77,9 +93,10 @@ module OneLogin
77
93
  # @param validate_cert [Boolean] If true and the URL is HTTPs, the cert of the domain is checked.
78
94
  #
79
95
  # @param options [Hash] options used for parsing the metadata
80
- # @option options [Array<String>, nil] :sso_binding an ordered list of bindings to detect the single signon URL. The first binding in the list that is included in the metadata will be used.
81
- # @option options [Array<String>, nil] :slo_binding an ordered list of bindings to detect the single logout URL. The first binding in the list that is included in the metadata will be used.
82
- # @option options [String, nil] :entity_id when this is given, the entity descriptor for this ID is used. When ommitted, all found IdPs are returned.
96
+ # @option options [String, nil] :entity_id when this is given, the entity descriptor for this ID is used. When omitted, all found IdPs are returned.
97
+ # @option options [String, Array<String>, nil] :sso_binding an ordered list of bindings to detect the single signon URL. The first binding in the list that is included in the metadata will be used.
98
+ # @option options [String, Array<String>, nil] :slo_binding an ordered list of bindings to detect the single logout URL. The first binding in the list that is included in the metadata will be used.
99
+ # @option options [String, Array<String>, nil] :name_id_format an ordered list of NameIDFormats to detect a desired value. The first NameIDFormat in the list that is included in the metadata will be used.
83
100
  #
84
101
  # @return [Array<Hash>]
85
102
  #
@@ -95,14 +112,27 @@ module OneLogin
95
112
  #
96
113
  # @param options [Hash] :settings to provide the OneLogin::RubySaml::Settings object or an hash for Settings overrides
97
114
  # @option options [OneLogin::RubySaml::Settings, Hash] :settings the OneLogin::RubySaml::Settings object which gets the parsed metadata merged into or an hash for Settings overrides.
98
- # @option options [Array<String>, nil] :sso_binding an ordered list of bindings to detect the single signon URL. The first binding in the list that is included in the metadata will be used.
99
- # @option options [Array<String>, nil] :slo_binding an ordered list of bindings to detect the single logout URL. The first binding in the list that is included in the metadata will be used.
100
- # @option options [String, nil] :entity_id when this is given, the entity descriptor for this ID is used. When ommitted, the first entity descriptor is used.
115
+ # @option options [String, nil] :entity_id when this is given, the entity descriptor for this ID is used. When omitted, the first entity descriptor is used.
116
+ # @option options [String, Array<String>, nil] :sso_binding an ordered list of bindings to detect the single signon URL. The first binding in the list that is included in the metadata will be used.
117
+ # @option options [String, Array<String>, nil] :slo_binding an ordered list of bindings to detect the single logout URL. The first binding in the list that is included in the metadata will be used.
118
+ # @option options [String, Array<String>, nil] :name_id_format an ordered list of NameIDFormats to detect a desired value. The first NameIDFormat in the list that is included in the metadata will be used.
101
119
  #
102
120
  # @return [OneLogin::RubySaml::Settings]
103
121
  def parse(idp_metadata, options = {})
104
122
  parsed_metadata = parse_to_hash(idp_metadata, options)
105
123
 
124
+ unless parsed_metadata[:cache_duration].nil?
125
+ cache_valid_until_timestamp = OneLogin::RubySaml::Utils.parse_duration(parsed_metadata[:cache_duration])
126
+ unless cache_valid_until_timestamp.nil?
127
+ if parsed_metadata[:valid_until].nil? || cache_valid_until_timestamp < Time.parse(parsed_metadata[:valid_until], Time.now.utc).to_i
128
+ parsed_metadata[:valid_until] = Time.at(cache_valid_until_timestamp).utc.strftime("%Y-%m-%dT%H:%M:%SZ")
129
+ end
130
+ end
131
+ end
132
+ # Remove the cache_duration because on the settings
133
+ # we only gonna suppot valid_until
134
+ parsed_metadata.delete(:cache_duration)
135
+
106
136
  settings = options[:settings]
107
137
 
108
138
  if settings.nil?
@@ -119,9 +149,10 @@ module OneLogin
119
149
  # @param idp_metadata [String]
120
150
  #
121
151
  # @param options [Hash] options used for parsing the metadata and the returned Settings instance
122
- # @option options [Array<String>, nil] :sso_binding an ordered list of bindings to detect the single signon URL. The first binding in the list that is included in the metadata will be used.
123
- # @option options [Array<String>, nil] :slo_binding an ordered list of bindings to detect the single logout URL. The first binding in the list that is included in the metadata will be used.
124
- # @option options [String, nil] :entity_id when this is given, the entity descriptor for this ID is used. When ommitted, the first entity descriptor is used.
152
+ # @option options [String, nil] :entity_id when this is given, the entity descriptor for this ID is used. When omitted, the first entity descriptor is used.
153
+ # @option options [String, Array<String>, nil] :sso_binding an ordered list of bindings to detect the single signon URL. The first binding in the list that is included in the metadata will be used.
154
+ # @option options [String, Array<String>, nil] :slo_binding an ordered list of bindings to detect the single logout URL. The first binding in the list that is included in the metadata will be used.
155
+ # @option options [String, Array<String>, nil] :name_id_format an ordered list of NameIDFormats to detect a desired value. The first NameIDFormat in the list that is included in the metadata will be used.
125
156
  #
126
157
  # @return [Hash]
127
158
  def parse_to_hash(idp_metadata, options = {})
@@ -133,21 +164,26 @@ module OneLogin
133
164
  # @param idp_metadata [String]
134
165
  #
135
166
  # @param options [Hash] options used for parsing the metadata and the returned Settings instance
136
- # @option options [Array<String>, nil] :sso_binding an ordered list of bindings to detect the single signon URL. The first binding in the list that is included in the metadata will be used.
137
- # @option options [Array<String>, nil] :slo_binding an ordered list of bindings to detect the single logout URL. The first binding in the list that is included in the metadata will be used.
138
- # @option options [String, nil] :entity_id when this is given, the entity descriptor for this ID is used. When ommitted, all found IdPs are returned.
167
+ # @option options [String, nil] :entity_id when this is given, the entity descriptor for this ID is used. When omitted, all found IdPs are returned.
168
+ # @option options [String, Array<String>, nil] :sso_binding an ordered list of bindings to detect the single signon URL. The first binding in the list that is included in the metadata will be used.
169
+ # @option options [String, Array<String>, nil] :slo_binding an ordered list of bindings to detect the single logout URL. The first binding in the list that is included in the metadata will be used.
170
+ # @option options [String, Array<String>, nil] :name_id_format an ordered list of NameIDFormats to detect a desired value. The first NameIDFormat in the list that is included in the metadata will be used.
139
171
  #
140
172
  # @return [Array<Hash>]
141
173
  def parse_to_array(idp_metadata, options = {})
174
+ parse_to_idp_metadata_array(idp_metadata, options).map { |idp_md| idp_md.to_hash(options) }
175
+ end
176
+
177
+ def parse_to_idp_metadata_array(idp_metadata, options = {})
142
178
  @document = REXML::Document.new(idp_metadata)
143
179
  @options = options
144
180
 
145
- idpsso_descriptors = IdpMetadata::get_idps(@document, options[:entity_id])
181
+ idpsso_descriptors = self.class.get_idps(@document, options[:entity_id])
146
182
  if !idpsso_descriptors.any?
147
183
  raise ArgumentError.new("idp_metadata must contain an IDPSSODescriptor element")
148
184
  end
149
185
 
150
- return idpsso_descriptors.map{|id| IdpMetadata.new(id, id.parent.attributes["entityID"]).to_hash(options)}
186
+ idpsso_descriptors.map {|id| IdpMetadata.new(id, id.parent.attributes["entityID"])}
151
187
  end
152
188
 
153
189
  private
@@ -175,6 +211,7 @@ module OneLogin
175
211
  end
176
212
 
177
213
  get = Net::HTTP::Get.new(uri.request_uri)
214
+ get.basic_auth uri.user, uri.password if uri.user
178
215
  @response = http.request(get)
179
216
  return response.body if response.is_a? Net::HTTPSuccess
180
217
 
@@ -184,14 +221,7 @@ module OneLogin
184
221
  end
185
222
 
186
223
  class IdpMetadata
187
- def self.get_idps(metadata_document, only_entity_id=nil)
188
- path = "//md:EntityDescriptor#{only_entity_id && '[@entityID="' + only_entity_id + '"]'}/md:IDPSSODescriptor"
189
- REXML::XPath.match(
190
- metadata_document,
191
- path,
192
- SamlMetadata::NAMESPACE
193
- )
194
- end
224
+ attr_reader :idpsso_descriptor, :entity_id
195
225
 
196
226
  def initialize(idpsso_descriptor, entity_id)
197
227
  @idpsso_descriptor = idpsso_descriptor
@@ -199,32 +229,27 @@ module OneLogin
199
229
  end
200
230
 
201
231
  def to_hash(options = {})
232
+ sso_binding = options[:sso_binding]
233
+ slo_binding = options[:slo_binding]
202
234
  {
203
235
  :idp_entity_id => @entity_id,
204
- :name_identifier_format => idp_name_id_format,
205
- :idp_sso_target_url => single_signon_service_url(options),
206
- :idp_slo_target_url => single_logout_service_url(options),
236
+ :name_identifier_format => idp_name_id_format(options[:name_id_format]),
237
+ :idp_sso_service_url => single_signon_service_url(sso_binding),
238
+ :idp_sso_service_binding => single_signon_service_binding(sso_binding),
239
+ :idp_slo_service_url => single_logout_service_url(slo_binding),
240
+ :idp_slo_service_binding => single_logout_service_binding(slo_binding),
241
+ :idp_slo_response_service_url => single_logout_response_service_url(slo_binding),
207
242
  :idp_attribute_names => attribute_names,
208
243
  :idp_cert => nil,
209
244
  :idp_cert_fingerprint => nil,
210
245
  :idp_cert_multi => nil,
211
- :valid_until => valid_until
246
+ :valid_until => valid_until,
247
+ :cache_duration => cache_duration,
212
248
  }.tap do |response_hash|
213
249
  merge_certificates_into(response_hash) unless certificates.nil?
214
250
  end
215
251
  end
216
252
 
217
- # @return [String|nil] IdP Name ID Format value if exists
218
- #
219
- def idp_name_id_format
220
- node = REXML::XPath.first(
221
- @idpsso_descriptor,
222
- "md:NameIDFormat",
223
- SamlMetadata::NAMESPACE
224
- )
225
- Utils.element_text(node)
226
- end
227
-
228
253
  # @return [String|nil] 'validUntil' attribute of metadata
229
254
  #
230
255
  def valid_until
@@ -232,7 +257,26 @@ module OneLogin
232
257
  root.attributes['validUntil'] if root && root.attributes
233
258
  end
234
259
 
235
- # @param binding_priority [Array]
260
+ # @return [String|nil] 'cacheDuration' attribute of metadata
261
+ #
262
+ def cache_duration
263
+ root = @idpsso_descriptor.root
264
+ root.attributes['cacheDuration'] if root && root.attributes
265
+ end
266
+
267
+ # @param name_id_priority [String|Array<String>] The prioritized list of NameIDFormat values to select. Will select first value if nil.
268
+ # @return [String|nil] IdP NameIDFormat value if exists
269
+ #
270
+ def idp_name_id_format(name_id_priority = nil)
271
+ nodes = REXML::XPath.match(
272
+ @idpsso_descriptor,
273
+ "md:NameIDFormat",
274
+ SamlMetadata::NAMESPACE
275
+ )
276
+ first_ranked_text(nodes, name_id_priority)
277
+ end
278
+
279
+ # @param binding_priority [String|Array<String>] The prioritized list of Binding values to select. Will select first value if nil.
236
280
  # @return [String|nil] SingleSignOnService binding if exists
237
281
  #
238
282
  def single_signon_service_binding(binding_priority = nil)
@@ -241,19 +285,26 @@ module OneLogin
241
285
  "md:SingleSignOnService/@Binding",
242
286
  SamlMetadata::NAMESPACE
243
287
  )
244
- if binding_priority
245
- values = nodes.map(&:value)
246
- binding_priority.detect{ |binding| values.include? binding }
247
- else
248
- nodes.first.value if nodes.any?
249
- end
288
+ first_ranked_value(nodes, binding_priority)
289
+ end
290
+
291
+ # @param binding_priority [String|Array<String>] The prioritized list of Binding values to select. Will select first value if nil.
292
+ # @return [String|nil] SingleLogoutService binding if exists
293
+ #
294
+ def single_logout_service_binding(binding_priority = nil)
295
+ nodes = REXML::XPath.match(
296
+ @idpsso_descriptor,
297
+ "md:SingleLogoutService/@Binding",
298
+ SamlMetadata::NAMESPACE
299
+ )
300
+ first_ranked_value(nodes, binding_priority)
250
301
  end
251
302
 
252
- # @param options [Hash]
303
+ # @param binding_priority [String|Array<String>] The prioritized list of Binding values to select. Will select first value if nil.
253
304
  # @return [String|nil] SingleSignOnService endpoint if exists
254
305
  #
255
- def single_signon_service_url(options = {})
256
- binding = single_signon_service_binding(options[:sso_binding])
306
+ def single_signon_service_url(binding_priority = nil)
307
+ binding = single_signon_service_binding(binding_priority)
257
308
  return if binding.nil?
258
309
 
259
310
  node = REXML::XPath.first(
@@ -261,39 +312,37 @@ module OneLogin
261
312
  "md:SingleSignOnService[@Binding=\"#{binding}\"]/@Location",
262
313
  SamlMetadata::NAMESPACE
263
314
  )
264
- return node.value if node
315
+ node.value if node
265
316
  end
266
317
 
267
- # @param binding_priority [Array]
268
- # @return [String|nil] SingleLogoutService binding if exists
318
+ # @param binding_priority [String|Array<String>] The prioritized list of Binding values to select. Will select first value if nil.
319
+ # @return [String|nil] SingleLogoutService endpoint if exists
269
320
  #
270
- def single_logout_service_binding(binding_priority = nil)
271
- nodes = REXML::XPath.match(
321
+ def single_logout_service_url(binding_priority = nil)
322
+ binding = single_logout_service_binding(binding_priority)
323
+ return if binding.nil?
324
+
325
+ node = REXML::XPath.first(
272
326
  @idpsso_descriptor,
273
- "md:SingleLogoutService/@Binding",
327
+ "md:SingleLogoutService[@Binding=\"#{binding}\"]/@Location",
274
328
  SamlMetadata::NAMESPACE
275
329
  )
276
- if binding_priority
277
- values = nodes.map(&:value)
278
- binding_priority.detect{ |binding| values.include? binding }
279
- else
280
- nodes.first.value if nodes.any?
281
- end
330
+ node.value if node
282
331
  end
283
332
 
284
- # @param options [Hash]
285
- # @return [String|nil] SingleLogoutService endpoint if exists
333
+ # @param binding_priority [String|Array<String>] The prioritized list of Binding values to select. Will select first value if nil.
334
+ # @return [String|nil] SingleLogoutService response url if exists
286
335
  #
287
- def single_logout_service_url(options = {})
288
- binding = single_logout_service_binding(options[:slo_binding])
336
+ def single_logout_response_service_url(binding_priority = nil)
337
+ binding = single_logout_service_binding(binding_priority)
289
338
  return if binding.nil?
290
339
 
291
340
  node = REXML::XPath.first(
292
341
  @idpsso_descriptor,
293
- "md:SingleLogoutService[@Binding=\"#{binding}\"]/@Location",
342
+ "md:SingleLogoutService[@Binding=\"#{binding}\"]/@ResponseLocation",
294
343
  SamlMetadata::NAMESPACE
295
344
  )
296
- return node.value if node
345
+ node.value if node
297
346
  end
298
347
 
299
348
  # @return [String|nil] Unformatted Certificate if exists
@@ -375,15 +424,41 @@ module OneLogin
375
424
  parsed_metadata[:idp_cert_fingerprint_algorithm]
376
425
  )
377
426
  end
378
- else
379
- # symbolize keys of certificates and pass it on
380
- parsed_metadata[:idp_cert_multi] = Hash[certificates.map { |k, v| [k.to_sym, v] }]
381
427
  end
428
+
429
+ # symbolize keys of certificates and pass it on
430
+ parsed_metadata[:idp_cert_multi] = Hash[certificates.map { |k, v| [k.to_sym, v] }]
382
431
  end
383
432
 
384
433
  def certificates_has_one(key)
385
434
  certificates.key?(key) && certificates[key].size == 1
386
435
  end
436
+
437
+ private
438
+
439
+ def first_ranked_text(nodes, priority = nil)
440
+ return unless nodes.any?
441
+
442
+ priority = Array(priority)
443
+ if priority.any?
444
+ values = nodes.map(&:text)
445
+ Array(priority).detect { |candidate| values.include?(candidate) }
446
+ else
447
+ nodes.first.text
448
+ end
449
+ end
450
+
451
+ def first_ranked_value(nodes, priority = nil)
452
+ return unless nodes.any?
453
+
454
+ priority = Array(priority)
455
+ if priority.any?
456
+ values = nodes.map(&:value)
457
+ priority.detect { |candidate| values.include?(candidate) }
458
+ else
459
+ nodes.first.value
460
+ end
461
+ end
387
462
  end
388
463
 
389
464
  def merge_parsed_metadata_into(settings, parsed_metadata)
@@ -393,10 +468,6 @@ module OneLogin
393
468
 
394
469
  settings
395
470
  end
396
-
397
- if self.respond_to?(:private_constant)
398
- private_constant :SamlMetadata, :IdpMetadata
399
- end
400
471
  end
401
472
  end
402
473
  end