ruby-saml 1.11.0 → 1.14.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 +5 -5
  2. data/.github/workflows/test.yml +25 -0
  3. data/{changelog.md → CHANGELOG.md} +49 -1
  4. data/README.md +363 -218
  5. data/UPGRADING.md +149 -0
  6. data/lib/onelogin/ruby-saml/attributes.rb +24 -1
  7. data/lib/onelogin/ruby-saml/authrequest.rb +12 -8
  8. data/lib/onelogin/ruby-saml/idp_metadata_parser.rb +154 -83
  9. data/lib/onelogin/ruby-saml/logoutrequest.rb +13 -7
  10. data/lib/onelogin/ruby-saml/logoutresponse.rb +6 -2
  11. data/lib/onelogin/ruby-saml/metadata.rb +62 -17
  12. data/lib/onelogin/ruby-saml/response.rb +57 -32
  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 +92 -50
  16. data/lib/onelogin/ruby-saml/slo_logoutrequest.rb +17 -30
  17. data/lib/onelogin/ruby-saml/slo_logoutresponse.rb +32 -18
  18. data/lib/onelogin/ruby-saml/utils.rb +83 -8
  19. data/lib/onelogin/ruby-saml/version.rb +1 -1
  20. data/lib/xml_security.rb +39 -13
  21. data/ruby-saml.gemspec +14 -5
  22. metadata +29 -288
  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
@@ -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
+ 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
@@ -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
@@ -11,7 +12,7 @@ module OneLogin
11
12
  class Logoutrequest < SamlMessage
12
13
 
13
14
  # Logout Request ID
14
- attr_reader :uuid
15
+ attr_accessor :uuid
15
16
 
16
17
  # Initializes the Logout Request. A Logoutrequest Object that is an extension of the SamlMessage class.
17
18
  # Asigns an ID, a random uuid.
@@ -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
@@ -208,7 +212,7 @@ module OneLogin
208
212
  return true unless options.has_key? :get_params
209
213
  return true unless options[:get_params].has_key? 'Signature'
210
214
 
211
- options[:raw_get_params] = OneLogin::RubySaml::Utils.prepare_raw_get_params(options[:raw_get_params], options[:get_params])
215
+ options[:raw_get_params] = OneLogin::RubySaml::Utils.prepare_raw_get_params(options[:raw_get_params], options[:get_params], settings.security[:lowercase_url_encoding])
212
216
 
213
217
  if options[:get_params]['SigAlg'].nil? && !options[:raw_get_params]['SigAlg'].nil?
214
218
  options[:get_params]['SigAlg'] = CGI.unescape(options[:raw_get_params]['SigAlg'])
@@ -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