kl-ruby-saml 0.0.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.
Files changed (137) hide show
  1. checksums.yaml +7 -0
  2. data/.document +5 -0
  3. data/.gitignore +14 -0
  4. data/.travis.yml +17 -0
  5. data/Gemfile +9 -0
  6. data/LICENSE +19 -0
  7. data/README.md +575 -0
  8. data/Rakefile +41 -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 +156 -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 +722 -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 +358 -0
  43. data/ruby-saml.gemspec +57 -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 +1094 -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_wrapped.xml.base64 +150 -0
  117. data/test/responses/signed_message_encrypted_signed_assertion.xml.base64 +1 -0
  118. data/test/responses/signed_message_encrypted_unsigned_assertion.xml.base64 +1 -0
  119. data/test/responses/simple_saml_php.xml +71 -0
  120. data/test/responses/starfield_response.xml.base64 +1 -0
  121. data/test/responses/test_sign.xml +43 -0
  122. data/test/responses/unsigned_message_aes128_encrypted_signed_assertion.xml.base64 +1 -0
  123. data/test/responses/unsigned_message_aes192_encrypted_signed_assertion.xml.base64 +1 -0
  124. data/test/responses/unsigned_message_aes256_encrypted_signed_assertion.xml.base64 +1 -0
  125. data/test/responses/unsigned_message_des192_encrypted_signed_assertion.xml.base64 +1 -0
  126. data/test/responses/unsigned_message_encrypted_assertion_without_saml_namespace.xml.base64 +1 -0
  127. data/test/responses/unsigned_message_encrypted_signed_assertion.xml.base64 +1 -0
  128. data/test/responses/unsigned_message_encrypted_unsigned_assertion.xml.base64 +1 -0
  129. data/test/responses/valid_response.xml.base64 +1 -0
  130. data/test/saml_message_test.rb +56 -0
  131. data/test/settings_test.rb +218 -0
  132. data/test/slo_logoutrequest_test.rb +275 -0
  133. data/test/slo_logoutresponse_test.rb +185 -0
  134. data/test/test_helper.rb +252 -0
  135. data/test/utils_test.rb +145 -0
  136. data/test/xml_security_test.rb +329 -0
  137. metadata +415 -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
+ @schema ||= 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