ruby-saml 0.8.14 → 0.8.18

Sign up to get free protection for your applications and to get access to all the features.

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-----