ruby-saml 1.12.4 → 1.18.1

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.
data/UPGRADING.md ADDED
@@ -0,0 +1,158 @@
1
+ # Ruby SAML Migration Guide
2
+
3
+ ## Updating from 1.17.x to 1.18.0
4
+
5
+ Version `1.18.0` changes the way the toolkit validates SAML signatures. There is a new order
6
+ how validation happens in the toolkit and also the toolkit by default will check malformed doc
7
+ when parsing a SAML Message (`settings.check_malformed_doc`).
8
+
9
+ The SignedDocument class defined at xml_security.rb experienced several changes.
10
+ We don't expect compatibilty issues if you use the main methods offered by ruby-saml, but if you use a fork or customized usage, is possible that you need to adapt your code.
11
+
12
+ ## Updating from 1.12.x to 1.13.0
13
+
14
+ Version `1.13.0` adds `settings.idp_sso_service_binding` and `settings.idp_slo_service_binding`, and
15
+ deprecates `settings.security[:embed_sign]`. If specified, new binding parameters will be used in place of `:embed_sign`
16
+ to determine how to handle SAML message signing (`HTTP-POST` embeds signature and `HTTP-Redirect` does not.)
17
+
18
+ In addition, the `IdpMetadataParser#parse`, `#parse_to_hash` and `#parse_to_array` methods now retrieve
19
+ `idp_sso_service_binding` and `idp_slo_service_binding`.
20
+
21
+ Lastly, for convenience you may now use the Symbol aliases `:post` and `:redirect` for any `settings.*_binding` parameter.
22
+
23
+ ## Upgrading from 1.11.x to 1.12.0
24
+
25
+ Version `1.12.0` adds support for gcm algorithm and
26
+ change/adds specific error messages for signature validations
27
+
28
+ `idp_sso_target_url` and `idp_slo_target_url` attributes of the Settings class deprecated
29
+ in favor of `idp_sso_service_url` and `idp_slo_service_url`. The `IdpMetadataParser#parse`,
30
+ `#parse_to_hash` and `#parse_to_array` methods now retrieve SSO URL and SLO URL endpoints with
31
+ `idp_sso_service_url` and `idp_slo_service_url` (previously `idp_sso_target_url` and
32
+ `idp_slo_target_url` respectively).
33
+
34
+ ## Upgrading from 1.10.x to 1.11.0
35
+
36
+ Version `1.11.0` deprecates the use of `settings.issuer` in favour of `settings.sp_entity_id`.
37
+ There are two new security settings: `settings.security[:check_idp_cert_expiration]` and
38
+ `settings.security[:check_sp_cert_expiration]` (both false by default) that check if the
39
+ IdP or SP X.509 certificate has expired, respectively.
40
+
41
+ Version `1.10.2` includes the `valid_until` attribute in parsed IdP metadata.
42
+
43
+ Version `1.10.1` improves Ruby 1.8.7 support.
44
+
45
+ ## Upgrading from 1.9.0 to 1.10.0
46
+
47
+ Version `1.10.0` improves IdpMetadataParser to allow parse multiple IDPSSODescriptor,
48
+ Add Subject support on AuthNRequest to allow SPs provide info to the IdP about the user
49
+ to be authenticated and updates the format_cert method to accept certs with /\x0d/
50
+
51
+ ## Upgrading from 1.8.0 to 1.9.0
52
+
53
+ Version `1.9.0` better supports Ruby 2.4+ and JRuby 9.2.0.0. `Settings` initialization
54
+ now has a second parameter, `keep_security_settings` (default: false), which saves security
55
+ settings attributes that are not explicitly overridden, if set to true.
56
+
57
+ ## Upgrading from 1.7.x to 1.8.0
58
+
59
+ On Version `1.8.0`, creating AuthRequests/LogoutRequests/LogoutResponses with nil RelayState
60
+ param will not generate a URL with an empty RelayState parameter anymore. It also changes
61
+ the invalid audience error message.
62
+
63
+ ## Upgrading from 1.6.0 to 1.7.0
64
+
65
+ Version `1.7.0` is a recommended update for all Ruby SAML users as it includes a fix for
66
+ the [CVE-2017-11428](https://www.cvedetails.com/cve/CVE-2017-11428/) vulnerability.
67
+
68
+ ## Upgrading from 1.5.0 to 1.6.0
69
+
70
+ Version `1.6.0` changes the preferred way to construct instances of `Logoutresponse` and
71
+ `SloLogoutrequest`. Previously the _SAMLResponse_, _RelayState_, and _SigAlg_ parameters
72
+ of these message types were provided via the constructor's `options[:get_params]` parameter.
73
+ Unfortunately this can result in incompatibility with other SAML implementations; signatures
74
+ are specified to be computed based on the _sender's_ URI-encoding of the message, which can
75
+ differ from that of Ruby SAML. In particular, Ruby SAML's URI-encoding does not match that
76
+ of Microsoft ADFS, so messages from ADFS can fail signature validation.
77
+
78
+ The new preferred way to provide _SAMLResponse_, _RelayState_, and _SigAlg_ is via the
79
+ `options[:raw_get_params]` parameter. For example:
80
+
81
+ ```ruby
82
+ # In this example `query_params` is assumed to contain decoded query parameters,
83
+ # and `raw_query_params` is assumed to contain encoded query parameters as sent by the IDP.
84
+ settings = {
85
+ settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA1
86
+ settings.soft = false
87
+ }
88
+ options = {
89
+ get_params: {
90
+ "Signature" => query_params["Signature"],
91
+ },
92
+ raw_get_params: {
93
+ "SAMLRequest" => raw_query_params["SAMLRequest"],
94
+ "SigAlg" => raw_query_params["SigAlg"],
95
+ "RelayState" => raw_query_params["RelayState"],
96
+ },
97
+ }
98
+ slo_logout_request = OneLogin::RubySaml::SloLogoutrequest.new(query_params["SAMLRequest"], settings, options)
99
+ raise "Invalid Logout Request" unless slo_logout_request.is_valid?
100
+ ```
101
+
102
+ The old form is still supported for backward compatibility, but all Ruby SAML users
103
+ should prefer `options[:raw_get_params]` where possible to ensure compatibility with
104
+ other SAML implementations.
105
+
106
+ ## Upgrading from 1.4.2 to 1.4.3
107
+
108
+ Version `1.4.3` introduces Recipient validation of SubjectConfirmation elements.
109
+ The 'Recipient' value is compared with the settings.assertion_consumer_service_url
110
+ value.
111
+
112
+ If you want to skip that validation, add the :skip_recipient_check option to the
113
+ initialize method of the Response object.
114
+
115
+ Parsing metadata that contains more than one certificate will propagate the
116
+ idp_cert_multi property rather than idp_cert. See [signature validation
117
+ section](#signature-validation) for details.
118
+
119
+ ## Upgrading from 1.3.x to 1.4.x
120
+
121
+ Version `1.4.0` is a recommended update for all Ruby SAML users as it includes security improvements.
122
+
123
+ ## Upgrading from 1.2.x to 1.3.x
124
+
125
+ Version `1.3.0` is a recommended update for all Ruby SAML users as it includes security fixes.
126
+ It adds security improvements in order to prevent Signature wrapping attacks.
127
+ [CVE-2016-5697](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2016-5697)
128
+
129
+ ## Upgrading from 1.1.x to 1.2.x
130
+
131
+ Version `1.2` adds IDP metadata parsing improvements, uuid deprecation in favour of SecureRandom,
132
+ refactor error handling and some minor improvements.
133
+
134
+ There is no compatibility issue detected.
135
+
136
+ For more details, please review [CHANGELOG.md](CHANGELOG.md).
137
+
138
+ ## Upgrading from 1.0.x to 1.1.x
139
+
140
+ Version `1.1` adds some improvements on signature validation and solves some namespace conflicts.
141
+
142
+ ## Upgrading from 0.9.x to 1.0.x
143
+
144
+ Version `1.0` is a recommended update for all Ruby SAML users as it includes security fixes.
145
+
146
+ Version `1.0` adds security improvements like entity expansion limitation, more SAML message validations, and other important improvements like decrypt support.
147
+
148
+ ### Important Changes
149
+
150
+ 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.
151
+
152
+ ## Upgrading from 0.8.x to 0.9.x
153
+
154
+ Version `0.9` adds many new features and improvements.
155
+
156
+ ## Upgrading from 0.7.x to 0.8.x
157
+
158
+ Version `0.8.x` changes the namespace of the gem from `OneLogin::Saml` to `OneLogin::RubySaml`. Please update your implementations of the gem accordingly.
@@ -15,7 +15,7 @@ module OneLogin
15
15
  class Authrequest < SamlMessage
16
16
 
17
17
  # AuthNRequest ID
18
- attr_reader :uuid
18
+ attr_accessor :uuid
19
19
 
20
20
  # Initializes the AuthNRequest. An Authrequest Object that is an extension of the SamlMessage class.
21
21
  # Asigns an ID, a random uuid.
@@ -39,7 +39,7 @@ module OneLogin
39
39
  saml_request = CGI.escape(params.delete("SAMLRequest"))
40
40
  request_params = "#{params_prefix}SAMLRequest=#{saml_request}"
41
41
  params.each_pair do |key, value|
42
- request_params << "&#{key.to_s}=#{CGI.escape(value.to_s)}"
42
+ request_params << "&#{key}=#{CGI.escape(value.to_s)}"
43
43
  end
44
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
45
  @login_url = settings.idp_sso_service_url + request_params
@@ -64,7 +64,7 @@ module OneLogin
64
64
  request_doc = create_authentication_xml_doc(settings)
65
65
  request_doc.context[:attribute_quote] = :quote if settings.double_quote_xml_attribute_values
66
66
 
67
- request = ""
67
+ request = "".dup
68
68
  request_doc.write(request)
69
69
 
70
70
  Logging.debug "Created AuthnRequest: #{request}"
@@ -72,9 +72,10 @@ module OneLogin
72
72
  request = deflate(request) if settings.compress_request
73
73
  base64_request = encode(request)
74
74
  request_params = {"SAMLRequest" => base64_request}
75
+ sp_signing_key = settings.get_sp_signing_key
75
76
 
76
- if settings.security[:authn_requests_signed] && !settings.security[:embed_sign] && settings.private_key
77
- params['SigAlg'] = settings.security[:signature_method]
77
+ if settings.idp_sso_service_binding == Utils::BINDINGS[:redirect] && settings.security[:authn_requests_signed] && sp_signing_key
78
+ params['SigAlg'] = settings.security[:signature_method]
78
79
  url_string = OneLogin::RubySaml::Utils.build_query(
79
80
  :type => 'SAMLRequest',
80
81
  :data => base64_request,
@@ -82,7 +83,7 @@ module OneLogin
82
83
  :sig_alg => params['SigAlg']
83
84
  )
84
85
  sign_algorithm = XMLSecurity::BaseDocument.new.algorithm(settings.security[:signature_method])
85
- signature = settings.get_sp_key.sign(sign_algorithm.new, url_string)
86
+ signature = sp_signing_key.sign(sign_algorithm.new, url_string)
86
87
  params['Signature'] = encode(signature)
87
88
  end
88
89
 
@@ -179,16 +180,13 @@ module OneLogin
179
180
  end
180
181
 
181
182
  def sign_document(document, settings)
182
- # embed signature
183
- if settings.security[:authn_requests_signed] && settings.private_key && settings.certificate && settings.security[:embed_sign]
184
- private_key = settings.get_sp_key
185
- cert = settings.get_sp_cert
183
+ cert, private_key = settings.get_sp_signing_pair
184
+ if settings.idp_sso_service_binding == Utils::BINDINGS[:post] && settings.security[:authn_requests_signed] && private_key && cert
186
185
  document.sign_document(private_key, cert, settings.security[:signature_method], settings.security[:digest_method])
187
186
  end
188
187
 
189
188
  document
190
189
  end
191
-
192
190
  end
193
191
  end
194
192
  end
@@ -11,6 +11,10 @@ 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
@@ -43,7 +47,7 @@ module OneLogin
43
47
  SamlMetadata::NAMESPACE
44
48
  )
45
49
  end
46
-
50
+
47
51
  # Parse the Identity Provider metadata and update the settings with the
48
52
  # IdP values
49
53
  #
@@ -52,9 +56,10 @@ module OneLogin
52
56
  #
53
57
  # @param options [Hash] options used for parsing the metadata and the returned Settings instance
54
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.
55
- # @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.
56
- # @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.
57
- # @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.
58
63
  #
59
64
  # @return [OneLogin::RubySaml::Settings]
60
65
  #
@@ -70,9 +75,10 @@ module OneLogin
70
75
  # @param validate_cert [Boolean] If true and the URL is HTTPs, the cert of the domain is checked.
71
76
  #
72
77
  # @param options [Hash] options used for parsing the metadata
73
- # @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.
74
- # @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.
75
- # @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.
76
82
  #
77
83
  # @return [Hash]
78
84
  #
@@ -87,9 +93,10 @@ module OneLogin
87
93
  # @param validate_cert [Boolean] If true and the URL is HTTPs, the cert of the domain is checked.
88
94
  #
89
95
  # @param options [Hash] options used for parsing the metadata
90
- # @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.
91
- # @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.
92
- # @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.
93
100
  #
94
101
  # @return [Array<Hash>]
95
102
  #
@@ -105,9 +112,10 @@ module OneLogin
105
112
  #
106
113
  # @param options [Hash] :settings to provide the OneLogin::RubySaml::Settings object or an hash for Settings overrides
107
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.
108
- # @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.
109
- # @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.
110
- # @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.
111
119
  #
112
120
  # @return [OneLogin::RubySaml::Settings]
113
121
  def parse(idp_metadata, options = {})
@@ -115,8 +123,10 @@ module OneLogin
115
123
 
116
124
  unless parsed_metadata[:cache_duration].nil?
117
125
  cache_valid_until_timestamp = OneLogin::RubySaml::Utils.parse_duration(parsed_metadata[:cache_duration])
118
- if parsed_metadata[:valid_until].nil? || cache_valid_until_timestamp < Time.parse(parsed_metadata[:valid_until], Time.now.utc).to_i
119
- parsed_metadata[:valid_until] = Time.at(cache_valid_until_timestamp).utc.strftime("%Y-%m-%dT%H:%M:%SZ")
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
120
130
  end
121
131
  end
122
132
  # Remove the cache_duration because on the settings
@@ -139,9 +149,10 @@ module OneLogin
139
149
  # @param idp_metadata [String]
140
150
  #
141
151
  # @param options [Hash] options used for parsing the metadata and the returned Settings instance
142
- # @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.
143
- # @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.
144
- # @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.
145
156
  #
146
157
  # @return [Hash]
147
158
  def parse_to_hash(idp_metadata, options = {})
@@ -153,13 +164,14 @@ module OneLogin
153
164
  # @param idp_metadata [String]
154
165
  #
155
166
  # @param options [Hash] options used for parsing the metadata and the returned Settings instance
156
- # @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.
157
- # @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.
158
- # @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.
159
171
  #
160
172
  # @return [Array<Hash>]
161
173
  def parse_to_array(idp_metadata, options = {})
162
- parse_to_idp_metadata_array(idp_metadata, options).map{|idp_md| idp_md.to_hash(options)}
174
+ parse_to_idp_metadata_array(idp_metadata, options).map { |idp_md| idp_md.to_hash(options) }
163
175
  end
164
176
 
165
177
  def parse_to_idp_metadata_array(idp_metadata, options = {})
@@ -171,10 +183,8 @@ module OneLogin
171
183
  raise ArgumentError.new("idp_metadata must contain an IDPSSODescriptor element")
172
184
  end
173
185
 
174
- return idpsso_descriptors.map{|id| IdpMetadata.new(id, id.parent.attributes["entityID"])}
186
+ idpsso_descriptors.map {|id| IdpMetadata.new(id, id.parent.attributes["entityID"])}
175
187
  end
176
-
177
- private
178
188
 
179
189
  # Retrieve the remote IdP metadata from the URL or a cached copy.
180
190
  # @param url [String] Url where the XML of the Identity Provider Metadata is published.
@@ -208,21 +218,27 @@ module OneLogin
208
218
  )
209
219
  end
210
220
 
221
+ private
222
+
211
223
  class IdpMetadata
212
224
  attr_reader :idpsso_descriptor, :entity_id
213
-
225
+
214
226
  def initialize(idpsso_descriptor, entity_id)
215
227
  @idpsso_descriptor = idpsso_descriptor
216
228
  @entity_id = entity_id
217
229
  end
218
230
 
219
231
  def to_hash(options = {})
232
+ sso_binding = options[:sso_binding]
233
+ slo_binding = options[:slo_binding]
220
234
  {
221
235
  :idp_entity_id => @entity_id,
222
- :name_identifier_format => idp_name_id_format,
223
- :idp_sso_service_url => single_signon_service_url(options),
224
- :idp_slo_service_url => single_logout_service_url(options),
225
- :idp_slo_response_service_url => single_logout_response_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),
226
242
  :idp_attribute_names => attribute_names,
227
243
  :idp_cert => nil,
228
244
  :idp_cert_fingerprint => nil,
@@ -234,17 +250,6 @@ module OneLogin
234
250
  end
235
251
  end
236
252
 
237
- # @return [String|nil] IdP Name ID Format value if exists
238
- #
239
- def idp_name_id_format
240
- node = REXML::XPath.first(
241
- @idpsso_descriptor,
242
- "md:NameIDFormat",
243
- SamlMetadata::NAMESPACE
244
- )
245
- Utils.element_text(node)
246
- end
247
-
248
253
  # @return [String|nil] 'validUntil' attribute of metadata
249
254
  #
250
255
  def valid_until
@@ -259,39 +264,31 @@ module OneLogin
259
264
  root.attributes['cacheDuration'] if root && root.attributes
260
265
  end
261
266
 
262
- # @param binding_priority [Array]
263
- # @return [String|nil] SingleSignOnService binding if exists
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
264
269
  #
265
- def single_signon_service_binding(binding_priority = nil)
270
+ def idp_name_id_format(name_id_priority = nil)
266
271
  nodes = REXML::XPath.match(
267
272
  @idpsso_descriptor,
268
- "md:SingleSignOnService/@Binding",
273
+ "md:NameIDFormat",
269
274
  SamlMetadata::NAMESPACE
270
275
  )
271
- if binding_priority
272
- values = nodes.map(&:value)
273
- binding_priority.detect{ |binding| values.include? binding }
274
- else
275
- nodes.first.value if nodes.any?
276
- end
276
+ first_ranked_text(nodes, name_id_priority)
277
277
  end
278
278
 
279
- # @param options [Hash]
280
- # @return [String|nil] SingleSignOnService endpoint if exists
279
+ # @param binding_priority [String|Array<String>] The prioritized list of Binding values to select. Will select first value if nil.
280
+ # @return [String|nil] SingleSignOnService binding if exists
281
281
  #
282
- def single_signon_service_url(options = {})
283
- binding = single_signon_service_binding(options[:sso_binding])
284
- return if binding.nil?
285
-
286
- node = REXML::XPath.first(
282
+ def single_signon_service_binding(binding_priority = nil)
283
+ nodes = REXML::XPath.match(
287
284
  @idpsso_descriptor,
288
- "md:SingleSignOnService[@Binding=\"#{binding}\"]/@Location",
285
+ "md:SingleSignOnService/@Binding",
289
286
  SamlMetadata::NAMESPACE
290
287
  )
291
- return node.value if node
288
+ first_ranked_value(nodes, binding_priority)
292
289
  end
293
290
 
294
- # @param binding_priority [Array]
291
+ # @param binding_priority [String|Array<String>] The prioritized list of Binding values to select. Will select first value if nil.
295
292
  # @return [String|nil] SingleLogoutService binding if exists
296
293
  #
297
294
  def single_logout_service_binding(binding_priority = nil)
@@ -300,19 +297,29 @@ module OneLogin
300
297
  "md:SingleLogoutService/@Binding",
301
298
  SamlMetadata::NAMESPACE
302
299
  )
303
- if binding_priority
304
- values = nodes.map(&:value)
305
- binding_priority.detect{ |binding| values.include? binding }
306
- else
307
- nodes.first.value if nodes.any?
308
- end
300
+ first_ranked_value(nodes, binding_priority)
301
+ end
302
+
303
+ # @param binding_priority [String|Array<String>] The prioritized list of Binding values to select. Will select first value if nil.
304
+ # @return [String|nil] SingleSignOnService endpoint if exists
305
+ #
306
+ def single_signon_service_url(binding_priority = nil)
307
+ binding = single_signon_service_binding(binding_priority)
308
+ return if binding.nil?
309
+
310
+ node = REXML::XPath.first(
311
+ @idpsso_descriptor,
312
+ "md:SingleSignOnService[@Binding=\"#{binding}\"]/@Location",
313
+ SamlMetadata::NAMESPACE
314
+ )
315
+ node.value if node
309
316
  end
310
317
 
311
- # @param options [Hash]
318
+ # @param binding_priority [String|Array<String>] The prioritized list of Binding values to select. Will select first value if nil.
312
319
  # @return [String|nil] SingleLogoutService endpoint if exists
313
320
  #
314
- def single_logout_service_url(options = {})
315
- binding = single_logout_service_binding(options[:slo_binding])
321
+ def single_logout_service_url(binding_priority = nil)
322
+ binding = single_logout_service_binding(binding_priority)
316
323
  return if binding.nil?
317
324
 
318
325
  node = REXML::XPath.first(
@@ -320,14 +327,14 @@ module OneLogin
320
327
  "md:SingleLogoutService[@Binding=\"#{binding}\"]/@Location",
321
328
  SamlMetadata::NAMESPACE
322
329
  )
323
- return node.value if node
330
+ node.value if node
324
331
  end
325
332
 
326
- # @param options [Hash]
333
+ # @param binding_priority [String|Array<String>] The prioritized list of Binding values to select. Will select first value if nil.
327
334
  # @return [String|nil] SingleLogoutService response url if exists
328
335
  #
329
- def single_logout_response_service_url(options = {})
330
- 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)
331
338
  return if binding.nil?
332
339
 
333
340
  node = REXML::XPath.first(
@@ -335,7 +342,7 @@ module OneLogin
335
342
  "md:SingleLogoutService[@Binding=\"#{binding}\"]/@ResponseLocation",
336
343
  SamlMetadata::NAMESPACE
337
344
  )
338
- return node.value if node
345
+ node.value if node
339
346
  end
340
347
 
341
348
  # @return [String|nil] Unformatted Certificate if exists
@@ -377,14 +384,12 @@ module OneLogin
377
384
  # @return [String|nil] the fingerpint of the X509Certificate if it exists
378
385
  #
379
386
  def fingerprint(certificate, fingerprint_algorithm = XMLSecurity::Document::SHA1)
380
- @fingerprint ||= begin
381
- return unless certificate
387
+ return unless certificate
382
388
 
383
- cert = OpenSSL::X509::Certificate.new(Base64.decode64(certificate))
389
+ cert = OpenSSL::X509::Certificate.new(Base64.decode64(certificate))
384
390
 
385
- fingerprint_alg = XMLSecurity::BaseDocument.new.algorithm(fingerprint_algorithm).new
386
- fingerprint_alg.hexdigest(cert.to_der).upcase.scan(/../).join(":")
387
- end
391
+ fingerprint_alg = XMLSecurity::BaseDocument.new.algorithm(fingerprint_algorithm).new
392
+ fingerprint_alg.hexdigest(cert.to_der).upcase.scan(/../).join(":")
388
393
  end
389
394
 
390
395
  # @return [Array] the names of all SAML attributes if any exist
@@ -417,15 +422,41 @@ module OneLogin
417
422
  parsed_metadata[:idp_cert_fingerprint_algorithm]
418
423
  )
419
424
  end
420
- else
421
- # symbolize keys of certificates and pass it on
422
- parsed_metadata[:idp_cert_multi] = Hash[certificates.map { |k, v| [k.to_sym, v] }]
423
425
  end
426
+
427
+ # symbolize keys of certificates and pass it on
428
+ parsed_metadata[:idp_cert_multi] = Hash[certificates.map { |k, v| [k.to_sym, v] }]
424
429
  end
425
430
 
426
431
  def certificates_has_one(key)
427
432
  certificates.key?(key) && certificates[key].size == 1
428
433
  end
434
+
435
+ private
436
+
437
+ def first_ranked_text(nodes, priority = nil)
438
+ return unless nodes.any?
439
+
440
+ priority = Array(priority)
441
+ if priority.any?
442
+ values = nodes.map(&:text)
443
+ priority.detect { |candidate| values.include?(candidate) }
444
+ else
445
+ nodes.first.text
446
+ end
447
+ end
448
+
449
+ def first_ranked_value(nodes, priority = nil)
450
+ return unless nodes.any?
451
+
452
+ priority = Array(priority)
453
+ if priority.any?
454
+ values = nodes.map(&:value)
455
+ priority.detect { |candidate| values.include?(candidate) }
456
+ else
457
+ nodes.first.value
458
+ end
459
+ end
429
460
  end
430
461
 
431
462
  def merge_parsed_metadata_into(settings, parsed_metadata)
@@ -12,7 +12,7 @@ module OneLogin
12
12
  class Logoutrequest < SamlMessage
13
13
 
14
14
  # Logout Request ID
15
- attr_reader :uuid
15
+ attr_accessor :uuid
16
16
 
17
17
  # Initializes the Logout Request. A Logoutrequest Object that is an extension of the SamlMessage class.
18
18
  # Asigns an ID, a random uuid.
@@ -36,7 +36,7 @@ module OneLogin
36
36
  saml_request = CGI.escape(params.delete("SAMLRequest"))
37
37
  request_params = "#{params_prefix}SAMLRequest=#{saml_request}"
38
38
  params.each_pair do |key, value|
39
- request_params << "&#{key.to_s}=#{CGI.escape(value.to_s)}"
39
+ request_params << "&#{key}=#{CGI.escape(value.to_s)}"
40
40
  end
41
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
42
  @logout_url = settings.idp_slo_service_url + request_params
@@ -61,7 +61,7 @@ module OneLogin
61
61
  request_doc = create_logout_request_xml_doc(settings)
62
62
  request_doc.context[:attribute_quote] = :quote if settings.double_quote_xml_attribute_values
63
63
 
64
- request = ""
64
+ request = "".dup
65
65
  request_doc.write(request)
66
66
 
67
67
  Logging.debug "Created SLO Logout Request: #{request}"
@@ -69,9 +69,10 @@ module OneLogin
69
69
  request = deflate(request) if settings.compress_request
70
70
  base64_request = encode(request)
71
71
  request_params = {"SAMLRequest" => base64_request}
72
+ sp_signing_key = settings.get_sp_signing_key
72
73
 
73
- if settings.security[:logout_requests_signed] && !settings.security[:embed_sign] && settings.private_key
74
- params['SigAlg'] = settings.security[:signature_method]
74
+ if settings.idp_slo_service_binding == Utils::BINDINGS[:redirect] && settings.security[:logout_requests_signed] && sp_signing_key
75
+ params['SigAlg'] = settings.security[:signature_method]
75
76
  url_string = OneLogin::RubySaml::Utils.build_query(
76
77
  :type => 'SAMLRequest',
77
78
  :data => base64_request,
@@ -79,7 +80,7 @@ module OneLogin
79
80
  :sig_alg => params['SigAlg']
80
81
  )
81
82
  sign_algorithm = XMLSecurity::BaseDocument.new.algorithm(settings.security[:signature_method])
82
- signature = settings.get_sp_key.sign(sign_algorithm.new, url_string)
83
+ signature = settings.get_sp_signing_key.sign(sign_algorithm.new, url_string)
83
84
  params['Signature'] = encode(signature)
84
85
  end
85
86
 
@@ -138,9 +139,8 @@ module OneLogin
138
139
 
139
140
  def sign_document(document, settings)
140
141
  # embed signature
141
- if settings.security[:logout_requests_signed] && settings.private_key && settings.certificate && settings.security[:embed_sign]
142
- private_key = settings.get_sp_key
143
- cert = settings.get_sp_cert
142
+ cert, private_key = settings.get_sp_signing_pair
143
+ if settings.idp_slo_service_binding == Utils::BINDINGS[:post] && settings.security[:logout_requests_signed] && private_key && cert
144
144
  document.sign_document(private_key, cert, settings.security[:signature_method], settings.security[:digest_method])
145
145
  end
146
146