r-saml 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (140) hide show
  1. checksums.yaml +7 -0
  2. data/.document +5 -0
  3. data/.gitignore +14 -0
  4. data/.travis.yml +23 -0
  5. data/Gemfile +6 -0
  6. data/LICENSE +19 -0
  7. data/README.md +584 -0
  8. data/Rakefile +27 -0
  9. data/changelog.md +75 -0
  10. data/gemfiles/nokogiri-1.5.gemfile +5 -0
  11. data/lib/onelogin/ruby-saml.rb +17 -0
  12. data/lib/onelogin/ruby-saml/attribute_service.rb +57 -0
  13. data/lib/onelogin/ruby-saml/attributes.rb +128 -0
  14. data/lib/onelogin/ruby-saml/authrequest.rb +165 -0
  15. data/lib/onelogin/ruby-saml/http_error.rb +7 -0
  16. data/lib/onelogin/ruby-saml/idp_metadata_parser.rb +161 -0
  17. data/lib/onelogin/ruby-saml/logging.rb +30 -0
  18. data/lib/onelogin/ruby-saml/logoutrequest.rb +131 -0
  19. data/lib/onelogin/ruby-saml/logoutresponse.rb +241 -0
  20. data/lib/onelogin/ruby-saml/metadata.rb +123 -0
  21. data/lib/onelogin/ruby-saml/response.rb +735 -0
  22. data/lib/onelogin/ruby-saml/saml_message.rb +158 -0
  23. data/lib/onelogin/ruby-saml/settings.rb +165 -0
  24. data/lib/onelogin/ruby-saml/slo_logoutrequest.rb +258 -0
  25. data/lib/onelogin/ruby-saml/slo_logoutresponse.rb +136 -0
  26. data/lib/onelogin/ruby-saml/utils.rb +172 -0
  27. data/lib/onelogin/ruby-saml/validation_error.rb +7 -0
  28. data/lib/onelogin/ruby-saml/version.rb +5 -0
  29. data/lib/ruby-saml.rb +1 -0
  30. data/lib/schemas/saml-schema-assertion-2.0.xsd +283 -0
  31. data/lib/schemas/saml-schema-authn-context-2.0.xsd +23 -0
  32. data/lib/schemas/saml-schema-authn-context-types-2.0.xsd +821 -0
  33. data/lib/schemas/saml-schema-metadata-2.0.xsd +337 -0
  34. data/lib/schemas/saml-schema-protocol-2.0.xsd +302 -0
  35. data/lib/schemas/sstc-metadata-attr.xsd +35 -0
  36. data/lib/schemas/sstc-saml-attribute-ext.xsd +25 -0
  37. data/lib/schemas/sstc-saml-metadata-algsupport-v1.0.xsd +41 -0
  38. data/lib/schemas/sstc-saml-metadata-ui-v1.0.xsd +89 -0
  39. data/lib/schemas/xenc-schema.xsd +136 -0
  40. data/lib/schemas/xml.xsd +287 -0
  41. data/lib/schemas/xmldsig-core-schema.xsd +309 -0
  42. data/lib/xml_security.rb +368 -0
  43. data/r-saml.gemspec +64 -0
  44. data/test/certificates/certificate1 +12 -0
  45. data/test/certificates/certificate_without_head_foot +1 -0
  46. data/test/certificates/formatted_certificate +14 -0
  47. data/test/certificates/formatted_private_key +12 -0
  48. data/test/certificates/formatted_rsa_private_key +12 -0
  49. data/test/certificates/invalid_certificate1 +1 -0
  50. data/test/certificates/invalid_certificate2 +1 -0
  51. data/test/certificates/invalid_certificate3 +12 -0
  52. data/test/certificates/invalid_private_key1 +1 -0
  53. data/test/certificates/invalid_private_key2 +1 -0
  54. data/test/certificates/invalid_private_key3 +10 -0
  55. data/test/certificates/invalid_rsa_private_key1 +1 -0
  56. data/test/certificates/invalid_rsa_private_key2 +1 -0
  57. data/test/certificates/invalid_rsa_private_key3 +10 -0
  58. data/test/certificates/ruby-saml.crt +14 -0
  59. data/test/certificates/ruby-saml.key +15 -0
  60. data/test/idp_metadata_parser_test.rb +95 -0
  61. data/test/logging_test.rb +62 -0
  62. data/test/logout_requests/invalid_slo_request.xml +6 -0
  63. data/test/logout_requests/slo_request.xml +4 -0
  64. data/test/logout_requests/slo_request.xml.base64 +1 -0
  65. data/test/logout_requests/slo_request_deflated.xml.base64 +1 -0
  66. data/test/logout_requests/slo_request_with_session_index.xml +5 -0
  67. data/test/logout_responses/logoutresponse_fixtures.rb +67 -0
  68. data/test/logoutrequest_test.rb +211 -0
  69. data/test/logoutresponse_test.rb +258 -0
  70. data/test/metadata_test.rb +203 -0
  71. data/test/request_test.rb +282 -0
  72. data/test/response_test.rb +1159 -0
  73. data/test/responses/adfs_response_sha1.xml +46 -0
  74. data/test/responses/adfs_response_sha256.xml +46 -0
  75. data/test/responses/adfs_response_sha384.xml +46 -0
  76. data/test/responses/adfs_response_sha512.xml +46 -0
  77. data/test/responses/adfs_response_xmlns.xml +45 -0
  78. data/test/responses/attackxee.xml +13 -0
  79. data/test/responses/idp_descriptor.xml +3 -0
  80. data/test/responses/invalids/invalid_audience.xml.base64 +1 -0
  81. data/test/responses/invalids/invalid_issuer_assertion.xml.base64 +1 -0
  82. data/test/responses/invalids/invalid_issuer_message.xml.base64 +1 -0
  83. data/test/responses/invalids/invalid_signature_position.xml.base64 +1 -0
  84. data/test/responses/invalids/invalid_subjectconfirmation_inresponse.xml.base64 +1 -0
  85. data/test/responses/invalids/invalid_subjectconfirmation_nb.xml.base64 +1 -0
  86. data/test/responses/invalids/invalid_subjectconfirmation_noa.xml.base64 +1 -0
  87. data/test/responses/invalids/invalid_subjectconfirmation_recipient.xml.base64 +1 -0
  88. data/test/responses/invalids/multiple_assertions.xml.base64 +2 -0
  89. data/test/responses/invalids/multiple_signed.xml.base64 +1 -0
  90. data/test/responses/invalids/no_id.xml.base64 +1 -0
  91. data/test/responses/invalids/no_saml2.xml.base64 +1 -0
  92. data/test/responses/invalids/no_signature.xml.base64 +1 -0
  93. data/test/responses/invalids/no_status.xml.base64 +1 -0
  94. data/test/responses/invalids/no_status_code.xml.base64 +1 -0
  95. data/test/responses/invalids/no_subjectconfirmation_data.xml.base64 +1 -0
  96. data/test/responses/invalids/no_subjectconfirmation_method.xml.base64 +1 -0
  97. data/test/responses/invalids/response_encrypted_attrs.xml.base64 +1 -0
  98. data/test/responses/invalids/response_invalid_signed_element.xml.base64 +1 -0
  99. data/test/responses/invalids/status_code_responder.xml.base64 +1 -0
  100. data/test/responses/invalids/status_code_responer_and_msg.xml.base64 +1 -0
  101. data/test/responses/no_signature_ns.xml +48 -0
  102. data/test/responses/open_saml_response.xml +56 -0
  103. data/test/responses/response_assertion_wrapped.xml.base64 +93 -0
  104. data/test/responses/response_encrypted_nameid.xml.base64 +1 -0
  105. data/test/responses/response_eval.xml +7 -0
  106. data/test/responses/response_no_cert_and_encrypted_attrs.xml +29 -0
  107. data/test/responses/response_unsigned_xml_base64 +1 -0
  108. data/test/responses/response_with_ampersands.xml +139 -0
  109. data/test/responses/response_with_ampersands.xml.base64 +93 -0
  110. data/test/responses/response_with_multiple_attribute_values.xml +67 -0
  111. data/test/responses/response_with_saml2_namespace.xml.base64 +102 -0
  112. data/test/responses/response_with_signed_assertion.xml.base64 +66 -0
  113. data/test/responses/response_with_signed_assertion_2.xml.base64 +1 -0
  114. data/test/responses/response_with_undefined_recipient.xml.base64 +1 -0
  115. data/test/responses/response_without_attributes.xml.base64 +79 -0
  116. data/test/responses/response_without_reference_uri.xml.base64 +1 -0
  117. data/test/responses/response_wrapped.xml.base64 +150 -0
  118. data/test/responses/signed_message_encrypted_signed_assertion.xml.base64 +1 -0
  119. data/test/responses/signed_message_encrypted_unsigned_assertion.xml.base64 +1 -0
  120. data/test/responses/signed_nameid_in_atts.xml +47 -0
  121. data/test/responses/signed_unqual_nameid_in_atts.xml +47 -0
  122. data/test/responses/simple_saml_php.xml +71 -0
  123. data/test/responses/starfield_response.xml.base64 +1 -0
  124. data/test/responses/test_sign.xml +43 -0
  125. data/test/responses/unsigned_message_aes128_encrypted_signed_assertion.xml.base64 +1 -0
  126. data/test/responses/unsigned_message_aes192_encrypted_signed_assertion.xml.base64 +1 -0
  127. data/test/responses/unsigned_message_aes256_encrypted_signed_assertion.xml.base64 +1 -0
  128. data/test/responses/unsigned_message_des192_encrypted_signed_assertion.xml.base64 +1 -0
  129. data/test/responses/unsigned_message_encrypted_assertion_without_saml_namespace.xml.base64 +1 -0
  130. data/test/responses/unsigned_message_encrypted_signed_assertion.xml.base64 +1 -0
  131. data/test/responses/unsigned_message_encrypted_unsigned_assertion.xml.base64 +1 -0
  132. data/test/responses/valid_response.xml.base64 +1 -0
  133. data/test/saml_message_test.rb +56 -0
  134. data/test/settings_test.rb +218 -0
  135. data/test/slo_logoutrequest_test.rb +275 -0
  136. data/test/slo_logoutresponse_test.rb +185 -0
  137. data/test/test_helper.rb +257 -0
  138. data/test/utils_test.rb +145 -0
  139. data/test/xml_security_test.rb +328 -0
  140. metadata +421 -0
@@ -0,0 +1,158 @@
1
+ require 'cgi'
2
+ require 'zlib'
3
+ require 'base64'
4
+ require 'nokogiri'
5
+ require 'rexml/document'
6
+ require 'rexml/xpath'
7
+ require 'thread'
8
+
9
+ # Only supports SAML 2.0
10
+ module OneLogin
11
+ module RubySaml
12
+
13
+ # SAML2 Message
14
+ #
15
+ class SamlMessage
16
+ include REXML
17
+
18
+ ASSERTION = "urn:oasis:names:tc:SAML:2.0:assertion"
19
+ PROTOCOL = "urn:oasis:names:tc:SAML:2.0:protocol"
20
+
21
+ BASE64_FORMAT = %r(\A[A-Za-z0-9+/]{4}*[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=?\Z)
22
+
23
+ # @return [Nokogiri::XML::Schema] Gets the schema object of the SAML 2.0 Protocol schema
24
+ #
25
+ def self.schema
26
+ Mutex.new.synchronize do
27
+ Dir.chdir(File.expand_path("../../../schemas", __FILE__)) do
28
+ ::Nokogiri::XML::Schema(File.read("saml-schema-protocol-2.0.xsd"))
29
+ end
30
+ end
31
+ end
32
+
33
+ # @return [String|nil] Gets the Version attribute from the SAML Message if exists.
34
+ #
35
+ def version(document)
36
+ @version ||= begin
37
+ node = REXML::XPath.first(
38
+ document,
39
+ "/p:AuthnRequest | /p:Response | /p:LogoutResponse | /p:LogoutRequest",
40
+ { "p" => PROTOCOL }
41
+ )
42
+ node.nil? ? nil : node.attributes['Version']
43
+ end
44
+ end
45
+
46
+ # @return [String|nil] Gets the ID attribute from the SAML Message if exists.
47
+ #
48
+ def id(document)
49
+ @id ||= begin
50
+ node = REXML::XPath.first(
51
+ document,
52
+ "/p:AuthnRequest | /p:Response | /p:LogoutResponse | /p:LogoutRequest",
53
+ { "p" => PROTOCOL }
54
+ )
55
+ node.nil? ? nil : node.attributes['ID']
56
+ end
57
+ end
58
+
59
+ # Validates the SAML Message against the specified schema.
60
+ # @param document [REXML::Document] The message that will be validated
61
+ # @param soft [Boolean] soft Enable or Disable the soft mode (In order to raise exceptions when the message is invalid or not)
62
+ # @return [Boolean] True if the XML is valid, otherwise False, if soft=True
63
+ # @raise [ValidationError] if soft == false and validation fails
64
+ #
65
+ def valid_saml?(document, soft = true)
66
+ begin
67
+ xml = Nokogiri::XML(document.to_s) do |config|
68
+ config.options = XMLSecurity::BaseDocument::NOKOGIRI_OPTIONS
69
+ end
70
+ rescue Exception => error
71
+ return false if soft
72
+ validation_error("XML load failed: #{error.message}")
73
+ end
74
+
75
+ SamlMessage.schema.validate(xml).map do |error|
76
+ return false if soft
77
+ validation_error("#{error.message}\n\n#{xml.to_s}")
78
+ end
79
+ end
80
+
81
+ # Raise a ValidationError with the provided message
82
+ # @param message [String] Message of the exception
83
+ # @raise [ValidationError]
84
+ #
85
+ def validation_error(message)
86
+ raise ValidationError.new(message)
87
+ end
88
+
89
+ private
90
+
91
+ # Base64 decode and try also to inflate a SAML Message
92
+ # @param saml [String] The deflated and encoded SAML Message
93
+ # @return [String] The plain SAML Message
94
+ #
95
+ def decode_raw_saml(saml)
96
+ return saml unless base64_encoded?(saml)
97
+
98
+ decoded = decode(saml)
99
+ begin
100
+ inflate(decoded)
101
+ rescue
102
+ decoded
103
+ end
104
+ end
105
+
106
+ # Deflate, base64 encode and url-encode a SAML Message (To be used in the HTTP-redirect binding)
107
+ # @param saml [String] The plain SAML Message
108
+ # @param settings [OneLogin::RubySaml::Settings|nil] Toolkit settings
109
+ # @return [String] The deflated and encoded SAML Message (encoded if the compression is requested)
110
+ #
111
+ def encode_raw_saml(saml, settings)
112
+ saml = deflate(saml) if settings.compress_request
113
+
114
+ CGI.escape(Base64.encode64(saml))
115
+ end
116
+
117
+ # Base 64 decode method
118
+ # @param string [String] The string message
119
+ # @return [String] The decoded string
120
+ #
121
+ def decode(string)
122
+ Base64.decode64(string)
123
+ end
124
+
125
+ # Base 64 encode method
126
+ # @param string [String] The string
127
+ # @return [String] The encoded string
128
+ #
129
+ def encode(string)
130
+ Base64.encode64(string).gsub(/\n/, "")
131
+ end
132
+
133
+ # Check if a string is base64 encoded
134
+ # @param string [String] string to check the encoding of
135
+ # @return [true, false] whether or not the string is base64 encoded
136
+ #
137
+ def base64_encoded?(string)
138
+ !!string.gsub(/[\r\n]|\\r|\\n/, "").match(BASE64_FORMAT)
139
+ end
140
+
141
+ # Inflate method
142
+ # @param deflated [String] The string
143
+ # @return [String] The inflated string
144
+ #
145
+ def inflate(deflated)
146
+ Zlib::Inflate.new(-Zlib::MAX_WBITS).inflate(deflated)
147
+ end
148
+
149
+ # Deflate method
150
+ # @param inflated [String] The string
151
+ # @return [String] The deflated string
152
+ #
153
+ def deflate(inflated)
154
+ Zlib::Deflate.deflate(inflated, 9)[2..-5]
155
+ end
156
+ end
157
+ end
158
+ end
@@ -0,0 +1,165 @@
1
+ require "xml_security"
2
+ require "onelogin/ruby-saml/attribute_service"
3
+ require "onelogin/ruby-saml/utils"
4
+
5
+ # Only supports SAML 2.0
6
+ module OneLogin
7
+ module RubySaml
8
+
9
+ # SAML2 Toolkit Settings
10
+ #
11
+ class Settings
12
+ def initialize(overrides = {})
13
+ config = DEFAULTS.merge(overrides)
14
+ config.each do |k,v|
15
+ acc = "#{k.to_s}=".to_sym
16
+ if respond_to? acc
17
+ value = v.is_a?(Hash) ? v.dup : v
18
+ send(acc, value)
19
+ end
20
+ end
21
+ @attribute_consuming_service = AttributeService.new
22
+ end
23
+
24
+ # IdP Data
25
+ attr_accessor :idp_entity_id
26
+ attr_accessor :idp_sso_target_url
27
+ attr_accessor :idp_slo_target_url
28
+ attr_accessor :idp_cert
29
+ attr_accessor :idp_cert_fingerprint
30
+ attr_accessor :idp_cert_fingerprint_algorithm
31
+ # SP Data
32
+ attr_accessor :issuer
33
+ attr_accessor :assertion_consumer_service_url
34
+ attr_accessor :assertion_consumer_service_binding
35
+ attr_accessor :sp_name_qualifier
36
+ attr_accessor :name_identifier_format
37
+ attr_accessor :name_identifier_value
38
+ attr_accessor :sessionindex
39
+ attr_accessor :compress_request
40
+ attr_accessor :compress_response
41
+ attr_accessor :double_quote_xml_attribute_values
42
+ attr_accessor :passive
43
+ attr_accessor :protocol_binding
44
+ attr_accessor :attributes_index
45
+ attr_accessor :force_authn
46
+ attr_accessor :certificate
47
+ attr_accessor :private_key
48
+ attr_accessor :authn_context
49
+ attr_accessor :authn_context_comparison
50
+ attr_accessor :authn_context_decl_ref
51
+ attr_reader :attribute_consuming_service
52
+ # Work-flow
53
+ attr_accessor :security
54
+ attr_accessor :soft
55
+ # Compability
56
+ attr_accessor :assertion_consumer_logout_service_url
57
+ attr_accessor :assertion_consumer_logout_service_binding
58
+
59
+ # @return [String] Single Logout Service URL.
60
+ #
61
+ def single_logout_service_url
62
+ val = nil
63
+ if @single_logout_service_url.nil?
64
+ if @assertion_consumer_logout_service_url
65
+ val = @assertion_consumer_logout_service_url
66
+ end
67
+ else
68
+ val = @single_logout_service_url
69
+ end
70
+ val
71
+ end
72
+
73
+ # Setter for the Single Logout Service URL.
74
+ # @param url [String].
75
+ #
76
+ def single_logout_service_url=(url)
77
+ @single_logout_service_url = url
78
+ end
79
+
80
+ # @return [String] Single Logout Service Binding.
81
+ #
82
+ def single_logout_service_binding
83
+ val = nil
84
+ if @single_logout_service_binding.nil?
85
+ if @assertion_consumer_logout_service_binding
86
+ val = @assertion_consumer_logout_service_binding
87
+ end
88
+ else
89
+ val = @single_logout_service_binding
90
+ end
91
+ val
92
+ end
93
+
94
+ # Setter for Single Logout Service Binding.
95
+ #
96
+ # (Currently we only support "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect")
97
+ # @param url [String]
98
+ #
99
+ def single_logout_service_binding=(url)
100
+ @single_logout_service_binding = url
101
+ end
102
+
103
+ # Calculates the fingerprint of the IdP x509 certificate.
104
+ # @return [String] The fingerprint
105
+ #
106
+ def get_fingerprint
107
+ idp_cert_fingerprint || begin
108
+ idp_cert = get_idp_cert
109
+ if idp_cert
110
+ fingerprint_alg = XMLSecurity::BaseDocument.new.algorithm(idp_cert_fingerprint_algorithm).new
111
+ fingerprint_alg.hexdigest(idp_cert.to_der).upcase.scan(/../).join(":")
112
+ end
113
+ end
114
+ end
115
+
116
+ # @return [OpenSSL::X509::Certificate|nil] Build the IdP certificate from the settings (previously format it)
117
+ #
118
+ def get_idp_cert
119
+ return nil if idp_cert.nil? || idp_cert.empty?
120
+
121
+ formated_cert = OneLogin::RubySaml::Utils.format_cert(idp_cert)
122
+ OpenSSL::X509::Certificate.new(formated_cert)
123
+ end
124
+
125
+ # @return [OpenSSL::X509::Certificate|nil] Build the SP certificate from the settings (previously format it)
126
+ #
127
+ def get_sp_cert
128
+ return nil if certificate.nil? || certificate.empty?
129
+
130
+ formated_cert = OneLogin::RubySaml::Utils.format_cert(certificate)
131
+ OpenSSL::X509::Certificate.new(formated_cert)
132
+ end
133
+
134
+ # @return [OpenSSL::PKey::RSA] Build the SP private from the settings (previously format it)
135
+ #
136
+ def get_sp_key
137
+ return nil if private_key.nil? || private_key.empty?
138
+
139
+ formated_private_key = OneLogin::RubySaml::Utils.format_private_key(private_key)
140
+ OpenSSL::PKey::RSA.new(formated_private_key)
141
+ end
142
+
143
+ private
144
+
145
+ DEFAULTS = {
146
+ :assertion_consumer_service_binding => "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST".freeze,
147
+ :single_logout_service_binding => "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect".freeze,
148
+ :idp_cert_fingerprint_algorithm => XMLSecurity::Document::SHA1,
149
+ :compress_request => true,
150
+ :compress_response => true,
151
+ :soft => true,
152
+ :security => {
153
+ :authn_requests_signed => false,
154
+ :logout_requests_signed => false,
155
+ :logout_responses_signed => false,
156
+ :metadata_signed => false,
157
+ :embed_sign => false,
158
+ :digest_method => XMLSecurity::Document::SHA1,
159
+ :signature_method => XMLSecurity::Document::RSA_SHA1
160
+ }.freeze,
161
+ :double_quote_xml_attribute_values => false,
162
+ }.freeze
163
+ end
164
+ end
165
+ end
@@ -0,0 +1,258 @@
1
+ require 'zlib'
2
+ require 'time'
3
+ require 'nokogiri'
4
+
5
+ require "onelogin/ruby-saml/saml_message"
6
+
7
+ # Only supports SAML 2.0
8
+ module OneLogin
9
+ module RubySaml
10
+
11
+ # SAML2 Logout Request (SLO IdP initiated, Parser)
12
+ #
13
+ class SloLogoutrequest < SamlMessage
14
+
15
+ # OneLogin::RubySaml::Settings Toolkit settings
16
+ attr_accessor :settings
17
+
18
+ # Array with the causes [Array of strings]
19
+ attr_accessor :errors
20
+
21
+ attr_reader :document
22
+ attr_reader :request
23
+ attr_reader :options
24
+
25
+ attr_accessor :soft
26
+
27
+ # Constructs the Logout Request. A Logout Request Object that is an extension of the SamlMessage class.
28
+ # @param request [String] A UUEncoded Logout Request from the IdP.
29
+ # @param options [Hash] :settings to provide the OneLogin::RubySaml::Settings object
30
+ # Or :allowed_clock_drift for the logout request validation process to allow a clock drift when checking dates with
31
+ #
32
+ # @raise [ArgumentError] If Request is nil
33
+ #
34
+ def initialize(request, options = {})
35
+ @errors = []
36
+ raise ArgumentError.new("Request cannot be nil") if request.nil?
37
+ @options = options
38
+
39
+ @soft = true
40
+ if !options.empty? && !options[:settings].nil?
41
+ @settings = options[:settings]
42
+ if !options[:settings].soft.nil?
43
+ @soft = options[:settings].soft
44
+ end
45
+ end
46
+
47
+ @request = decode_raw_saml(request)
48
+ @document = REXML::Document.new(@request)
49
+ end
50
+
51
+ # Append the cause to the errors array, and based on the value of soft, return false or raise
52
+ # an exception
53
+ def append_error(error_msg)
54
+ @errors << error_msg
55
+ return soft ? false : validation_error(error_msg)
56
+ end
57
+
58
+ # Reset the errors array
59
+ def reset_errors!
60
+ @errors = []
61
+ end
62
+
63
+ # Validates the Logout Request with the default values (soft = true)
64
+ # @return [Boolean] TRUE if the Logout Request is valid
65
+ #
66
+ def is_valid?
67
+ validate
68
+ end
69
+
70
+ # @return [String] Gets the NameID of the Logout Request.
71
+ #
72
+ def name_id
73
+ @name_id ||= begin
74
+ node = REXML::XPath.first(document, "/p:LogoutRequest/a:NameID", { "p" => PROTOCOL, "a" => ASSERTION })
75
+ node.nil? ? nil : node.text
76
+ end
77
+ end
78
+
79
+ alias_method :nameid, :name_id
80
+
81
+ # @return [String|nil] Gets the ID attribute from the Logout Request. if exists.
82
+ #
83
+ def id
84
+ super(document)
85
+ end
86
+
87
+ # @return [String] Gets the Issuer from the Logout Request.
88
+ #
89
+ def issuer
90
+ @issuer ||= begin
91
+ node = REXML::XPath.first(
92
+ document,
93
+ "/p:LogoutRequest/a:Issuer",
94
+ { "p" => PROTOCOL, "a" => ASSERTION }
95
+ )
96
+ node.nil? ? nil : node.text
97
+ end
98
+ end
99
+
100
+ # @return [Time|nil] Gets the NotOnOrAfter Attribute value if exists.
101
+ #
102
+ def not_on_or_after
103
+ @not_on_or_after ||= begin
104
+ node = REXML::XPath.first(
105
+ document,
106
+ "/p:LogoutRequest",
107
+ { "p" => PROTOCOL }
108
+ )
109
+ if node && node.attributes["NotOnOrAfter"]
110
+ Time.parse(node.attributes["NotOnOrAfter"])
111
+ end
112
+ end
113
+ end
114
+
115
+ # @return [Array] Gets the SessionIndex if exists (Supported multiple values). Empty Array if none found
116
+ #
117
+ def session_indexes
118
+ s_indexes = []
119
+ nodes = REXML::XPath.match(
120
+ document,
121
+ "/p:LogoutRequest/p:SessionIndex",
122
+ { "p" => PROTOCOL }
123
+ )
124
+
125
+ nodes.each do |node|
126
+ s_indexes << node.text
127
+ end
128
+
129
+ s_indexes
130
+ end
131
+
132
+ private
133
+
134
+ # Hard aux function to validate the Logout Request
135
+ # @return [Boolean] TRUE if the Logout Request is valid
136
+ # @raise [ValidationError] if soft == false and validation fails
137
+ #
138
+ def validate
139
+ reset_errors!
140
+
141
+ validate_request_state &&
142
+ validate_id &&
143
+ validate_version &&
144
+ validate_structure &&
145
+ validate_not_on_or_after &&
146
+ validate_issuer &&
147
+ validate_signature
148
+ end
149
+
150
+ # Validates that the Logout Request contains an ID
151
+ # If fails, the error is added to the errors array.
152
+ # @return [Boolean] True if the Logout Request contains an ID, otherwise returns False
153
+ #
154
+ def validate_id
155
+ unless id
156
+ return append_error("Missing ID attribute on Logout Request")
157
+ end
158
+
159
+ true
160
+ end
161
+
162
+ # Validates the SAML version (2.0)
163
+ # If fails, the error is added to the errors array.
164
+ # @return [Boolean] True if the Logout Request is 2.0, otherwise returns False
165
+ #
166
+ def validate_version
167
+ unless version(document) == "2.0"
168
+ return append_error("Unsupported SAML version")
169
+ end
170
+
171
+ true
172
+ end
173
+
174
+ # Validates the time. (If the logout request was initialized with the :allowed_clock_drift option, the timing validations are relaxed by the allowed_clock_drift value)
175
+ # If fails, the error is added to the errors array
176
+ # @return [Boolean] True if satisfies the conditions, otherwise False if soft=True
177
+ # @raise [ValidationError] if soft == false and validation fails
178
+ #
179
+ def validate_not_on_or_after
180
+ now = Time.now.utc
181
+ if not_on_or_after && now >= (not_on_or_after + (options[:allowed_clock_drift] || 0))
182
+ return append_error("Current time is on or after NotOnOrAfter (#{now} >= #{not_on_or_after})")
183
+ end
184
+
185
+ true
186
+ end
187
+
188
+ # Validates the Logout Request against the specified schema.
189
+ # @return [Boolean] True if the XML is valid, otherwise False if soft=True
190
+ # @raise [ValidationError] if soft == false and validation fails
191
+ #
192
+ def validate_structure
193
+ unless valid_saml?(document, soft)
194
+ return append_error("Invalid SAML Logout Request. Not match the saml-schema-protocol-2.0.xsd")
195
+ end
196
+
197
+ true
198
+ end
199
+
200
+ # Validates that the Logout Request provided in the initialization is not empty,
201
+ # @return [Boolean] True if the required info is found, otherwise False if soft=True
202
+ # @raise [ValidationError] if soft == false and validation fails
203
+ #
204
+ def validate_request_state
205
+ return append_error("Blank logout request") if request.nil? || request.empty?
206
+
207
+ true
208
+ end
209
+
210
+ # Validates the Issuer of the Logout Request
211
+ # If fails, the error is added to the errors array
212
+ # @return [Boolean] True if the Issuer matchs the IdP entityId, otherwise False if soft=True
213
+ # @raise [ValidationError] if soft == false and validation fails
214
+ #
215
+ def validate_issuer
216
+ return true if settings.idp_entity_id.nil? || issuer.nil?
217
+
218
+ unless URI.parse(issuer) == URI.parse(settings.idp_entity_id)
219
+ return append_error("Doesn't match the issuer, expected: <#{settings.idp_entity_id}>, but was: <#{issuer}>")
220
+ end
221
+
222
+ true
223
+ end
224
+
225
+ # Validates the Signature if exists and GET parameters are provided
226
+ # @return [Boolean] True if not contains a Signature or if the Signature is valid, otherwise False if soft=True
227
+ # @raise [ValidationError] if soft == false and validation fails
228
+ #
229
+ def validate_signature
230
+ return true if options.nil?
231
+ return true unless options.has_key? :get_params
232
+ return true unless options[:get_params].has_key? 'Signature'
233
+ return true if settings.nil? || settings.get_idp_cert.nil?
234
+
235
+ query_string = OneLogin::RubySaml::Utils.build_query(
236
+ :type => 'SAMLRequest',
237
+ :data => options[:get_params]['SAMLRequest'],
238
+ :relay_state => options[:get_params]['RelayState'],
239
+ :sig_alg => options[:get_params]['SigAlg']
240
+ )
241
+
242
+ valid = OneLogin::RubySaml::Utils.verify_signature(
243
+ :cert => settings.get_idp_cert,
244
+ :sig_alg => options[:get_params]['SigAlg'],
245
+ :signature => options[:get_params]['Signature'],
246
+ :query_string => query_string
247
+ )
248
+
249
+ unless valid
250
+ return append_error("Invalid Signature on Logout Request")
251
+ end
252
+
253
+ true
254
+ end
255
+
256
+ end
257
+ end
258
+ end