ruby-saml 0.8.14 → 0.8.18

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.

Potentially problematic release.


This version of ruby-saml might be problematic. Click here for more details.

Files changed (41) hide show
  1. checksums.yaml +7 -0
  2. data/lib/onelogin/ruby-saml/authrequest.rb +5 -1
  3. data/lib/onelogin/ruby-saml/logoutrequest.rb +7 -2
  4. data/lib/onelogin/ruby-saml/logoutresponse.rb +19 -27
  5. data/lib/onelogin/ruby-saml/response.rb +104 -15
  6. data/lib/onelogin/ruby-saml/settings.rb +28 -10
  7. data/lib/onelogin/ruby-saml/slo_logoutrequest.rb +112 -0
  8. data/lib/onelogin/ruby-saml/slo_logoutresponse.rb +21 -13
  9. data/lib/onelogin/ruby-saml/utils.rb +27 -0
  10. data/lib/onelogin/ruby-saml/version.rb +1 -1
  11. data/lib/ruby-saml.rb +1 -0
  12. data/lib/xml_security.rb +5 -1
  13. data/test/certificates/certificate.der +0 -0
  14. data/test/certificates/formatted_certificate +14 -0
  15. data/test/certificates/formatted_chained_certificate +42 -0
  16. data/test/certificates/formatted_private_key +12 -0
  17. data/test/certificates/formatted_rsa_private_key +12 -0
  18. data/test/certificates/invalid_certificate1 +1 -0
  19. data/test/certificates/invalid_certificate2 +1 -0
  20. data/test/certificates/invalid_certificate3 +12 -0
  21. data/test/certificates/invalid_chained_certificate1 +1 -0
  22. data/test/certificates/invalid_private_key1 +1 -0
  23. data/test/certificates/invalid_private_key2 +1 -0
  24. data/test/certificates/invalid_private_key3 +10 -0
  25. data/test/certificates/invalid_rsa_private_key1 +1 -0
  26. data/test/certificates/invalid_rsa_private_key2 +1 -0
  27. data/test/certificates/invalid_rsa_private_key3 +10 -0
  28. data/test/logoutrequest_test.rb +11 -0
  29. data/test/logoutresponse_test.rb +10 -17
  30. data/test/request_test.rb +10 -0
  31. data/test/requests/logoutrequest_fixtures.rb +47 -0
  32. data/test/response_test.rb +60 -0
  33. data/test/responses/invalids/invalid_issuer_assertion.xml.base64 +1 -0
  34. data/test/responses/invalids/invalid_issuer_message.xml.base64 +1 -0
  35. data/test/responses/logoutresponse_fixtures.rb +7 -6
  36. data/test/settings_test.rb +106 -0
  37. data/test/slo_logoutrequest_test.rb +73 -0
  38. data/test/slo_logoutresponse_test.rb +19 -0
  39. data/test/utils_test.rb +191 -1
  40. data/test/xml_security_test.rb +5 -0
  41. metadata +57 -24
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: f7f02b4fb1490e44140c1e24ea61bf0ef061f0da
4
+ data.tar.gz: 3b2fec942140bb2fd2e49a17fc29dd0d0327d814
5
+ SHA512:
6
+ metadata.gz: a2dade6d6d672b213a3f8d3af088edfa208f3af4482aae033a40ff8ac9c500ca48fded7eee073e67dd6f37daa27786a6303777e0381a3338f987f87a63f460a2
7
+ data.tar.gz: 0cc6bb2264de5c688545bcb24794313758a97cca3c9bfa5c69b07331ffbc719acb1a69039ec39aa2e5e6817e8917e7495da7c07a41eff120d57f30e9708962bc
@@ -9,7 +9,7 @@ module OneLogin
9
9
 
10
10
  class Authrequest
11
11
  # AuthNRequest ID
12
- attr_reader :uuid
12
+ attr_accessor :uuid
13
13
 
14
14
  # Initializes the AuthNRequest. An Authrequest Object.
15
15
  # Asigns an ID, a random uuid.
@@ -18,6 +18,10 @@ module OneLogin
18
18
  @uuid = OneLogin::RubySaml::Utils.uuid
19
19
  end
20
20
 
21
+ def request_id
22
+ @uuid
23
+ end
24
+
21
25
  def create(settings, params = {})
22
26
  params = create_params(settings, params)
23
27
  params_prefix = (settings.idp_sso_target_url =~ /\?/) ? '&' : '?'
@@ -10,12 +10,16 @@ module OneLogin
10
10
 
11
11
  class Logoutrequest
12
12
 
13
- attr_reader :uuid # Can be obtained if neccessary
13
+ attr_accessor :uuid
14
14
 
15
15
  def initialize
16
16
  @uuid = OneLogin::RubySaml::Utils.uuid
17
17
  end
18
18
 
19
+ def request_id
20
+ @uuid
21
+ end
22
+
19
23
  def create(settings, params={})
20
24
  params = create_params(settings, params)
21
25
  params_prefix = (settings.idp_slo_target_url =~ /\?/) ? '&' : '?'
@@ -114,7 +118,8 @@ module OneLogin
114
118
 
115
119
  if settings.name_identifier_value
116
120
  name_id = root.add_element "saml:NameID", { "xmlns:saml" => "urn:oasis:names:tc:SAML:2.0:assertion" }
117
- name_id.attributes['NameQualifier'] = settings.sp_name_qualifier if settings.sp_name_qualifier
121
+ nameid.attributes['NameQualifier'] = settings.idp_name_qualifier if settings.idp_name_qualifier
122
+ nameid.attributes['SPNameQualifier'] = settings.sp_name_qualifier if settings.sp_name_qualifier
118
123
  name_id.attributes['Format'] = settings.name_identifier_format if settings.name_identifier_format
119
124
  name_id.text = settings.name_identifier_value
120
125
  end
@@ -29,7 +29,18 @@ module OneLogin
29
29
 
30
30
  @options = options
31
31
  @response = OneLogin::RubySaml::Utils.decode_raw_saml(response)
32
- @document = XMLSecurity::SignedDocument.new(response)
32
+ @document = XMLSecurity::SignedDocument.new(@response)
33
+ end
34
+
35
+ def response_id
36
+ @response_id ||= begin
37
+ node = REXML::XPath.first(
38
+ document,
39
+ "/p:LogoutResponse",
40
+ { "p" => PROTOCOL }
41
+ )
42
+ node.nil? ? nil : node.attributes['ID']
43
+ end
33
44
  end
34
45
 
35
46
  def validate!
@@ -37,7 +48,7 @@ module OneLogin
37
48
  end
38
49
 
39
50
  def validate(soft = true)
40
- return false unless valid_saml?(soft) && valid_state?(soft)
51
+ return false unless validate_structure(soft)
41
52
 
42
53
  valid_in_response_to?(soft) && valid_issuer?(soft) && success?(soft)
43
54
  end
@@ -51,7 +62,7 @@ module OneLogin
51
62
 
52
63
  def in_response_to
53
64
  @in_response_to ||= begin
54
- node = REXML::XPath.first(document, "/p:LogoutResponse", { "p" => PROTOCOL, "a" => ASSERTION })
65
+ node = REXML::XPath.first(document, "/p:LogoutResponse", { "p" => PROTOCOL })
55
66
  node.nil? ? nil : node.attributes['InResponseTo']
56
67
  end
57
68
  end
@@ -59,7 +70,6 @@ module OneLogin
59
70
  def issuer
60
71
  @issuer ||= begin
61
72
  node = REXML::XPath.first(document, "/p:LogoutResponse/a:Issuer", { "p" => PROTOCOL, "a" => ASSERTION })
62
- node ||= REXML::XPath.first(document, "/p:LogoutResponse/a:Assertion/a:Issuer", { "p" => PROTOCOL, "a" => ASSERTION })
63
73
  Utils.element_text(node)
64
74
  end
65
75
  end
@@ -73,7 +83,7 @@ module OneLogin
73
83
 
74
84
  private
75
85
 
76
- def valid_saml?(soft = true)
86
+ def validate_structure(soft = true)
77
87
  Dir.chdir(File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'schemas'))) do
78
88
  @schema = Nokogiri::XML::Schema(IO.read('saml20protocol_schema.xsd'))
79
89
  @xml = Nokogiri::XML(self.document.to_s)
@@ -85,26 +95,6 @@ module OneLogin
85
95
  end
86
96
  end
87
97
 
88
- def valid_state?(soft = true)
89
- if response.empty?
90
- return soft ? false : validation_error("Blank response")
91
- end
92
-
93
- if settings.nil?
94
- return soft ? false : validation_error("No settings on response")
95
- end
96
-
97
- if settings.sp_entity_id.nil?
98
- return soft ? false : validation_error("No sp_entity_id in settings")
99
- end
100
-
101
- if settings.idp_cert_fingerprint.nil? && settings.idp_cert.nil?
102
- return soft ? false : validation_error("No fingerprint or certificate on settings")
103
- end
104
-
105
- true
106
- end
107
-
108
98
  def valid_in_response_to?(soft = true)
109
99
  return true unless self.options.has_key? :matches_request_id
110
100
 
@@ -116,8 +106,10 @@ module OneLogin
116
106
  end
117
107
 
118
108
  def valid_issuer?(soft = true)
119
- unless URI.parse(issuer) == URI.parse(self.settings.sp_entity_id)
120
- return soft ? false : validation_error("Doesn't match the issuer, expected: <#{self.settings.sp_entity_id}>, but was: <#{issuer}>")
109
+ return true if settings.nil? || settings.idp_entity_id.nil? || issuer.nil?
110
+
111
+ unless OneLogin::RubySaml::Utils.uri_match?(issuer, settings.idp_entity_id)
112
+ return soft ? false : validation_error("Doesn't match the issuer, expected: <#{self.settings.idp_entity_id}>, but was: <#{issuer}>")
121
113
  end
122
114
  true
123
115
  end
@@ -27,6 +27,24 @@ module OneLogin
27
27
  @document = XMLSecurity::SignedDocument.new(@response)
28
28
  end
29
29
 
30
+ def response_id
31
+ @response_id ||= begin
32
+ node = REXML::XPath.first(
33
+ document,
34
+ "/p:Response",
35
+ { "p" => PROTOCOL }
36
+ )
37
+ node.nil? ? nil : node.attributes['ID']
38
+ end
39
+ end
40
+
41
+ def assertion_id
42
+ @assertion_id ||= begin
43
+ node = xpath_first_from_signed_assertion("")
44
+ node.nil? ? nil : node.attributes['ID']
45
+ end
46
+ end
47
+
30
48
  def is_valid?
31
49
  validate
32
50
  end
@@ -35,16 +53,48 @@ module OneLogin
35
53
  validate(false)
36
54
  end
37
55
 
38
- # The value of the user identifier as designated by the initialization request response
39
- def name_id
56
+ def name_id_node
40
57
  @name_id ||= begin
41
- node = xpath_first_from_signed_assertion('/a:Subject/a:NameID')
42
- Utils.element_text(node)
58
+ xpath_first_from_signed_assertion('/a:Subject/a:NameID')
43
59
  end
44
60
  end
45
61
 
62
+ # The value of the user identifier as designated by the initialization request response
63
+ def name_id
64
+ @name_id ||= Utils.element_text(name_id_node)
65
+ end
66
+
46
67
  alias nameid name_id
47
68
 
69
+ # @return [String] the NameID Format provided by the SAML response from the IdP.
70
+ #
71
+ def name_id_format
72
+ @name_id_format ||=
73
+ if name_id_node && name_id_node.attribute("Format")
74
+ name_id_node.attribute("Format").value
75
+ end
76
+ end
77
+
78
+ alias_method :nameid_format, :name_id_format
79
+
80
+ # @return [String] the NameID SPNameQualifier provided by the SAML response from the IdP.
81
+ #
82
+ def name_id_spnamequalifier
83
+ @name_id_spnamequalifier ||=
84
+ if name_id_node && name_id_node.attribute("SPNameQualifier")
85
+ name_id_node.attribute("SPNameQualifier").value
86
+ end
87
+ end
88
+
89
+ # @return [String] the NameID NameQualifier provided by the SAML response from the IdP.
90
+ #
91
+ def name_id_namequalifier
92
+ @name_id_namequalifier ||=
93
+ if name_id_node && name_id_node.attribute("NameQualifier")
94
+ name_id_node.attribute("NameQualifier").value
95
+ end
96
+ end
97
+
48
98
  def sessionindex
49
99
  @sessionindex ||= begin
50
100
  node = xpath_first_from_signed_assertion('/a:AuthnStatement')
@@ -134,6 +184,34 @@ module OneLogin
134
184
  end
135
185
  end
136
186
 
187
+ # Gets the Issuers (from Response and Assertion).
188
+ # (returns the first node that matches the supplied xpath from the Response and from the Assertion)
189
+ # @return [Array] Array with the Issuers (REXML::Element)
190
+ #
191
+ def issuers
192
+ @issuers ||= begin
193
+ issuer_response_nodes = REXML::XPath.match(
194
+ document,
195
+ "/p:Response/a:Issuer",
196
+ { "p" => PROTOCOL, "a" => ASSERTION }
197
+ )
198
+
199
+ unless issuer_response_nodes.size == 1
200
+ error_msg = "Issuer of the Response not found or multiple."
201
+ raise ValidationError.new(error_msg)
202
+ end
203
+
204
+ issuer_assertion_nodes = xpath_from_signed_assertion("/a:Issuer")
205
+ unless issuer_assertion_nodes.size == 1
206
+ error_msg = "Issuer of the Assertion not found or multiple."
207
+ raise ValidationError.new(error_msg)
208
+ end
209
+
210
+ nodes = issuer_response_nodes + issuer_assertion_nodes
211
+ nodes.map { |node| Utils.element_text(node) }.compact.uniq
212
+ end
213
+ end
214
+
137
215
  # @return [Array] The Audience elements from the Contitions of the SAML Response.
138
216
  #
139
217
  def audiences
@@ -157,6 +235,7 @@ module OneLogin
157
235
  validate_response_state(soft) &&
158
236
  validate_conditions(soft) &&
159
237
  validate_audience(soft) &&
238
+ validate_issuer(soft) &&
160
239
  validate_signature(soft) &&
161
240
  success?
162
241
  end
@@ -373,15 +452,6 @@ module OneLogin
373
452
  ))
374
453
  end
375
454
 
376
- def get_fingerprint
377
- if settings.idp_cert
378
- cert = OpenSSL::X509::Certificate.new(settings.idp_cert)
379
- Digest::SHA1.hexdigest(cert.to_der).upcase.scan(/../).join(":")
380
- else
381
- settings.idp_cert_fingerprint
382
- end
383
- end
384
-
385
455
  def validate_conditions(soft = true)
386
456
  return true if conditions.nil?
387
457
  return true if options[:skip_conditions]
@@ -399,6 +469,25 @@ module OneLogin
399
469
  true
400
470
  end
401
471
 
472
+ def validate_issuer(soft = true)
473
+ return true if settings.idp_entity_id.nil?
474
+
475
+ begin
476
+ obtained_issuers = issuers
477
+ rescue ValidationError => e
478
+ return soft ? false : validation_error("Error while extracting issuers")
479
+ end
480
+
481
+ obtained_issuers.each do |issuer|
482
+ unless OneLogin::RubySaml::Utils.uri_match?(issuer, settings.idp_entity_id)
483
+ error_msg = "Doesn't match the issuer, expected: <#{settings.idp_entity_id}>, but was: <#{issuer}>"
484
+ return soft ? false : validation_error(error_msg)
485
+ end
486
+ end
487
+
488
+ true
489
+ end
490
+
402
491
  def validate_signature(soft = true)
403
492
  error_msg = "Invalid Signature on SAML Response"
404
493
 
@@ -430,8 +519,8 @@ module OneLogin
430
519
 
431
520
  opts = {}
432
521
  opts[:fingerprint_alg] = OpenSSL::Digest::SHA1.new
433
- opts[:cert] = settings.idp_cert
434
- fingerprint = get_fingerprint
522
+ opts[:cert] = settings.get_idp_cert
523
+ fingerprint = settings.get_fingerprint
435
524
 
436
525
  unless fingerprint
437
526
  return soft ? false : validation_error("No fingerprint or certificate on settings")
@@ -27,16 +27,17 @@ module OneLogin
27
27
  attr_accessor :idp_cert_fingerprint
28
28
  attr_accessor :idp_cert
29
29
  attr_accessor :idp_slo_target_url
30
+ attr_accessor :idp_entity_id
30
31
  #sp data
31
32
  attr_accessor :sp_entity_id
32
33
  attr_accessor :assertion_consumer_service_url
33
34
  attr_accessor :authn_context
34
35
  attr_accessor :sp_name_qualifier
36
+ attr_accessor :idp_name_qualifier
35
37
  attr_accessor :name_identifier_format
36
38
  attr_accessor :name_identifier_value
37
39
  attr_accessor :name_identifier_value_requested
38
40
  attr_accessor :sessionindex
39
- attr_accessor :assertion_consumer_logout_service_url
40
41
  attr_accessor :compress_request
41
42
  attr_accessor :compress_response
42
43
  attr_accessor :double_quote_xml_attribute_values
@@ -117,21 +118,38 @@ module OneLogin
117
118
  @single_logout_service_binding = url
118
119
  end
119
120
 
120
- # @return [OpenSSL::X509::Certificate|nil] Build the SP certificate from the settings (previously format it)
121
+ # Calculates the fingerprint of the IdP x509 certificate.
122
+ # @return [String] The fingerprint
121
123
  #
122
- def get_sp_cert
123
- return nil if certificate.nil? || certificate.empty?
124
+ def get_fingerprint
125
+ idp_cert_fingerprint || begin
126
+ idp_cert = get_idp_cert
127
+ if idp_cert
128
+ Digest::SHA1.hexdigest(idp_cert.to_der).upcase.scan(/../).join(":")
129
+ end
130
+ end
131
+ end
124
132
 
125
- formatted_cert = OneLogin::RubySaml::Utils.format_cert(certificate)
126
- OpenSSL::X509::Certificate.new(formatted_cert)
133
+ # @return [OpenSSL::X509::Certificate|nil] Build the IdP certificate from the settings (previously format it)
134
+ #
135
+ def get_idp_cert
136
+ return nil if idp_cert.nil?
137
+
138
+ if idp_cert.respond_to?(:to_pem)
139
+ idp_cert
140
+ else
141
+ return nil if idp_cert.empty?
142
+ formatted_cert = OneLogin::RubySaml::Utils.format_cert(idp_cert)
143
+ OpenSSL::X509::Certificate.new(formatted_cert)
144
+ end
127
145
  end
128
146
 
129
- # @return [OpenSSL::X509::Certificate|nil] Build the New SP certificate from the settings (previously format it)
147
+ # @return [OpenSSL::X509::Certificate|nil] Build the SP certificate from the settings (previously format it)
130
148
  #
131
- def get_sp_cert_new
132
- return nil if certificate_new.nil? || certificate_new.empty?
149
+ def get_sp_cert
150
+ return nil if certificate.nil? || certificate.empty?
133
151
 
134
- formatted_cert = OneLogin::RubySaml::Utils.format_cert(certificate_new)
152
+ formatted_cert = OneLogin::RubySaml::Utils.format_cert(certificate)
135
153
  OpenSSL::X509::Certificate.new(formatted_cert)
136
154
  end
137
155
 
@@ -0,0 +1,112 @@
1
+ require "xml_security"
2
+ require "time"
3
+
4
+ # Only supports SAML 2.0
5
+ # SAML2 Logout Request (SLO IdP initiated, Parser)
6
+ module OneLogin
7
+ module RubySaml
8
+ class SloLogoutrequest
9
+
10
+ ASSERTION = "urn:oasis:names:tc:SAML:2.0:assertion"
11
+ PROTOCOL = "urn:oasis:names:tc:SAML:2.0:protocol"
12
+
13
+ # OneLogin::RubySaml::Settings Toolkit settings
14
+ attr_accessor :settings
15
+
16
+ attr_reader :document
17
+ attr_reader :request
18
+ attr_reader :options
19
+
20
+ def initialize(request, settings = nil, options = {})
21
+ raise ArgumentError.new("Request cannot be nil") if request.nil?
22
+ self.settings = settings
23
+
24
+ @options = options
25
+ @request = OneLogin::RubySaml::Utils.decode_raw_saml(request)
26
+ @document = XMLSecurity::SignedDocument.new(@request)
27
+ end
28
+
29
+ def request_id
30
+ @request_id ||= begin
31
+ node = REXML::XPath.first(
32
+ document,
33
+ "/p:LogoutRequest",
34
+ { "p" => PROTOCOL }
35
+ )
36
+ node.nil? ? nil : node.attributes['ID']
37
+ end
38
+ end
39
+
40
+ def validate!
41
+ validate(false)
42
+ end
43
+
44
+ def validate(soft = true)
45
+ return false unless validate_structure(soft)
46
+
47
+ valid_issuer?(soft)
48
+ end
49
+
50
+ def name_id
51
+ @name_id ||= begin
52
+ node = REXML::XPath.first(document, "/p:LogoutRequest/a:NameID", { "p" => PROTOCOL, "a" => ASSERTION })
53
+ Utils.element_text(node)
54
+ end
55
+ end
56
+
57
+ alias_method :nameid, :name_id
58
+
59
+ def name_id_format
60
+ @name_id_node ||= REXML::XPath.first(document, "/p:LogoutRequest/a:NameID", { "p" => PROTOCOL, "a" => ASSERTION })
61
+ @name_id_format ||=
62
+ if @name_id_node && @name_id_node.attribute("Format")
63
+ @name_id_node.attribute("Format").value
64
+ end
65
+ end
66
+
67
+ alias_method :nameid_format, :name_id_format
68
+
69
+ def id
70
+ @id ||= begin
71
+ node = REXML::XPath.first(document, "/p:LogoutRequest", { "p" => PROTOCOL } )
72
+ node.nil? ? nil : node.attributes['ID']
73
+ end
74
+ end
75
+
76
+ def issuer
77
+ @issuer ||= begin
78
+ node = REXML::XPath.first(document, "/p:LogoutRequest/a:Issuer", { "p" => PROTOCOL, "a" => ASSERTION })
79
+ Utils.element_text(node)
80
+ end
81
+ end
82
+
83
+ private
84
+
85
+ def validate_structure(soft = true)
86
+ Dir.chdir(File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'schemas'))) do
87
+ @schema = Nokogiri::XML::Schema(IO.read('saml20protocol_schema.xsd'))
88
+ @xml = Nokogiri::XML(self.document.to_s)
89
+ end
90
+ if soft
91
+ @schema.validate(@xml).map{ return false }
92
+ else
93
+ @schema.validate(@xml).map{ |error| validation_error("#{error.message}\n\n#{@xml.to_s}") }
94
+ end
95
+ end
96
+
97
+ def valid_issuer?(soft = true)
98
+ return true if settings.nil? || settings.idp_entity_id.nil? || issuer.nil?
99
+
100
+ unless OneLogin::RubySaml::Utils.uri_match?(issuer, settings.idp_entity_id)
101
+ return soft ? false : validation_error("Doesn't match the issuer, expected: <#{self.settings.idp_entity_id}>, but was: <#{issuer}>")
102
+ end
103
+ true
104
+ end
105
+
106
+ def validation_error(message)
107
+ raise ValidationError.new(message)
108
+ end
109
+
110
+ end
111
+ end
112
+ end
@@ -12,7 +12,7 @@ module OneLogin
12
12
  class SloLogoutresponse
13
13
 
14
14
  # Logout Response ID
15
- attr_reader :uuid
15
+ attr_accessor :uuid
16
16
 
17
17
  # Initializes the Logout Response. A SloLogoutresponse Object.
18
18
  # Asigns an ID, a random uuid.
@@ -21,15 +21,20 @@ module OneLogin
21
21
  @uuid = OneLogin::RubySaml::Utils.uuid
22
22
  end
23
23
 
24
+ def response_id
25
+ @uuid
26
+ end
27
+
24
28
  # Creates the Logout Response string.
25
29
  # @param settings [OneLogin::RubySaml::Settings|nil] Toolkit settings
26
30
  # @param request_id [String] The ID of the LogoutRequest sent by this SP to the IdP. That ID will be placed as the InResponseTo in the logout response
27
31
  # @param logout_message [String] The Message to be placed as StatusMessage in the logout response
28
32
  # @param params [Hash] Some extra parameters to be added in the GET for example the RelayState
33
+ # @param logout_status_code [String] The StatusCode to be placed as StatusMessage in the logout response
29
34
  # @return [String] Logout Request string that includes the SAMLRequest
30
35
  #
31
- def create(settings, request_id = nil, logout_message = nil, params = {})
32
- params = create_params(settings, request_id, logout_message, params)
36
+ def create(settings, request_id = nil, logout_message = nil, params = {}, logout_status_code = nil)
37
+ params = create_params(settings, request_id, logout_message, params, logout_status_code)
33
38
  params_prefix = (settings.idp_slo_target_url =~ /\?/) ? '&' : '?'
34
39
  saml_response = CGI.escape(params.delete("SAMLResponse"))
35
40
  response_params = "#{params_prefix}SAMLResponse=#{saml_response}"
@@ -45,9 +50,10 @@ module OneLogin
45
50
  # @param request_id [String] The ID of the LogoutRequest sent by this SP to the IdP. That ID will be placed as the InResponseTo in the logout response
46
51
  # @param logout_message [String] The Message to be placed as StatusMessage in the logout response
47
52
  # @param params [Hash] Some extra parameters to be added in the GET for example the RelayState
53
+ # @param logout_status_code [String] The StatusCode to be placed as StatusMessage in the logout response
48
54
  # @return [Hash] Parameters
49
55
  #
50
- def create_params(settings, request_id = nil, logout_message = nil, params = {})
56
+ def create_params(settings, request_id = nil, logout_message = nil, params = {}, logout_status_code = nil)
51
57
  # The method expects :RelayState but sometimes we get 'RelayState' instead.
52
58
  # Based on the HashWithIndifferentAccess value in Rails we could experience
53
59
  # conflicts so this line will solve them.
@@ -58,7 +64,7 @@ module OneLogin
58
64
  params.delete('RelayState')
59
65
  end
60
66
 
61
- response_doc = create_logout_response_xml_doc(settings, request_id, logout_message)
67
+ response_doc = create_logout_response_xml_doc(settings, request_id, logout_message, logout_status_code)
62
68
  response_doc.context[:attribute_quote] = :quote if settings.double_quote_xml_attribute_values
63
69
 
64
70
  response = ""
@@ -102,14 +108,15 @@ module OneLogin
102
108
  # @param settings [OneLogin::RubySaml::Settings|nil] Toolkit settings
103
109
  # @param request_id [String] The ID of the LogoutRequest sent by this SP to the IdP. That ID will be placed as the InResponseTo in the logout response
104
110
  # @param logout_message [String] The Message to be placed as StatusMessage in the logout response
111
+ # @param logout_status_code [String] The StatusCode to be placed as StatusMessage in the logout response
105
112
  # @return [String] The SAMLResponse String.
106
113
  #
107
- def create_logout_response_xml_doc(settings, request_id = nil, logout_message = nil)
108
- document = create_xml_document(settings, request_id, logout_message)
114
+ def create_logout_response_xml_doc(settings, request_id = nil, logout_message = nil, logout_status_code = nil)
115
+ document = create_xml_document(settings, request_id, logout_message, logout_status_code)
109
116
  sign_document(document, settings)
110
117
  end
111
118
 
112
- def create_xml_document(settings, request_id = nil, logout_message = nil)
119
+ def create_xml_document(settings, request_id = nil, logout_message = nil, status_code = nil)
113
120
  time = Time.now.utc.strftime('%Y-%m-%dT%H:%M:%SZ')
114
121
 
115
122
  response_doc = XMLSecurity::Document.new
@@ -127,14 +134,15 @@ module OneLogin
127
134
  issuer.text = settings.sp_entity_id
128
135
  end
129
136
 
130
- # add success message
137
+ # add status
131
138
  status = root.add_element 'samlp:Status'
132
139
 
133
- # success status code
134
- status_code = status.add_element 'samlp:StatusCode'
135
- status_code.attributes['Value'] = 'urn:oasis:names:tc:SAML:2.0:status:Success'
140
+ # status code
141
+ status_code ||= 'urn:oasis:names:tc:SAML:2.0:status:Success'
142
+ status_code_elem = status.add_element 'samlp:StatusCode'
143
+ status_code_elem.attributes['Value'] = status_code
136
144
 
137
- # success status message
145
+ # status message
138
146
  logout_message ||= 'Successfully Signed Out'
139
147
  status_message = status.add_element 'samlp:StatusMessage'
140
148
  status_message.text = logout_message
@@ -179,6 +179,33 @@ module OneLogin
179
179
  Zlib::Deflate.deflate(inflated, 9)[2..-5]
180
180
  end
181
181
 
182
+ # Given two strings, attempt to match them as URIs using Rails' parse method. If they can be parsed,
183
+ # then the fully-qualified domain name and the host should performa a case-insensitive match, per the
184
+ # RFC for URIs. If Rails can not parse the string in to URL pieces, return a boolean match of the
185
+ # two strings. This maintains the previous functionality.
186
+ # @return [Boolean]
187
+ def self.uri_match?(destination_url, settings_url)
188
+ dest_uri = URI.parse(destination_url)
189
+ acs_uri = URI.parse(settings_url)
190
+
191
+ if dest_uri.scheme.nil? || acs_uri.scheme.nil? || dest_uri.host.nil? || acs_uri.host.nil?
192
+ raise URI::InvalidURIError
193
+ else
194
+ dest_uri.scheme.downcase == acs_uri.scheme.downcase &&
195
+ dest_uri.host.downcase == acs_uri.host.downcase &&
196
+ dest_uri.path == acs_uri.path &&
197
+ dest_uri.query == acs_uri.query
198
+ end
199
+ rescue URI::InvalidURIError
200
+ original_uri_match?(destination_url, settings_url)
201
+ end
202
+
203
+ # If Rails' URI.parse can't match to valid URL, default back to the original matching service.
204
+ # @return [Boolean]
205
+ def self.original_uri_match?(destination_url, settings_url)
206
+ destination_url == settings_url
207
+ end
208
+
182
209
  end
183
210
  end
184
211
  end
@@ -1,5 +1,5 @@
1
1
  module OneLogin
2
2
  module RubySaml
3
- VERSION = '0.8.14'
3
+ VERSION = '0.8.18'
4
4
  end
5
5
  end
data/lib/ruby-saml.rb CHANGED
@@ -2,6 +2,7 @@ require 'onelogin/ruby-saml/logging'
2
2
  require 'onelogin/ruby-saml/authrequest'
3
3
  require 'onelogin/ruby-saml/logoutrequest'
4
4
  require 'onelogin/ruby-saml/logoutresponse'
5
+ require 'onelogin/ruby-saml/slo_logoutrequest'
5
6
  require 'onelogin/ruby-saml/slo_logoutresponse'
6
7
  require 'onelogin/ruby-saml/response'
7
8
  require 'onelogin/ruby-saml/settings'
data/lib/xml_security.rb CHANGED
@@ -222,7 +222,11 @@ module XMLSecurity
222
222
  end
223
223
  else
224
224
  if options[:cert]
225
- base64_cert = Base64.encode64(options[:cert].to_pem)
225
+ cert = options[:cert]
226
+ if cert.is_a? String
227
+ cert = OpenSSL::X509::Certificate.new(cert)
228
+ end
229
+ base64_cert = Base64.encode64(cert.to_pem)
226
230
  else
227
231
  return soft ? false : (raise OneLogin::RubySaml::ValidationError.new("Certificate element missing in response (ds:X509Certificate) and not cert provided at settings"))
228
232
  end
Binary file
@@ -0,0 +1,14 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIICPDCCAaWgAwIBAgIIEiC/9HMAWWAwDQYJKoZIhvcNAQEFBQAwTzELMAkGA1UE
3
+ BhMCVVMxDDAKBgNVBAoTA2libTEMMAoGA1UECxMDc3NvMSQwIgYDVQQDExtjMjVh
4
+ MDI3Ny50b3JvbnRvLmNhLmlibS5jb20wHhcNMTEwNTI0MTYzNTQ4WhcNMjEwNTIx
5
+ wsQMPBj4WQTNzTYMCQYDVQQGEwJVUzEMMAoGA1UEChMDaWJtMQwwCgYDVQQLEwNz
6
+ c28xJDAiBgNVBAMTG2MyNWEwMjc3LnRvcm9udG8uY2EuaWJtLmNvbTCBnzANBgkq
7
+ hkiG9w0BAQEFAAOBjQAwgYkCgYEAgzfYQZuf5FVdJTcrsIQZ+YHTPjOsw2MGo0jC
8
+ mdGMcp4brWeFgk1OVaOmytPx6P76wHWR436AleX3crHBPd8gPxuZdnvBQ7PkrKpw
9
+ Vvaq52juenFrho8JY0TeVgVkY5jAh45YzytjP2y2k/cGQurI/56NT0PpQJ0S1G3N
10
+ 4eTg718CAwEAAaMhMB8wHQYDVR0OBBYEFCYVLJqcJ7WgdzGIsuJ/TzDGDqinMA0G
11
+ CSqGSIb3DQEBBQUAA4GBAB80bIePf+qWDvWe+9bEEnbFTw7pCknLexxZ0AMqrsmZ
12
+ +4jmI+evP1JZYCjfIg9X+MBH01hfp5dFcetz3o6w6SkV+BxLYLgfcy5KUcYsIM/1
13
+ 2Zkedj87bS1glzOy5B89pKD2DMbu6828Abzgc+4lyQ2ASifsqM4cZdVayzo8n+dQ
14
+ -----END CERTIFICATE-----