From 7ae2af6cac086777b121cbd128edf5e81caf8c6f Mon Sep 17 00:00:00 2001 From: Sixto Martin Date: Fri, 21 Nov 2025 10:41:24 +0100 Subject: [PATCH 1/7] Fix typos on README and UPGRADING --- README.md | 26 +++++++++++++------------- UPGRADING.md | 17 +++++++++-------- 2 files changed, 22 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index e2365249..af6facef 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ Response assertions from Identity Providers (IdPs). **Important:** This libary does not support the IdP-side of SAML authentication, such as creating SAML Response messages to assert a user's identity. -A Rails 4 reference implemenation is avaiable at the +A Rails 4 reference implementation is available at the [Ruby SAML Demo Project](https://github.com/saml-toolkits/ruby-saml-example). ### Vulnerability Reporting @@ -46,9 +46,10 @@ it by email to the maintainer: sixto.martin.garcia+security@gmail.com and from a trusted source. Ruby SAML does not perform any validation that the URL you entered is correct and/or safe. - **False-Positive Security Warnings:** Some tools may incorrectly report Ruby SAML as a - potential security vulnerability, due to it's dependency on Nokogiri. Such warnings can + potential security vulnerability, due to its dependency on Nokogiri. Such warnings can be ignored; Ruby SAML uses Nokogiri in a safe way, by always disabling its DTDLOAD option and enabling its NONET option. +- **Prevent Replay attacks:** A replay attack is when an attacker intercepts a valid SAML assertion and "replays" it at a later time to gain unauthorized access. The `ruby-saml` library provides the tools to prevent this, but **you, the developer, must implement the core logic**, see an specific section later in the README. ### Supported Ruby Versions @@ -179,7 +180,7 @@ def saml_settings end ``` -The use of settings.issuer is deprecated in favour of settings.sp_entity_id since version 1.11.0 +The use of settings.issuer is deprecated in favor of settings.sp_entity_id since version 1.11.0 Some assertion validations can be skipped by passing parameters to `RubySaml::Response.new()`. For example, you can skip the `AuthnStatement`, `Conditions`, `Recipient`, or the `SubjectConfirmation` @@ -255,13 +256,13 @@ Ruby SAML allows different ways to validate the signature of the SAML Response: `idp_cert_fingerprint` and `idp_cert_fingerprint_algorithm` parameters. In addition, you may pass the option `:relax_signature_validation` to `SloLogoutrequest` and -`Logoutresponse` if want to skip signature validation on logout. +`Logoutresponse` if you want to skip signature validation on logout. The `idp_cert_fingerprint` option is deprecated for the following reasons. It will be removed in Ruby SAML version 2.1.0. 1. It only works with HTTP-POST binding, not HTTP-Redirect, since the full certificate is not sent in the Redirect URL parameters. -2. It is theoretically be susceptible to collision attacks, by which a malicious +2. It is theoretically susceptible to collision attacks, by which a malicious actor could impersonate the IdP. (However, as of January 2025, such attacks have not been publicly demonstrated for SHA-256.) 3. It has been removed already from several other SAML libraries in other languages. @@ -365,8 +366,7 @@ Those return an Hash instead of a `Settings` object, which may be useful for con ### Validating Signature of Metadata and retrieve settings -Right now there is no method at ruby_saml to validate the signature of the metadata that gonna be parsed, -but it can be done as follows: +Right now there is no method at ruby_saml to validate the signature of the metadata that is going to be parsed, but it can be done as follows: * Download the XML. * Validate the Signature, providing the cert. * Provide the XML to the parse method if the signature was validated @@ -403,7 +403,7 @@ if valid entity_id: "" ) else - print "Metadata Signarture failed to be verified with the cert provided" + print "Metadata Signature failed to be verified with the cert provided" end ``` @@ -632,7 +632,7 @@ settings.security[:logout_requests_signed] = true # Enable signature on Logout settings.security[:logout_responses_signed] = true # Enable signature on Logout Response ``` -Signatures will be handled automatically for both `HTTP-Redirect` and `HTTP-Redirect` Binding. +Signatures will be handled automatically for both `HTTP-POST` and `HTTP-Redirect` Binding. Note that the RelayState parameter is used when creating the Signature on the `HTTP-Redirect` Binding. Remember to provide it to the Signature builder if you are sending a `GET RelayState` parameter or the signature validation process will fail at the IdP. @@ -655,7 +655,7 @@ settings.security[:want_assertions_encrypted] = true # Invalidate SAML messages ### Verifying Signature on IdP Assertions You may require the IdP to sign its SAML Assertions using the following setting. -With will add `` to your SP Metadata XML. +This will add `` to your SP Metadata XML. The signature will be checked against the `` element present in the IdP's metadata. @@ -729,7 +729,7 @@ JRuby cannot support ECDSA due to a [known issue](https://github.com/jruby/jruby ### Audience Validation A service provider should only consider a SAML response valid if the IdP includes an -element containting an element that uniquely identifies the service provider. Unless you specify +element containing an element that uniquely identifies the service provider. Unless you specify the `skip_audience` option, Ruby SAML will validate that each SAML response includes an element whose contents matches `settings.sp_entity_id`. @@ -762,7 +762,7 @@ def sp_logout_request settings = saml_settings if settings.idp_slo_service_url.nil? - logger.info "SLO IdP Endpoint not found in settings, executing then a normal logout'" + logger.info "SLO IdP Endpoint not found in settings, then executing a normal logout'" delete_session else @@ -936,7 +936,7 @@ or underscore, and can only contain letters, digits, underscores, hyphens, and p ### Custom Metadata Fields -Some IdPs may require to add SPs to add additional fields (Organization, ContactPerson, etc.) +Some IdPs may require SPs to add additional fields (Organization, ContactPerson, etc.) into the SP metadata. This can be done by extending the `RubySaml::Metadata` class and overriding the `#add_extras` method where the first arg is a [Nokogiri::XML::Builder](https://nokogiri.org/rdoc/Nokogiri/XML/Builder.html) object as per diff --git a/UPGRADING.md b/UPGRADING.md index 9c84103f..5ac25dd5 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -26,8 +26,8 @@ This issue is likely not critical for most IdPs, but since it is not tested, it ### Root "OneLogin" namespace changed to "RubySaml" -RubySaml version `2.0.0` changes the root namespace from `RubySaml::` to just `RubySaml::`. -Please remove `` and `onelogin/` everywhere in your codebase. Aside from this namespace change, +RubySaml version `2.0.0` changes the root namespace from `OneLogin::RubySaml::` to just `RubySaml::`. +Please remove `onelogin/` everywhere in your codebase. Aside from this namespace change, the class names themselves have intentionally been kept the same. Note that the project folder structure has also been updated accordingly. Notably, the directory @@ -36,6 +36,7 @@ Note that the project folder structure has also been updated accordingly. Notabl For backward compatibility, the alias `OneLogin = Object` has been set, so `RubySaml::` will still work as before. This alias will be removed in RubySaml version `3.0.0`. + ### Deprecation and removal of "XMLSecurity" namespace RubySaml version `2.0.0` deprecates the `::XMLSecurity` namespace and the following classes: @@ -75,7 +76,7 @@ settings.security[:signature_method] = RubySaml::XML::RSA_SHA1 RubySaml `1.x` used a combination of REXML and Nokogiri for XML parsing and generation. In `2.0.0`, REXML has been replaced with Nokogiri. As a result, there are minor differences -in how XML is generated, ncluding SAML requests and SP Metadata: +in how XML is generated, including SAML requests and SP Metadata: 1. All XML namespace declarations will be on the root node of the XML. Previously, some declarations such as `xmlns:ds` were done on child nodes. @@ -121,7 +122,7 @@ The reasons for this change are: ### Removal of embed_sign setting The deprecated `settings.security[:embed_sign]` parameter has been removed. If you were using it, please instead switch -to using both the `settings.idp_sso_service_binding` and `settings.idp_slo_service_binding` parameters as show below. +to using both the `settings.idp_sso_service_binding` and `settings.idp_slo_service_binding` parameters as shown below. (This new syntax is supported on version 1.13.0 and later.) ```ruby @@ -231,7 +232,7 @@ when parsing a SAML Message (`settings.check_malformed_doc`). The SignedDocument class defined at xml_security.rb experienced several changes. We don't expect compatibilty issues if you use the main methods offered by ruby-saml, but if -you use a fork or customized usage, is possible that you need to adapt your code. +you use a fork or customized usage, it is possible that you will need to adapt your code. ## Upgrading from 1.12.x to 1.13.0 @@ -257,7 +258,7 @@ in favor of `idp_sso_service_url` and `idp_slo_service_url`. The `IdpMetadataPar ## Upgrading from 1.10.x to 1.11.0 -Version `1.11.0` deprecates the use of `settings.issuer` in favour of `settings.sp_entity_id`. +Version `1.11.0` deprecates the use of `settings.issuer` in favor of `settings.sp_entity_id`. There are two new security settings: `settings.security[:check_idp_cert_expiration]` and `settings.security[:check_sp_cert_expiration]` (both false by default) that check if the IdP or SP X.509 certificate has expired, respectively. @@ -352,7 +353,7 @@ It adds security improvements in order to prevent Signature wrapping attacks. ## Upgrading from 1.1.x to 1.2.x -Version `1.2` adds IDP metadata parsing improvements, uuid deprecation in favour of SecureRandom, +Version `1.2` adds IDP metadata parsing improvements, uuid deprecation in favor of SecureRandom, refactor error handling and some minor improvements. There is no compatibility issue detected. @@ -367,7 +368,7 @@ Version `1.1` adds some improvements on signature validation and solves some nam Version `1.0` is a recommended update for all Ruby SAML users as it includes security fixes. -Version `1.0` adds security improvements like entity expansion limitation, more SAML message validations, and other important improvements like decrypt support. +Version `1.0` adds security improvements like entity expansion limitation, more SAML message validations, and other important improvements like decryption support. ### Important Changes From 428926357cd8df5be91629e078b6181b126ad6b6 Mon Sep 17 00:00:00 2001 From: Sixto Martin Date: Fri, 21 Nov 2025 11:22:43 +0100 Subject: [PATCH 2/7] Adjust OneLogin namespace compatibility, defining Module instead Alias to Object. Fix compatibility with Logging --- UPGRADING.md | 11 +++- lib/ruby_saml.rb | 16 +++++- test/logging_test.rb | 51 +++++++++++++++++++ ...st.rb => onelogin_rubysaml_compat_test.rb} | 32 +++++++++--- 4 files changed, 99 insertions(+), 11 deletions(-) rename test/{onelogin_alias_test.rb => onelogin_rubysaml_compat_test.rb} (90%) diff --git a/UPGRADING.md b/UPGRADING.md index 5ac25dd5..75787c88 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -33,8 +33,15 @@ the class names themselves have intentionally been kept the same. Note that the project folder structure has also been updated accordingly. Notably, the directory `lib/onelogin/schemas` is now `lib/ruby_saml/schemas`. -For backward compatibility, the alias `OneLogin = Object` has been set, so `RubySaml::` will still work -as before. This alias will be removed in RubySaml version `3.0.0`. +For backward compatibility, a module is defined at lib/ruby_saml.rb, so `RubySaml::` will still work +as before. This module will be removed in RubySaml version `3.0.0`. +``` +unless defined?(::OneLogin) + module OneLogin + RubySaml = ::RubySaml + end +end +``` ### Deprecation and removal of "XMLSecurity" namespace diff --git a/lib/ruby_saml.rb b/lib/ruby_saml.rb index cefe824b..f3da7181 100644 --- a/lib/ruby_saml.rb +++ b/lib/ruby_saml.rb @@ -20,5 +20,17 @@ require 'ruby_saml/utils' require 'ruby_saml/version' -# @deprecated This alias adds compatibility with v1.x and will be removed in v3.0.0 -OneLogin = Object +# @deprecated This module adds compatibility with v1.x and will be removed in v3.0.0 +unless defined?(::OneLogin) + module OneLogin + RubySaml = ::RubySaml + end +end + +unless defined?(::OneLogin::RubySaml::Logging) + module OneLogin + module RubySaml + Logging = ::RubySaml::Logging + end + end +end \ No newline at end of file diff --git a/test/logging_test.rb b/test/logging_test.rb index 65cb1da3..ac360c13 100644 --- a/test/logging_test.rb +++ b/test/logging_test.rb @@ -58,5 +58,56 @@ class LoggingTest < Minitest::Test RubySaml::Logging.info('sup?') end end + + describe "OneLogin::RubySaml::Logging compatibility" do + it "defines OneLogin::RubySaml::Logging as an alias to RubySaml::Logging" do + assert defined?(OneLogin::RubySaml::Logging), + "Expected OneLogin::RubySaml::Logging to be defined" + + assert_equal RubySaml::Logging.object_id, + OneLogin::RubySaml::Logging.object_id, + "Expected OneLogin::RubySaml::Logging to alias RubySaml::Logging" + end + + it "shares the same logger instance when set via RubySaml::Logging" do + logger = mock('Logger') + RubySaml::Logging.logger = logger + + assert_same logger, OneLogin::RubySaml::Logging.logger + end + + it "shares the same logger instance when set via OneLogin::RubySaml::Logging" do + logger = mock('Logger') + OneLogin::RubySaml::Logging.logger = logger + + assert_same logger, RubySaml::Logging.logger + end + + it "delegates to the configured logger when using the legacy constant" do + logger = mock('Logger') + OneLogin::RubySaml::Logging.logger = logger + + logger.expects(:debug).with('hi mom') + logger.expects(:info).with('sup?') + + OneLogin::RubySaml::Logging.debug('hi mom') + OneLogin::RubySaml::Logging.info('sup?') + end + + it "respects ENV['ruby-saml/testing'] and does not log when set (legacy constant)" do + logger = mock('Logger') + OneLogin::RubySaml::Logging.logger = logger + + ENV["ruby-saml/testing"] = "1" + + # No expectations on logger; any call would cause Mocha to fail the test. + OneLogin::RubySaml::Logging.debug('hi mom') + OneLogin::RubySaml::Logging.info('sup?') + OneLogin::RubySaml::Logging.warn('hey') + OneLogin::RubySaml::Logging.error('oops') + + ENV.delete("ruby-saml/testing") + end + end end end diff --git a/test/onelogin_alias_test.rb b/test/onelogin_rubysaml_compat_test.rb similarity index 90% rename from test/onelogin_alias_test.rb rename to test/onelogin_rubysaml_compat_test.rb index 174fc6bd..5d62203e 100644 --- a/test/onelogin_alias_test.rb +++ b/test/onelogin_rubysaml_compat_test.rb @@ -2,16 +2,34 @@ require_relative 'test_helper' -class OneloginAliasTest < Minitest::Test +class OneLoginRubySamlCompatTest < Minitest::Test - describe 'legacy OneLogin namespace alias' do + describe 'legacy OneLogin namespace compatibility' do - describe 'equality with Object' do - it "should be equal" do - assert_equal OneLogin, Object - assert_equal ::OneLogin, Object - assert_equal OneLogin::RubySaml, OneLogin::RubySaml + describe 'namespace equality' do + it "defines OneLogin::RubySaml as an alias to the RubySaml module" do + assert defined?(OneLogin::RubySaml), "Expected OneLogin::RubySaml to be defined" assert_equal ::OneLogin::RubySaml, ::RubySaml + assert_equal RubySaml.object_id, OneLogin::RubySaml.object_id, "Expected OneLogin::RubySaml to alias RubySaml" + end + + it "exposes Logging under OneLogin::RubySaml::Logging as an alias to RubySaml::Logging" do + assert defined?(OneLogin::RubySaml::Logging), "Expected OneLogin::RubySaml::Logging to be defined" + assert_equal ::OneLogin::RubySaml::Logging, ::RubySaml::Logging + assert_equal RubySaml::Logging.object_id, OneLogin::RubySaml::Logging.object_id, "Expected OneLogin::RubySaml::Logging to alias RubySaml::Logging" + end + + it "shares the same logger instance between RubySaml::Logging and OneLogin::RubySaml::Logging" do + logger = mock("Logger") + + RubySaml::Logging.logger = logger + assert_same logger, OneLogin::RubySaml::Logging.logger + + other_logger = mock("OtherLogger") + OneLogin::RubySaml::Logging.logger = other_logger + assert_same other_logger, RubySaml::Logging.logger + ensure + RubySaml::Logging.logger = ::TEST_LOGGER end end From 3879a5e41872ff2aa2bc53dfd7a0191d179c6646 Mon Sep 17 00:00:00 2001 From: Sixto Martin Date: Fri, 21 Nov 2025 11:23:51 +0100 Subject: [PATCH 3/7] Adjust regular expression for base64_encoded? to avoid 'character class has duplicated range' warning --- lib/ruby_saml/xml/decoder.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ruby_saml/xml/decoder.rb b/lib/ruby_saml/xml/decoder.rb index 6d8e58d0..10e12787 100644 --- a/lib/ruby_saml/xml/decoder.rb +++ b/lib/ruby_saml/xml/decoder.rb @@ -61,7 +61,7 @@ def base64_encode(string) # @param string [String] string to check the encoding of # @return [true, false] whether or not the string is base64 encoded def base64_encoded?(string) - string.gsub(/[\s\r\n]|\\r|\\n/, '').match?(BASE64_FORMAT) + string.gsub(/\s|\\r|\\n/, '').match?(BASE64_FORMAT) end # Attempt inflating a string, if it fails, return the original string. From cff9c06abb4a26e068f72d2cf0438d50034c32fd Mon Sep 17 00:00:00 2001 From: Sixto Martin Date: Fri, 21 Nov 2025 18:06:07 +0100 Subject: [PATCH 4/7] Fix Rubocop and minor typos --- UPGRADING.md | 4 +- lib/ruby_saml.rb | 20 +- lib/ruby_saml/attributes.rb | 2 +- lib/ruby_saml/idp_metadata_parser.rb | 12 +- lib/ruby_saml/logoutresponse.rb | 14 +- lib/ruby_saml/metadata.rb | 2 +- lib/ruby_saml/response.rb | 30 +- lib/ruby_saml/settings.rb | 4 +- lib/ruby_saml/slo_logoutrequest.rb | 18 +- lib/ruby_saml/slo_logoutresponse.rb | 4 +- lib/ruby_saml/utils.rb | 8 +- lib/ruby_saml/xml/decryptor.rb | 4 +- test/attributes_test.rb | 8 +- test/authrequest_test.rb | 255 ++-- test/idp_metadata_parser_test.rb | 14 +- test/logoutrequest_test.rb | 310 ++-- test/logoutresponse_test.rb | 12 +- test/metadata_test.rb | 288 ++-- test/onelogin_rubysaml_compat_test.rb | 4 +- test/pem_formatter_test.rb | 16 +- test/response_test.rb | 1330 +++++++++-------- ... status_code_responder_and_msg.xml.base64} | 0 test/settings_test.rb | 526 ++++--- test/slo_logoutrequest_test.rb | 268 ++-- test/slo_logoutresponse_test.rb | 4 +- test/test_helper.rb | 687 ++++----- test/utils_test.rb | 8 +- test/xml_security_shim_test.rb | 8 +- test/xml_test.rb | 4 +- 29 files changed, 2063 insertions(+), 1801 deletions(-) rename test/responses/invalids/{status_code_responer_and_msg.xml.base64 => status_code_responder_and_msg.xml.base64} (100%) diff --git a/UPGRADING.md b/UPGRADING.md index 75787c88..1fb31290 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -238,7 +238,7 @@ how validation happens in the toolkit and also the toolkit by default will check when parsing a SAML Message (`settings.check_malformed_doc`). The SignedDocument class defined at xml_security.rb experienced several changes. -We don't expect compatibilty issues if you use the main methods offered by ruby-saml, but if +We don't expect compatibility issues if you use the main methods offered by ruby-saml, but if you use a fork or customized usage, it is possible that you will need to adapt your code. ## Upgrading from 1.12.x to 1.13.0 @@ -276,7 +276,7 @@ Version `1.10.1` improves Ruby 1.8.7 support. ## Upgrading from 1.9.0 to 1.10.0 -Version `1.10.0` improves IdpMetadataParser to allow parse multiple IDPSSODescriptor, +Version `1.10.0` improves IdpMetadataParser to allow parsing multiple IDPSSODescriptor, Add Subject support on AuthNRequest to allow SPs provide info to the IdP about the user to be authenticated and updates the format_cert method to accept certs with /\x0d/ diff --git a/lib/ruby_saml.rb b/lib/ruby_saml.rb index f3da7181..793a1930 100644 --- a/lib/ruby_saml.rb +++ b/lib/ruby_saml.rb @@ -21,16 +21,16 @@ require 'ruby_saml/version' # @deprecated This module adds compatibility with v1.x and will be removed in v3.0.0 -unless defined?(::OneLogin) - module OneLogin - RubySaml = ::RubySaml - end +unless defined?(OneLogin) + module OneLogin + RubySaml = ::RubySaml + end end -unless defined?(::OneLogin::RubySaml::Logging) - module OneLogin - module RubySaml - Logging = ::RubySaml::Logging - end +unless defined?(OneLogin::RubySaml::Logging) + module OneLogin + module RubySaml + Logging = ::RubySaml::Logging end -end \ No newline at end of file + end +end diff --git a/lib/ruby_saml/attributes.rb b/lib/ruby_saml/attributes.rb index b4c1dbe3..8514e274 100644 --- a/lib/ruby_saml/attributes.rb +++ b/lib/ruby_saml/attributes.rb @@ -102,7 +102,7 @@ def add(name, values = []) # Make comparable to another Attributes collection based on attributes # @param other [Attributes] An Attributes object to compare with - # @return [Boolean] True if are contains the same attributes and values + # @return [Boolean] True if it contains the same attributes and values # def ==(other) if other.is_a?(Attributes) diff --git a/lib/ruby_saml/idp_metadata_parser.rb b/lib/ruby_saml/idp_metadata_parser.rb index 666155c3..7e1551d5 100644 --- a/lib/ruby_saml/idp_metadata_parser.rb +++ b/lib/ruby_saml/idp_metadata_parser.rb @@ -31,7 +31,7 @@ def self.get_idps(noko_document, only_entity_id = nil) # IdP values # # @param url [String] Url where the XML of the Identity Provider Metadata is published. - # @param validate_cert [Boolean] If true and the URL is HTTPs, the cert of the domain is checked. + # @param validate_cert [Boolean] If true and the URL is HTTPS, the cert of the domain is checked. # # @param options [Hash] options used for parsing the metadata and the returned Settings instance # @option options [RubySaml::Settings, Hash] :settings the RubySaml::Settings object which gets the parsed metadata merged into or an hash for Settings overrides. @@ -54,7 +54,7 @@ def parse_remote(url, validate_cert = true, options = {}) # Parse the Identity Provider metadata and return the results as Hash # # @param url [String] Url where the XML of the Identity Provider Metadata is published. - # @param validate_cert [Boolean] If true and the URL is HTTPs, the cert of the domain is checked. + # @param validate_cert [Boolean] If true and the URL is HTTPS, the cert of the domain is checked. # # @param options [Hash] options used for parsing the metadata # @option options [String, nil] :entity_id when this is given, the entity descriptor for this ID is used. When omitted, the first entity descriptor is used. @@ -75,7 +75,7 @@ def parse_remote_to_hash(url, validate_cert = true, options = {}) # Parse all Identity Provider metadata and return the results as Array # # @param url [String] Url where the XML of the Identity Provider Metadata is published. - # @param validate_cert [Boolean] If true and the URL is HTTPs, the cert of the domain is checked. + # @param validate_cert [Boolean] If true and the URL is HTTPS, the cert of the domain is checked. # # @param options [Hash] options used for parsing the metadata # @option options [String, nil] :entity_id when this is given, the entity descriptor for this ID is used. When omitted, all found IdPs are returned. @@ -116,7 +116,7 @@ def parse(idp_metadata, options = {}) end end # Remove the cache_duration because on the settings - # we only gonna suppot valid_until + # we only going to support valid_until parsed_metadata.delete(:cache_duration) settings = options[:settings] @@ -174,7 +174,7 @@ def parse_to_idp_metadata_array(idp_metadata, options = {}) # Retrieve the remote IdP metadata from the URL or a cached copy. # @param url [String] Url where the XML of the Identity Provider Metadata is published. - # @param validate_cert [Boolean] If true and the URL is HTTPs, the cert of the domain is checked. + # @param validate_cert [Boolean] If true and the URL is HTTPS, the cert of the domain is checked. # @param options [Hash] Options used for requesting the remote URL # @option options [Numeric, nil] :open_timeout Number of seconds to wait for the connection to open. See Net::HTTP#open_timeout for more info. Default is the Net::HTTP default. # @option options [Numeric, nil] :read_timeout Number of seconds to wait for one block to be read. See Net::HTTP#read_timeout for more info. Default is the Net::HTTP default. @@ -362,7 +362,7 @@ def certificates end end - # @return [String|nil] the fingerpint of the X509Certificate if it exists + # @return [String|nil] the fingerprint of the X509Certificate if it exists # def fingerprint(certificate, fingerprint_algorithm = RubySaml::XML::SHA256) return unless certificate diff --git a/lib/ruby_saml/logoutresponse.rb b/lib/ruby_saml/logoutresponse.rb index 86537974..3368675e 100644 --- a/lib/ruby_saml/logoutresponse.rb +++ b/lib/ruby_saml/logoutresponse.rb @@ -18,13 +18,13 @@ class Logoutresponse < SamlMessage attr_accessor :soft - # Constructs the Logout Response. A Logout Response Object that is an extension of the SamlMessage class. + # Constructs the Logout Response. A Logout Response object that is an extension of the SamlMessage class. # @param response [String] A UUEncoded logout response from the IdP. # @param settings [RubySaml::Settings|nil] Toolkit settings # @param options [Hash] Extra parameters. # :matches_request_id It will validate that the logout response matches the ID of the request. # :get_params GET Parameters, including the SAMLResponse - # :relax_signature_validation to accept signatures if no idp certificate registered on settings + # :relax_signature_validation to accept signatures if no IdP certificate registered on settings # # @raise [ArgumentError] if response is nil # @@ -50,7 +50,7 @@ def response_id end # Checks if the Status has the "Success" code - # @return [Boolean] True if the StatusCode is Sucess + # @return [Boolean] True if the StatusCode is Success # @raise [ValidationError] if soft == false and validation fails # def success? @@ -145,7 +145,7 @@ def validate_structure end # Validates that the Logout Response provided in the initialization is not empty, - # also check that the setting and the IdP cert were also provided + # also check that the settings and the IdP cert were also provided # @return [Boolean] True if the required info is found, otherwise False if soft=True # @raise [ValidationError] if soft == false and validation fails # @@ -163,9 +163,9 @@ def valid_state? true end - # Validates if a provided :matches_request_id matchs the inResponseTo value. + # Validates if a provided :matches_request_id matches the inResponseTo value. # @param soft [String|nil] request_id The ID of the Logout Request sent by this SP to the IdP (if was sent any) - # @return [Boolean] True if there is no request_id or it match, otherwise False if soft=True + # @return [Boolean] True if there is no request_id or it matches, otherwise False if soft=True # @raise [ValidationError] if soft == false and validation fails # def valid_in_response_to? @@ -178,7 +178,7 @@ def valid_in_response_to? end # Validates the Issuer of the Logout Response - # @return [Boolean] True if the Issuer matchs the IdP entityId, otherwise False if soft=True + # @return [Boolean] True if the Issuer matches the IdP entityId, otherwise False if soft=True # @raise [ValidationError] if soft == false and validation fails # def valid_issuer? diff --git a/lib/ruby_saml/metadata.rb b/lib/ruby_saml/metadata.rb index 58a64ce4..10c30fc4 100644 --- a/lib/ruby_saml/metadata.rb +++ b/lib/ruby_saml/metadata.rb @@ -10,7 +10,7 @@ class Metadata # Return SP metadata based on the settings. # @param settings [RubySaml::Settings|nil] Toolkit settings # @param pretty_print [Boolean] Pretty print or not the response - # (No pretty print if you gonna validate the signature) + # (No pretty print if you are going to validate the signature) # @param valid_until [DateTime] Metadata's valid time # @param cache_duration [Integer] Duration of the cache in seconds # @return [String] XML Metadata of the Service Provider diff --git a/lib/ruby_saml/response.rb b/lib/ruby_saml/response.rb index 10bd6931..127c14c7 100644 --- a/lib/ruby_saml/response.rb +++ b/lib/ruby_saml/response.rb @@ -70,7 +70,7 @@ def initialize(response, options = {}) end # Validates the SAML Response with the default values (soft = true) - # @param collect_errors [Boolean] Stop validation when first error appears or keep validating. (if soft=true) + # @param collect_errors [Boolean] Stop validation when the first error appears or keep validating. (if soft=true) # @return [Boolean] TRUE if the SAML Response is valid # def is_valid?(collect_errors = false) @@ -209,7 +209,7 @@ def authn_context_class_ref end # Checks if the Status has the "Success" code - # @return [Boolean] True if the StatusCode is Sucess + # @return [Boolean] True if the StatusCode is Success # def success? status_code == 'urn:oasis:names:tc:SAML:2.0:status:Success' @@ -430,7 +430,7 @@ def validate_structure end # Validates that the SAML Response provided in the initialization is not empty, - # also check that the setting and the IdP cert were also provided + # also check that the settings and the IdP cert were also provided # @return [Boolean] True if the required info is found, false otherwise # def validate_response_state @@ -515,7 +515,7 @@ def validate_no_duplicated_attributes # Validates the Signed elements # If fails, the error is added to the errors array # @return [Boolean] True if there is 1 or 2 Elements signed in the SAML Response - # an are a Response or an Assertion Element, otherwise False if soft=True + # and are a Response or an Assertion Element, otherwise False if soft=True # def validate_signed_elements signature_nodes = (decrypted_document.nil? ? document : decrypted_document).xpath( @@ -574,9 +574,9 @@ def validate_signed_elements true end - # Validates if the provided request_id match the inResponseTo value. + # Validates if the provided request_id matches the inResponseTo value. # If fails, the error is added to the errors array - # @return [Boolean] True if there is no request_id or it match, otherwise False if soft=True + # @return [Boolean] True if there is no request_id or it matches, otherwise False if soft=True # @raise [ValidationError] if soft == false and validation fails # def validate_in_response_to @@ -655,7 +655,7 @@ def validate_one_conditions # Checks that the samlp:Response/saml:Assertion/saml:AuthnStatement element exists and is unique. # If fails, the error is added to the errors array - # @return [Boolean] True if there is a authnstatement element and is unique + # @return [Boolean] True if there is a AuthnStatement element and is unique # def validate_one_authnstatement return true if options[:skip_authnstatement] @@ -717,7 +717,7 @@ def validate_issuer true end - # Validates that the Session haven't expired (If the response was initialized with the :allowed_clock_drift option, + # Validates that the Session hasn't expired (If the response was initialized with the :allowed_clock_drift option, # this time validation is relaxed by the allowed_clock_drift value) # If fails, the error is added to the errors array # @param soft [Boolean] soft Enable or Disable the soft mode (In order to raise exceptions when the response is invalid or not) @@ -736,12 +736,12 @@ def validate_session_expiration true end - # Validates if exists valid SubjectConfirmation (If the response was initialized with the :allowed_clock_drift option, - # timimg validation are relaxed by the allowed_clock_drift value. If the response was initialized with the + # Validates if a valid SubjectConfirmation (If the response was initialized with the :allowed_clock_drift option, + # timing validation are relaxed by the allowed_clock_drift value. If the response was initialized with the # :skip_subject_confirmation option, this validation is skipped) # There is also an optional Recipient check # If fails, the error is added to the errors array - # @return [Boolean] True if exists a valid SubjectConfirmation, otherwise False if soft=True + # @return [Boolean] True if a valid SubjectConfirmation, otherwise False if soft=True # @raise [ValidationError] if soft == false and validation fails # def validate_subject_confirmation @@ -945,8 +945,8 @@ def signed_assertion @signed_assertion ||= cached_signed_assertion end - # Extracts the first appearance that matchs the subpath (pattern) - # Search on any Assertion that is signed, or has a Response parent signed + # Extracts the first appearance that matches the subpath (pattern) + # Searches on any Assertion that is signed, or has a Response parent signed # @param subpath [String] The XPath pattern # @return [Nokogiri::XML::Element | nil] If any matches, return the Element def xpath_first_from_signed_assertion(subpath = nil) @@ -954,8 +954,8 @@ def xpath_first_from_signed_assertion(subpath = nil) signed_assertion.at_xpath("./#{subpath}", SAML_NAMESPACES) end - # Extracts all the appearances that matchs the subpath (pattern) - # Search on any Assertion that is signed, or has a Response parent signed + # Extracts all the appearances that matches the subpath (pattern) + # Searches on any Assertion that is signed, or has a Response parent signed # @param subpath [String] The XPath pattern # @return [Array of Nokogiri::XML::Element] Return all matches def xpath_from_signed_assertion(subpath = nil) diff --git a/lib/ruby_saml/settings.rb b/lib/ruby_saml/settings.rb index c9351244..03dc6347 100644 --- a/lib/ruby_saml/settings.rb +++ b/lib/ruby_saml/settings.rb @@ -130,7 +130,7 @@ def get_fingerprint end end - # @return [OpenSSL::X509::Certificate|nil] Build the IdP certificate from the settings (previously format it) + # @return [OpenSSL::X509::Certificate|nil] Build the IdP certificate from the settings (previously formatting it) # def get_idp_cert RubySaml::Utils.build_cert_object(idp_cert) @@ -190,7 +190,7 @@ def get_sp_cert node[0] if node end - # @return [OpenSSL::PKey::RSA] The SP signing key. + # @return [OpenSSL::PKey::PKey] The SP signing key. def get_sp_signing_key node = get_sp_signing_pair node[1] if node diff --git a/lib/ruby_saml/slo_logoutrequest.rb b/lib/ruby_saml/slo_logoutrequest.rb index bcfac86d..854ab306 100644 --- a/lib/ruby_saml/slo_logoutrequest.rb +++ b/lib/ruby_saml/slo_logoutrequest.rb @@ -24,7 +24,7 @@ class SloLogoutrequest < SamlMessage # @param request [String] A UUEncoded Logout Request from the IdP. # @param options [Hash] :settings to provide the RubySaml::Settings object # Or :allowed_clock_drift for the logout request validation process to allow a clock drift when checking dates with - # Or :relax_signature_validation to accept signatures if no idp certificate registered on settings + # Or :relax_signature_validation to accept signatures if no IdP certificate registered on settings # # @raise [ArgumentError] If Request is nil # @@ -49,7 +49,7 @@ def request_id end # Validates the Logout Request with the default values (soft = true) - # @param collect_errors [Boolean] Stop validation when first error appears or keep validating. + # @param collect_errors [Boolean] Stop validation when the first error appears or keep validating. # @return [Boolean] TRUE if the Logout Request is valid # def is_valid?(collect_errors = false) @@ -79,7 +79,7 @@ def name_id_node end end - # @return [String|nil] Gets the ID attribute from the Logout Request. if exists. + # @return [String|nil] Gets the ID attribute from the Logout Request. if it exists. # def id super(document) @@ -94,7 +94,7 @@ def issuer )&.text end - # @return [Time|nil] Gets the NotOnOrAfter Attribute value if exists. + # @return [Time|nil] Gets the NotOnOrAfter Attribute value if it exists. # def not_on_or_after @not_on_or_after ||= begin @@ -109,7 +109,7 @@ def not_on_or_after end end - # @return [Array] Gets the SessionIndex if exists (Supported multiple values). Empty Array if none found + # @return [Array] Gets the SessionIndex if exists (Supports multiple values). Empty Array if none found # def session_indexes document.xpath( @@ -127,7 +127,7 @@ def allowed_clock_drift end # Hard aux function to validate the Logout Request - # @param collect_errors [Boolean] Stop validation when first error appears or keep validating. (if soft=true) + # @param collect_errors [Boolean] Stop validation when the first error appears or keep validating. (if soft=true) # @return [Boolean] TRUE if the Logout Request is valid # @raise [ValidationError] if soft == false and validation fails # @@ -211,7 +211,7 @@ def validate_request_state # Validates the Issuer of the Logout Request # If fails, the error is added to the errors array - # @return [Boolean] True if the Issuer matchs the IdP entityId, otherwise False if soft=True + # @return [Boolean] True if the Issuer matches the IdP entityId, otherwise False if soft=True # @raise [ValidationError] if soft == false and validation fails # def validate_issuer @@ -224,8 +224,8 @@ def validate_issuer true end - # Validates the Signature if exists and GET parameters are provided - # @return [Boolean] True if not contains a Signature or if the Signature is valid, otherwise False if soft=True + # Validates the Signature if it exists and GET parameters are provided + # @return [Boolean] True if does not contain a Signature or if the Signature is valid, otherwise False if soft=True # @raise [ValidationError] if soft == false and validation fails # def validate_signature diff --git a/lib/ruby_saml/slo_logoutresponse.rb b/lib/ruby_saml/slo_logoutresponse.rb index 45d3b353..7058976a 100644 --- a/lib/ruby_saml/slo_logoutresponse.rb +++ b/lib/ruby_saml/slo_logoutresponse.rb @@ -17,7 +17,7 @@ class SloLogoutresponse < SamlMessage # @param settings [RubySaml::Settings|nil] Toolkit settings # @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 # @param logout_message [String] The Message to be placed as StatusMessage in the logout response - # @param params [Hash] Some extra parameters to be added in the GET for example the RelayState + # @param params [Hash] Some extra parameters to be added in the GET for example, the RelayState # @param logout_status_code [String] The StatusCode to be placed as StatusMessage in the logout response # @return [String] Logout Request string that includes the SAMLRequest def create(settings, request_id = nil, logout_message = nil, params = {}, logout_status_code = nil) @@ -39,7 +39,7 @@ def create(settings, request_id = nil, logout_message = nil, params = {}, logout # @param settings [RubySaml::Settings|nil] Toolkit settings # @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 # @param logout_message [String] The Message to be placed as StatusMessage in the logout response - # @param params [Hash] Some extra parameters to be added in the GET for example the RelayState + # @param params [Hash] Some extra parameters to be added in the GET for example, the RelayState # @param logout_status_code [String] The StatusCode to be placed as StatusMessage in the logout response # @return [Hash] Parameters def create_params(settings, request_id = nil, logout_message = nil, params = {}, logout_status_code = nil) diff --git a/lib/ruby_saml/utils.rb b/lib/ruby_saml/utils.rb index 45b2d3bc..c45e8607 100644 --- a/lib/ruby_saml/utils.rb +++ b/lib/ruby_saml/utils.rb @@ -184,7 +184,7 @@ def build_query_from_raw_parts(params) # # @param rawparams [Hash] Raw GET Parameters # @param params [Hash] GET Parameters - # @param lowercase_url_encoding [bool] Lowercase URL Encoding (For ADFS urlencode compatiblity) + # @param lowercase_url_encoding [bool] Lowercase URL Encoding (For ADFS urlencode compatibility) # @return [Hash] New raw parameters # def prepare_raw_get_params(rawparams, params, lowercase_url_encoding=false) @@ -230,7 +230,7 @@ def verify_signature(params) # Build the status error message # @param status_code [String] StatusCode value - # @param status_message [Strig] StatusMessage value + # @param status_message [String] StatusMessage value # @return [String] The status error message def status_error_msg(error_msg, raw_status_code = nil, status_message = nil) unless raw_status_code.nil? @@ -269,8 +269,8 @@ def generate_uuid(prefix = nil) alias_method :uuid, :generate_uuid # Given two strings, attempt to match them as URIs using Rails' parse method. If they can be parsed, - # then the fully-qualified domain name and the host should performa a case-insensitive match, per the - # RFC for URIs. If Rails can not parse the string in to URL pieces, return a boolean match of the + # then the fully-qualified domain name and the host should perform a case-insensitive match, per the + # RFC for URIs. If Rails can not parse the string into URL pieces, return a boolean match of the # two strings. This maintains the previous functionality. # @return [Boolean] def uri_match?(destination_url, settings_url) diff --git a/lib/ruby_saml/xml/decryptor.rb b/lib/ruby_saml/xml/decryptor.rb index e481c8af..3ec54e6e 100644 --- a/lib/ruby_saml/xml/decryptor.rb +++ b/lib/ruby_saml/xml/decryptor.rb @@ -44,7 +44,7 @@ def decrypt_assertion(encrypted_assertion_node, decryption_keys) # Decrypts an EncryptedID element # @param encrypted_id_node [Nokogiri::XML::Element] The EncryptedID element # @param decryption_keys [Array] Array of private keys for decryption - # @return [Nokogiri::XML::Document] The decrypted EncrypedtID element + # @return [Nokogiri::XML::Document] The decrypted EncryptedID element def decrypt_nameid(encrypted_id_node, decryption_keys) decrypt_node(encrypted_id_node, %r{(.*)}m, decryption_keys) end @@ -104,7 +104,7 @@ def validate_decryption_keys!(decryption_keys) # Obtains the decrypted string from an Encrypted node element in XML, # given multiple private keys to try. - # @param encrypted_node [Nokogirl::XML::Element] The Encrypted element + # @param encrypted_node [Nokogiri::XML::Element] The Encrypted element # @param decryption_keys [OpenSSL::PKey::RSA | Array] The SP private key(s) # @return [String] The decrypted data def decrypt_node_with_multiple_keys(encrypted_node, decryption_keys) diff --git a/test/attributes_test.rb b/test/attributes_test.rb index e2a05e9e..db6c769e 100644 --- a/test/attributes_test.rb +++ b/test/attributes_test.rb @@ -6,10 +6,10 @@ class AttributesTest < Minitest::Test describe 'Attributes' do let(:attributes) do RubySaml::Attributes.new({ - 'email' => ['tom@hanks.com'], - 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname' => ['Tom'], - 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname' => ['Hanks'] - }) + 'email' => ['tom@hanks.com'], + 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname' => ['Tom'], + 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname' => ['Hanks'] + }) end it 'fetches string attribute' do diff --git a/test/authrequest_test.rb b/test/authrequest_test.rb index 63bd2b5b..4fa3750c 100644 --- a/test/authrequest_test.rb +++ b/test/authrequest_test.rb @@ -3,18 +3,18 @@ require_relative 'test_helper' class AuthrequestTest < Minitest::Test - - describe "Authrequest" do + describe 'Authrequest' do let(:settings) { RubySaml::Settings.new } before do - settings.idp_sso_service_url = "http://example.com" + settings.idp_sso_service_url = 'http://example.com' end - it "create the deflated SAMLRequest URL parameter" do + it 'create the deflated SAMLRequest URL parameter' do auth_url = RubySaml::Authrequest.new.create(settings) - assert_match(/^http:\/\/example\.com\?SAMLRequest=/, auth_url) - payload = CGI.unescape(auth_url.split("=").last) + + assert_match(%r{^http://example\.com\?SAMLRequest=}, auth_url) + payload = CGI.unescape(auth_url.split('=').last) decoded = Base64.decode64(payload) zstream = Zlib::Inflate.new(-Zlib::MAX_WBITS) @@ -25,9 +25,9 @@ class AuthrequestTest < Minitest::Test assert_match(/^') - assert inflated.include?('testuser@example.com') - assert inflated.include?('') + assert_includes inflated, '' + assert_includes inflated, 'testuser@example.com' + assert_includes inflated, '' end - it "accept extra parameters" do - auth_url = RubySaml::Authrequest.new.create(settings, { :hello => "there" }) + it 'accept extra parameters' do + auth_url = RubySaml::Authrequest.new.create(settings, { hello: 'there' }) + assert_match(/&hello=there$/, auth_url) - auth_url = RubySaml::Authrequest.new.create(settings, { :hello => nil }) + auth_url = RubySaml::Authrequest.new.create(settings, { hello: nil }) + assert_match(/&hello=$/, auth_url) end - it "RelayState cases" do - auth_url = RubySaml::Authrequest.new.create(settings, { :RelayState => nil }) - assert !auth_url.include?('RelayState') + it 'RelayState cases' do + auth_url = RubySaml::Authrequest.new.create(settings, { RelayState: nil }) + + refute_includes auth_url, 'RelayState' - auth_url = RubySaml::Authrequest.new.create(settings, { :RelayState => "http://example.com" }) - assert auth_url.include?('&RelayState=http%3A%2F%2Fexample.com') + auth_url = RubySaml::Authrequest.new.create(settings, { RelayState: 'http://example.com' }) + + assert_includes auth_url, '&RelayState=http%3A%2F%2Fexample.com' auth_url = RubySaml::Authrequest.new.create(settings, { 'RelayState' => nil }) - assert !auth_url.include?('RelayState') - auth_url = RubySaml::Authrequest.new.create(settings, { 'RelayState' => "http://example.com" }) - assert auth_url.include?('&RelayState=http%3A%2F%2Fexample.com') + refute_includes auth_url, 'RelayState' + + auth_url = RubySaml::Authrequest.new.create(settings, { 'RelayState' => 'http://example.com' }) + + assert_includes auth_url, '&RelayState=http%3A%2F%2Fexample.com' end - describe "uuid" do - it "uuid is initialized to nil" do + describe 'uuid' do + it 'uuid is initialized to nil' do request = RubySaml::Authrequest.new assert_nil request.uuid @@ -176,7 +191,7 @@ class AuthrequestTest < Minitest::Test assert_equal request.uuid, request.request_id end - it "does not change even after repeated #create calls" do + it 'does not change even after repeated #create calls' do request = RubySaml::Authrequest.new request.create(settings) @@ -187,7 +202,7 @@ class AuthrequestTest < Minitest::Test assert_equal request.uuid, request.request_id end - it "creates request with ID prefixed by Settings#sp_uuid_prefix" do + it 'creates request with ID prefixed by Settings#sp_uuid_prefix' do settings.sp_uuid_prefix = 'test' request = RubySaml::Authrequest.new request.create(settings) @@ -196,23 +211,25 @@ class AuthrequestTest < Minitest::Test assert_equal request.uuid, request.request_id end - it "can mutate the uuid" do + it 'can mutate the uuid' do request = RubySaml::Authrequest.new + assert_nil request.uuid assert_nil request.request_id - request.uuid = "new_uuid" - assert_equal "new_uuid", request.uuid + request.uuid = 'new_uuid' + + assert_equal 'new_uuid', request.uuid assert_equal request.uuid, request.request_id end end - describe "when the target url is not set" do + describe 'when the target url is not set' do before do settings.idp_sso_service_url = nil end - it "raises an error with a descriptive message" do + it 'raises an error with a descriptive message' do err = assert_raises RubySaml::SettingError do RubySaml::Authrequest.new.create(settings) end @@ -221,94 +238,105 @@ class AuthrequestTest < Minitest::Test end describe "when the target url doesn't contain a query string" do - it "create the SAMLRequest parameter correctly" do - + it 'create the SAMLRequest parameter correctly' do auth_url = RubySaml::Authrequest.new.create(settings) - assert_match(/^http:\/\/example\.com\?SAMLRequest/, auth_url) + + assert_match(%r{^http://example\.com\?SAMLRequest}, auth_url) end end - describe "when the target url contains a query string" do - it "create the SAMLRequest parameter correctly" do - settings.idp_sso_service_url = "http://example.com?field=value" + describe 'when the target url contains a query string' do + it 'create the SAMLRequest parameter correctly' do + settings.idp_sso_service_url = 'http://example.com?field=value' auth_url = RubySaml::Authrequest.new.create(settings) - assert_match(/^http:\/\/example\.com\?field=value&SAMLRequest/, auth_url) + + assert_match(%r{^http://example\.com\?field=value&SAMLRequest}, auth_url) end end - it "create the saml:AuthnContextClassRef element correctly" do + it 'create the saml:AuthnContextClassRef element correctly' do settings.authn_context = 'secure/name/password/uri' auth_doc = RubySaml::Authrequest.new.create_authentication_xml_doc(settings) - assert_match(/secure\/name\/password\/uri<\/saml:AuthnContextClassRef>/, auth_doc.to_s) + + assert_match(%r{secure/name/password/uri}, auth_doc.to_s) end - it "create multiple saml:AuthnContextClassRef elements correctly" do + it 'create multiple saml:AuthnContextClassRef elements correctly' do settings.authn_context = ['secure/name/password/uri', 'secure/email/password/uri'] auth_doc = RubySaml::Authrequest.new.create_authentication_xml_doc(settings) - assert_match(/secure\/name\/password\/uri<\/saml:AuthnContextClassRef>/, auth_doc.to_s) - assert_match(/secure\/email\/password\/uri<\/saml:AuthnContextClassRef>/, auth_doc.to_s) + + assert_match(%r{secure/name/password/uri}, auth_doc.to_s) + assert_match(%r{secure/email/password/uri}, auth_doc.to_s) end - it "create the saml:AuthnContextClassRef with comparison exact" do + it 'create the saml:AuthnContextClassRef with comparison exact' do settings.authn_context = 'secure/name/password/uri' auth_doc = RubySaml::Authrequest.new.create_authentication_xml_doc(settings) + assert_match(/secure\/name\/password\/uri<\/saml:AuthnContextClassRef>/, auth_doc.to_s) + assert_match(%r{secure/name/password/uri}, auth_doc.to_s) end - it "create the saml:AuthnContextClassRef with comparison minimun" do + it 'create the saml:AuthnContextClassRef with comparison minimum' do settings.authn_context = 'secure/name/password/uri' - settings.authn_context_comparison = 'minimun' + settings.authn_context_comparison = 'minimum' auth_doc = RubySaml::Authrequest.new.create_authentication_xml_doc(settings) - assert_match(/secure\/name\/password\/uri<\/saml:AuthnContextClassRef>/, auth_doc.to_s) + + assert_match(/secure/name/password/uri}, auth_doc.to_s) end - it "create the saml:AuthnContextDeclRef element correctly" do + it 'create the saml:AuthnContextDeclRef element correctly' do settings.authn_context_decl_ref = 'urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport' auth_doc = RubySaml::Authrequest.new.create_authentication_xml_doc(settings) - assert_match(/urn:oasis:names:tc:SAML:2\.0:ac:classes:PasswordProtectedTransport<\/saml:AuthnContextDeclRef>/, auth_doc.to_s) + + assert_match(%r{urn:oasis:names:tc:SAML:2\.0:ac:classes:PasswordProtectedTransport}, auth_doc.to_s) end - it "create the saml:AuthnContextClassRef element correctly" do + it 'create the saml:AuthnContextClassRef element correctly' do settings.authn_context = 'secure/name/password/uri' auth_doc = RubySaml::Authrequest.new.create_authentication_xml_doc(settings) - assert auth_doc.to_s =~ /secure\/name\/password\/uri<\/saml:AuthnContextClassRef>/ + + assert_match %r{secure/name/password/uri}, auth_doc.to_s end - it "create the saml:AuthnContextClassRef with comparison exact" do + it 'create the saml:AuthnContextClassRef with comparison exact' do settings.authn_context = 'secure/name/password/uri' auth_doc = RubySaml::Authrequest.new.create_authentication_xml_doc(settings) - assert auth_doc.to_s =~ /secure\/name\/password\/uri<\/saml:AuthnContextClassRef>/ + + assert_match(/secure/name/password/uri}, auth_doc.to_s end - it "create the saml:AuthnContextClassRef with comparison minimun" do + it 'create the saml:AuthnContextClassRef with comparison minimun' do settings.authn_context = 'secure/name/password/uri' settings.authn_context_comparison = 'minimun' auth_doc = RubySaml::Authrequest.new.create_authentication_xml_doc(settings) - assert auth_doc.to_s =~ /secure\/name\/password\/uri<\/saml:AuthnContextClassRef>/ + + assert_match(/secure/name/password/uri}, auth_doc.to_s end - it "create the saml:AuthnContextDeclRef element correctly" do + it 'create the saml:AuthnContextDeclRef element correctly' do settings.authn_context_decl_ref = 'urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport' auth_doc = RubySaml::Authrequest.new.create_authentication_xml_doc(settings) - assert auth_doc.to_s =~ /urn:oasis:names:tc:SAML:2\.0:ac:classes:PasswordProtectedTransport<\/saml:AuthnContextDeclRef>/ + + assert_match %r{urn:oasis:names:tc:SAML:2\.0:ac:classes:PasswordProtectedTransport}, auth_doc.to_s end - it "create multiple saml:AuthnContextDeclRef elements correctly " do + it 'create multiple saml:AuthnContextDeclRef elements correctly ' do settings.authn_context_decl_ref = ['name/password/uri', 'example/decl/ref'] auth_doc = RubySaml::Authrequest.new.create_authentication_xml_doc(settings) - assert auth_doc.to_s =~ /name\/password\/uri<\/saml:AuthnContextDeclRef>/ - assert auth_doc.to_s =~ /example\/decl\/ref<\/saml:AuthnContextDeclRef>/ + + assert_match %r{name/password/uri}, auth_doc.to_s + assert_match %r{example/decl/ref}, auth_doc.to_s end each_signature_algorithm do |sp_key_algo, sp_hash_algo| - describe "#create_params signing with HTTP-POST binding" do + describe '#create_params signing with HTTP-POST binding' do before do - settings.idp_sso_service_url = "http://example.com?field=value" + settings.idp_sso_service_url = 'http://example.com?field=value' settings.idp_sso_service_binding = :post settings.security[:authn_requests_signed] = true settings.certificate, settings.private_key = CertificateHelper.generate_pem_array(sp_key_algo) @@ -316,9 +344,9 @@ class AuthrequestTest < Minitest::Test settings.security[:digest_method] = digest_method(sp_hash_algo) end - it "create a signed request" do + it 'create a signed request' do params = RubySaml::Authrequest.new.create_params(settings) - request_xml = Base64.decode64(params["SAMLRequest"]) + request_xml = Base64.decode64(params['SAMLRequest']) assert_match(signature_value_matcher, request_xml) assert_match(signature_method_matcher(sp_key_algo, sp_hash_algo), request_xml) @@ -330,7 +358,7 @@ class AuthrequestTest < Minitest::Test # RSA is ignored here; only the hash sp_key_algo is used settings.security[:signature_method] = RubySaml::XML::RSA_SHA256 params = RubySaml::Authrequest.new.create_params(settings) - request_xml = Base64.decode64(params["SAMLRequest"]) + request_xml = Base64.decode64(params['SAMLRequest']) assert_match(signature_value_matcher, request_xml) assert_match(signature_method_matcher(sp_key_algo, :sha256), request_xml) @@ -340,7 +368,7 @@ class AuthrequestTest < Minitest::Test it 'using mixed signature and digest methods (digest SHA256)' do settings.security[:digest_method] = RubySaml::XML::SHA256 params = RubySaml::Authrequest.new.create_params(settings) - request_xml = Base64.decode64(params["SAMLRequest"]) + request_xml = Base64.decode64(params['SAMLRequest']) assert_match(signature_value_matcher, request_xml) assert_match(signature_method_matcher(sp_key_algo, sp_hash_algo), request_xml) @@ -348,7 +376,7 @@ class AuthrequestTest < Minitest::Test end end - it "creates a signed request using the first certificate and key" do + it 'creates a signed request using the first certificate and key' do settings.certificate = nil settings.private_key = nil settings.sp_cert_multi = { @@ -358,14 +386,14 @@ class AuthrequestTest < Minitest::Test ] } params = RubySaml::Authrequest.new.create_params(settings) - request_xml = Base64.decode64(params["SAMLRequest"]) + request_xml = Base64.decode64(params['SAMLRequest']) assert_match(signature_value_matcher, request_xml) assert_match(signature_method_matcher(sp_key_algo, sp_hash_algo), request_xml) assert_match(digest_method_matcher(sp_hash_algo), request_xml) end - it "creates a signed request using the first valid certificate and key when :check_sp_cert_expiration is true" do + it 'creates a signed request using the first valid certificate and key when :check_sp_cert_expiration is true' do settings.certificate = nil settings.private_key = nil settings.security[:check_sp_cert_expiration] = true @@ -376,14 +404,14 @@ class AuthrequestTest < Minitest::Test ] } params = RubySaml::Authrequest.new.create_params(settings) - request_xml = Base64.decode64(params["SAMLRequest"]) + request_xml = Base64.decode64(params['SAMLRequest']) assert_match(signature_value_matcher, request_xml) assert_match(signature_method_matcher(sp_key_algo, sp_hash_algo), request_xml) assert_match(digest_method_matcher(sp_hash_algo), request_xml) end - it "raises error when no valid certs and :check_sp_cert_expiration is true" do + it 'raises error when no valid certs and :check_sp_cert_expiration is true' do settings.certificate, settings.private_key = CertificateHelper.generate_pem_array(sp_key_algo, not_after: Time.now - 60) settings.security[:check_sp_cert_expiration] = true @@ -392,16 +420,15 @@ class AuthrequestTest < Minitest::Test end end end - end - each_signature_algorithm do |sp_key_algo, sp_hash_algo| - describe "#create_params signing with HTTP-Redirect binding" do + describe '#create_params signing with HTTP-Redirect binding' do let(:cert) { OpenSSL::X509::Certificate.new(ruby_saml_cert_text) } before do - settings.idp_sso_service_url = "http://example.com?field=value" + settings.idp_sso_service_url = 'http://example.com?field=value' settings.idp_sso_service_binding = :redirect - settings.assertion_consumer_service_binding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST-SimpleSign" + + settings.assertion_consumer_service_binding = 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST-SimpleSign' settings.security[:authn_requests_signed] = true @cert, @pkey = CertificateHelper.generate_pair(sp_key_algo) settings.certificate, settings.private_key = [@cert, @pkey].map(&:to_pem) @@ -409,8 +436,8 @@ class AuthrequestTest < Minitest::Test settings.security[:digest_method] = digest_method(sp_hash_algo) end - it "create a signature parameter and validate it" do - params = RubySaml::Authrequest.new.create_params(settings, :RelayState => 'http://example.com') + it 'create a signature parameter and validate it' do + params = RubySaml::Authrequest.new.create_params(settings, RelayState: 'http://example.com') assert params['SAMLRequest'] assert params[:RelayState] @@ -428,7 +455,7 @@ class AuthrequestTest < Minitest::Test it 'using mixed signature and digest methods (signature SHA256)' do # RSA is ignored here; only the hash sp_key_algo is used settings.security[:signature_method] = RubySaml::XML::RSA_SHA256 - params = RubySaml::Authrequest.new.create_params(settings, :RelayState => 'http://example.com') + params = RubySaml::Authrequest.new.create_params(settings, RelayState: 'http://example.com') assert params['SAMLRequest'] assert params[:RelayState] @@ -444,7 +471,7 @@ class AuthrequestTest < Minitest::Test it 'using mixed signature and digest methods (digest SHA256)' do settings.security[:digest_method] = RubySaml::XML::SHA256 - params = RubySaml::Authrequest.new.create_params(settings, :RelayState => 'http://example.com') + params = RubySaml::Authrequest.new.create_params(settings, RelayState: 'http://example.com') assert params['SAMLRequest'] assert params[:RelayState] @@ -459,7 +486,7 @@ class AuthrequestTest < Minitest::Test end end - it "create a signature parameter using the first certificate and key" do + it 'create a signature parameter using the first certificate and key' do settings.security[:signature_method] = RubySaml::XML::RSA_SHA1 settings.certificate = nil settings.private_key = nil @@ -471,7 +498,8 @@ class AuthrequestTest < Minitest::Test ] } - params = RubySaml::Authrequest.new.create_params(settings, :RelayState => 'http://example.com') + params = RubySaml::Authrequest.new.create_params(settings, RelayState: 'http://example.com') + assert params['SAMLRequest'] assert params[:RelayState] assert params['Signature'] @@ -482,16 +510,17 @@ class AuthrequestTest < Minitest::Test query_string << "&SigAlg=#{CGI.escape(params['SigAlg'])}" signature_algorithm = RubySaml::XML.hash_algorithm(params['SigAlg']) + assert_equal signature_algorithm, OpenSSL::Digest::SHA1 assert cert.public_key.verify(signature_algorithm.new, Base64.decode64(params['Signature']), query_string) end - it "raises error when no valid certs and :check_sp_cert_expiration is true" do + it 'raises error when no valid certs and :check_sp_cert_expiration is true' do settings.certificate, settings.private_key = CertificateHelper.generate_pem_array(sp_key_algo, not_after: Time.now - 60) settings.security[:check_sp_cert_expiration] = true assert_raises(RubySaml::ValidationError, 'The SP certificate expired.') do - RubySaml::Authrequest.new.create_params(settings, :RelayState => 'http://example.com') + RubySaml::Authrequest.new.create_params(settings, RelayState: 'http://example.com') end end end diff --git a/test/idp_metadata_parser_test.rb b/test/idp_metadata_parser_test.rb index d3a0a64b..7da7d90a 100644 --- a/test/idp_metadata_parser_test.rb +++ b/test/idp_metadata_parser_test.rb @@ -538,7 +538,7 @@ def initialize; end assert_equal entity_id, settings.idp_entity_id end - it "should retreive data" do + it "should retrieve data" do assert_equal "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified", @settings.name_identifier_format assert_equal "https://hello.example.com/access/saml/login", @settings.idp_sso_service_url assert_equal "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect", @settings.idp_sso_service_binding @@ -566,7 +566,7 @@ def initialize; end @idp_metadata = no_idp_metadata_descriptor end - it "raise due no IDPSSODescriptor element" do + it "raise due to no IDPSSODescriptor element" do assert_raises(ArgumentError) { @idp_metadata_parser.parse(@idp_metadata) } end end @@ -578,7 +578,7 @@ def initialize; end @settings = @idp_metadata_parser.parse(@idp_metadata) end - it "should return a idp_cert_multi and no idp_cert and no idp_cert_fingerprint" do + it "should return an idp_cert_multi and no idp_cert and no idp_cert_fingerprint" do assert_nil @settings.idp_cert assert_nil @settings.idp_cert_fingerprint @@ -649,7 +649,7 @@ def initialize; end @settings = @idp_metadata_parser.parse(@idp_metadata) end - it "should return a idp_cert_multi and no idp_cert and no idp_cert_fingerprint" do + it "should return an idp_cert_multi and no idp_cert and no idp_cert_fingerprint" do assert_nil @settings.idp_cert assert_nil @settings.idp_cert_fingerprint @@ -742,7 +742,7 @@ def initialize; end @settings = @idp_metadata_parser.parse(@idp_metadata) end - it "should return a idp_cert_multi and no idp_cert and no idp_cert_fingerprint" do + it "should return an idp_cert_multi and no idp_cert and no idp_cert_fingerprint" do assert_nil @settings.idp_cert assert_nil @settings.idp_cert_fingerprint @@ -806,7 +806,7 @@ def initialize; end end describe "metadata with different singlelogout response location" do - it "should return the responselocation if it exists" do + it "should return the responseLocation if it exists" do idp_metadata_parser = RubySaml::IdpMetadataParser.new settings = idp_metadata_parser.parse(idp_different_slo_response_location) @@ -816,7 +816,7 @@ def initialize; end assert_equal "https://hello.example.com/access/saml/logout/return", settings.idp_slo_response_service_url end - it "should set the responselocation to nil if it doesnt exist" do + it "should set the responseLocation to nil if it doesn't exist" do idp_metadata_parser = RubySaml::IdpMetadataParser.new settings = idp_metadata_parser.parse(idp_without_slo_response_location) diff --git a/test/logoutrequest_test.rb b/test/logoutrequest_test.rb index 2230257b..d60b75a1 100644 --- a/test/logoutrequest_test.rb +++ b/test/logoutrequest_test.rb @@ -3,99 +3,109 @@ require_relative 'test_helper' class RequestTest < Minitest::Test - - describe "Logoutrequest" do + describe 'Logoutrequest' do let(:settings) { RubySaml::Settings.new } before do - settings.idp_slo_service_url = "http://unauth.com/logout" - settings.name_identifier_value = "f00f00" + settings.idp_slo_service_url = 'http://unauth.com/logout' + settings.name_identifier_value = 'f00f00' end - it "creates the deflated SAMLRequest URL parameter" do + it 'creates the deflated SAMLRequest URL parameter' do unauth_url = RubySaml::Logoutrequest.new.create(settings) - assert_match(/^http:\/\/unauth\.com\/logout\?SAMLRequest=/, unauth_url) + + assert_match(%r{^http://unauth\.com/logout\?SAMLRequest=}, unauth_url) inflated = decode_saml_request_payload(unauth_url) + assert_match(/^ nil }) + it 'supports additional params' do + unauth_url = RubySaml::Logoutrequest.new.create(settings, { hello: nil }) + assert_match(/&hello=$/, unauth_url) - unauth_url = RubySaml::Logoutrequest.new.create(settings, { :foo => "bar" }) + unauth_url = RubySaml::Logoutrequest.new.create(settings, { foo: 'bar' }) + assert_match(/&foo=bar$/, unauth_url) end - it "RelayState cases" do - unauth_url = RubySaml::Logoutrequest.new.create(settings, { :RelayState => nil }) - assert !unauth_url.include?('RelayState') + it 'RelayState cases' do + unauth_url = RubySaml::Logoutrequest.new.create(settings, { RelayState: nil }) + + refute_includes unauth_url, 'RelayState' - unauth_url = RubySaml::Logoutrequest.new.create(settings, { :RelayState => "http://example.com" }) - assert unauth_url.include?('&RelayState=http%3A%2F%2Fexample.com') + unauth_url = RubySaml::Logoutrequest.new.create(settings, { RelayState: 'http://example.com' }) + + assert_includes unauth_url, '&RelayState=http%3A%2F%2Fexample.com' unauth_url = RubySaml::Logoutrequest.new.create(settings, { 'RelayState' => nil }) - assert !unauth_url.include?('RelayState') - unauth_url = RubySaml::Logoutrequest.new.create(settings, { 'RelayState' => "http://example.com" }) - assert unauth_url.include?('&RelayState=http%3A%2F%2Fexample.com') + refute_includes unauth_url, 'RelayState' + + unauth_url = RubySaml::Logoutrequest.new.create(settings, { 'RelayState' => 'http://example.com' }) + + assert_includes unauth_url, '&RelayState=http%3A%2F%2Fexample.com' end - it "set sessionindex" do - settings.idp_slo_service_url = "http://example.com" + it 'set sessionindex' do + settings.idp_slo_service_url = 'http://example.com' sessionidx = RubySaml::Utils.uuid settings.sessionindex = sessionidx - unauth_url = RubySaml::Logoutrequest.new.create(settings, { :nameid => "there" }) + unauth_url = RubySaml::Logoutrequest.new.create(settings, { nameid: 'there' }) inflated = decode_saml_request_payload(unauth_url) assert_match(/), inflated + assert_match %r{#{sessionidx}}, inflated end - it "set name_identifier_value" do - settings.name_identifier_format = "transient" - name_identifier_value = "abc123" + it 'set name_identifier_value' do + settings.name_identifier_format = 'transient' + name_identifier_value = 'abc123' settings.name_identifier_value = name_identifier_value - unauth_url = RubySaml::Logoutrequest.new.create(settings, { :nameid => "there" }) + unauth_url = RubySaml::Logoutrequest.new.create(settings, { nameid: 'there' }) inflated = decode_saml_request_payload(unauth_url) assert_match(/), inflated + assert_match %r{#{name_identifier_value}}, inflated end describe "when the target url doesn't contain a query string" do - it "creates the SAMLRequest parameter correctly" do + it 'creates the SAMLRequest parameter correctly' do unauth_url = RubySaml::Logoutrequest.new.create(settings) - assert_match(/^http:\/\/unauth.com\/logout\?SAMLRequest/, unauth_url) + + assert_match(%r{^http://unauth.com/logout\?SAMLRequest}, unauth_url) end end - describe "when the target url contains a query string" do - it "creates the SAMLRequest parameter correctly" do - settings.idp_slo_service_url = "http://example.com?field=value" + describe 'when the target url contains a query string' do + it 'creates the SAMLRequest parameter correctly' do + settings.idp_slo_service_url = 'http://example.com?field=value' unauth_url = RubySaml::Logoutrequest.new.create(settings) - assert_match(/^http:\/\/example\.com\?field=value&SAMLRequest/, unauth_url) + + assert_match(%r{^http://example\.com\?field=value&SAMLRequest}, unauth_url) end end - describe "consumation of logout may need to track the transaction" do - it "have access to the request uuid" do - settings.idp_slo_service_url = "http://example.com?field=value" + describe 'consummation of logout may need to track the transaction' do + it 'has access to the request uuid' do + settings.idp_slo_service_url = 'http://example.com?field=value' unauth_req = RubySaml::Logoutrequest.new unauth_url = unauth_req.create(settings) inflated = decode_saml_request_payload(unauth_url) - assert_match %r[ID="#{unauth_req.uuid}"], inflated + + assert_match(/ID="#{unauth_req.uuid}"/, inflated) end end - describe "uuid" do - it "uuid is initialized to nil" do + describe 'uuid' do + it 'uuid is initialized to nil' do request = RubySaml::Logoutrequest.new assert_nil request.uuid @@ -110,7 +120,7 @@ class RequestTest < Minitest::Test assert_equal request.uuid, request.request_id end - it "does not change even after repeated #create calls" do + it 'does not change even after repeated #create calls' do request = RubySaml::Logoutrequest.new request.create(settings) @@ -121,7 +131,7 @@ class RequestTest < Minitest::Test assert_equal request.uuid, request.request_id end - it "creates request with ID prefixed by Settings#sp_uuid_prefix" do + it 'creates request with ID prefixed by Settings#sp_uuid_prefix' do settings.sp_uuid_prefix = 'test' request = RubySaml::Logoutrequest.new request.create(settings) @@ -130,13 +140,15 @@ class RequestTest < Minitest::Test assert_equal request.uuid, request.request_id end - it "can mutate the uuid" do + it 'can mutate the uuid' do request = RubySaml::Logoutrequest.new + assert_nil request.uuid assert_nil request.request_id - request.uuid = "new_uuid" - assert_equal "new_uuid", request.uuid + request.uuid = 'new_uuid' + + assert_equal 'new_uuid', request.uuid assert_equal request.uuid, request.request_id end end @@ -161,7 +173,7 @@ class RequestTest < Minitest::Test refute_match(/ 'http://example.com') - - assert params['SAMLRequest'] - assert params[:RelayState] - assert params['Signature'] - assert_equal params['SigAlg'], signature_method(sp_key_algo, sp_hash_algo) - - query_string = "SAMLRequest=#{CGI.escape(params['SAMLRequest'])}" - query_string << "&RelayState=#{CGI.escape(params[:RelayState])}" - query_string << "&SigAlg=#{CGI.escape(params['SigAlg'])}" - - assert @cert.public_key.verify(RubySaml::XML.hash_algorithm(params['SigAlg']).new, Base64.decode64(params['Signature']), query_string) - end - - unless sp_hash_algo == :sha256 - it 'using mixed signature and digest methods (signature SHA256)' do - # RSA is ignored here; only the hash sp_key_algo is used - settings.security[:signature_method] = RubySaml::XML::RSA_SHA256 - params = RubySaml::Logoutrequest.new.create_params(settings, :RelayState => 'http://example.com') - assert params['SAMLRequest'] - assert params[:RelayState] - assert params['Signature'] - assert_equal params['SigAlg'], signature_method(sp_key_algo, :sha256) - - query_string = "SAMLRequest=#{CGI.escape(params['SAMLRequest'])}" - query_string << "&RelayState=#{CGI.escape(params[:RelayState])}" - query_string << "&SigAlg=#{CGI.escape(params['SigAlg'])}" - - assert @cert.public_key.verify(RubySaml::XML.hash_algorithm(params['SigAlg']).new, Base64.decode64(params['Signature']), query_string) - end - - it 'using mixed signature and digest methods (digest SHA256)' do - settings.security[:digest_method] = RubySaml::XML::SHA256 - params = RubySaml::Logoutrequest.new.create_params(settings, :RelayState => 'http://example.com') - - assert params['SAMLRequest'] - assert params[:RelayState] - assert params['Signature'] - assert_equal params['SigAlg'], signature_method(sp_key_algo, sp_hash_algo) - - query_string = "SAMLRequest=#{CGI.escape(params['SAMLRequest'])}" - query_string << "&RelayState=#{CGI.escape(params[:RelayState])}" - query_string << "&SigAlg=#{CGI.escape(params['SigAlg'])}" - - assert @cert.public_key.verify(RubySaml::XML.hash_algorithm(params['SigAlg']).new, Base64.decode64(params['Signature']), query_string) - end - end - - it "creates a signature parameter using the first certificate and key" do - settings.certificate = nil - settings.private_key = nil - cert, pkey = CertificateHelper.generate_pair(sp_key_algo) - settings.sp_cert_multi = { - signing: [ - { certificate: cert.to_pem, private_key: pkey.to_pem }, - CertificateHelper.generate_pem_hash - ] - } - params = RubySaml::Logoutrequest.new.create_params(settings, :RelayState => 'http://example.com') - - assert params['SAMLRequest'] - assert params[:RelayState] - assert params['Signature'] - assert_equal params['SigAlg'], signature_method(sp_key_algo, sp_hash_algo) - - query_string = "SAMLRequest=#{CGI.escape(params['SAMLRequest'])}" - query_string << "&RelayState=#{CGI.escape(params[:RelayState])}" - query_string << "&SigAlg=#{CGI.escape(params['SigAlg'])}" - - assert cert.public_key.verify(RubySaml::XML.hash_algorithm(params['SigAlg']).new, Base64.decode64(params['Signature']), query_string) - end - - it "raises error when no valid certs and :check_sp_cert_expiration is true" do - settings.certificate, settings.private_key = CertificateHelper.generate_pem_array(sp_key_algo, not_after: Time.now - 60) - settings.security[:check_sp_cert_expiration] = true - - assert_raises(RubySaml::ValidationError, 'The SP certificate expired.') do - RubySaml::Logoutrequest.new.create_params(settings, :RelayState => 'http://example.com') - end - end - end + describe 'signing with HTTP-Redirect binding' do + before do + settings.idp_slo_service_binding = :redirect + settings.idp_sso_service_binding = :post + settings.security[:logout_requests_signed] = true + @cert, @pkey = CertificateHelper.generate_pair(sp_key_algo) + settings.certificate, settings.private_key = [@cert, @pkey].map(&:to_pem) + settings.security[:signature_method] = signature_method(sp_key_algo, sp_hash_algo) + settings.security[:digest_method] = digest_method(sp_hash_algo) + end + + it 'creates a signature parameter and validate it' do + params = RubySaml::Logoutrequest.new.create_params(settings, RelayState: 'http://example.com') + + assert params['SAMLRequest'] + assert params[:RelayState] + assert params['Signature'] + assert_equal params['SigAlg'], signature_method(sp_key_algo, sp_hash_algo) + + query_string = "SAMLRequest=#{CGI.escape(params['SAMLRequest'])}" + query_string << "&RelayState=#{CGI.escape(params[:RelayState])}" + query_string << "&SigAlg=#{CGI.escape(params['SigAlg'])}" + + assert @cert.public_key.verify(RubySaml::XML.hash_algorithm(params['SigAlg']).new, Base64.decode64(params['Signature']), query_string) + end + + unless sp_hash_algo == :sha256 + it 'using mixed signature and digest methods (signature SHA256)' do + # RSA is ignored here; only the hash sp_key_algo is used + settings.security[:signature_method] = RubySaml::XML::RSA_SHA256 + params = RubySaml::Logoutrequest.new.create_params(settings, RelayState: 'http://example.com') + + assert params['SAMLRequest'] + assert params[:RelayState] + assert params['Signature'] + assert_equal params['SigAlg'], signature_method(sp_key_algo, :sha256) + + query_string = "SAMLRequest=#{CGI.escape(params['SAMLRequest'])}" + query_string << "&RelayState=#{CGI.escape(params[:RelayState])}" + query_string << "&SigAlg=#{CGI.escape(params['SigAlg'])}" + + assert @cert.public_key.verify(RubySaml::XML.hash_algorithm(params['SigAlg']).new, Base64.decode64(params['Signature']), query_string) + end + + it 'using mixed signature and digest methods (digest SHA256)' do + settings.security[:digest_method] = RubySaml::XML::SHA256 + params = RubySaml::Logoutrequest.new.create_params(settings, RelayState: 'http://example.com') + + assert params['SAMLRequest'] + assert params[:RelayState] + assert params['Signature'] + assert_equal params['SigAlg'], signature_method(sp_key_algo, sp_hash_algo) + + query_string = "SAMLRequest=#{CGI.escape(params['SAMLRequest'])}" + query_string << "&RelayState=#{CGI.escape(params[:RelayState])}" + query_string << "&SigAlg=#{CGI.escape(params['SigAlg'])}" + + assert @cert.public_key.verify(RubySaml::XML.hash_algorithm(params['SigAlg']).new, Base64.decode64(params['Signature']), query_string) + end + end + + it 'creates a signature parameter using the first certificate and key' do + settings.certificate = nil + settings.private_key = nil + cert, pkey = CertificateHelper.generate_pair(sp_key_algo) + settings.sp_cert_multi = { + signing: [ + { certificate: cert.to_pem, private_key: pkey.to_pem }, + CertificateHelper.generate_pem_hash + ] + } + params = RubySaml::Logoutrequest.new.create_params(settings, RelayState: 'http://example.com') + + assert params['SAMLRequest'] + assert params[:RelayState] + assert params['Signature'] + assert_equal params['SigAlg'], signature_method(sp_key_algo, sp_hash_algo) + + query_string = "SAMLRequest=#{CGI.escape(params['SAMLRequest'])}" + query_string << "&RelayState=#{CGI.escape(params[:RelayState])}" + query_string << "&SigAlg=#{CGI.escape(params['SigAlg'])}" + + assert cert.public_key.verify(RubySaml::XML.hash_algorithm(params['SigAlg']).new, Base64.decode64(params['Signature']), query_string) + end + + it 'raises error when no valid certs and :check_sp_cert_expiration is true' do + settings.certificate, settings.private_key = CertificateHelper.generate_pem_array(sp_key_algo, not_after: Time.now - 60) + settings.security[:check_sp_cert_expiration] = true + + assert_raises(RubySaml::ValidationError, 'The SP certificate expired.') do + RubySaml::Logoutrequest.new.create_params(settings, RelayState: 'http://example.com') + end + end + end end end end diff --git a/test/logoutresponse_test.rb b/test/logoutresponse_test.rb index fdeb7741..08129600 100644 --- a/test/logoutresponse_test.rb +++ b/test/logoutresponse_test.rb @@ -123,7 +123,7 @@ class RubySamlTest < Minitest::Test assert_includes logoutresponse.errors, "The status code of the Logout Response was not Success, was Requester -> Logoutrequest expired" end - it "invalidate logout response when in lack of sp_entity_id setting" do + it "invalidate logout response when the sp_entity_id setting is missing" do bad_settings = settings bad_settings.issuer = nil bad_settings.sp_entity_id = nil @@ -304,7 +304,7 @@ class RubySamlTest < Minitest::Test ) # Modify the query string so that it encodes the same values, # but with different percent-encoding. Sanity-check that they - # really are equialent before moving on. + # really are equivalent before moving on. original_query = query.dup query.gsub!("example", "ex%61mple") @@ -340,14 +340,14 @@ class RubySamlTest < Minitest::Test ) # Modify the query string so that it encodes the same values, # but with different percent-encoding. Sanity-check that they - # really are equialent before moving on. + # really are equivalent before moving on. original_query = query.dup query.gsub!("example", "ex%61mple") refute_equal(query, original_query) assert_equal(CGI.unescape(query), CGI.unescape(original_query)) - # Make normalised signature based on our modified params. + # Make normalized signature based on our modified params. hash_algorithm = RubySaml::XML.hash_algorithm(settings.security[:signature_method]) signature = settings.get_sp_signing_key.sign(hash_algorithm.new, query) params['Signature'] = Base64.strict_encode64(signature) @@ -369,7 +369,7 @@ class RubySamlTest < Minitest::Test settings.idp_cert = nil end - it "return true when at least a idp_cert is valid" do + it "return true when at least one IdP cert is valid" do params['RelayState'] = params[:RelayState] options = {} options[:get_params] = params @@ -382,7 +382,7 @@ class RubySamlTest < Minitest::Test assert logoutresponse_sign_test.send(:validate_signature) end - it "return false when cert expired and check_idp_cert_expiration expired" do + it "return false when cert is expired and check_idp_cert_expiration is enabled" do settings.security[:check_idp_cert_expiration] = true settings.idp_cert = nil settings.idp_cert_multi = { diff --git a/test/metadata_test.rb b/test/metadata_test.rb index 9fb547d0..452482a8 100644 --- a/test/metadata_test.rb +++ b/test/metadata_test.rb @@ -3,174 +3,181 @@ require_relative 'test_helper' class MetadataTest < Minitest::Test - describe 'Metadata' do let(:settings) { RubySaml::Settings.new } let(:xml_text) { RubySaml::Metadata.new.generate(settings, false) } let(:xml_doc) { Nokogiri::XML(xml_text) } - let(:spsso_descriptor) { xml_doc.at_xpath("//md:SPSSODescriptor", "md" => "urn:oasis:names:tc:SAML:2.0:metadata") } - let(:acs) { xml_doc.at_xpath("//md:AssertionConsumerService", "md" => "urn:oasis:names:tc:SAML:2.0:metadata") } + let(:spsso_descriptor) { xml_doc.at_xpath('//md:SPSSODescriptor', 'md' => 'urn:oasis:names:tc:SAML:2.0:metadata') } + let(:acs) { xml_doc.at_xpath('//md:AssertionConsumerService', 'md' => 'urn:oasis:names:tc:SAML:2.0:metadata') } before do - settings.sp_entity_id = "https://example.com" - settings.name_identifier_format = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress" - settings.assertion_consumer_service_url = "https://foo.example/saml/consume" + settings.sp_entity_id = 'https://example.com' + settings.name_identifier_format = 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress' + + settings.assertion_consumer_service_url = 'https://foo.example/saml/consume' end - it "generates Pretty Print Service Provider Metadata" do + it 'generates Pretty Print Service Provider Metadata' do xml_text = RubySaml::Metadata.new.generate(settings, true) # assert correct xml declaration start = "\n "urn:oasis:names:tc:SAML:2.0:metadata"})["entityID"] + assert_equal xml_text[0..start.length - 1], start + + assert_equal 'https://example.com', xml_doc.at_xpath('//md:EntityDescriptor', { 'md' => 'urn:oasis:names:tc:SAML:2.0:metadata' })['entityID'] - assert_equal RubySaml::XML::NS_PROTOCOL, spsso_descriptor["protocolSupportEnumeration"] - assert_equal "false", spsso_descriptor["AuthnRequestsSigned"] - assert_equal "false", spsso_descriptor["WantAssertionsSigned"] + assert_equal RubySaml::XML::NS_PROTOCOL, spsso_descriptor['protocolSupportEnumeration'] + assert_equal 'false', spsso_descriptor['AuthnRequestsSigned'] + assert_equal 'false', spsso_descriptor['WantAssertionsSigned'] - assert_equal "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", xml_doc.at_xpath("//md:NameIDFormat", {"md" => "urn:oasis:names:tc:SAML:2.0:metadata"}).text.strip + assert_equal 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress', xml_doc.at_xpath('//md:NameIDFormat', { 'md' => 'urn:oasis:names:tc:SAML:2.0:metadata' }).text.strip - assert_equal "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST", acs["Binding"] - assert_equal "https://foo.example/saml/consume", acs["Location"] + assert_equal 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST', acs['Binding'] + assert_equal 'https://foo.example/saml/consume', acs['Location'] - assert validate_xml!(xml_text, "saml-schema-metadata-2.0.xsd") + assert validate_xml!(xml_text, 'saml-schema-metadata-2.0.xsd') end - it "generates Service Provider Metadata" do - settings.single_logout_service_binding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" - settings.single_logout_service_url = "https://foo.example/saml/sls" + it 'generates Service Provider Metadata' do + settings.single_logout_service_binding = 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect' + settings.single_logout_service_url = 'https://foo.example/saml/sls' xml_metadata = RubySaml::Metadata.new.generate(settings, false) start = "\n "urn:oasis:names:tc:SAML:2.0:metadata"}) + sls = doc_metadata.at_xpath('//md:SingleLogoutService', { 'md' => 'urn:oasis:names:tc:SAML:2.0:metadata' }) - assert_equal "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect", sls["Binding"] - assert_equal "https://foo.example/saml/sls", sls["Location"] - assert_equal "https://foo.example/saml/sls", sls["ResponseLocation"] - assert_nil sls["isDefault"] - assert_nil sls["index"] + assert_equal 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect', sls['Binding'] + assert_equal 'https://foo.example/saml/sls', sls['Location'] + assert_equal 'https://foo.example/saml/sls', sls['ResponseLocation'] + assert_nil sls['isDefault'] + assert_nil sls['index'] - assert validate_xml!(xml_text, "saml-schema-metadata-2.0.xsd") + assert validate_xml!(xml_text, 'saml-schema-metadata-2.0.xsd') end - it "generates Service Provider Metadata with single logout service" do + it 'generates Service Provider Metadata with single logout service' do start = "\n "urn:oasis:names:tc:SAML:2.0:metadata"})["entityID"] + assert_equal xml_text[0..start.length - 1], start + + assert_equal 'https://example.com', xml_doc.at_xpath('//md:EntityDescriptor', { 'md' => 'urn:oasis:names:tc:SAML:2.0:metadata' })['entityID'] - assert_equal RubySaml::XML::NS_PROTOCOL, spsso_descriptor["protocolSupportEnumeration"] - assert_equal "false", spsso_descriptor["AuthnRequestsSigned"] - assert_equal "false", spsso_descriptor["WantAssertionsSigned"] + assert_equal RubySaml::XML::NS_PROTOCOL, spsso_descriptor['protocolSupportEnumeration'] + assert_equal 'false', spsso_descriptor['AuthnRequestsSigned'] + assert_equal 'false', spsso_descriptor['WantAssertionsSigned'] - assert_equal "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", xml_doc.at_xpath("//md:NameIDFormat", {"md" => "urn:oasis:names:tc:SAML:2.0:metadata"}).text.strip + assert_equal 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress', xml_doc.at_xpath('//md:NameIDFormat', { 'md' => 'urn:oasis:names:tc:SAML:2.0:metadata' }).text.strip - assert_equal "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST", acs["Binding"] - assert_equal "https://foo.example/saml/consume", acs["Location"] + assert_equal 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST', acs['Binding'] + assert_equal 'https://foo.example/saml/consume', acs['Location'] - assert validate_xml!(xml_text, "saml-schema-metadata-2.0.xsd") + assert validate_xml!(xml_text, 'saml-schema-metadata-2.0.xsd') end - it "generates Service Provider Metadata with ValidUntil and CacheDuration" do - valid_until = Time.now + 172800 - cache_duration = 604800 + it 'generates Service Provider Metadata with ValidUntil and CacheDuration' do + valid_until = Time.now + 172_800 + cache_duration = 604_800 xml_metadata = RubySaml::Metadata.new.generate(settings, false, valid_until, cache_duration) start = "\n "urn:oasis:names:tc:SAML:2.0:metadata"})["validUntil"] - assert_equal "PT604800S", doc_metadata.at_xpath("//md:EntityDescriptor", {"md" => "urn:oasis:names:tc:SAML:2.0:metadata"})["cacheDuration"] + + assert_equal valid_until.strftime('%Y-%m-%dT%H:%M:%SZ'), doc_metadata.at_xpath('//md:EntityDescriptor', { 'md' => 'urn:oasis:names:tc:SAML:2.0:metadata' })['validUntil'] + assert_equal 'PT604800S', doc_metadata.at_xpath('//md:EntityDescriptor', { 'md' => 'urn:oasis:names:tc:SAML:2.0:metadata' })['cacheDuration'] end - describe "WantAssertionsSigned" do - it "generates Service Provider Metadata with WantAssertionsSigned = false" do + describe 'WantAssertionsSigned' do + it 'generates Service Provider Metadata with WantAssertionsSigned = false' do settings.security[:want_assertions_signed] = false - assert_equal "false", spsso_descriptor["WantAssertionsSigned"] - assert validate_xml!(xml_text, "saml-schema-metadata-2.0.xsd") + + assert_equal 'false', spsso_descriptor['WantAssertionsSigned'] + assert validate_xml!(xml_text, 'saml-schema-metadata-2.0.xsd') end - it "generates Service Provider Metadata with WantAssertionsSigned = true" do + it 'generates Service Provider Metadata with WantAssertionsSigned = true' do settings.security[:want_assertions_signed] = true - assert_equal "true", spsso_descriptor["WantAssertionsSigned"] - assert validate_xml!(xml_text, "saml-schema-metadata-2.0.xsd") + + assert_equal 'true', spsso_descriptor['WantAssertionsSigned'] + assert validate_xml!(xml_text, 'saml-schema-metadata-2.0.xsd') end end - describe "with a sign/encrypt certificate" do + describe 'with a sign/encrypt certificate' do let(:key_descriptors) do xml_doc.xpath( - "//md:KeyDescriptor", - "md" => "urn:oasis:names:tc:SAML:2.0:metadata" + '//md:KeyDescriptor', + 'md' => 'urn:oasis:names:tc:SAML:2.0:metadata' ) end let(:cert_nodes) do xml_doc.xpath( - "//md:KeyDescriptor/ds:KeyInfo/ds:X509Data/ds:X509Certificate", - "md" => "urn:oasis:names:tc:SAML:2.0:metadata", - "ds" => "http://www.w3.org/2000/09/xmldsig#" + '//md:KeyDescriptor/ds:KeyInfo/ds:X509Data/ds:X509Certificate', + 'md' => 'urn:oasis:names:tc:SAML:2.0:metadata', + 'ds' => 'http://www.w3.org/2000/09/xmldsig#' ) end - let(:cert) { OpenSSL::X509::Certificate.new(Base64.decode64(cert_nodes[0].text)) } + let(:cert) { OpenSSL::X509::Certificate.new(Base64.decode64(cert_nodes[0].text)) } before do settings.certificate = ruby_saml_cert_text end - it "generates Service Provider Metadata with X509Certificate for sign" do + it 'generates Service Provider Metadata with X509Certificate for sign' do assert_equal 1, key_descriptors.length - assert_equal "signing", key_descriptors[0]["use"] + assert_equal 'signing', key_descriptors[0]['use'] assert_equal 1, cert_nodes.length assert_equal ruby_saml_cert.to_der, cert.to_der - assert validate_xml!(xml_text, "saml-schema-metadata-2.0.xsd") + assert validate_xml!(xml_text, 'saml-schema-metadata-2.0.xsd') end - describe "and signed authentication requests" do + describe 'and signed authentication requests' do before do settings.security[:authn_requests_signed] = true end - it "generates Service Provider Metadata with AuthnRequestsSigned" do - assert_equal "true", spsso_descriptor["AuthnRequestsSigned"] - assert validate_xml!(xml_text, "saml-schema-metadata-2.0.xsd") + it 'generates Service Provider Metadata with AuthnRequestsSigned' do + assert_equal 'true', spsso_descriptor['AuthnRequestsSigned'] + assert validate_xml!(xml_text, 'saml-schema-metadata-2.0.xsd') end end - describe "and encrypted assertions" do + describe 'and encrypted assertions' do before do settings.security[:want_assertions_encrypted] = true end - it "generates Service Provider Metadata with X509Certificate for encrypt" do + it 'generates Service Provider Metadata with X509Certificate for encrypt' do assert_equal 2, key_descriptors.length - assert_equal "encryption", key_descriptors[1]["use"] + assert_equal 'encryption', key_descriptors[1]['use'] assert_equal 2, cert_nodes.length assert_equal cert_nodes[0].text, cert_nodes[1].text - assert validate_xml!(xml_text, "saml-schema-metadata-2.0.xsd") + assert validate_xml!(xml_text, 'saml-schema-metadata-2.0.xsd') end end end - describe "with a future SP certificate" do + describe 'with a future SP certificate' do let(:key_descriptors) do xml_doc.xpath( - "//md:KeyDescriptor", - "md" => "urn:oasis:names:tc:SAML:2.0:metadata" + '//md:KeyDescriptor', + 'md' => 'urn:oasis:names:tc:SAML:2.0:metadata' ) end let(:cert_nodes) do xml_doc.xpath( - "//md:KeyDescriptor/ds:KeyInfo/ds:X509Data/ds:X509Certificate", - "md" => "urn:oasis:names:tc:SAML:2.0:metadata", - "ds" => "http://www.w3.org/2000/09/xmldsig#" + '//md:KeyDescriptor/ds:KeyInfo/ds:X509Data/ds:X509Certificate', + 'md' => 'urn:oasis:names:tc:SAML:2.0:metadata', + 'ds' => 'http://www.w3.org/2000/09/xmldsig#' ) end @@ -179,10 +186,10 @@ class MetadataTest < Minitest::Test settings.certificate_new = ruby_saml_cert_text2 end - it "generates Service Provider Metadata with 2 X509Certificate for sign" do + it 'generates Service Provider Metadata with 2 X509Certificate for sign' do assert_equal 2, key_descriptors.length - assert_equal "signing", key_descriptors[0]["use"] - assert_equal "signing", key_descriptors[1]["use"] + assert_equal 'signing', key_descriptors[0]['use'] + assert_equal 'signing', key_descriptors[1]['use'] cert = OpenSSL::X509::Certificate.new(Base64.decode64(cert_nodes[0].text)) cert_new = OpenSSL::X509::Certificate.new(Base64.decode64(cert_nodes[1].text)) @@ -191,40 +198,40 @@ class MetadataTest < Minitest::Test assert_equal ruby_saml_cert.to_der, cert.to_der assert_equal ruby_saml_cert2.to_der, cert_new.to_der - assert validate_xml!(xml_text, "saml-schema-metadata-2.0.xsd") + assert validate_xml!(xml_text, 'saml-schema-metadata-2.0.xsd') end - describe "and signed authentication requests" do + describe 'and signed authentication requests' do before do settings.security[:authn_requests_signed] = true end - it "generates Service Provider Metadata with AuthnRequestsSigned" do - assert_equal "true", spsso_descriptor["AuthnRequestsSigned"] - assert validate_xml!(xml_text, "saml-schema-metadata-2.0.xsd") + it 'generates Service Provider Metadata with AuthnRequestsSigned' do + assert_equal 'true', spsso_descriptor['AuthnRequestsSigned'] + assert validate_xml!(xml_text, 'saml-schema-metadata-2.0.xsd') end end - describe "and encrypted assertions" do + describe 'and encrypted assertions' do before do settings.security[:want_assertions_encrypted] = true end - it "generates Service Provider Metadata with X509Certificate for encrypt" do + it 'generates Service Provider Metadata with X509Certificate for encrypt' do assert_equal 4, key_descriptors.length - assert_equal "signing", key_descriptors[0]["use"] - assert_equal "signing", key_descriptors[1]["use"] - assert_equal "encryption", key_descriptors[2]["use"] - assert_equal "encryption", key_descriptors[3]["use"] + assert_equal 'signing', key_descriptors[0]['use'] + assert_equal 'signing', key_descriptors[1]['use'] + assert_equal 'encryption', key_descriptors[2]['use'] + assert_equal 'encryption', key_descriptors[3]['use'] assert_equal 4, cert_nodes.length assert_equal cert_nodes[0].text, cert_nodes[2].text assert_equal cert_nodes[1].text, cert_nodes[3].text - assert validate_xml!(xml_text, "saml-schema-metadata-2.0.xsd") + assert validate_xml!(xml_text, 'saml-schema-metadata-2.0.xsd') end end - describe "with check_sp_cert_expiration and expired keys" do + describe 'with check_sp_cert_expiration and expired keys' do before do settings.security[:want_assertions_encrypted] = true settings.security[:check_sp_cert_expiration] = true @@ -240,96 +247,97 @@ class MetadataTest < Minitest::Test } end - it "generates Service Provider Metadata with X509Certificate for encrypt" do + it 'generates Service Provider Metadata with X509Certificate for encrypt' do assert_equal 2, key_descriptors.length - assert_equal "signing", key_descriptors[0]["use"] - assert_equal "encryption", key_descriptors[1]["use"] + assert_equal 'signing', key_descriptors[0]['use'] + assert_equal 'encryption', key_descriptors[1]['use'] assert_equal 2, cert_nodes.length - assert validate_xml!(xml_text, "saml-schema-metadata-2.0.xsd") + assert validate_xml!(xml_text, 'saml-schema-metadata-2.0.xsd') end end end - describe "when attribute service is configured with multiple attribute values" do - let(:attr_svc) { xml_doc.at_xpath("//md:AttributeConsumingService", {"md" => "urn:oasis:names:tc:SAML:2.0:metadata"}) } - let(:req_attr) { xml_doc.at_xpath("//md:RequestedAttribute", {"md" => "urn:oasis:names:tc:SAML:2.0:metadata"}) } + describe 'when attribute service is configured with multiple attribute values' do + let(:attr_svc) { xml_doc.at_xpath('//md:AttributeConsumingService', { 'md' => 'urn:oasis:names:tc:SAML:2.0:metadata' }) } + let(:req_attr) { xml_doc.at_xpath('//md:RequestedAttribute', { 'md' => 'urn:oasis:names:tc:SAML:2.0:metadata' }) } before do settings.attribute_consuming_service.configure do - service_name "Test Service" - add_attribute(:name => 'Name', :name_format => 'Name Format', :friendly_name => 'Friendly Name', :attribute_value => ['Attribute Value One', false]) + service_name 'Test Service' + add_attribute(name: 'Name', name_format: 'Name Format', friendly_name: 'Friendly Name', attribute_value: ['Attribute Value One', false]) end end - it "generates attribute service" do - assert_equal "true", attr_svc["isDefault"] - assert_equal "1", attr_svc["index"] - assert_equal xml_doc.at_xpath("//md:ServiceName", {"md" => "urn:oasis:names:tc:SAML:2.0:metadata"}).text.strip, "Test Service" + it 'generates attribute service' do + assert_equal 'true', attr_svc['isDefault'] + assert_equal '1', attr_svc['index'] + assert_equal 'Test Service', xml_doc.at_xpath('//md:ServiceName', { 'md' => 'urn:oasis:names:tc:SAML:2.0:metadata' }).text.strip + + assert_equal 'Name', req_attr['Name'] + assert_equal 'Name Format', req_attr['NameFormat'] + assert_equal 'Friendly Name', req_attr['FriendlyName'] - assert_equal "Name", req_attr["Name"] - assert_equal "Name Format", req_attr["NameFormat"] - assert_equal "Friendly Name", req_attr["FriendlyName"] + attribute_values = xml_doc.xpath('//saml:AttributeValue', { 'saml' => RubySaml::XML::NS_ASSERTION }).map(&:text) - attribute_values = xml_doc.xpath("//saml:AttributeValue", {"saml" => RubySaml::XML::NS_ASSERTION}).map(&:text) - assert_equal "Attribute Value One", attribute_values[0] + assert_equal 'Attribute Value One', attribute_values[0] assert_equal 'false', attribute_values[1] - assert validate_xml!(xml_text, "saml-schema-metadata-2.0.xsd") + assert validate_xml!(xml_text, 'saml-schema-metadata-2.0.xsd') end end - describe "when attribute service is configured" do - let(:attr_svc) { xml_doc.at_xpath('//md:AttributeConsumingService', {"md" => "urn:oasis:names:tc:SAML:2.0:metadata"}) } - let(:req_attr) { xml_doc.at_xpath('//md:RequestedAttribute', {"md" => "urn:oasis:names:tc:SAML:2.0:metadata"}) } + describe 'when attribute service is configured' do + let(:attr_svc) { xml_doc.at_xpath('//md:AttributeConsumingService', { 'md' => 'urn:oasis:names:tc:SAML:2.0:metadata' }) } + let(:req_attr) { xml_doc.at_xpath('//md:RequestedAttribute', { 'md' => 'urn:oasis:names:tc:SAML:2.0:metadata' }) } before do settings.attribute_consuming_service.configure do - service_name "Test Service" - add_attribute(:name => 'active', :name_format => 'format', :friendly_name => 'Active', :attribute_value => true) + service_name 'Test Service' + add_attribute(name: 'active', name_format: 'format', friendly_name: 'Active', attribute_value: true) end end - it "generates attribute service" do - assert_equal "true", attr_svc["isDefault"] - assert_equal "1", attr_svc["index"] - assert_equal xml_doc.at_xpath("//md:ServiceName", {"md" => "urn:oasis:names:tc:SAML:2.0:metadata"}).text.strip, "Test Service" + it 'generates attribute service' do + assert_equal 'true', attr_svc['isDefault'] + assert_equal '1', attr_svc['index'] + assert_equal 'Test Service', xml_doc.at_xpath('//md:ServiceName', { 'md' => 'urn:oasis:names:tc:SAML:2.0:metadata' }).text.strip assert_equal 'active', req_attr['Name'] assert_equal 'format', req_attr['NameFormat'] assert_equal 'Active', req_attr['FriendlyName'] - assert_equal 'true', xml_doc.at_xpath('//saml:AttributeValue', {"saml" => RubySaml::XML::NS_ASSERTION}).text.strip + assert_equal 'true', xml_doc.at_xpath('//saml:AttributeValue', { 'saml' => RubySaml::XML::NS_ASSERTION }).text.strip - assert validate_xml!(xml_text, "saml-schema-metadata-2.0.xsd") + assert validate_xml!(xml_text, 'saml-schema-metadata-2.0.xsd') end - describe "#service_name" do + describe '#service_name' do before do - settings.attribute_consuming_service.service_name("Test2 Service") + settings.attribute_consuming_service.service_name('Test2 Service') end - it "change service name" do - assert_equal xml_doc.at_xpath("//md:ServiceName", {"md" => "urn:oasis:names:tc:SAML:2.0:metadata"}).text.strip, "Test2 Service" + it 'change service name' do + assert_equal 'Test2 Service', xml_doc.at_xpath('//md:ServiceName', { 'md' => 'urn:oasis:names:tc:SAML:2.0:metadata' }).text.strip end end - describe "#service_index" do + describe '#service_index' do before do settings.attribute_consuming_service.service_index(2) end - it "change service index" do - assert_equal "2", attr_svc["index"] + it 'change service index' do + assert_equal '2', attr_svc['index'] end end end - describe "when the settings indicate to sign (embedded) metadata" do + describe 'when the settings indicate to sign (embedded) metadata' do before do settings.security[:metadata_signed] = true end - it "uses RSA SHA256 by default" do + it 'uses RSA SHA256 by default' do @cert, @pkey = CertificateHelper.generate_pair(:rsa) settings.certificate, settings.private_key = [@cert, @pkey].map(&:to_pem) @fingerprint = OpenSSL::Digest.new('SHA256', @cert.to_der).to_s @@ -338,11 +346,11 @@ class MetadataTest < Minitest::Test assert_match(signature_method_matcher(:rsa, :sha256), xml_text) assert_match(digest_method_matcher(:sha256), xml_text) assert(RubySaml::XML::SignedDocumentValidator.validate_document(xml_text, @fingerprint, soft: false)) - assert(validate_xml!(xml_text, "saml-schema-metadata-2.0.xsd")) + assert(validate_xml!(xml_text, 'saml-schema-metadata-2.0.xsd')) end each_signature_algorithm do |sp_key_algo, sp_hash_algo| - describe "specifying algo" do + describe 'specifying algo' do before do @cert, @pkey = CertificateHelper.generate_pair(sp_key_algo) settings.certificate, settings.private_key = [@cert, @pkey].map(&:to_pem) @@ -351,13 +359,13 @@ class MetadataTest < Minitest::Test settings.security[:digest_method] = digest_method(sp_hash_algo) end - it "creates a signed metadata" do + it 'creates a signed metadata' do assert_match(signature_value_matcher, xml_text) assert_match(signature_method_matcher(sp_key_algo, sp_hash_algo), xml_text) assert_match(digest_method_matcher(sp_hash_algo), xml_text) assert RubySaml::XML::SignedDocumentValidator.validate_document(xml_text, @fingerprint, soft: false) - assert validate_xml!(xml_text, "saml-schema-metadata-2.0.xsd") + assert validate_xml!(xml_text, 'saml-schema-metadata-2.0.xsd') end unless sp_hash_algo == :sha256 @@ -369,7 +377,7 @@ class MetadataTest < Minitest::Test assert_match(signature_method_matcher(sp_key_algo, :sha256), xml_text) assert_match(digest_method_matcher(sp_hash_algo), xml_text) assert(RubySaml::XML::SignedDocumentValidator.validate_document(xml_text, @fingerprint, soft: false)) - assert(validate_xml!(xml_text, "saml-schema-metadata-2.0.xsd")) + assert(validate_xml!(xml_text, 'saml-schema-metadata-2.0.xsd')) end it 'using mixed signature and digest methods (digest SHA256)' do @@ -379,11 +387,11 @@ class MetadataTest < Minitest::Test assert_match(signature_method_matcher(sp_key_algo, sp_hash_algo), xml_text) assert_match(digest_method_matcher(:sha256), xml_text) assert(RubySaml::XML::SignedDocumentValidator.validate_document(xml_text, @fingerprint, soft: false)) - assert(validate_xml!(xml_text, "saml-schema-metadata-2.0.xsd")) + assert(validate_xml!(xml_text, 'saml-schema-metadata-2.0.xsd')) end end - describe "when custom metadata elements have been inserted" do + describe 'when custom metadata elements have been inserted' do let(:xml_text) { subclass.new.generate(settings, false) } let(:subclass) do Class.new(RubySaml::Metadata) do @@ -404,17 +412,17 @@ def add_extras(xml, _settings) end end - it "inserts signature as the first child of root element" do + it 'inserts signature as the first child of root element' do xml_text = subclass.new.generate(settings, false) first_child = xml_doc.root.element_children.first - assert_equal first_child.namespace.prefix, 'ds' - assert_equal first_child.name, 'Signature' + assert_equal 'ds', first_child.namespace.prefix + assert_equal 'Signature', first_child.name assert_match(signature_value_matcher, xml_text) assert_match(signature_method_matcher(sp_key_algo, sp_hash_algo), xml_text) assert_match(digest_method_matcher(sp_hash_algo), xml_text) assert(RubySaml::XML::SignedDocumentValidator.validate_document(xml_text, @fingerprint, soft: false)) - assert validate_xml!(xml_text, "saml-schema-metadata-2.0.xsd") + assert validate_xml!(xml_text, 'saml-schema-metadata-2.0.xsd') end end end diff --git a/test/onelogin_rubysaml_compat_test.rb b/test/onelogin_rubysaml_compat_test.rb index 5d62203e..8d6376fe 100644 --- a/test/onelogin_rubysaml_compat_test.rb +++ b/test/onelogin_rubysaml_compat_test.rb @@ -107,7 +107,7 @@ class OneLoginRubySamlCompatTest < Minitest::Test let(:response_no_status) { OneLogin::RubySaml::Response.new(read_invalid_response("no_status.xml.base64")) } let(:response_no_statuscode) { OneLogin::RubySaml::Response.new(read_invalid_response("no_status_code.xml.base64")) } let(:response_statuscode_responder) { OneLogin::RubySaml::Response.new(read_invalid_response("status_code_responder.xml.base64")) } - let(:response_statuscode_responder_and_msg) { OneLogin::RubySaml::Response.new(read_invalid_response("status_code_responer_and_msg.xml.base64")) } + let(:response_statuscode_responder_and_msg) { OneLogin::RubySaml::Response.new(read_invalid_response("status_code_responder_and_msg.xml.base64")) } let(:response_double_statuscode) { OneLogin::RubySaml::Response.new(response_document_double_status_code) } let(:response_encrypted_attrs) { OneLogin::RubySaml::Response.new(response_document_encrypted_attrs) } let(:response_no_signed_elements) { OneLogin::RubySaml::Response.new(read_invalid_response("no_signature.xml.base64")) } @@ -182,7 +182,7 @@ def generate_audience_error(expected, actual) @response.settings.idp_cert_fingerprint = ruby_saml_cert_fingerprint end - it "it normalizes CDATA but reject SAMLResponse due signature invalidation" do + it "it normalizes CDATA but rejects the SAMLResponse due to signature invalidation" do assert_equal "test@onelogin.com.evil.com", @response.name_id assert !@response.is_valid? assert_includes @response.errors, "Invalid Signature on SAML Response" diff --git a/test/pem_formatter_test.rb b/test/pem_formatter_test.rb index 717c9ff9..549b8088 100644 --- a/test/pem_formatter_test.rb +++ b/test/pem_formatter_test.rb @@ -7,10 +7,10 @@ class PemFormatterTest < Minitest::Test "l\n bGwgeW91 IqE+bSBpbn\t NhbmUKQnV0IE\r\nkndmUgZ 2/0IGEgYmxhbmsgc" \ "3BhY+UsIGJhY nkKQW5kIEkn/Gwgd3\npdG UgeW91ciBuYW1l \n\r\n" BASE64_OUT = <<~BASE64.strip - R290IGEgbG9uZyBsaXN0IG9mIGV4LWx/dmVycwpUaGV5J2xsIHRlbGwgeW91IqE+ - bSBpbnNhbmUKQnV0IEkndmUgZ2/0IGEgYmxhbmsgc3BhY+UsIGJhYnkKQW5kIEkn - /Gwgd3pdGUgeW91ciBuYW1l - BASE64 + R290IGEgbG9uZyBsaXN0IG9mIGV4LWx/dmVycwpUaGV5J2xsIHRlbGwgeW91IqE+ + bSBpbnNhbmUKQnV0IEkndmUgZ2/0IGEgYmxhbmsgc3BhY+UsIGJhYnkKQW5kIEkn + /Gwgd3pdGUgeW91ciBuYW1l + BASE64 describe RubySaml::PemFormatter do def build_pem(label, body) @@ -112,7 +112,7 @@ def build_pkey(body) it 'ignores non-cert PEMs when multiple PEMs are given' do multi = "#{build_pkey('BAR=')}\n#{build_cert(BASE64_RAW)}\n #{build_cert("\n")} #{build_pkey('BAZ')} " \ - "#{build_pem("\t \nXXX \t\n\r CERTIFICATE \n ", 'F00==')} #{build_pkey('QUX==')}\n" + "#{build_pem("\t \nXXX \t\n\r CERTIFICATE \n ", 'F00==')} #{build_pkey('QUX==')}\n" input = multi.dup expected_ary = [build_cert(BASE64_OUT), build_cert('F00==')] expected_one = expected_ary.first @@ -124,7 +124,7 @@ def build_pkey(body) assert_equal input, multi end - it 'ignores non-cert PEMs array of PEMs is given' do + it 'ignores non-cert PEMs when an array of PEMs is given' do array = [build_pkey('BAR='), "#{build_cert("\n")} \n#{build_cert(BASE64_RAW)}\n #{build_pkey('BAZ')} ", build_pkey('BAZ'), @@ -312,7 +312,7 @@ def build_pkey(body) it 'formats PEM to exactly 64 characters per line' do input = 'A' * 130 - expected = build_cert("#{('A' * 64)}\n#{('A' * 64)}\nAA") + expected = build_cert("#{'A' * 64}\n#{'A' * 64}\nAA") assert_equal [expected], RubySaml::PemFormatter.format_cert_array(input) assert_equal [expected], RubySaml::PemFormatter.format_cert_array(build_cert(input)) @@ -611,7 +611,7 @@ def build_pkey(body) it 'formats PEM to exactly 64 characters per line' do input = 'A' * 130 - expected = build_pkey("#{('A' * 64)}\n#{('A' * 64)}\nAA") + expected = build_pkey("#{'A' * 64}\n#{'A' * 64}\nAA") assert_equal [expected], RubySaml::PemFormatter.format_private_key_array(input) assert_equal [expected], RubySaml::PemFormatter.format_private_key_array(build_pkey(input)) diff --git a/test/response_test.rb b/test/response_test.rb index a0050692..239e3d65 100644 --- a/test/response_test.rb +++ b/test/response_test.rb @@ -3,8 +3,7 @@ require_relative 'test_helper' class RubySamlTest < Minitest::Test - - describe "Response" do + describe 'Response' do let(:settings) { RubySaml::Settings.new } let(:response) { RubySaml::Response.new(response_document_without_recipient) } let(:response_without_recipient) { RubySaml::Response.new(signed_response_document_without_recipient) } @@ -12,49 +11,49 @@ class RubySamlTest < Minitest::Test let(:response_with_multiple_attribute_statements) { RubySaml::Response.new(fixture(:response_with_multiple_attribute_statements)) } let(:response_without_reference_uri) { RubySaml::Response.new(response_document_without_reference_uri) } let(:response_with_signed_assertion) { RubySaml::Response.new(response_document_with_signed_assertion) } - let(:response_with_ds_namespace_at_the_root) { RubySaml::Response.new(response_document_with_ds_namespace_at_the_root)} + let(:response_with_ds_namespace_at_the_root) { RubySaml::Response.new(response_document_with_ds_namespace_at_the_root) } let(:response_unsigned) { RubySaml::Response.new(response_document_unsigned) } let(:response_wrapped) { RubySaml::Response.new(response_document_wrapped) } let(:response_multiple_attr_values) { RubySaml::Response.new(fixture(:response_with_multiple_attribute_values)) } let(:response_valid_signed) { RubySaml::Response.new(response_document_valid_signed) } - let(:response_valid_signed_without_recipient) { RubySaml::Response.new(response_document_valid_signed, {:skip_recipient_check => true })} + let(:response_valid_signed_without_recipient) { RubySaml::Response.new(response_document_valid_signed, { skip_recipient_check: true }) } let(:response_valid_signed_without_x509certificate) { RubySaml::Response.new(response_document_valid_signed_without_x509certificate) } - let(:response_no_id) { RubySaml::Response.new(read_invalid_response("no_id.xml.base64")) } - let(:response_no_version) { RubySaml::Response.new(read_invalid_response("no_saml2.xml.base64")) } - let(:response_multi_assertion) { RubySaml::Response.new(read_invalid_response("multiple_assertions.xml.base64")) } - let(:response_no_conditions) { RubySaml::Response.new(read_invalid_response("no_conditions.xml.base64")) } - let(:response_no_conditions_with_skip) { RubySaml::Response.new(read_invalid_response("no_conditions.xml.base64"), { :skip_conditions => true }) } - let(:response_no_authnstatement) { RubySaml::Response.new(read_invalid_response("no_authnstatement.xml.base64")) } - let(:response_no_authnstatement_with_skip) { RubySaml::Response.new(read_invalid_response("no_authnstatement.xml.base64"), {:skip_authnstatement => true}) } - let(:response_empty_destination) { RubySaml::Response.new(read_invalid_response("empty_destination.xml.base64")) } - let(:response_empty_destination_with_skip) { RubySaml::Response.new(read_invalid_response("empty_destination.xml.base64"), {:skip_destination => true}) } - let(:response_no_status) { RubySaml::Response.new(read_invalid_response("no_status.xml.base64")) } - let(:response_no_statuscode) { RubySaml::Response.new(read_invalid_response("no_status_code.xml.base64")) } - let(:response_statuscode_responder) { RubySaml::Response.new(read_invalid_response("status_code_responder.xml.base64")) } - let(:response_statuscode_responder_and_msg) { RubySaml::Response.new(read_invalid_response("status_code_responer_and_msg.xml.base64")) } + let(:response_no_id) { RubySaml::Response.new(read_invalid_response('no_id.xml.base64')) } + let(:response_no_version) { RubySaml::Response.new(read_invalid_response('no_saml2.xml.base64')) } + let(:response_multi_assertion) { RubySaml::Response.new(read_invalid_response('multiple_assertions.xml.base64')) } + let(:response_no_conditions) { RubySaml::Response.new(read_invalid_response('no_conditions.xml.base64')) } + let(:response_no_conditions_with_skip) { RubySaml::Response.new(read_invalid_response('no_conditions.xml.base64'), { skip_conditions: true }) } + let(:response_no_authnstatement) { RubySaml::Response.new(read_invalid_response('no_authnstatement.xml.base64')) } + let(:response_no_authnstatement_with_skip) { RubySaml::Response.new(read_invalid_response('no_authnstatement.xml.base64'), { skip_authnstatement: true }) } + let(:response_empty_destination) { RubySaml::Response.new(read_invalid_response('empty_destination.xml.base64')) } + let(:response_empty_destination_with_skip) { RubySaml::Response.new(read_invalid_response('empty_destination.xml.base64'), { skip_destination: true }) } + let(:response_no_status) { RubySaml::Response.new(read_invalid_response('no_status.xml.base64')) } + let(:response_no_statuscode) { RubySaml::Response.new(read_invalid_response('no_status_code.xml.base64')) } + let(:response_statuscode_responder) { RubySaml::Response.new(read_invalid_response('status_code_responder.xml.base64')) } + let(:response_statuscode_responder_and_msg) { RubySaml::Response.new(read_invalid_response('status_code_responder_and_msg.xml.base64')) } let(:response_double_statuscode) { RubySaml::Response.new(response_document_double_status_code) } let(:response_encrypted_attrs) { RubySaml::Response.new(response_document_encrypted_attrs) } - let(:response_no_signed_elements) { RubySaml::Response.new(read_invalid_response("no_signature.xml.base64")) } - let(:response_multiple_signed) { RubySaml::Response.new(read_invalid_response("multiple_signed.xml.base64")) } - let(:response_audience_self_closed) { RubySaml::Response.new(read_response("response_audience_self_closed_tag.xml.base64")) } - let(:response_invalid_audience) { RubySaml::Response.new(read_invalid_response("invalid_audience.xml.base64")) } - let(:response_invalid_audience_with_skip) { RubySaml::Response.new(read_invalid_response("invalid_audience.xml.base64"), {:skip_audience => true}) } - let(:response_invalid_signed_element) { RubySaml::Response.new(read_invalid_response("response_invalid_signed_element.xml.base64")) } - let(:response_invalid_issuer_assertion) { RubySaml::Response.new(read_invalid_response("invalid_issuer_assertion.xml.base64")) } - let(:response_invalid_issuer_message) { RubySaml::Response.new(read_invalid_response("invalid_issuer_message.xml.base64")) } - let(:response_no_issuer_response) { RubySaml::Response.new(read_invalid_response("no_issuer_response.xml.base64")) } - let(:response_no_issuer_assertion) { RubySaml::Response.new(read_invalid_response("no_issuer_assertion.xml.base64")) } - let(:response_no_nameid) { RubySaml::Response.new(read_invalid_response("no_nameid.xml.base64")) } - let(:response_empty_nameid) { RubySaml::Response.new(read_invalid_response("empty_nameid.xml.base64")) } - let(:response_wrong_spnamequalifier) { RubySaml::Response.new(read_invalid_response("wrong_spnamequalifier.xml.base64")) } - let(:response_duplicated_attributes) { RubySaml::Response.new(read_invalid_response("duplicated_attributes.xml.base64")) } - let(:response_no_subjectconfirmation_data) { RubySaml::Response.new(read_invalid_response("no_subjectconfirmation_data.xml.base64")) } - let(:response_no_subjectconfirmation_method) { RubySaml::Response.new(read_invalid_response("no_subjectconfirmation_method.xml.base64")) } - let(:response_invalid_subjectconfirmation_inresponse) { RubySaml::Response.new(read_invalid_response("invalid_subjectconfirmation_inresponse.xml.base64")) } - let(:response_invalid_subjectconfirmation_recipient) { RubySaml::Response.new(read_invalid_response("invalid_subjectconfirmation_recipient.xml.base64")) } - let(:response_invalid_subjectconfirmation_nb) { RubySaml::Response.new(read_invalid_response("invalid_subjectconfirmation_nb.xml.base64")) } - let(:response_invalid_subjectconfirmation_noa) { RubySaml::Response.new(read_invalid_response("invalid_subjectconfirmation_noa.xml.base64")) } - let(:response_invalid_signature_position) { RubySaml::Response.new(read_invalid_response("invalid_signature_position.xml.base64")) } + let(:response_no_signed_elements) { RubySaml::Response.new(read_invalid_response('no_signature.xml.base64')) } + let(:response_multiple_signed) { RubySaml::Response.new(read_invalid_response('multiple_signed.xml.base64')) } + let(:response_audience_self_closed) { RubySaml::Response.new(read_response('response_audience_self_closed_tag.xml.base64')) } + let(:response_invalid_audience) { RubySaml::Response.new(read_invalid_response('invalid_audience.xml.base64')) } + let(:response_invalid_audience_with_skip) { RubySaml::Response.new(read_invalid_response('invalid_audience.xml.base64'), { skip_audience: true }) } + let(:response_invalid_signed_element) { RubySaml::Response.new(read_invalid_response('response_invalid_signed_element.xml.base64')) } + let(:response_invalid_issuer_assertion) { RubySaml::Response.new(read_invalid_response('invalid_issuer_assertion.xml.base64')) } + let(:response_invalid_issuer_message) { RubySaml::Response.new(read_invalid_response('invalid_issuer_message.xml.base64')) } + let(:response_no_issuer_response) { RubySaml::Response.new(read_invalid_response('no_issuer_response.xml.base64')) } + let(:response_no_issuer_assertion) { RubySaml::Response.new(read_invalid_response('no_issuer_assertion.xml.base64')) } + let(:response_no_nameid) { RubySaml::Response.new(read_invalid_response('no_nameid.xml.base64')) } + let(:response_empty_nameid) { RubySaml::Response.new(read_invalid_response('empty_nameid.xml.base64')) } + let(:response_wrong_spnamequalifier) { RubySaml::Response.new(read_invalid_response('wrong_spnamequalifier.xml.base64')) } + let(:response_duplicated_attributes) { RubySaml::Response.new(read_invalid_response('duplicated_attributes.xml.base64')) } + let(:response_no_subjectconfirmation_data) { RubySaml::Response.new(read_invalid_response('no_subjectconfirmation_data.xml.base64')) } + let(:response_no_subjectconfirmation_method) { RubySaml::Response.new(read_invalid_response('no_subjectconfirmation_method.xml.base64')) } + let(:response_invalid_subjectconfirmation_inresponse) { RubySaml::Response.new(read_invalid_response('invalid_subjectconfirmation_inresponse.xml.base64')) } + let(:response_invalid_subjectconfirmation_recipient) { RubySaml::Response.new(read_invalid_response('invalid_subjectconfirmation_recipient.xml.base64')) } + let(:response_invalid_subjectconfirmation_nb) { RubySaml::Response.new(read_invalid_response('invalid_subjectconfirmation_nb.xml.base64')) } + let(:response_invalid_subjectconfirmation_noa) { RubySaml::Response.new(read_invalid_response('invalid_subjectconfirmation_noa.xml.base64')) } + let(:response_invalid_signature_position) { RubySaml::Response.new(read_invalid_response('invalid_signature_position.xml.base64')) } let(:response_encrypted_nameid) { RubySaml::Response.new(response_document_encrypted_nameid) } def generate_audience_error(expected, actual) @@ -62,18 +61,19 @@ def generate_audience_error(expected, actual) "Invalid Audience#{s}. The audience#{s} #{actual.join(',')}, did not match the expected audience #{expected}" end - it "raise an exception when response is initialized with nil" do + it 'raise an exception when response is initialized with nil' do assert_raises(ArgumentError) { RubySaml::Response.new(nil) } end - it "not filter available options only" do - options = { :skip_destination => true, :foo => :bar } + it 'not filter available options only' do + options = { skip_destination: true, foo: :bar } response = RubySaml::Response.new(response_document_valid_signed, options) + assert_includes response.options.keys, :skip_destination assert_includes response.options.keys, :foo end - it "be able to parse a document which contains ampersands" do + it 'be able to parse a document which contains ampersands' do RubySaml::XML::SignedDocumentValidator.stubs(:digests_match?).returns(true) RubySaml::Response.any_instance.stubs(:validate_conditions).returns(true) @@ -81,159 +81,162 @@ def generate_audience_error(expected, actual) ampersands_response.settings = settings ampersands_response.settings.idp_cert_fingerprint = 'c51985d947f1be57082025050846eb27f6cab783' - assert !ampersands_response.is_valid? - assert_includes ampersands_response.errors, "SAML Response must contain 1 assertion" + refute ampersands_response.is_valid? + assert_includes ampersands_response.errors, 'SAML Response must contain 1 assertion' end - describe "Prevent node text with comment attack (VU#475445)" do + describe 'Prevent node text with comment attack (VU#475445)' do before do @response = RubySaml::Response.new(read_response('response_node_text_attack.xml.base64')) end - it "receives the full NameID when there is an injected comment" do - assert_equal "support@onelogin.com", @response.name_id + it 'receives the full NameID when there is an injected comment' do + assert_equal 'support@onelogin.com', @response.name_id end - it "receives the full AttributeValue when there is an injected comment" do - assert_equal "smith", @response.attributes["surname"] + it 'receives the full AttributeValue when there is an injected comment' do + assert_equal 'smith', @response.attributes['surname'] end end - describe "Another test to prevent with comment attack (VU#475445)" do + describe 'Another test to prevent with comment attack (VU#475445)' do before do - @response = RubySaml::Response.new(read_response('response_node_text_attack2.xml.base64'), {:skip_recipient_check => true }) + @response = RubySaml::Response.new(read_response('response_node_text_attack2.xml.base64'), { skip_recipient_check: true }) @response.settings = settings @response.settings.idp_cert_fingerprint = ruby_saml_cert_fingerprint end - it "receives the full NameID when there is an injected comment, validates the response" do - assert_equal "test@onelogin.com", @response.name_id + it 'receives the full NameID when there is an injected comment, validates the response' do + assert_equal 'test@onelogin.com', @response.name_id end end - describe "Another test with CDATA injected" do + describe 'Another test with CDATA injected' do before do - @response = RubySaml::Response.new(read_response('response_node_text_attack3.xml.base64'), {:skip_recipient_check => true }) + @response = RubySaml::Response.new(read_response('response_node_text_attack3.xml.base64'), { skip_recipient_check: true }) @response.settings = settings @response.settings.idp_cert_fingerprint = ruby_saml_cert_fingerprint end - it "it normalizes CDATA but reject SAMLResponse due signature invalidation" do - assert_equal "test@onelogin.com.evil.com", @response.name_id - assert !@response.is_valid? - assert_includes @response.errors, "Invalid Signature on SAML Response" + it 'it normalizes CDATA but reject SAMLResponse due signature invalidation' do + assert_equal 'test@onelogin.com.evil.com', @response.name_id + refute @response.is_valid? + assert_includes @response.errors, 'Invalid Signature on SAML Response' end end - describe "Prevent XEE attack" do + describe 'Prevent XEE attack' do before do @response = RubySaml::Response.new(fixture(:attackxee)) end - it "false when evil attack vector is present, soft = true" do + it 'false when evil attack vector is present, soft = true' do @response.soft = true - assert !@response.send(:validate_structure) - assert_includes @response.errors, "Invalid SAML Response. Not match the saml-schema-protocol-2.0.xsd" + + refute @response.send(:validate_structure) + assert_includes @response.errors, 'Invalid SAML Response. Not match the saml-schema-protocol-2.0.xsd' end - it "raise when evil attack vector is present, soft = false " do + it 'raise when evil attack vector is present, soft = false ' do @response.soft = false - error_msg = "XML load failed: Dangerous XML detected. No Doctype nodes allowed" + error_msg = 'XML load failed: Dangerous XML detected. No Doctype nodes allowed' assert_raises(RubySaml::ValidationError, error_msg) do @response.send(:validate_structure) end end end - it "adapt namespace" do + it 'adapt namespace' do refute_nil response.nameid refute_nil response_without_attributes.nameid refute_nil response_with_signed_assertion.nameid end - it "default to raw input when a response is not Base64 encoded" do - decoded = Base64.decode64(response_document_without_attributes) + it 'default to raw input when a response is not Base64 encoded' do + decoded = Base64.decode64(response_document_without_attributes) response_from_raw = RubySaml::Response.new(decoded) + assert response_from_raw.document end - describe "Assertion" do - it "only retreive an assertion with an ID that matches the signature's reference URI" do + describe 'Assertion' do + it "only retrieve an assertion with an ID that matches the signature's reference URI" do response_wrapped.stubs(:conditions).returns(nil) settings.idp_cert_fingerprint = signature_fingerprint_1 response_wrapped.settings = settings + assert_nil response_wrapped.nameid end end - describe "#is_valid?" do - describe "soft = false" do - + describe '#is_valid?' do + describe 'soft = false' do before do response.soft = false response_valid_signed.soft = false end - it "raise when response is initialized with blank data" do + it 'raise when response is initialized with blank data' do blank_response = RubySaml::Response.new('') blank_response.soft = false - error_msg = "Blank response" + error_msg = 'Blank response' assert_raises(RubySaml::ValidationError, error_msg) do blank_response.is_valid? end assert_includes blank_response.errors, error_msg end - it "raise when settings have not been set" do - error_msg = "No settings on response" + it 'raise when settings have not been set' do + error_msg = 'No settings on response' assert_raises(RubySaml::ValidationError, error_msg) do response.is_valid? end assert_includes response.errors, error_msg end - it "raise when No fingerprint or certificate on settings" do + it 'raise when No fingerprint or certificate on settings' do settings.idp_cert_fingerprint = nil settings.idp_cert = nil settings.idp_cert_multi = nil response.settings = settings - error_msg = "No fingerprint or certificate on settings" + error_msg = 'No fingerprint or certificate on settings' assert_raises(RubySaml::ValidationError, error_msg) do response.is_valid? end assert_includes response.errors, error_msg end - it "raise when signature wrapping attack" do + it 'raise when signature wrapping attack' do response_wrapped.stubs(:conditions).returns(nil) response_wrapped.stubs(:validate_subject_confirmation).returns(true) settings.idp_cert_fingerprint = signature_fingerprint_1 response_wrapped.settings = settings - assert !response_wrapped.is_valid? + + refute response_wrapped.is_valid? end - it "raise when no signature" do + it 'raise when no signature' do settings.idp_cert_fingerprint = signature_fingerprint_1 response_no_signed_elements.settings = settings response_no_signed_elements.soft = false - error_msg = "Found an unexpected number of Signature Element. SAML Response rejected" + error_msg = 'Found an unexpected number of Signature Element. SAML Response rejected' assert_raises(RubySaml::ValidationError, error_msg) do response_no_signed_elements.is_valid? end end - it "raise when multiple signatures" do + it 'raise when multiple signatures' do settings.idp_cert_fingerprint = signature_fingerprint_1 response_multiple_signed.settings = settings response_multiple_signed.soft = false - error_msg = "Duplicated ID. SAML Response rejected" + error_msg = 'Duplicated ID. SAML Response rejected' assert_raises(RubySaml::ValidationError, error_msg) do response_multiple_signed.is_valid? end end - it "validate SAML 2.0 XML structure" do - resp_xml = Base64.decode64(response_document_unsigned).gsub(/emailAddress/,'test') + it 'validate SAML 2.0 XML structure' do + resp_xml = Base64.decode64(response_document_unsigned).gsub('emailAddress', 'test') response_unsigned_mod = RubySaml::Response.new(Base64.strict_encode64(resp_xml)) response_unsigned_mod.stubs(:conditions).returns(nil) settings.idp_cert_fingerprint = signature_fingerprint_1 @@ -244,19 +247,19 @@ def generate_audience_error(expected, actual) end end - it "raise when encountering a condition that prevents the document from being valid" do + it 'raise when encountering a condition that prevents the document from being valid' do settings.idp_cert_fingerprint = ruby_saml_cert_fingerprint response_without_recipient.settings = settings response_without_recipient.soft = false - error_msg = "Current time is on or after NotOnOrAfter condition" + error_msg = 'Current time is on or after NotOnOrAfter condition' assert_raises(RubySaml::ValidationError, error_msg) do response_without_recipient.is_valid? end - assert !response_without_recipient.errors.empty? + refute_empty response_without_recipient.errors assert_includes response_without_recipient.errors[0], error_msg end - it "raise when encountering a SAML Response with bad formatted" do + it 'raise when encountering a SAML Response with bad formatting' do settings.idp_cert_fingerprint = signature_fingerprint_1 response_without_attributes.settings = settings response_without_attributes.soft = false @@ -265,21 +268,21 @@ def generate_audience_error(expected, actual) end end - it "raise when the inResponseTo value does not match the Request ID" do + it 'raise when the inResponseTo value does not match the Request ID' do settings.soft = false settings.idp_cert_fingerprint = ruby_saml_cert_fingerprint opts = {} opts[:settings] = settings - opts[:matches_request_id] = "invalid_request_id" + opts[:matches_request_id] = 'invalid_request_id' response_valid_signed = RubySaml::Response.new(response_document_valid_signed, opts) - error_msg = "The InResponseTo of the Response: _fc4a34b0-7efb-012e-caae-782bcb13bb38, does not match the ID of the AuthNRequest sent by the SP: invalid_request_id" + error_msg = 'The InResponseTo of the Response: _fc4a34b0-7efb-012e-caae-782bcb13bb38, does not match the ID of the AuthNRequest sent by the SP: invalid_request_id' assert_raises(RubySaml::ValidationError, error_msg) do response_valid_signed.is_valid? end assert_includes response_valid_signed.errors, error_msg end - it "raise when there is no valid audience" do + it 'raise when there is no valid audience' do settings.idp_cert_fingerprint = ruby_saml_cert_fingerprint settings.sp_entity_id = 'invalid' response_valid_signed.settings = settings @@ -291,22 +294,22 @@ def generate_audience_error(expected, actual) assert_includes response_valid_signed.errors, error_msg end - it "raise when no ID present in the SAML Response" do + it 'raise when no ID present in the SAML Response' do settings.idp_cert_fingerprint = signature_fingerprint_1 response_no_id.settings = settings response_no_id.soft = false - error_msg = "Missing ID attribute on SAML Response" + error_msg = 'Missing ID attribute on SAML Response' assert_raises(RubySaml::ValidationError, error_msg) do response_no_id.is_valid? end assert_includes response_no_id.errors, error_msg end - it "raise when no 2.0 Version present in the SAML Response" do + it 'raise when no 2.0 Version present in the SAML Response' do settings.idp_cert_fingerprint = signature_fingerprint_1 response_no_version.settings = settings response_no_version.soft = false - error_msg = "Unsupported SAML version" + error_msg = 'Unsupported SAML version' assert_raises(RubySaml::ValidationError, error_msg) do response_no_version.is_valid? end @@ -314,128 +317,141 @@ def generate_audience_error(expected, actual) end end - describe "soft = true" do + describe 'soft = true' do before do response.soft = true response_valid_signed.soft = true end - it "return true when the response is initialized with valid data" do + it 'return true when the response is initialized with valid data' do response_valid_signed_without_recipient.stubs(:conditions).returns(nil) response_valid_signed_without_recipient.settings = settings response_valid_signed_without_recipient.settings.idp_cert_fingerprint = ruby_saml_cert_fingerprint - assert response_valid_signed_without_recipient.is_valid? + + assert_predicate response_valid_signed_without_recipient, :is_valid? assert_empty response_valid_signed_without_recipient.errors end - it "return true when the response is initialized with valid data and using certificate instead of fingerprint" do + it 'return true when the response is initialized with valid data and using certificate instead of fingerprint' do response_valid_signed_without_recipient.stubs(:conditions).returns(nil) response_valid_signed_without_recipient.settings = settings response_valid_signed_without_recipient.settings.idp_cert = ruby_saml_cert_text - assert response_valid_signed_without_recipient.is_valid? + + assert_predicate response_valid_signed_without_recipient, :is_valid? assert_empty response_valid_signed_without_recipient.errors end - it "return false when response is initialized with blank data" do + it 'return false when response is initialized with blank data' do blank_response = RubySaml::Response.new('') blank_response.soft = true - assert !blank_response.is_valid? - assert_includes blank_response.errors, "Blank response" + + refute blank_response.is_valid? + assert_includes blank_response.errors, 'Blank response' end - it "return false if settings have not been set" do - assert !response.is_valid? - assert_includes response.errors, "No settings on response" + it 'return false if settings have not been set' do + refute response.is_valid? + assert_includes response.errors, 'No settings on response' end - it "return false if fingerprint or certificate not been set on settings" do + it 'return false if fingerprint or certificate not been set on settings' do response.settings = settings - assert !response.is_valid? - assert_includes response.errors, "No fingerprint or certificate on settings" + + refute response.is_valid? + assert_includes response.errors, 'No fingerprint or certificate on settings' end - it "should be idempotent when the response is initialized with invalid data" do + it 'should be idempotent when the response is initialized with invalid data' do response_unsigned.stubs(:conditions).returns(nil) response_unsigned.settings = settings - assert !response_unsigned.is_valid? - assert !response_unsigned.is_valid? + + refute response_unsigned.is_valid? + refute response_unsigned.is_valid? end - it "should be idempotent when the response is initialized with valid data" do + it 'should be idempotent when the response is initialized with valid data' do response_valid_signed_without_recipient.stubs(:conditions).returns(nil) response_valid_signed_without_recipient.settings = settings response_valid_signed_without_recipient.settings.idp_cert_fingerprint = ruby_saml_cert_fingerprint - assert response_valid_signed_without_recipient.is_valid? - assert response_valid_signed_without_recipient.is_valid? + + assert_predicate response_valid_signed_without_recipient, :is_valid? + assert_predicate response_valid_signed_without_recipient, :is_valid? end - it "not allow signature wrapping attack" do + it 'not allow signature wrapping attack' do response_wrapped.stubs(:conditions).returns(nil) response_wrapped.stubs(:validate_subject_confirmation).returns(true) settings.idp_cert_fingerprint = signature_fingerprint_1 response_wrapped.settings = settings - assert !response_wrapped.is_valid? + + refute response_wrapped.is_valid? end - it "support dynamic namespace resolution on signature elements" do - no_signature_response = RubySaml::Response.new(fixture("inclusive_namespaces.xml")) + it 'support dynamic namespace resolution on signature elements' do + no_signature_response = RubySaml::Response.new(fixture('inclusive_namespaces.xml')) no_signature_response.stubs(:conditions).returns(nil) no_signature_response.stubs(:validate_subject_confirmation).returns(true) no_signature_response.settings = settings - no_signature_response.settings.idp_cert_fingerprint = "E0:89:CF:86:E3:00:C0:C8:B9:BC:04:16:D7:F3:8D:8D:9C:8F:20:B3:FE:7C:EC:64:D5:5D:90:E3:7B:8B:5A:51" - assert no_signature_response.is_valid? + no_signature_response.settings.idp_cert_fingerprint = 'E0:89:CF:86:E3:00:C0:C8:B9:BC:04:16:D7:F3:8D:8D:9C:8F:20:B3:FE:7C:EC:64:D5:5D:90:E3:7B:8B:5A:51' + + assert_predicate no_signature_response, :is_valid? end - it "validate ADFS assertions" do + it 'validate ADFS assertions' do adfs_response = RubySaml::Response.new(fixture(:adfs_response_sha256)) adfs_response.stubs(:conditions).returns(nil) adfs_response.stubs(:validate_subject_confirmation).returns(true) - settings.idp_cert_fingerprint = "3D:C5:BC:58:60:5D:19:64:94:E3:BA:C8:3D:49:01:D5:56:34:44:65:C2:85:0A:A8:65:A5:AC:76:7E:65:1F:F7" + settings.idp_cert_fingerprint = '3D:C5:BC:58:60:5D:19:64:94:E3:BA:C8:3D:49:01:D5:56:34:44:65:C2:85:0A:A8:65:A5:AC:76:7E:65:1F:F7' adfs_response.settings = settings adfs_response.soft = true - assert adfs_response.is_valid? + + assert_predicate adfs_response, :is_valid? end - it "validate SAML 2.0 XML structure" do - resp_xml = Base64.decode64(response_document_unsigned).gsub(/emailAddress/,'test') + it 'validate SAML 2.0 XML structure' do + resp_xml = Base64.decode64(response_document_unsigned).gsub('emailAddress', 'test') response_unsigned_mod = RubySaml::Response.new(Base64.strict_encode64(resp_xml)) response_unsigned_mod.stubs(:conditions).returns(nil) settings.idp_cert_fingerprint = signature_fingerprint_1 response_unsigned_mod.settings = settings response_unsigned_mod.soft = true - assert !response_unsigned_mod.is_valid? + + refute response_unsigned_mod.is_valid? end - it "return false when encountering a condition that prevents the document from being valid" do + it 'return false when encountering a condition that prevents the document from being valid' do settings.idp_cert_fingerprint = ruby_saml_cert_fingerprint response_without_recipient.settings = settings - error_msg = "Current time is on or after NotOnOrAfter condition" - assert !response_without_recipient.is_valid? - assert !response_without_recipient.errors.empty? + error_msg = 'Current time is on or after NotOnOrAfter condition' + + refute response_without_recipient.is_valid? + refute_empty response_without_recipient.errors assert_includes response_without_recipient.errors[0], error_msg end - it "return false when encountering a SAML Response with bad formatted" do + it 'return false when encountering a SAML Response with bad formatted' do settings.idp_cert_fingerprint = signature_fingerprint_1 response_without_attributes.settings = settings response_without_attributes.soft = true - error_msg = "Invalid SAML Response. Not match the saml-schema-protocol-2.0.xsd" + error_msg = 'Invalid SAML Response. Not match the saml-schema-protocol-2.0.xsd' response_without_attributes.is_valid? + assert_includes response_without_attributes.errors, error_msg end - it "return false when the inResponseTo value does not match the Request ID" do + it 'return false when the inResponseTo value does not match the Request ID' do settings.soft = true settings.idp_cert_fingerprint = ruby_saml_cert_fingerprint opts = {} opts[:settings] = settings - opts[:matches_request_id] = "invalid_request_id" + opts[:matches_request_id] = 'invalid_request_id' response_valid_signed = RubySaml::Response.new(response_document_valid_signed, opts) response_valid_signed.is_valid? - assert_includes response_valid_signed.errors, "The InResponseTo of the Response: _fc4a34b0-7efb-012e-caae-782bcb13bb38, does not match the ID of the AuthNRequest sent by the SP: invalid_request_id" + + assert_includes response_valid_signed.errors, 'The InResponseTo of the Response: _fc4a34b0-7efb-012e-caae-782bcb13bb38, does not match the ID of the AuthNRequest sent by the SP: invalid_request_id' end - it "return false when there is no valid audience" do + it 'return false when there is no valid audience' do settings.idp_cert_fingerprint = ruby_saml_cert_fingerprint settings.sp_entity_id = 'invalid' response_valid_signed.settings = settings @@ -444,933 +460,1039 @@ def generate_audience_error(expected, actual) assert_includes response_valid_signed.errors, generate_audience_error(response_valid_signed.settings.sp_entity_id, ['https://someone.example.com/audience']) end - it "return false when no ID present in the SAML Response" do + it 'return false when no ID present in the SAML Response' do settings.idp_cert_fingerprint = signature_fingerprint_1 response_no_id.settings = settings response_no_id.soft = true response_no_id.is_valid? - assert_includes response_no_id.errors, "Missing ID attribute on SAML Response" + + assert_includes response_no_id.errors, 'Missing ID attribute on SAML Response' end - it "return false when no 2.0 Version present in the SAML Response" do + it 'return false when no 2.0 Version present in the SAML Response' do settings.idp_cert_fingerprint = signature_fingerprint_1 response_no_version.settings = settings response_no_version.soft = true - error_msg = "Unsupported SAML version" + error_msg = 'Unsupported SAML version' response_no_version.is_valid? + assert_includes response_no_version.errors, error_msg end - it "return true when a nil URI is given in the ds:Reference" do + it 'return true when a nil URI is given in the ds:Reference' do settings.idp_cert = ruby_saml_cert_text - settings.assertion_consumer_service_url = "http://localhost:9001/v1/users/authorize/saml" + + settings.assertion_consumer_service_url = 'http://localhost:9001/v1/users/authorize/saml' response_without_reference_uri.settings = settings response_without_reference_uri.stubs(:conditions).returns(nil) response_without_reference_uri.is_valid? + assert_empty response_without_reference_uri.errors assert 'saml@user.com', response_without_reference_uri.attributes['http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress'] end - it "collect errors when collect_errors=true" do + it 'collect errors when collect_errors=true' do settings.idp_cert = ruby_saml_cert_text settings.sp_entity_id = 'invalid' response_invalid_subjectconfirmation_recipient.settings = settings collect_errors = true response_invalid_subjectconfirmation_recipient.is_valid?(collect_errors) + assert_includes response_invalid_subjectconfirmation_recipient.errors, generate_audience_error('invalid', ['http://stuff.com/endpoints/metadata.php']) - assert_includes response_invalid_subjectconfirmation_recipient.errors, "Invalid Signature on SAML Response" + assert_includes response_invalid_subjectconfirmation_recipient.errors, 'Invalid Signature on SAML Response' end end end - describe "#validate_audience" do - it "return true when the audience is valid" do + describe '#validate_audience' do + it 'return true when the audience is valid' do response.settings = settings response.settings.sp_entity_id = '{audience}' + assert response.send(:validate_audience) assert_empty response.errors end - it "return true when the audience is self closing and strict audience validation is not enabled" do + it 'return true when the audience is self closing and strict audience validation is not enabled' do response_audience_self_closed.settings = settings response_audience_self_closed.settings.sp_entity_id = '{audience}' + assert response_audience_self_closed.send(:validate_audience) assert_empty response_audience_self_closed.errors end - it "return false when the audience is self closing and strict audience validation is enabled" do + it 'return false when the audience is self closing and strict audience validation is enabled' do response_audience_self_closed.settings = settings response_audience_self_closed.settings.security[:strict_audience_validation] = true response_audience_self_closed.settings.sp_entity_id = '{audience}' + refute response_audience_self_closed.send(:validate_audience) - assert_includes response_audience_self_closed.errors, "Invalid Audiences. The element contained only empty elements. Expected audience {audience}." + assert_includes response_audience_self_closed.errors, 'Invalid Audiences. The element contained only empty elements. Expected audience {audience}.' end - it "return false when the audience is invalid" do + it 'return false when the audience is invalid' do response.settings = settings response.settings.sp_entity_id = 'invalid_audience' - assert !response.send(:validate_audience) + + refute response.send(:validate_audience) assert_includes response.errors, generate_audience_error(response.settings.sp_entity_id, ['{audience}']) end end - describe "#validate_destination" do - it "return true when the destination of the SAML Response matches the assertion consumer service url" do + describe '#validate_destination' do + it 'return true when the destination of the SAML Response matches the assertion consumer service url' do response.settings = settings + assert response.send(:validate_destination) assert_empty response.errors end - it "return false when the destination of the SAML Response does not match the assertion consumer service url" do + it 'return false when the destination of the SAML Response does not match the assertion consumer service url' do response.settings = settings + response.settings.assertion_consumer_service_url = 'invalid_acs' - assert !response.send(:validate_destination) + refute response.send(:validate_destination) assert_includes response.errors, "The response was received at #{response.destination} instead of #{response.settings.assertion_consumer_service_url}" end - it "return false when the destination of the SAML Response is empty" do + it 'return false when the destination of the SAML Response is empty' do response_empty_destination.settings = settings - assert !response_empty_destination.send(:validate_destination) - assert_includes response_empty_destination.errors, "The response has an empty Destination value" + + refute response_empty_destination.send(:validate_destination) + assert_includes response_empty_destination.errors, 'The response has an empty Destination value' end - it "return true when the destination of the SAML Response is empty but skip_destination option is used" do + it 'return true when the destination of the SAML Response is empty but skip_destination option is used' do response_empty_destination_with_skip.settings = settings + assert response_empty_destination_with_skip.send(:validate_destination) assert_empty response_empty_destination.errors end - it "returns true on a case insensitive match on the domain" do + it 'returns true on a case insensitive match on the domain' do response_valid_signed_without_x509certificate.settings = settings + response_valid_signed_without_x509certificate.settings.assertion_consumer_service_url = 'http://APP.muDa.no/sso/consume' assert response_valid_signed_without_x509certificate.send(:validate_destination) assert_empty response_valid_signed_without_x509certificate.errors end - it "returns true on a case insensitive match on the scheme" do + it 'returns true on a case insensitive match on the scheme' do response_valid_signed_without_x509certificate.settings = settings + response_valid_signed_without_x509certificate.settings.assertion_consumer_service_url = 'HTTP://app.muda.no/sso/consume' assert response_valid_signed_without_x509certificate.send(:validate_destination) assert_empty response_valid_signed_without_x509certificate.errors end - it "returns false on a case insenstive match on the path" do + it 'returns false on a case insensitive match on the path' do response_valid_signed_without_x509certificate.settings = settings + response_valid_signed_without_x509certificate.settings.assertion_consumer_service_url = 'http://app.muda.no/SSO/consume' - assert !response_valid_signed_without_x509certificate.send(:validate_destination) - assert_includes response_valid_signed_without_x509certificate.errors, "The response was received at #{response_valid_signed_without_x509certificate.destination} instead of #{response_valid_signed_without_x509certificate.settings.assertion_consumer_service_url}" + refute response_valid_signed_without_x509certificate.send(:validate_destination) + assert_includes response_valid_signed_without_x509certificate.errors, + "The response was received at #{response_valid_signed_without_x509certificate.destination} instead of #{response_valid_signed_without_x509certificate.settings.assertion_consumer_service_url}" end it "returns true if it can't parse out a full URI." do response_valid_signed_without_x509certificate.settings = settings + response_valid_signed_without_x509certificate.settings.assertion_consumer_service_url = 'presenter' - assert !response_valid_signed_without_x509certificate.send(:validate_destination) - assert_includes response_valid_signed_without_x509certificate.errors, "The response was received at #{response_valid_signed_without_x509certificate.destination} instead of #{response_valid_signed_without_x509certificate.settings.assertion_consumer_service_url}" + refute response_valid_signed_without_x509certificate.send(:validate_destination) + assert_includes response_valid_signed_without_x509certificate.errors, + "The response was received at #{response_valid_signed_without_x509certificate.destination} instead of #{response_valid_signed_without_x509certificate.settings.assertion_consumer_service_url}" end end - describe "#validate_issuer" do - it "return true when the issuer of the Message/Assertion matches the IdP entityId" do + describe '#validate_issuer' do + it 'return true when the issuer of the Message/Assertion matches the IdP entityId' do response_valid_signed.settings = settings + assert response_valid_signed.send(:validate_issuer) response_valid_signed.settings.idp_entity_id = 'https://app.onelogin.com/saml2' + assert response_valid_signed.send(:validate_issuer) end - it "return false when the issuer of the Message does not match the IdP entityId" do + it 'return false when the issuer of the Message does not match the IdP entityId' do response_invalid_issuer_message.settings = settings response_invalid_issuer_message.settings.idp_entity_id = 'http://idp.example.com/' - assert !response_invalid_issuer_message.send(:validate_issuer) + + refute response_invalid_issuer_message.send(:validate_issuer) assert_includes response_invalid_issuer_message.errors, "Doesn't match the issuer, expected: <#{response_invalid_issuer_message.settings.idp_entity_id}>, but was: " end - it "return false when the issuer of the Assertion does not match the IdP entityId" do + it 'return false when the issuer of the Assertion does not match the IdP entityId' do response_invalid_issuer_assertion.settings = settings response_invalid_issuer_assertion.settings.idp_entity_id = 'http://idp.example.com/' - assert !response_invalid_issuer_assertion.send(:validate_issuer) + + refute response_invalid_issuer_assertion.send(:validate_issuer) assert_includes response_invalid_issuer_assertion.errors, "Doesn't match the issuer, expected: <#{response_invalid_issuer_assertion.settings.idp_entity_id}>, but was: " end end - describe "#validate_num_assertion" do - it "return true when SAML Response contains 1 assertion" do + describe '#validate_num_assertion' do + it 'return true when SAML Response contains 1 assertion' do assert response.send(:validate_num_assertion) assert_empty response.errors end - it "return false when no 2.0 Version present in the SAML Response" do - assert !response_multi_assertion.send(:validate_num_assertion) - assert_includes response_multi_assertion.errors, "SAML Response must contain 1 assertion" + it 'return false when no 2.0 Version present in the SAML Response' do + refute response_multi_assertion.send(:validate_num_assertion) + assert_includes response_multi_assertion.errors, 'SAML Response must contain 1 assertion' end end - describe "validate_success_status" do + describe 'validate_success_status' do it "return true when the status is 'Success'" do assert response.send(:validate_success_status) assert_empty response.errors end - it "return false when no Status provided" do - assert !response_no_status.send(:validate_success_status) - assert_includes response_no_status.errors, "The status code of the Response was not Success" + it 'return false when no Status provided' do + refute response_no_status.send(:validate_success_status) + assert_includes response_no_status.errors, 'The status code of the Response was not Success' end - it "return false when no StatusCode provided" do - assert !response_no_statuscode.send(:validate_success_status) - assert_includes response_no_statuscode.errors, "The status code of the Response was not Success" + it 'return false when no StatusCode provided' do + refute response_no_statuscode.send(:validate_success_status) + assert_includes response_no_statuscode.errors, 'The status code of the Response was not Success' end it "return false when the status is not 'Success'" do - assert !response_statuscode_responder.send(:validate_success_status) - assert_includes response_statuscode_responder.errors, "The status code of the Response was not Success, was Responder" + refute response_statuscode_responder.send(:validate_success_status) + assert_includes response_statuscode_responder.errors, 'The status code of the Response was not Success, was Responder' end it "return false when the status is not 'Success', and shows the StatusMessage" do - assert !response_statuscode_responder_and_msg.send(:validate_success_status) - assert_includes response_statuscode_responder_and_msg.errors, "The status code of the Response was not Success, was Responder -> something_is_wrong" + refute response_statuscode_responder_and_msg.send(:validate_success_status) + assert_includes response_statuscode_responder_and_msg.errors, 'The status code of the Response was not Success, was Responder -> something_is_wrong' end it "return false when the status is not 'Success'" do - assert !response_double_statuscode.send(:validate_success_status) - assert_includes response_double_statuscode.errors, "The status code of the Response was not Success, was Requester => UnsupportedBinding" + refute response_double_statuscode.send(:validate_success_status) + assert_includes response_double_statuscode.errors, 'The status code of the Response was not Success, was Requester => UnsupportedBinding' end end - describe "#validate_structure" do - it "return true when encountering a wellformed SAML Response" do + describe '#validate_structure' do + it 'return true when encountering a wellformed SAML Response' do assert response.send(:validate_structure) assert_empty response.errors end - it "return false when encountering a mailformed element that prevents the document from being valid" do + it 'return false when encountering a malformed element that prevents the document from being valid' do response_without_attributes.soft = true response_without_attributes.send(:validate_structure) - assert response_without_attributes.errors.include? "Invalid SAML Response. Not match the saml-schema-protocol-2.0.xsd" + + assert_includes response_without_attributes.errors, 'Invalid SAML Response. Not match the saml-schema-protocol-2.0.xsd' end - it "raise when encountering a mailformed element that prevents the document from being valid" do + it 'raise when encountering a malformed element that prevents the document from being valid' do response_without_attributes.soft = false - assert_raises(RubySaml::ValidationError) { + assert_raises(RubySaml::ValidationError) do response_without_attributes.send(:validate_structure) - } + end end end - describe "validate_formatted_x509_certificate" do + describe 'validate_formatted_x509_certificate' do let(:response_with_formatted_x509certificate) do - RubySaml::Response.new(read_response("valid_response_with_formatted_x509certificate.xml.base64"), { - :skip_conditions => true, - :skip_subject_confirmation => true }) + RubySaml::Response.new(read_response('valid_response_with_formatted_x509certificate.xml.base64'), { + skip_conditions: true, + skip_subject_confirmation: true + }) end - it "be able to parse the response wihout errors" do + it 'be able to parse the response without errors' do response_with_formatted_x509certificate.settings = settings response_with_formatted_x509certificate.settings.idp_cert = ruby_saml_cert_text - assert response_with_formatted_x509certificate.is_valid? + + assert_predicate response_with_formatted_x509certificate, :is_valid? assert_empty response_with_formatted_x509certificate.errors end end - describe "#validate_in_response_to" do - it "return true when the inResponseTo value matches the Request ID" do - response = RubySaml::Response.new(response_document_valid_signed, settings: settings, matches_request_id: "_fc4a34b0-7efb-012e-caae-782bcb13bb38") + describe '#validate_in_response_to' do + it 'return true when the inResponseTo value matches the Request ID' do + response = RubySaml::Response.new(response_document_valid_signed, settings: settings, matches_request_id: '_fc4a34b0-7efb-012e-caae-782bcb13bb38') + assert response.send(:validate_in_response_to) assert_empty response.errors end - it "return true when no Request ID is provided for checking" do + it 'return true when no Request ID is provided for checking' do response = RubySaml::Response.new(response_document_valid_signed, settings: settings) + assert response.send(:validate_in_response_to) assert_empty response.errors end - it "return false when the inResponseTo value does not match the Request ID" do - response = RubySaml::Response.new(response_document_valid_signed, settings: settings, matches_request_id: "invalid_request_id") - assert !response.send(:validate_in_response_to) - assert_includes response.errors, "The InResponseTo of the Response: _fc4a34b0-7efb-012e-caae-782bcb13bb38, does not match the ID of the AuthNRequest sent by the SP: invalid_request_id" + it 'return false when the inResponseTo value does not match the Request ID' do + response = RubySaml::Response.new(response_document_valid_signed, settings: settings, matches_request_id: 'invalid_request_id') + + refute response.send(:validate_in_response_to) + assert_includes response.errors, 'The InResponseTo of the Response: _fc4a34b0-7efb-012e-caae-782bcb13bb38, does not match the ID of the AuthNRequest sent by the SP: invalid_request_id' end end - describe "#validate_audience" do - it "return true when the audience is valid" do + describe '#validate_audience' do + it 'return true when the audience is valid' do response_valid_signed.settings = settings - response_valid_signed.settings.sp_entity_id = "https://someone.example.com/audience" + response_valid_signed.settings.sp_entity_id = 'https://someone.example.com/audience' + assert response_valid_signed.send(:validate_audience) assert_empty response_valid_signed.errors end - it "return true when there is not sp_entity_id defined" do + it 'return true when there is not sp_entity_id defined' do response_valid_signed.settings = settings response_valid_signed.settings.sp_entity_id = nil + assert response_valid_signed.send(:validate_audience) assert_empty response_valid_signed.errors end - it "return false when there is no valid audience" do + it 'return false when there is no valid audience' do response_invalid_audience.settings = settings - response_invalid_audience.settings.sp_entity_id = "https://invalid.example.com/audience" - assert !response_invalid_audience.send(:validate_audience) + response_invalid_audience.settings.sp_entity_id = 'https://invalid.example.com/audience' + + refute response_invalid_audience.send(:validate_audience) assert_includes response_invalid_audience.errors, generate_audience_error(response_invalid_audience.settings.sp_entity_id, ['http://invalid.audience.com']) end - it "return true when there is no valid audience but skip_destination option is used" do + it 'return true when there is no valid audience but skip_destination option is used' do response_invalid_audience_with_skip.settings = settings - response_invalid_audience_with_skip.settings.sp_entity_id = "https://invalid.example.com/audience" + response_invalid_audience_with_skip.settings.sp_entity_id = 'https://invalid.example.com/audience' + assert response_invalid_audience_with_skip.send(:validate_audience) assert_empty response_invalid_audience_with_skip.errors end end - describe "#validate_issuer" do - it "return true when the issuer of the Message/Assertion matches the IdP entityId or it was empty" do + describe '#validate_issuer' do + it 'return true when the issuer of the Message/Assertion matches the IdP entityId or it was empty' do response_valid_signed.settings = settings + assert response_valid_signed.send(:validate_issuer) assert_empty response_valid_signed.errors response_valid_signed.settings.idp_entity_id = 'https://app.onelogin.com/saml2' + assert response_valid_signed.send(:validate_issuer) assert_empty response_valid_signed.errors end - it "return false when the issuer of the Message does not match the IdP entityId" do + it 'return false when the issuer of the Message does not match the IdP entityId' do response_invalid_issuer_message.settings = settings response_invalid_issuer_message.settings.idp_entity_id = 'http://idp.example.com/' - assert !response_invalid_issuer_message.send(:validate_issuer) + + refute response_invalid_issuer_message.send(:validate_issuer) assert_includes response_invalid_issuer_message.errors, "Doesn't match the issuer, expected: <#{response_invalid_issuer_message.settings.idp_entity_id}>, but was: " end - it "return false when the issuer of the Assertion does not match the IdP entityId" do + it 'return false when the issuer of the Assertion does not match the IdP entityId' do response_invalid_issuer_assertion.settings = settings response_invalid_issuer_assertion.settings.idp_entity_id = 'http://idp.example.com/' - assert !response_invalid_issuer_assertion.send(:validate_issuer) + + refute response_invalid_issuer_assertion.send(:validate_issuer) assert_includes response_invalid_issuer_assertion.errors, "Doesn't match the issuer, expected: <#{response_invalid_issuer_assertion.settings.idp_entity_id}>, but was: " end - it "return false when the no issuer at the Response" do + it 'return false when the no issuer at the Response' do response_no_issuer_response.settings = settings response_no_issuer_response.settings.idp_entity_id = 'http://idp.example.com/' - assert !response_no_issuer_response.send(:validate_issuer) - assert_includes response_no_issuer_response.errors, "Issuer of the Response not found or multiple." + + refute response_no_issuer_response.send(:validate_issuer) + assert_includes response_no_issuer_response.errors, 'Issuer of the Response not found or multiple.' end - it "return false when the no issuer at the Assertion" do + it 'return false when the no issuer at the Assertion' do response_no_issuer_assertion.settings = settings response_no_issuer_assertion.settings.idp_entity_id = 'http://idp.example.com/' - assert !response_no_issuer_assertion.send(:validate_issuer) - assert_includes response_no_issuer_assertion.errors, "Issuer of the Assertion not found or multiple." + + refute response_no_issuer_assertion.send(:validate_issuer) + assert_includes response_no_issuer_assertion.errors, 'Issuer of the Assertion not found or multiple.' end end - describe "#validate_subject_confirmation" do - it "return true when valid subject confirmation" do + describe '#validate_subject_confirmation' do + it 'return true when valid subject confirmation' do response_valid_signed.settings = settings + response_valid_signed.settings.assertion_consumer_service_url = 'recipient' assert response_valid_signed.send(:validate_subject_confirmation) assert_empty response_valid_signed.errors end - it "return false when no subject confirmation data" do + it 'return false when no subject confirmation data' do response_no_subjectconfirmation_data.settings = settings - assert !response_no_subjectconfirmation_data.send(:validate_subject_confirmation) - assert_includes response_no_subjectconfirmation_data.errors, "A valid SubjectConfirmation was not found on this Response" + + refute response_no_subjectconfirmation_data.send(:validate_subject_confirmation) + assert_includes response_no_subjectconfirmation_data.errors, 'A valid SubjectConfirmation was not found on this Response' end - it "return false when no valid subject confirmation method" do + it 'return false when no valid subject confirmation method' do response_no_subjectconfirmation_method.settings = settings - assert !response_no_subjectconfirmation_method.send(:validate_subject_confirmation) - assert_includes response_no_subjectconfirmation_method.errors, "A valid SubjectConfirmation was not found on this Response" + + refute response_no_subjectconfirmation_method.send(:validate_subject_confirmation) + assert_includes response_no_subjectconfirmation_method.errors, 'A valid SubjectConfirmation was not found on this Response' end - it "return false when invalid inresponse" do + it 'return false when invalid inresponse' do response_invalid_subjectconfirmation_inresponse.settings = settings - assert !response_invalid_subjectconfirmation_inresponse.send(:validate_subject_confirmation) - assert_includes response_invalid_subjectconfirmation_inresponse.errors, "A valid SubjectConfirmation was not found on this Response" + + refute response_invalid_subjectconfirmation_inresponse.send(:validate_subject_confirmation) + assert_includes response_invalid_subjectconfirmation_inresponse.errors, 'A valid SubjectConfirmation was not found on this Response' end - it "return false when invalid NotBefore" do + it 'return false when invalid NotBefore' do response_invalid_subjectconfirmation_nb.settings = settings - assert !response_invalid_subjectconfirmation_nb.send(:validate_subject_confirmation) - assert_includes response_invalid_subjectconfirmation_nb.errors, "A valid SubjectConfirmation was not found on this Response" + + refute response_invalid_subjectconfirmation_nb.send(:validate_subject_confirmation) + assert_includes response_invalid_subjectconfirmation_nb.errors, 'A valid SubjectConfirmation was not found on this Response' end - it "return false when invalid NotOnOrAfter" do + it 'return false when invalid NotOnOrAfter' do response_invalid_subjectconfirmation_noa.settings = settings - assert !response_invalid_subjectconfirmation_noa.send(:validate_subject_confirmation) - assert_includes response_invalid_subjectconfirmation_noa.errors, "A valid SubjectConfirmation was not found on this Response" + + refute response_invalid_subjectconfirmation_noa.send(:validate_subject_confirmation) + assert_includes response_invalid_subjectconfirmation_noa.errors, 'A valid SubjectConfirmation was not found on this Response' end - it "return true when valid subject confirmation recipient" do + it 'return true when valid subject confirmation recipient' do response_valid_signed.settings = settings + response_valid_signed.settings.assertion_consumer_service_url = 'recipient' assert response_valid_signed.send(:validate_subject_confirmation) assert_empty response_valid_signed.errors assert_empty response_valid_signed.errors end - it "return false when invalid subject confirmation recipient" do + it 'return false when invalid subject confirmation recipient' do response_valid_signed.settings = settings + response_valid_signed.settings.assertion_consumer_service_url = 'not-the-recipient' - assert !response_valid_signed.send(:validate_subject_confirmation) - assert_includes response_valid_signed.errors, "A valid SubjectConfirmation was not found on this Response" + refute response_valid_signed.send(:validate_subject_confirmation) + assert_includes response_valid_signed.errors, 'A valid SubjectConfirmation was not found on this Response' end - it "return false when invalid subject confirmation recipient, but skipping the check(default)" do + it 'return false when invalid subject confirmation recipient, but skipping the check(default)' do response_valid_signed_without_recipient.settings = settings + response_valid_signed_without_recipient.settings.assertion_consumer_service_url = 'not-the-recipient' assert response_valid_signed_without_recipient.send(:validate_subject_confirmation) assert_empty response_valid_signed_without_recipient.errors end - it "return true when the skip_subject_confirmation option is passed and the subject confirmation is valid" do + it 'return true when the skip_subject_confirmation option is passed and the subject confirmation is valid' do opts = {} opts[:skip_subject_confirmation] = true response_with_skip = RubySaml::Response.new(response_document_valid_signed, opts) response_with_skip.settings = settings + response_with_skip.settings.assertion_consumer_service_url = 'recipient' Time.expects(:now).times(0) # ensures the test isn't run and thus Time.now.utc is never called within the test + assert response_with_skip.send(:validate_subject_confirmation) assert_empty response_with_skip.errors end - it "return true when the skip_subject_confirmation option is passed and the response has an invalid subject confirmation" do + it 'return true when the skip_subject_confirmation option is passed and the response has an invalid subject confirmation' do opts = {} opts[:skip_subject_confirmation] = true - response_with_skip = RubySaml::Response.new(read_invalid_response("invalid_subjectconfirmation_noa.xml.base64"), opts) + response_with_skip = RubySaml::Response.new(read_invalid_response('invalid_subjectconfirmation_noa.xml.base64'), opts) response_with_skip.settings = settings Time.expects(:now).times(0) # ensures the test isn't run and thus Time.now.utc is never called within the test + assert response_with_skip.send(:validate_subject_confirmation) assert_empty response_with_skip.errors end end - describe "#validate_session_expiration" do - it "return true when the session has not expired" do + describe '#validate_session_expiration' do + it 'return true when the session has not expired' do response_valid_signed.settings = settings + assert response_valid_signed.send(:validate_session_expiration) assert_empty response_valid_signed.errors end - it "return false when the session has expired" do + it 'return false when the session has expired' do response.settings = settings - assert !response.send(:validate_session_expiration) - assert_includes response.errors, "The attributes have expired, based on the SessionNotOnOrAfter of the AuthnStatement of this Response" + + refute response.send(:validate_session_expiration) + assert_includes response.errors, 'The attributes have expired, based on the SessionNotOnOrAfter of the AuthnStatement of this Response' end - it "returns true when the session has expired, but is still within the allowed_clock_drift" do - drift = (Time.now - Time.parse("2010-11-19T21:57:37Z")) * 60 # seconds ago that this assertion expired + it 'returns true when the session has expired, but is still within the allowed_clock_drift' do + drift = (Time.now - Time.parse('2010-11-19T21:57:37Z')) * 60 # seconds ago that this assertion expired drift += 10 # add a buffer of 10 seconds to make sure the test passes opts = {} opts[:allowed_clock_drift] = drift response_with_drift = RubySaml::Response.new(response_document_without_recipient, opts) response_with_drift.settings = settings + assert response_with_drift.send(:validate_session_expiration) assert_empty response_with_drift.errors end end - describe "#validate_signature" do - it "return true when the signature is valid" do + describe '#validate_signature' do + it 'return true when the signature is valid' do settings.idp_cert_fingerprint = ruby_saml_cert_fingerprint response_valid_signed.settings = settings + assert response_valid_signed.send(:validate_signature) assert_empty response_valid_signed.errors end - it "return true when the signature is valid and ds namespace is at the root" do + it 'return true when the signature is valid and ds namespace is at the root' do settings.idp_cert_fingerprint = '19a4fff2e8fcc7f3ea5046348dbf1d81320654d1f712028cc97933cb1247fc99' response_with_ds_namespace_at_the_root.settings = settings + assert response_with_ds_namespace_at_the_root.send(:validate_signature) assert_empty response_with_ds_namespace_at_the_root.errors end - it "return true when the signature is valid and fingerprint provided" do + it 'return true when the signature is valid and fingerprint provided' do settings.idp_cert_fingerprint = 'D0:35:89:BB:11:16:CB:3C:26:B0:D4:DA:CE:2A:91:B9:E0:A6:D8:E8:BF:93:C2:5B:74:0D:52:01:47:72:CE:E4' xml = 'PHNhbWxwOlJlc3BvbnNlIHhtbG5zOnNhbWxwPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6cHJvdG9jb2wiIERlc3RpbmF0aW9uPSJodHRwczovL2NvZGVycGFkLmlvL3NhbWwvYWNzIiBJRD0iXzEwOGE1ZTg0MDllYzRjZjlhY2QxYzQ2OWU5ZDcxNGFkIiBJblJlc3BvbnNlVG89Il80ZmZmYWE2MC02OTZiLTAxMzMtMzg4Ni0wMjQxZjY1YzA2OTMiIElzc3VlSW5zdGFudD0iMjAxNS0xMS0wOVQyMzo1NTo0M1oiIFZlcnNpb249IjIuMCI+PHNhbWw6SXNzdWVyIHhtbG5zOnNhbWw9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphc3NlcnRpb24iPmh0dHBzOi8vbG9naW4uaHVsdS5jb208L3NhbWw6SXNzdWVyPjxkczpTaWduYXR1cmUgeG1sbnM6ZHM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyMiPjxkczpTaWduZWRJbmZvPjxkczpDYW5vbmljYWxpemF0aW9uTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8xMC94bWwtZXhjLWMxNG4jIj48L2RzOkNhbm9uaWNhbGl6YXRpb25NZXRob2Q+PGRzOlNpZ25hdHVyZU1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyNyc2Etc2hhMSI+PC9kczpTaWduYXR1cmVNZXRob2Q+PGRzOlJlZmVyZW5jZSBVUkk9IiNfMTA4YTVlODQwOWVjNGNmOWFjZDFjNDY5ZTlkNzE0YWQiPjxkczpUcmFuc2Zvcm1zPjxkczpUcmFuc2Zvcm0gQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjZW52ZWxvcGVkLXNpZ25hdHVyZSI+PC9kczpUcmFuc2Zvcm0+PGRzOlRyYW5zZm9ybSBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMTAveG1sLWV4Yy1jMTRuIyI+PC9kczpUcmFuc2Zvcm0+PC9kczpUcmFuc2Zvcm1zPjxkczpEaWdlc3RNZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjc2hhMSI+PC9kczpEaWdlc3RNZXRob2Q+PGRzOkRpZ2VzdFZhbHVlPm9sQllXbTQyRi9oZm0xdHJYTHk2a3V6MXlMUT08L2RzOkRpZ2VzdFZhbHVlPjwvZHM6UmVmZXJlbmNlPjwvZHM6U2lnbmVkSW5mbz48ZHM6U2lnbmF0dXJlVmFsdWU+dXNRTmY5WGpKTDRlOXVucnVCdWViSnQ3R0tXM2hJUk9teWVqTm1NMHM4WFhlWHN3WHc4U3ZCZi8zeDNNWEpkWnpNV0pOM3ExN2tGWHN2bTVna1JzbkE9PTwvZHM6U2lnbmF0dXJlVmFsdWU+PGRzOktleUluZm8+PGRzOlg1MDlEYXRhPjxkczpYNTA5Q2VydGlmaWNhdGU+TUlJQ1FEQ0NBZXFnQXdJQkFnSUpBSVZOdzVLRzR1aTFNQTBHQ1NxR1NJYjNEUUVCQlFVQU1Fd3hDekFKQmdOVkJBWVRBa2RDTVJJd0VBWURWUVFJRXdsQ1pYSnJjMmhwY21VeEVEQU9CZ05WQkFjVEIwNWxkMkoxY25reEZ6QVZCZ05WQkFvVERrMTVJRU52YlhCaGJua2dUSFJrTUI0WERURXlNVEF5TlRBMk1qY3pORm9YRFRJeU1UQXlNekEyTWpjek5Gb3dUREVMTUFrR0ExVUVCaE1DUjBJeEVqQVFCZ05WQkFnVENVSmxjbXR6YUdseVpURVFNQTRHQTFVRUJ4TUhUbVYzWW5WeWVURVhNQlVHQTFVRUNoTU9UWGtnUTI5dGNHRnVlU0JNZEdRd1hEQU5CZ2txaGtpRzl3MEJBUUVGQUFOTEFEQklBa0VBd1NOL2dpMzNSbXBBUW9MUWo3UDZ6QW5OVDBSbjdiakMzMjNuM3ExT25mdm52UjBmUWp2TnQ3ckRrQTVBdjVRbk02VjRZVU5Vbk1mYk9RcTBXTGJMU3dJREFRQUJvNEd1TUlHck1CMEdBMVVkRGdRV0JCUWZJSDFvZkJWcHNSQWNJTUsyaGJsN25nTVRZREI4QmdOVkhTTUVkVEJ6Z0JRZklIMW9mQlZwc1JBY0lNSzJoYmw3bmdNVFlLRlFwRTR3VERFTE1Ba0dBMVVFQmhNQ1IwSXhFakFRQmdOVkJBZ1RDVUpsY210emFHbHlaVEVRTUE0R0ExVUVCeE1IVG1WM1luVnllVEVYTUJVR0ExVUVDaE1PVFhrZ1EyOXRjR0Z1ZVNCTWRHU0NDUUNGVGNPU2h1TG90VEFNQmdOVkhSTUVCVEFEQVFIL01BMEdDU3FHU0liM0RRRUJCUVVBQTBFQXFvZ1YzdVBjbEtYRG1EWk1UN3ZsUFl4TEFxQ0dIWnRsQ3h6NGhNNEtTdGxEMi9HTmMxWGlMYjFoL0swQ0pMRG9zckVJYm0zd2lPMk12VEVSclZZU01RPT08L2RzOlg1MDlDZXJ0aWZpY2F0ZT48L2RzOlg1MDlEYXRhPjwvZHM6S2V5SW5mbz48L2RzOlNpZ25hdHVyZT48c2FtbHA6U3RhdHVzPjxzYW1scDpTdGF0dXNDb2RlIFZhbHVlPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6c3RhdHVzOlN1Y2Nlc3MiPjwvc2FtbHA6U3RhdHVzQ29kZT48L3NhbWxwOlN0YXR1cz48c2FtbDpBc3NlcnRpb24geG1sbnM6c2FtbD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFzc2VydGlvbiIgSUQ9Il8wMTg4MmRhOTM2OTQ0ZDFlYTZlZmY0NDA2NTc2MzFiNSIgSXNzdWVJbnN0YW50PSIyMDE1LTExLTA5VDIzOjU1OjQzWiIgVmVyc2lvbj0iMi4wIj48c2FtbDpJc3N1ZXI+aHR0cHM6Ly9sb2dpbi5odWx1LmNvbTwvc2FtbDpJc3N1ZXI+PGRzOlNpZ25hdHVyZSB4bWxuczpkcz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnIyI+PGRzOlNpZ25lZEluZm8+PGRzOkNhbm9uaWNhbGl6YXRpb25NZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzEwL3htbC1leGMtYzE0biMiPjwvZHM6Q2Fub25pY2FsaXphdGlvbk1ldGhvZD48ZHM6U2lnbmF0dXJlTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnI3JzYS1zaGExIj48L2RzOlNpZ25hdHVyZU1ldGhvZD48ZHM6UmVmZXJlbmNlIFVSST0iI18wMTg4MmRhOTM2OTQ0ZDFlYTZlZmY0NDA2NTc2MzFiNSI+PGRzOlRyYW5zZm9ybXM+PGRzOlRyYW5zZm9ybSBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyNlbnZlbG9wZWQtc2lnbmF0dXJlIj48L2RzOlRyYW5zZm9ybT48ZHM6VHJhbnNmb3JtIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8xMC94bWwtZXhjLWMxNG4jIj48L2RzOlRyYW5zZm9ybT48L2RzOlRyYW5zZm9ybXM+PGRzOkRpZ2VzdE1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyNzaGExIj48L2RzOkRpZ2VzdE1ldGhvZD48ZHM6RGlnZXN0VmFsdWU+cmo2YzhucC9BUmV0ZkJ1dWVOSzNPS0xDYnowPTwvZHM6RGlnZXN0VmFsdWU+PC9kczpSZWZlcmVuY2U+PC9kczpTaWduZWRJbmZvPjxkczpTaWduYXR1cmVWYWx1ZT5hR05FemZHM1dLcExKc2ZLRGJSNmpva2d6OEFnZ0FIRVVESEZyd0dsTHVQeWpyNEl3M09NcFNkV2gyL01YK1F3M1dPTk5mNHJNalh5TGVZSFJIVGpMQT09PC9kczpTaWduYXR1cmVWYWx1ZT48ZHM6S2V5SW5mbz48ZHM6WDUwOURhdGE+PGRzOlg1MDlDZXJ0aWZpY2F0ZT5NSUlDUURDQ0FlcWdBd0lCQWdJSkFJVk53NUtHNHVpMU1BMEdDU3FHU0liM0RRRUJCUVVBTUV3eEN6QUpCZ05WQkFZVEFrZENNUkl3RUFZRFZRUUlFd2xDWlhKcmMyaHBjbVV4RURBT0JnTlZCQWNUQjA1bGQySjFjbmt4RnpBVkJnTlZCQW9URGsxNUlFTnZiWEJoYm5rZ1RIUmtNQjRYRFRFeU1UQXlOVEEyTWpjek5Gb1hEVEl5TVRBeU16QTJNamN6TkZvd1RERUxNQWtHQTFVRUJoTUNSMEl4RWpBUUJnTlZCQWdUQ1VKbGNtdHphR2x5WlRFUU1BNEdBMVVFQnhNSFRtVjNZblZ5ZVRFWE1CVUdBMVVFQ2hNT1RYa2dRMjl0Y0dGdWVTQk1kR1F3WERBTkJna3Foa2lHOXcwQkFRRUZBQU5MQURCSUFrRUF3U04vZ2kzM1JtcEFRb0xRajdQNnpBbk5UMFJuN2JqQzMyM24zcTFPbmZ2bnZSMGZRanZOdDdyRGtBNUF2NVFuTTZWNFlVTlVuTWZiT1FxMFdMYkxTd0lEQVFBQm80R3VNSUdyTUIwR0ExVWREZ1FXQkJRZklIMW9mQlZwc1JBY0lNSzJoYmw3bmdNVFlEQjhCZ05WSFNNRWRUQnpnQlFmSUgxb2ZCVnBzUkFjSU1LMmhibDduZ01UWUtGUXBFNHdUREVMTUFrR0ExVUVCaE1DUjBJeEVqQVFCZ05WQkFnVENVSmxjbXR6YUdseVpURVFNQTRHQTFVRUJ4TUhUbVYzWW5WeWVURVhNQlVHQTFVRUNoTU9UWGtnUTI5dGNHRnVlU0JNZEdTQ0NRQ0ZUY09TaHVMb3RUQU1CZ05WSFJNRUJUQURBUUgvTUEwR0NTcUdTSWIzRFFFQkJRVUFBMEVBcW9nVjN1UGNsS1hEbURaTVQ3dmxQWXhMQXFDR0hadGxDeHo0aE00S1N0bEQyL0dOYzFYaUxiMWgvSzBDSkxEb3NyRUlibTN3aU8yTXZURVJyVllTTVE9PTwvZHM6WDUwOUNlcnRpZmljYXRlPjwvZHM6WDUwOURhdGE+PC9kczpLZXlJbmZvPjwvZHM6U2lnbmF0dXJlPjxzYW1sOlN1YmplY3Q+PHNhbWw6TmFtZUlEIEZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6MS4xOm5hbWVpZC1mb3JtYXQ6ZW1haWxBZGRyZXNzIiBTUE5hbWVRdWFsaWZpZXI9Imh0dHBzOi8vY29kZXJwYWQuaW8iPm1hdHQuanVyaWtAaHVsdS5jb208L3NhbWw6TmFtZUlEPjxzYW1sOlN1YmplY3RDb25maXJtYXRpb24gTWV0aG9kPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6Y206YmVhcmVyIj48c2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uRGF0YSBJblJlc3BvbnNlVG89Il80ZmZmYWE2MC02OTZiLTAxMzMtMzg4Ni0wMjQxZjY1YzA2OTMiIE5vdE9uT3JBZnRlcj0iMjAxNS0xMS0xMFQwMDoxMDo0M1oiIFJlY2lwaWVudD0iaHR0cHM6Ly9jb2RlcnBhZC5pby9zYW1sL2FjcyI+PC9zYW1sOlN1YmplY3RDb25maXJtYXRpb25EYXRhPjwvc2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uPjwvc2FtbDpTdWJqZWN0PjxzYW1sOkNvbmRpdGlvbnMgTm90QmVmb3JlPSIyMDE1LTExLTA5VDIyOjU1OjQzWiIgTm90T25PckFmdGVyPSIyMDE1LTExLTEwVDAwOjEwOjQzWiI+PHNhbWw6QXVkaWVuY2VSZXN0cmljdGlvbj48c2FtbDpBdWRpZW5jZT5odHRwczovL2NvZGVycGFkLmlvPC9zYW1sOkF1ZGllbmNlPjwvc2FtbDpBdWRpZW5jZVJlc3RyaWN0aW9uPjwvc2FtbDpDb25kaXRpb25zPjxzYW1sOkF1dGhuU3RhdGVtZW50IEF1dGhuSW5zdGFudD0iMjAxNS0xMS0wOVQyMzo1NTo0M1oiPjxzYW1sOkF1dGhuQ29udGV4dD48c2FtbDpBdXRobkNvbnRleHRDbGFzc1JlZj51cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YWM6Y2xhc3NlczpQYXNzd29yZDwvc2FtbDpBdXRobkNvbnRleHRDbGFzc1JlZj48L3NhbWw6QXV0aG5Db250ZXh0Pjwvc2FtbDpBdXRoblN0YXRlbWVudD48c2FtbDpBdHRyaWJ1dGVTdGF0ZW1lbnQ+PHNhbWw6QXR0cmlidXRlIE5hbWU9IkdpdmVuLW5hbWUiPjxzYW1sOkF0dHJpYnV0ZVZhbHVlPk1hdHQ8L3NhbWw6QXR0cmlidXRlVmFsdWU+PC9zYW1sOkF0dHJpYnV0ZT48c2FtbDpBdHRyaWJ1dGUgTmFtZT0iU3VybmFtZSI+PHNhbWw6QXR0cmlidXRlVmFsdWU+SnVyaWs8L3NhbWw6QXR0cmlidXRlVmFsdWU+PC9zYW1sOkF0dHJpYnV0ZT48c2FtbDpBdHRyaWJ1dGUgTmFtZT0iRW1haWwiPjxzYW1sOkF0dHJpYnV0ZVZhbHVlPm1hdHQuanVyaWtAaHVsdS5jb208L3NhbWw6QXR0cmlidXRlVmFsdWU+PC9zYW1sOkF0dHJpYnV0ZT48L3NhbWw6QXR0cmlidXRlU3RhdGVtZW50Pjwvc2FtbDpBc3NlcnRpb24+PC9zYW1scDpSZXNwb25zZT4=' response_x = RubySaml::Response.new(xml) response_x.settings = settings + assert response_x.send(:validate_signature) assert_empty response_x.errors end - it "return false when no fingerprint" do + it 'return false when no fingerprint' do settings.idp_cert_fingerprint = nil settings.idp_cert = nil response.settings = settings - assert !response.send(:validate_signature) - assert_includes response.errors, "Invalid Signature on SAML Response" + + refute response.send(:validate_signature) + assert_includes response.errors, 'Invalid Signature on SAML Response' end - it "return false when the signature is invalid" do + it 'return false when the signature is invalid' do settings.idp_cert_fingerprint = signature_fingerprint_1 response.settings = settings - assert !response.send(:validate_signature) - assert_includes response.errors, "Fingerprint mismatch" - assert_includes response.errors, "Invalid Signature on SAML Response" + + refute response.send(:validate_signature) + assert_includes response.errors, 'Fingerprint mismatch' + assert_includes response.errors, 'Invalid Signature on SAML Response' end - it "return false when no X509Certificate and no cert provided in settings" do + it 'return false when no X509Certificate and no cert provided in settings' do settings.idp_cert_fingerprint = ruby_saml_cert_fingerprint settings.idp_cert = nil response_valid_signed_without_x509certificate.settings = settings - assert !response_valid_signed_without_x509certificate.send(:validate_signature) - assert_includes response_valid_signed_without_x509certificate.errors, "Invalid Signature on SAML Response" + + refute response_valid_signed_without_x509certificate.send(:validate_signature) + assert_includes response_valid_signed_without_x509certificate.errors, 'Invalid Signature on SAML Response' end - it "return false when cert expired and check_idp_cert_expiration enabled" do + it 'return false when cert expired and check_idp_cert_expiration enabled' do settings.idp_cert_fingerprint = nil settings.idp_cert = ruby_saml_cert_text settings.security[:check_idp_cert_expiration] = true response_valid_signed.settings = settings - assert !response_valid_signed.send(:validate_signature) - assert_includes response_valid_signed.errors, "IdP x509 certificate expired" + + refute response_valid_signed.send(:validate_signature) + assert_includes response_valid_signed.errors, 'IdP x509 certificate expired' end - it "return false when X509Certificate and the cert provided at settings mismatches" do + it 'return false when X509Certificate and the cert provided at settings mismatches' do settings.idp_cert_fingerprint = nil settings.idp_cert = signature_1 response_valid_signed_without_x509certificate.settings = settings - assert !response_valid_signed_without_x509certificate.send(:validate_signature) - assert_includes response_valid_signed_without_x509certificate.errors, "Key validation error" - assert_includes response_valid_signed_without_x509certificate.errors, "Invalid Signature on SAML Response" + + refute response_valid_signed_without_x509certificate.send(:validate_signature) + assert_includes response_valid_signed_without_x509certificate.errors, 'Key validation error' + assert_includes response_valid_signed_without_x509certificate.errors, 'Invalid Signature on SAML Response' end - it "return false when X509Certificate has invalid content" do + it 'return false when X509Certificate has invalid content' do settings.idp_cert_fingerprint = nil settings.idp_cert = ruby_saml_cert_text content = read_response('response_with_signed_message_and_assertion.xml') content = content.sub(%r{.*}, - "an-invalid-certificate") + 'an-invalid-certificate') response_invalid_x509certificate = RubySaml::Response.new(content) response_invalid_x509certificate.settings = settings - assert !response_invalid_x509certificate.send(:validate_signature) - assert_includes response_invalid_x509certificate.errors, "Document Certificate Error" - assert_includes response_invalid_x509certificate.errors, "Invalid Signature on SAML Response" + + refute response_invalid_x509certificate.send(:validate_signature) + assert_includes response_invalid_x509certificate.errors, 'Document Certificate Error' + assert_includes response_invalid_x509certificate.errors, 'Invalid Signature on SAML Response' end - it "return true when X509Certificate and the cert provided at settings matches" do + it 'return true when X509Certificate and the cert provided at settings matches' do settings.idp_cert_fingerprint = nil settings.idp_cert = ruby_saml_cert_text response_valid_signed_without_x509certificate.settings = settings + assert response_valid_signed_without_x509certificate.send(:validate_signature) assert_empty response_valid_signed_without_x509certificate.errors end - it "return false when signature wrapping attack" do - signature_wrapping_attack = read_invalid_response("signature_wrapping_attack.xml.base64") + it 'return false when signature wrapping attack' do + signature_wrapping_attack = read_invalid_response('signature_wrapping_attack.xml.base64') response_wrapped = RubySaml::Response.new(signature_wrapping_attack) response_wrapped.stubs(:conditions).returns(nil) response_wrapped.stubs(:validate_subject_confirmation).returns(true) - settings.idp_cert_fingerprint = "afe71c28ef740bc87425be13a2263d37971da1f9" + settings.idp_cert_fingerprint = 'afe71c28ef740bc87425be13a2263d37971da1f9' response_wrapped.settings = settings - assert !response_wrapped.send(:validate_signature) - assert_includes response_wrapped.errors, "Invalid Signature on SAML Response" - assert_includes response_wrapped.errors, "Signed element id #pfxc3d2b542-0f7e-8767-8e87-5b0dc6913375 is not found" + + refute response_wrapped.send(:validate_signature) + assert_includes response_wrapped.errors, 'Invalid Signature on SAML Response' + assert_includes response_wrapped.errors, 'Signed element id #pfxc3d2b542-0f7e-8767-8e87-5b0dc6913375 is not found' end end - describe "#validate_signature with multiple idp certs" do - it "return true when at least a cert on idp_cert_multi is valid" do + describe '#validate_signature with multiple idp certs' do + it 'return true when at least a cert on idp_cert_multi is valid' do settings.idp_cert_multi = { - :signing => [ruby_saml_cert_text2, ruby_saml_cert_text], - :encryption => [] + signing: [ruby_saml_cert_text2, ruby_saml_cert_text], + encryption: [] } response_valid_signed.settings = settings response_valid_signed.send(:validate_signature) + assert_empty response_valid_signed.errors end - it "return true when at least a cert on idp_cert_multi is valid and keys are strings" do + it 'return true when at least a cert on idp_cert_multi is valid and keys are strings' do settings.idp_cert_multi = { - "signing" => [ruby_saml_cert_text2, ruby_saml_cert_text], - "encryption" => [] + 'signing' => [ruby_saml_cert_text2, ruby_saml_cert_text], + 'encryption' => [] } response_valid_signed.settings = settings + assert response_valid_signed.send(:validate_signature) assert_empty response_valid_signed.errors end - it "return false when none cert on idp_cert_multi is valid" do + it 'return false when none cert on idp_cert_multi is valid' do settings.idp_cert_fingerprint = ruby_saml_cert_fingerprint settings.idp_cert_multi = { - :signing => [ruby_saml_cert_text2, ruby_saml_cert_text2], - :encryption => [] + signing: [ruby_saml_cert_text2, ruby_saml_cert_text2], + encryption: [] } response_valid_signed.settings = settings - assert !response_valid_signed.send(:validate_signature) - assert_includes response_valid_signed.errors, "Certificate of the Signature element does not match provided certificate" - assert_includes response_valid_signed.errors, "Invalid Signature on SAML Response" + + refute response_valid_signed.send(:validate_signature) + assert_includes response_valid_signed.errors, 'Certificate of the Signature element does not match provided certificate' + assert_includes response_valid_signed.errors, 'Invalid Signature on SAML Response' end end - describe "#validate nameid" do - it "return false when no nameid element and required by settings" do + describe '#validate nameid' do + it 'return false when no nameid element and required by settings' do settings.security[:want_name_id] = true response_no_nameid.settings = settings - assert !response_no_nameid.send(:validate_name_id) - assert_includes response_no_nameid.errors, "No NameID element found in the assertion of the Response" + + refute response_no_nameid.send(:validate_name_id) + assert_includes response_no_nameid.errors, 'No NameID element found in the assertion of the Response' end - it "return false when no nameid element and required by settings" do + it 'return false when no nameid element and required by settings' do response_empty_nameid.settings = settings - assert !response_empty_nameid.send(:validate_name_id) - assert_includes response_empty_nameid.errors, "An empty NameID value found" + + refute response_empty_nameid.send(:validate_name_id) + assert_includes response_empty_nameid.errors, 'An empty NameID value found' end - it "return false when no nameid value" do + it 'return false when no nameid value' do response_empty_nameid.settings = settings - assert !response_empty_nameid.send(:validate_name_id) - assert_includes response_empty_nameid.errors, "An empty NameID value found" + + refute response_empty_nameid.send(:validate_name_id) + assert_includes response_empty_nameid.errors, 'An empty NameID value found' end - it "return false when wrong_spnamequalifier" do + it 'return false when wrong_spnamequalifier' do settings.sp_entity_id = 'sp_entity_id' response_wrong_spnamequalifier.settings = settings - assert !response_wrong_spnamequalifier.send(:validate_name_id) + + refute response_wrong_spnamequalifier.send(:validate_name_id) assert_includes response_wrong_spnamequalifier.errors, 'SPNameQualifier value does not match the SP entityID value.' end - it "return true when no nameid element but not required by settings" do + it 'return true when no nameid element but not required by settings' do settings.security[:want_name_id] = false response_no_nameid.settings = settings + assert response_no_nameid.send(:validate_name_id) end - it "return true when nameid is valid and response_wrong_spnamequalifier matches the SP issuer" do + it 'return true when nameid is valid and response_wrong_spnamequalifier matches the SP issuer' do settings.sp_entity_id = 'wrong-sp-entityid' response_wrong_spnamequalifier.settings = settings + assert response_wrong_spnamequalifier.send(:validate_name_id) end end - describe "#nameid" do - it "extract the value of the name id element" do - assert_equal "support@onelogin.com", response.nameid - assert_equal "someone@example.com", response_with_signed_assertion.nameid + describe '#nameid' do + it 'extract the value of the name id element' do + assert_equal 'support@onelogin.com', response.nameid + assert_equal 'someone@example.com', response_with_signed_assertion.nameid end - it "be extractable from an OpenSAML response" do + it 'be extractable from an OpenSAML response' do response_open_saml = RubySaml::Response.new(fixture(:open_saml)) - assert_equal "someone@example.org", response_open_saml.nameid + + assert_equal 'someone@example.org', response_open_saml.nameid end - it "be extractable from a Simple SAML PHP response" do + it 'be extractable from a Simple SAML PHP response' do response_ssp = RubySaml::Response.new(fixture(:simple_saml_php)) - assert_equal "someone@example.com", response_ssp.nameid + + assert_equal 'someone@example.com', response_ssp.nameid end end - describe "#name_id_format" do - it "extract the value of the name id element" do - assert_equal "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", response.name_id_format - assert_equal "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", response_with_signed_assertion.name_id_format + describe '#name_id_format' do + it 'extract the value of the name id element' do + assert_equal 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress', response.name_id_format + assert_equal 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress', response_with_signed_assertion.name_id_format end end - describe "#sessionindex" do - it "extract the value of the sessionindex element" do + describe '#sessionindex' do + it 'extract the value of the sessionindex element' do response = RubySaml::Response.new(fixture(:simple_saml_php)) - assert_equal "_51be37965feb5579d803141076936dc2e9d1d98ebf", response.sessionindex + + assert_equal '_51be37965feb5579d803141076936dc2e9d1d98ebf', response.sessionindex end end - describe "#check_one_conditions" do - it "return false when none or more than one conditions element" do + describe '#check_one_conditions' do + it 'return false when none or more than one conditions element' do response_no_conditions.soft = true - assert !response_no_conditions.send(:validate_one_conditions) - assert_includes response_no_conditions.errors, "The Assertion must include one Conditions element" + + refute response_no_conditions.send(:validate_one_conditions) + assert_includes response_no_conditions.errors, 'The Assertion must include one Conditions element' end - it "return true when one conditions element" do + it 'return true when one conditions element' do response.soft = true + assert response.send(:validate_one_conditions) end - it "return true when no conditions are present and skip_conditions is true" do + it 'return true when no conditions are present and skip_conditions is true' do response_no_conditions_with_skip.soft = true + assert response_no_conditions_with_skip.send(:validate_one_conditions) end end - describe "#check_one_authnstatement" do - it "return false when none or more than one authnstatement element" do + describe '#check_one_authnstatement' do + it 'return false when none or more than one authnstatement element' do response_no_authnstatement.soft = true - assert !response_no_authnstatement.send(:validate_one_authnstatement) - assert_includes response_no_authnstatement.errors, "The Assertion must include one AuthnStatement element" + + refute response_no_authnstatement.send(:validate_one_authnstatement) + assert_includes response_no_authnstatement.errors, 'The Assertion must include one AuthnStatement element' end - it "return true when one authnstatement element" do + it 'return true when one authnstatement element' do response.soft = true + assert response.send(:validate_one_authnstatement) end - it "return true when SAML Response is empty but skip_authstatement option is used" do + it 'return true when SAML Response is empty but skip_authstatement option is used' do response_no_authnstatement_with_skip.soft = true + assert response_no_authnstatement_with_skip.send(:validate_one_authnstatement) assert_empty response_empty_destination_with_skip.errors end end - describe "#check_conditions" do - it "check time conditions" do + describe '#check_conditions' do + it 'check time conditions' do response.soft = true - assert !response.send(:validate_conditions) + + refute response.send(:validate_conditions) response_time_updated = RubySaml::Response.new(response_document_without_recipient_with_time_updated) response_time_updated.soft = true + assert response_time_updated.send(:validate_conditions) - Timecop.freeze(Time.parse("2011-06-14T18:25:01.516Z")) do + Timecop.freeze(Time.parse('2011-06-14T18:25:01.516Z')) do response_with_saml2_namespace = RubySaml::Response.new(response_document_with_saml2_namespace) response_with_saml2_namespace.soft = true + assert response_with_saml2_namespace.send(:validate_conditions) end end - it "optionally allows for clock drift on NotBefore" do + it 'optionally allows for clock drift on NotBefore' do settings.soft = true # The NotBefore condition in the document is 2011-06-14T18:21:01.516Z - Timecop.freeze(Time.parse("2011-06-14T18:21:01Z")) do + Timecop.freeze(Time.parse('2011-06-14T18:21:01Z')) do special_response_with_saml2_namespace = RubySaml::Response.new( response_document_with_saml2_namespace, - :allowed_clock_drift => 0.515, + allowed_clock_drift: 0.515, settings: settings ) - assert !special_response_with_saml2_namespace.send(:validate_conditions) + + refute special_response_with_saml2_namespace.send(:validate_conditions) special_response_with_saml2_namespace = RubySaml::Response.new( response_document_with_saml2_namespace, - :allowed_clock_drift => 0.516 + allowed_clock_drift: 0.516 ) + assert special_response_with_saml2_namespace.send(:validate_conditions) special_response_with_saml2_namespace = RubySaml::Response.new( response_document_with_saml2_namespace, - :allowed_clock_drift => '0.515', + allowed_clock_drift: '0.515', settings: settings ) - assert !special_response_with_saml2_namespace.send(:validate_conditions) + + refute special_response_with_saml2_namespace.send(:validate_conditions) special_response_with_saml2_namespace = RubySaml::Response.new( response_document_with_saml2_namespace, - :allowed_clock_drift => '0.516' + allowed_clock_drift: '0.516' ) + assert special_response_with_saml2_namespace.send(:validate_conditions) end end - it "optionally allows for clock drift on NotOnOrAfter" do + it 'optionally allows for clock drift on NotOnOrAfter' do # Java Floats behave differently than MRI java = jruby? || truffleruby? settings.soft = true # The NotBefore condition in the document is 2011-06-1418:31:01.516Z - Timecop.freeze(Time.parse("2011-06-14T18:31:02Z")) do + Timecop.freeze(Time.parse('2011-06-14T18:31:02Z')) do special_response_with_saml2_namespace = RubySaml::Response.new( - response_document_with_saml2_namespace, - :allowed_clock_drift => 0.483, - settings: settings + response_document_with_saml2_namespace, + allowed_clock_drift: 0.483, + settings: settings ) - assert !special_response_with_saml2_namespace.send(:validate_conditions) + + refute special_response_with_saml2_namespace.send(:validate_conditions) special_response_with_saml2_namespace = RubySaml::Response.new( - response_document_with_saml2_namespace, - :allowed_clock_drift => java ? 0.485 : 0.484 + response_document_with_saml2_namespace, + allowed_clock_drift: java ? 0.485 : 0.484 ) + assert special_response_with_saml2_namespace.send(:validate_conditions) special_response_with_saml2_namespace = RubySaml::Response.new( - response_document_with_saml2_namespace, - :allowed_clock_drift => '0.483', - settings: settings + response_document_with_saml2_namespace, + allowed_clock_drift: '0.483', + settings: settings ) - assert !special_response_with_saml2_namespace.send(:validate_conditions) + + refute special_response_with_saml2_namespace.send(:validate_conditions) special_response_with_saml2_namespace = RubySaml::Response.new( - response_document_with_saml2_namespace, - :allowed_clock_drift => java ? '0.485' : '0.484' + response_document_with_saml2_namespace, + allowed_clock_drift: java ? '0.485' : '0.484' ) + assert special_response_with_saml2_namespace.send(:validate_conditions) end end end - describe "#attributes" do - it "extract the first attribute in a hash accessed via its symbol" do - assert_equal "demo", response.attributes[:uid] + describe '#attributes' do + it 'extract the first attribute in a hash accessed via its symbol' do + assert_equal 'demo', response.attributes[:uid] end - it "extract the first attribute in a hash accessed via its name" do - assert_equal "demo", response.attributes["uid"] + it 'extract the first attribute in a hash accessed via its name' do + assert_equal 'demo', response.attributes['uid'] end - it "extract all attributes" do - assert_equal "demo", response.attributes[:uid] - assert_equal "value", response.attributes[:another_value] + it 'extract all attributes' do + assert_equal 'demo', response.attributes[:uid] + assert_equal 'value', response.attributes[:another_value] end - it "work for implicit namespaces" do - assert_equal "someone@example.com", response_with_signed_assertion.attributes["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"] + it 'work for implicit namespaces' do + assert_equal 'someone@example.com', response_with_signed_assertion.attributes['http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress'] end - it "extract attributes from all AttributeStatement tags" do - assert_equal "smith", response_with_multiple_attribute_statements.attributes[:surname] - assert_equal "bob", response_with_multiple_attribute_statements.attributes[:firstname] + it 'extract attributes from all AttributeStatement tags' do + assert_equal 'smith', response_with_multiple_attribute_statements.attributes[:surname] + assert_equal 'bob', response_with_multiple_attribute_statements.attributes[:firstname] end - it "not raise on responses without attributes" do + it 'not raise on responses without attributes' do assert_equal RubySaml::Attributes.new, response_unsigned.attributes end - describe "#encrypted attributes" do - it "raise error when the assertion contains encrypted attributes but no private key to decrypt" do + describe '#encrypted attributes' do + it 'raise error when the assertion contains encrypted attributes but no private key to decrypt' do settings.private_key = nil response_encrypted_attrs.settings = settings - assert_raises(RubySaml::ValidationError, "An EncryptedAttribute found and no SP private key found on the settings to decrypt it") do + assert_raises(RubySaml::ValidationError, 'An EncryptedAttribute found and no SP private key found on the settings to decrypt it') do response_encrypted_attrs.attributes end end - it "extract attributes when the assertion contains encrypted attributes and the private key is provided" do + it 'extract attributes when the assertion contains encrypted attributes and the private key is provided' do settings.certificate = ruby_saml_cert_text settings.private_key = ruby_saml_key_text response_encrypted_attrs.settings = settings - assert_equal "test", response_encrypted_attrs.attributes[:uid] - assert_equal "test@example.com", response_encrypted_attrs.attributes[:mail] + + assert_equal 'test', response_encrypted_attrs.attributes[:uid] + assert_equal 'test@example.com', response_encrypted_attrs.attributes[:mail] end end - it "return false when validating a response with duplicate attributes" do + it 'return false when validating a response with duplicate attributes' do response_duplicated_attributes.settings = settings response_duplicated_attributes.options[:check_duplicated_attributes] = true - assert !response_duplicated_attributes.send(:validate_no_duplicated_attributes) - assert_includes response_duplicated_attributes.errors, "Found an Attribute element with duplicated Name" + + refute response_duplicated_attributes.send(:validate_no_duplicated_attributes) + assert_includes response_duplicated_attributes.errors, 'Found an Attribute element with duplicated Name' end - it "return true when validating a response with duplicate attributes but skip check" do + it 'return true when validating a response with duplicate attributes but skip check' do response_duplicated_attributes.settings = settings + assert response_duplicated_attributes.send(:validate_no_duplicated_attributes) end - describe "#multiple values" do - it "extract single value as string" do - assert_equal "demo", response_multiple_attr_values.attributes[:uid] + describe '#multiple values' do + it 'extract single value as string' do + assert_equal 'demo', response_multiple_attr_values.attributes[:uid] end - it "extract single value as string in compatibility mode off" do + it 'extract single value as string in compatibility mode off' do RubySaml::Attributes.single_value_compatibility = false - assert_equal ["demo"], response_multiple_attr_values.attributes[:uid] + + assert_equal ['demo'], response_multiple_attr_values.attributes[:uid] # classes are not reloaded between tests so restore default RubySaml::Attributes.single_value_compatibility = true end - it "extract first of multiple values as string for b/w compatibility" do + it 'extract first of multiple values as string for b/w compatibility' do assert_equal 'value1', response_multiple_attr_values.attributes[:another_value] end - it "extract first of multiple values as string for b/w compatibility in compatibility mode off" do + it 'extract first of multiple values as string for b/w compatibility in compatibility mode off' do RubySaml::Attributes.single_value_compatibility = false - assert_equal ['value1', 'value2'], response_multiple_attr_values.attributes[:another_value] + + assert_equal %w[value1 value2], response_multiple_attr_values.attributes[:another_value] RubySaml::Attributes.single_value_compatibility = true end - it "return array with all attributes when asked in XML order" do - assert_equal ['value1', 'value2'], response_multiple_attr_values.attributes.multi(:another_value) + it 'return array with all attributes when asked in XML order' do + assert_equal %w[value1 value2], response_multiple_attr_values.attributes.multi(:another_value) end - it "return array with all attributes when asked in XML order in compatibility mode off" do + it 'return array with all attributes when asked in XML order in compatibility mode off' do RubySaml::Attributes.single_value_compatibility = false - assert_equal ['value1', 'value2'], response_multiple_attr_values.attributes.multi(:another_value) + + assert_equal %w[value1 value2], response_multiple_attr_values.attributes.multi(:another_value) RubySaml::Attributes.single_value_compatibility = true end - it "return first of multiple values when multiple Attribute tags in XML" do + it 'return first of multiple values when multiple Attribute tags in XML' do assert_equal 'role1', response_multiple_attr_values.attributes[:role] end - it "return first of multiple values when multiple Attribute tags in XML in compatibility mode off" do + it 'return first of multiple values when multiple Attribute tags in XML in compatibility mode off' do RubySaml::Attributes.single_value_compatibility = false - assert_equal ['role1', 'role2', 'role3'], response_multiple_attr_values.attributes[:role] + + assert_equal %w[role1 role2 role3], response_multiple_attr_values.attributes[:role] RubySaml::Attributes.single_value_compatibility = true end - it "return all of multiple values in reverse order when multiple Attribute tags in XML" do - assert_equal ['role1', 'role2', 'role3'], response_multiple_attr_values.attributes.multi(:role) + it 'return all of multiple values in reverse order when multiple Attribute tags in XML' do + assert_equal %w[role1 role2 role3], response_multiple_attr_values.attributes.multi(:role) end - it "return all of multiple values in reverse order when multiple Attribute tags in XML in compatibility mode off" do + it 'return all of multiple values in reverse order when multiple Attribute tags in XML in compatibility mode off' do RubySaml::Attributes.single_value_compatibility = false - assert_equal ['role1', 'role2', 'role3'], response_multiple_attr_values.attributes.multi(:role) + + assert_equal %w[role1 role2 role3], response_multiple_attr_values.attributes.multi(:role) RubySaml::Attributes.single_value_compatibility = true end - it "return all of multiple values when multiple Attribute tags in multiple AttributeStatement tags" do + it 'return all of multiple values when multiple Attribute tags in multiple AttributeStatement tags' do RubySaml::Attributes.single_value_compatibility = false - assert_equal ['role1', 'role2', 'role3'], response_with_multiple_attribute_statements.attributes.multi(:role) + + assert_equal %w[role1 role2 role3], response_with_multiple_attribute_statements.attributes.multi(:role) RubySaml::Attributes.single_value_compatibility = true end - it "return nil value correctly" do + it 'return nil value correctly' do assert_nil response_multiple_attr_values.attributes[:attribute_with_nil_value] end - it "return nil value correctly when not in compatibility mode off" do + it 'return nil value correctly when not in compatibility mode off' do RubySaml::Attributes.single_value_compatibility = false + assert_equal [nil], response_multiple_attr_values.attributes[:attribute_with_nil_value] RubySaml::Attributes.single_value_compatibility = true end - it "return multiple values including nil and empty string" do + it 'return multiple values including nil and empty string' do response = RubySaml::Response.new(fixture(:response_with_multiple_attribute_values)) - assert_equal ["", "valuePresent", nil, nil], response.attributes.multi(:attribute_with_nils_and_empty_strings) + + assert_equal ['', 'valuePresent', nil, nil], response.attributes.multi(:attribute_with_nils_and_empty_strings) end - it "return multiple values from [] when not in compatibility mode off" do + it 'return multiple values from [] when not in compatibility mode off' do RubySaml::Attributes.single_value_compatibility = false - assert_equal ["", "valuePresent", nil, nil], response_multiple_attr_values.attributes[:attribute_with_nils_and_empty_strings] + + assert_equal ['', 'valuePresent', nil, nil], response_multiple_attr_values.attributes[:attribute_with_nils_and_empty_strings] RubySaml::Attributes.single_value_compatibility = true end - it "check what happens when trying retrieve attribute that does not exists" do + it 'check what happens when trying to retrieve attribute that does not exists' do assert_nil response_multiple_attr_values.attributes[:attribute_not_exists] assert_nil response_multiple_attr_values.attributes.single(:attribute_not_exists) assert_nil response_multiple_attr_values.attributes.multi(:attribute_not_exists) RubySaml::Attributes.single_value_compatibility = false + assert_nil response_multiple_attr_values.attributes[:attribute_not_exists] assert_nil response_multiple_attr_values.attributes.single(:attribute_not_exists) assert_nil response_multiple_attr_values.attributes.multi(:attribute_not_exists) RubySaml::Attributes.single_value_compatibility = true end - end end - describe "#session_expires_at" do - it "extract the value of the SessionNotOnOrAfter attribute" do - assert response.session_expires_at.is_a?(Time) + describe '#session_expires_at' do + it 'extract the value of the SessionNotOnOrAfter attribute' do + assert_kind_of Time, response.session_expires_at end - it "return nil when the value of the SessionNotOnOrAfter is not set" do + it 'return nil when the value of the SessionNotOnOrAfter is not set' do assert_nil response_without_attributes.session_expires_at end end - describe "#authn_instant" do - it "extract the value of the AuthnInstant attribute" do - assert_equal "2010-11-18T21:57:37Z", response.authn_instant + describe '#authn_instant' do + it 'extract the value of the AuthnInstant attribute' do + assert_equal '2010-11-18T21:57:37Z', response.authn_instant end end - describe "#authn_context_class_ref" do - it "extract the value of the AuthnContextClassRef attribute" do - assert_equal "urn:oasis:names:tc:SAML:2.0:ac:classes:Password", response.authn_context_class_ref + describe '#authn_context_class_ref' do + it 'extract the value of the AuthnContextClassRef attribute' do + assert_equal 'urn:oasis:names:tc:SAML:2.0:ac:classes:Password', response.authn_context_class_ref end end - describe "#success" do - it "find a status code that says success" do + describe '#success' do + it 'find a status code that says success' do response.success? end end @@ -1384,13 +1506,14 @@ def generate_audience_error(expected, actual) malicious_response.send(:xpath_first_from_signed_assertion) rescue RubySaml::ValidationError # TODO: This should be a more specific error end + assert_nil $evalled end end describe '#sign_document' do it 'sign an unsigned SAML Response XML and initiate the SAML object with it' do - xml = Base64.decode64(fixture("test_sign.xml")) + xml = Base64.decode64(fixture('test_sign.xml')) formatted_cert = RubySaml::Utils.format_cert(ruby_saml_cert_text) cert = OpenSSL::X509::Certificate.new(formatted_cert) @@ -1401,11 +1524,13 @@ def generate_audience_error(expected, actual) document = RubySaml::XML::DocumentSigner.sign_document(xml, private_key, cert) signed_response = RubySaml::Response.new(document.to_s) - settings.assertion_consumer_service_url = "http://recipient" + + settings.assertion_consumer_service_url = 'http://recipient' settings.idp_cert = ruby_saml_cert_text signed_response.settings = settings - Timecop.freeze(Time.parse("2015-03-18T04:50:24Z")) do - assert signed_response.is_valid? + + Timecop.freeze(Time.parse('2015-03-18T04:50:24Z')) do + assert_predicate signed_response, :is_valid? end assert_empty signed_response.errors end @@ -1419,8 +1544,8 @@ def generate_audience_error(expected, actual) end it 'returns false if :want_assertion_signed enabled and Assertion not signed' do - assert !@no_signed_assertion.send(:validate_signed_elements) - assert_includes @no_signed_assertion.errors, "The Assertion of the Response is not signed and the SP requires it" + refute @no_signed_assertion.send(:validate_signed_elements) + assert_includes @no_signed_assertion.errors, 'The Assertion of the Response is not signed and the SP requires it' end it 'returns true if :want_assertion_signed enabled and Assertion is signed' do @@ -1429,35 +1554,36 @@ def generate_audience_error(expected, actual) end end - describe "retrieve nameID" do - it 'is possible when nameID inside the assertion' do + describe 'retrieve nameID' do + it 'is possible when nameID is inside the assertion' do response_valid_signed.settings = settings - assert_equal "test@onelogin.com", response_valid_signed.nameid + + assert_equal 'test@onelogin.com', response_valid_signed.nameid end - it 'is not possible when encryptID inside the assertion but no private key' do + it 'is not possible when encryptID is inside the assertion but no private key' do response_encrypted_nameid.settings = settings - assert_raises(RubySaml::ValidationError, "An EncryptedID found and no SP private key found on the settings to decrypt it") do - assert_equal "test@onelogin.com", response_encrypted_nameid.nameid + assert_raises(RubySaml::ValidationError, 'An EncryptedID found and no SP private key found on the settings to decrypt it') do + assert_equal 'test@onelogin.com', response_encrypted_nameid.nameid end - assert_raises(RubySaml::ValidationError, "An EncryptedID found and no SP private key found on the settings to decrypt it") do - assert_equal "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", response_encrypted_nameid.name_id_format + assert_raises(RubySaml::ValidationError, 'An EncryptedID found and no SP private key found on the settings to decrypt it') do + assert_equal 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress', response_encrypted_nameid.name_id_format end end - it 'is possible when encryptID inside the assertion and settings has the private key' do + it 'is possible when encryptID is inside the assertion and settings has the private key' do settings.private_key = ruby_saml_key_text response_encrypted_nameid.settings = settings - assert_equal "test@onelogin.com", response_encrypted_nameid.nameid - assert_equal "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", response_encrypted_nameid.name_id_format - end + assert_equal 'test@onelogin.com', response_encrypted_nameid.nameid + assert_equal 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress', response_encrypted_nameid.name_id_format + end end describe 'try to initialize an encrypted response' do it 'raise if an encrypted assertion is found and no sp private key to decrypt it' do - error_msg = "An EncryptedAssertion found and no SP private key found on the settings to decrypt it. Be sure you provided the :settings parameter at the initialize method" + error_msg = 'An EncryptedAssertion found and no SP private key found on the settings to decrypt it. Be sure you provided the :settings parameter at the initialize method' assert_raises(RubySaml::ValidationError, error_msg) do RubySaml::Response.new(signed_message_encrypted_unsigned_assertion) @@ -1495,15 +1621,19 @@ def generate_audience_error(expected, actual) settings.certificate = ruby_saml_cert_text settings.private_key = ruby_saml_key_text response = RubySaml::Response.new(signed_message_encrypted_unsigned_assertion, settings: settings) + assert response.decrypted_document response2 = RubySaml::Response.new(signed_message_encrypted_signed_assertion, settings: settings) + assert response2.decrypted_document response3 = RubySaml::Response.new(unsigned_message_encrypted_signed_assertion, settings: settings) + assert response3.decrypted_document response4 = RubySaml::Response.new(unsigned_message_encrypted_unsigned_assertion, settings: settings) + assert response4.decrypted_document assert RubySaml::Response.new( @@ -1513,10 +1643,11 @@ def generate_audience_error(expected, actual) end end - describe "retrieve nameID and attributes from encrypted assertion" do + describe 'retrieve nameID and attributes from encrypted assertion' do before do settings.idp_cert_fingerprint = '55:FD:5F:3F:43:5A:AC:E6:79:89:BF:25:48:81:A1:C4:F3:37:3B:CB:1B:4D:68:A0:3E:A5:C9:FF:61:48:01:3F' settings.sp_entity_id = 'http://rubysaml.com:3000/saml/metadata' + settings.assertion_consumer_service_url = 'http://rubysaml.com:3000/saml/acs' settings.certificate = ruby_saml_cert_text settings.private_key = ruby_saml_key_text @@ -1524,91 +1655,91 @@ def generate_audience_error(expected, actual) it 'is possible when signed_message_encrypted_unsigned_assertion' do response = RubySaml::Response.new(signed_message_encrypted_unsigned_assertion, settings: settings) - Timecop.freeze(Time.parse("2015-03-19T14:30:31Z")) do - assert response.is_valid? + Timecop.freeze(Time.parse('2015-03-19T14:30:31Z')) do + assert_predicate response, :is_valid? assert_empty response.errors - assert_equal "test", response.attributes[:uid] - assert_equal "98e2bb61075e951b37d6b3be6954a54b340d86c7", response.nameid + assert_equal 'test', response.attributes[:uid] + assert_equal '98e2bb61075e951b37d6b3be6954a54b340d86c7', response.nameid end end it 'is possible when signed_message_encrypted_signed_assertion' do response = RubySaml::Response.new(signed_message_encrypted_signed_assertion, settings: settings) - Timecop.freeze(Time.parse("2015-03-19T14:30:31Z")) do - assert response.is_valid? + Timecop.freeze(Time.parse('2015-03-19T14:30:31Z')) do + assert_predicate response, :is_valid? assert_empty response.errors - assert_equal "test", response.attributes[:uid] - assert_equal "98e2bb61075e951b37d6b3be6954a54b340d86c7", response.nameid + assert_equal 'test', response.attributes[:uid] + assert_equal '98e2bb61075e951b37d6b3be6954a54b340d86c7', response.nameid end end it 'is possible when unsigned_message_encrypted_signed_assertion' do response = RubySaml::Response.new(unsigned_message_encrypted_signed_assertion, settings: settings) - Timecop.freeze(Time.parse("2015-03-19T14:30:31Z")) do - assert response.is_valid? + Timecop.freeze(Time.parse('2015-03-19T14:30:31Z')) do + assert_predicate response, :is_valid? assert_empty response.errors - assert_equal "test", response.attributes[:uid] - assert_equal "98e2bb61075e951b37d6b3be6954a54b340d86c7", response.nameid + assert_equal 'test', response.attributes[:uid] + assert_equal '98e2bb61075e951b37d6b3be6954a54b340d86c7', response.nameid end end it 'is not possible when unsigned_message_encrypted_unsigned_assertion' do response = RubySaml::Response.new(unsigned_message_encrypted_unsigned_assertion, settings: settings) - Timecop.freeze(Time.parse("2015-03-19T14:30:31Z")) do - assert !response.is_valid? - assert_includes response.errors, "Found an unexpected number of Signature Element. SAML Response rejected" + Timecop.freeze(Time.parse('2015-03-19T14:30:31Z')) do + refute response.is_valid? + assert_includes response.errors, 'Found an unexpected number of Signature Element. SAML Response rejected' end end end - describe "#decrypt_assertion" do + describe '#decrypt_assertion' do before do settings.private_key = ruby_saml_key_text end - describe "check right settings" do - - it "is not possible to decrypt the assertion if no private key" do + describe 'check right settings' do + it 'is not possible to decrypt the assertion if no private key' do response = RubySaml::Response.new(signed_message_encrypted_unsigned_assertion, settings: settings) encrypted_assertion_node = response.document.at_xpath( - "/p:Response/EncryptedAssertion | /p:Response/a:EncryptedAssertion", - { "p" => RubySaml::XML::NS_PROTOCOL, "a" => RubySaml::XML::NS_ASSERTION } + '/p:Response/EncryptedAssertion | /p:Response/a:EncryptedAssertion', + { 'p' => RubySaml::XML::NS_PROTOCOL, 'a' => RubySaml::XML::NS_ASSERTION } ) response.settings.private_key = nil - error_msg = "An EncryptedAssertion found and no SP private key found on the settings to decrypt it" + error_msg = 'An EncryptedAssertion found and no SP private key found on the settings to decrypt it' assert_raises(RubySaml::ValidationError, error_msg) do RubySaml::XML::Decryptor.decrypt_assertion(response.document, encrypted_assertion_node) end end - it "is not possible to decrypt the assertion if private key has expired and :check_sp_expiration is true" do + it 'is not possible to decrypt the assertion if the private key has expired and :check_sp_expiration is true' do settings.certificate = ruby_saml_cert_text settings.security[:check_sp_cert_expiration] = true - assert_raises(RubySaml::ValidationError, "The SP certificate expired.") do + assert_raises(RubySaml::ValidationError, 'The SP certificate expired.') do RubySaml::Response.new(signed_message_encrypted_unsigned_assertion, settings: settings) end end - it "is possible to decrypt the assertion if private key" do + it 'is possible to decrypt the assertion if the private key is provided' do response = RubySaml::Response.new(signed_message_encrypted_unsigned_assertion, settings: settings) encrypted_assertion_node = response.document.at_xpath( - "/p:Response/EncryptedAssertion | /p:Response/a:EncryptedAssertion", - { "p" => RubySaml::XML::NS_PROTOCOL, "a" => RubySaml::XML::NS_ASSERTION } + '/p:Response/EncryptedAssertion | /p:Response/a:EncryptedAssertion', + { 'p' => RubySaml::XML::NS_PROTOCOL, 'a' => RubySaml::XML::NS_ASSERTION } ) decrypted = RubySaml::XML::Decryptor.decrypt_assertion(encrypted_assertion_node, settings.get_sp_decryption_keys) encrypted_assertion_node2 = decrypted.at_xpath( - "/p:Response/EncryptedAssertion | /p:Response/a:EncryptedAssertion", - { "p" => "urn:oasis:names:tc:SAML:2.0:protocol", "a" => "urn:oasis:names:tc:SAML:2.0:assertion" } + '/p:Response/EncryptedAssertion | /p:Response/a:EncryptedAssertion', + { 'p' => 'urn:oasis:names:tc:SAML:2.0:protocol', 'a' => 'urn:oasis:names:tc:SAML:2.0:assertion' } ) + assert_nil encrypted_assertion_node2 - assert decrypted.name, "Assertion" + assert decrypted.name, 'Assertion' end - it "is possible to decrypt the assertion with one invalid and one valid private key" do + it 'is possible to decrypt the assertion with one invalid and one valid private key' do settings.private_key = nil settings.sp_cert_multi = { encryption: [ @@ -1619,151 +1750,163 @@ def generate_audience_error(expected, actual) response = RubySaml::Response.new(signed_message_encrypted_unsigned_assertion, settings: settings) encrypted_assertion_node = response.document.at_xpath( - "/p:Response/EncryptedAssertion | /p:Response/a:EncryptedAssertion", - { "p" => RubySaml::XML::NS_PROTOCOL, "a" => RubySaml::XML::NS_ASSERTION } + '/p:Response/EncryptedAssertion | /p:Response/a:EncryptedAssertion', + { 'p' => RubySaml::XML::NS_PROTOCOL, 'a' => RubySaml::XML::NS_ASSERTION } ) decrypted = RubySaml::XML::Decryptor.decrypt_assertion(encrypted_assertion_node, settings.get_sp_decryption_keys) - assert decrypted.name, "Assertion" + assert decrypted.name, 'Assertion' end - it "is possible to decrypt the assertion if private key provided and EncryptedKey RetrievalMethod presents in response" do + it 'is possible to decrypt the assertion if the private key is provided and EncryptedKey RetrievalMethod is present in the response' do settings.private_key = ruby_saml_key_text resp = read_response('response_with_retrieval_method.xml') response = RubySaml::Response.new(resp, settings: settings) encrypted_assertion_node = response.document.at_xpath( - "/p:Response/EncryptedAssertion | /p:Response/a:EncryptedAssertion", - { "p" => RubySaml::XML::NS_PROTOCOL, "a" => RubySaml::XML::NS_ASSERTION } + '/p:Response/EncryptedAssertion | /p:Response/a:EncryptedAssertion', + { 'p' => RubySaml::XML::NS_PROTOCOL, 'a' => RubySaml::XML::NS_ASSERTION } ) decrypted = RubySaml::XML::Decryptor.decrypt_assertion(encrypted_assertion_node, settings.get_sp_decryption_keys) encrypted_assertion_node2 = decrypted.at_xpath( - "/p:Response/EncryptedAssertion | /p:Response/a:EncryptedAssertion", - { "p" => "urn:oasis:names:tc:SAML:2.0:protocol", "a" => "urn:oasis:names:tc:SAML:2.0:assertion" } + '/p:Response/EncryptedAssertion | /p:Response/a:EncryptedAssertion', + { 'p' => 'urn:oasis:names:tc:SAML:2.0:protocol', 'a' => 'urn:oasis:names:tc:SAML:2.0:assertion' } ) assert_nil encrypted_assertion_node2 - assert decrypted.name, "Assertion" + assert decrypted.name, 'Assertion' end - it "is possible to decrypt the assertion if private key but no saml namespace on the Assertion Element that is inside the EncryptedAssertion" do + it 'is possible to decrypt the assertion if a private key is provided but there is no SAML namespace on the Assertion Element that is inside the EncryptedAssertion' do unsigned_message_encrypted_assertion_without_saml_namespace = read_response('unsigned_message_encrypted_assertion_without_saml_namespace.xml.base64') response = RubySaml::Response.new(unsigned_message_encrypted_assertion_without_saml_namespace, settings: settings) encrypted_assertion_node = response.document.at_xpath( - "/p:Response/EncryptedAssertion | /p:Response/a:EncryptedAssertion", - { "p" => RubySaml::XML::NS_PROTOCOL, "a" => RubySaml::XML::NS_ASSERTION } + '/p:Response/EncryptedAssertion | /p:Response/a:EncryptedAssertion', + { 'p' => RubySaml::XML::NS_PROTOCOL, 'a' => RubySaml::XML::NS_ASSERTION } ) decrypted = RubySaml::XML::Decryptor.decrypt_assertion(encrypted_assertion_node, settings.get_sp_decryption_keys) encrypted_assertion_node2 = decrypted.at_xpath( - "/p:Response/EncryptedAssertion | /p:Response/a:EncryptedAssertion", - { "p" => "urn:oasis:names:tc:SAML:2.0:protocol", "a" => "urn:oasis:names:tc:SAML:2.0:assertion" } + '/p:Response/EncryptedAssertion | /p:Response/a:EncryptedAssertion', + { 'p' => 'urn:oasis:names:tc:SAML:2.0:protocol', 'a' => 'urn:oasis:names:tc:SAML:2.0:assertion' } ) + assert_nil encrypted_assertion_node2 - assert decrypted.name, "Assertion" + assert decrypted.name, 'Assertion' end end - describe "check different encrypt methods supported" do - it "EncryptionMethod DES-192 && Key Encryption Algorithm RSA-1_5" do + describe 'check different encrypt methods supported' do + it 'EncryptionMethod DES-192 && Key Encryption Algorithm RSA-1_5' do unsigned_message_des192_encrypted_signed_assertion = read_response('unsigned_message_des192_encrypted_signed_assertion.xml.base64') response = RubySaml::Response.new(unsigned_message_des192_encrypted_signed_assertion, settings: settings) - assert_equal "test", response.attributes[:uid] - assert_equal "_ce3d2948b4cf20146dee0a0b3dd6f69b6cf86f62d7", response.nameid + + assert_equal 'test', response.attributes[:uid] + assert_equal '_ce3d2948b4cf20146dee0a0b3dd6f69b6cf86f62d7', response.nameid end - it "EncryptionMethod AES-128 && Key Encryption Algorithm RSA-OAEP-MGF1P" do + it 'EncryptionMethod AES-128 && Key Encryption Algorithm RSA-OAEP-MGF1P' do unsigned_message_aes128_encrypted_signed_assertion = read_response('unsigned_message_aes128_encrypted_signed_assertion.xml.base64') response = RubySaml::Response.new(unsigned_message_aes128_encrypted_signed_assertion, settings: settings) - assert_equal "test", response.attributes[:uid] - assert_equal "_ce3d2948b4cf20146dee0a0b3dd6f69b6cf86f62d7", response.nameid + + assert_equal 'test', response.attributes[:uid] + assert_equal '_ce3d2948b4cf20146dee0a0b3dd6f69b6cf86f62d7', response.nameid end - it "EncryptionMethod AES-192 && Key Encryption Algorithm RSA-OAEP-MGF1P" do + it 'EncryptionMethod AES-192 && Key Encryption Algorithm RSA-OAEP-MGF1P' do unsigned_message_aes192_encrypted_signed_assertion = read_response('unsigned_message_aes192_encrypted_signed_assertion.xml.base64') response = RubySaml::Response.new(unsigned_message_aes192_encrypted_signed_assertion, settings: settings) - assert_equal "test", response.attributes[:uid] - assert_equal "_ce3d2948b4cf20146dee0a0b3dd6f69b6cf86f62d7", response.nameid + + assert_equal 'test', response.attributes[:uid] + assert_equal '_ce3d2948b4cf20146dee0a0b3dd6f69b6cf86f62d7', response.nameid end - it "EncryptionMethod AES-256 && Key Encryption Algorithm RSA-OAEP-MGF1P" do + it 'EncryptionMethod AES-256 && Key Encryption Algorithm RSA-OAEP-MGF1P' do unsigned_message_aes256_encrypted_signed_assertion = read_response('unsigned_message_aes256_encrypted_signed_assertion.xml.base64') response = RubySaml::Response.new(unsigned_message_aes256_encrypted_signed_assertion, settings: settings) - assert_equal "test", response.attributes[:uid] - assert_equal "_ce3d2948b4cf20146dee0a0b3dd6f69b6cf86f62d7", response.nameid + + assert_equal 'test', response.attributes[:uid] + assert_equal '_ce3d2948b4cf20146dee0a0b3dd6f69b6cf86f62d7', response.nameid end - it "EncryptionMethod AES-128-GCM && Key Encryption Algorithm RSA-OAEP-MGF1P" do + it 'EncryptionMethod AES-128-GCM && Key Encryption Algorithm RSA-OAEP-MGF1P' do return unless OpenSSL::Cipher.ciphers.include? 'AES-128-GCM' + unsigned_message_aes128gcm_encrypted_signed_assertion = read_response('unsigned_message_aes128gcm_encrypted_signed_assertion.xml.base64') response = RubySaml::Response.new(unsigned_message_aes128gcm_encrypted_signed_assertion, settings: settings) - assert_equal "test", response.attributes[:uid] - assert_equal "_ce3d2948b4cf20146dee0a0b3dd6f69b6cf86f62d7", response.nameid + + assert_equal 'test', response.attributes[:uid] + assert_equal '_ce3d2948b4cf20146dee0a0b3dd6f69b6cf86f62d7', response.nameid end - it "EncryptionMethod AES-192-GCM && Key Encryption Algorithm RSA-OAEP-MGF1P" do + it 'EncryptionMethod AES-192-GCM && Key Encryption Algorithm RSA-OAEP-MGF1P' do return unless OpenSSL::Cipher.ciphers.include? 'AES-192-GCM' + unsigned_message_aes192gcm_encrypted_signed_assertion = read_response('unsigned_message_aes192gcm_encrypted_signed_assertion.xml.base64') response = RubySaml::Response.new(unsigned_message_aes192gcm_encrypted_signed_assertion, settings: settings) - assert_equal "test", response.attributes[:uid] - assert_equal "_ce3d2948b4cf20146dee0a0b3dd6f69b6cf86f62d7", response.nameid + + assert_equal 'test', response.attributes[:uid] + assert_equal '_ce3d2948b4cf20146dee0a0b3dd6f69b6cf86f62d7', response.nameid end - it "EncryptionMethod AES-256-GCM && Key Encryption Algorithm RSA-OAEP-MGF1P" do + it 'EncryptionMethod AES-256-GCM && Key Encryption Algorithm RSA-OAEP-MGF1P' do return unless OpenSSL::Cipher.ciphers.include? 'AES-256-GCM' + unsigned_message_aes256gcm_encrypted_signed_assertion = read_response('unsigned_message_aes256gcm_encrypted_signed_assertion.xml.base64') response = RubySaml::Response.new(unsigned_message_aes256gcm_encrypted_signed_assertion, settings: settings) - assert_equal "test", response.attributes[:uid] - assert_equal "_ce3d2948b4cf20146dee0a0b3dd6f69b6cf86f62d7", response.nameid + + assert_equal 'test', response.attributes[:uid] + assert_equal '_ce3d2948b4cf20146dee0a0b3dd6f69b6cf86f62d7', response.nameid end end - end - describe "#status_code" do + describe '#status_code' do it 'urn:oasis:names:tc:SAML:2.0:status:Responder' do - assert_equal response_statuscode_responder.status_code, 'urn:oasis:names:tc:SAML:2.0:status:Responder' + assert_equal 'urn:oasis:names:tc:SAML:2.0:status:Responder', response_statuscode_responder.status_code end it 'urn:oasis:names:tc:SAML:2.0:status:Requester and urn:oasis:names:tc:SAML:2.0:status:UnsupportedBinding' do - assert_equal response_double_statuscode.status_code, 'urn:oasis:names:tc:SAML:2.0:status:Requester | urn:oasis:names:tc:SAML:2.0:status:UnsupportedBinding' + assert_equal 'urn:oasis:names:tc:SAML:2.0:status:Requester | urn:oasis:names:tc:SAML:2.0:status:UnsupportedBinding', response_double_statuscode.status_code end end - describe "test qualified name id in attributes" do - it "parsed the nameid" do - response = RubySaml::Response.new(read_response("signed_nameid_in_atts.xml"), settings: settings) + describe 'test qualified name id in attributes' do + it 'parsed the NameId' do + response = RubySaml::Response.new(read_response('signed_nameid_in_atts.xml'), settings: settings) response.settings.idp_cert_fingerprint = 'c51985d947f1be57082025050846eb27f6cab783' + assert_empty response.errors - assert_equal "test", response.attributes[:uid] - assert_equal "http://idp.example.com/metadata.php/ZdrjpwEdw22vKoxWAbZB78/gQ7s=", response.attributes.single('urn:oid:1.3.6.1.4.1.5923.1.1.1.10') + assert_equal 'test', response.attributes[:uid] + assert_equal 'http://idp.example.com/metadata.php/ZdrjpwEdw22vKoxWAbZB78/gQ7s=', response.attributes.single('urn:oid:1.3.6.1.4.1.5923.1.1.1.10') end end - describe "test unqualified name id in attributes" do - it "parsed the nameid" do - response = RubySaml::Response.new(read_response("signed_unqual_nameid_in_atts.xml"), settings: settings) + describe 'test unqualified name id in attributes' do + it 'parsed the NameId' do + response = RubySaml::Response.new(read_response('signed_unqual_nameid_in_atts.xml'), settings: settings) response.settings.idp_cert_fingerprint = 'c51985d947f1be57082025050846eb27f6cab783' + assert_empty response.errors - assert_equal "test", response.attributes[:uid] - assert_equal "ZdrjpwEdw22vKoxWAbZB78/gQ7s=", response.attributes.single('urn:oid:1.3.6.1.4.1.5923.1.1.1.10') + assert_equal 'test', response.attributes[:uid] + assert_equal 'ZdrjpwEdw22vKoxWAbZB78/gQ7s=', response.attributes.single('urn:oid:1.3.6.1.4.1.5923.1.1.1.10') end end - describe "DOS attack via oversized payload" do - it "rejects oversized payloads before attempting Base64 validation" do - large_saml_response = "A" * (RubySaml::Settings::DEFAULTS[:message_max_bytesize] + 100) + describe 'DOS attack via oversized payload' do + it 'rejects oversized payloads before attempting Base64 validation' do + large_saml_response = 'A' * (RubySaml::Settings::DEFAULTS[:message_max_bytesize] + 100) assert_raises(RubySaml::ValidationError, "Encoded SAML Message exceeds #{RubySaml::Settings::DEFAULTS[:message_max_bytesize]} bytes, so was rejected") do RubySaml::Response.new(large_saml_response) end end - it "rejects oversized payloads before attempting Base64 validation with custom max_bytesize" do - custom_max = 10000 - large_saml_response = "A" * (custom_max + 100) + it 'rejects oversized payloads before attempting Base64 validation with custom max_bytesize' do + custom_max = 10_000 + large_saml_response = 'A' * (custom_max + 100) custom_settings = RubySaml::Settings.new({ message_max_bytesize: custom_max }) assert_raises(RubySaml::ValidationError, "Encoded SAML Message exceeds #{custom_max} bytes, so was rejected") do @@ -1772,8 +1915,8 @@ def generate_audience_error(expected, actual) end end - describe "Zlib bomb attack" do - it "rejects Zlib bomb attacks" do + describe 'Zlib bomb attack' do + it 'rejects Zlib bomb attacks' do # Create a message that when inflated would be extremely large bomb_prefix = <<~XML @@ -1791,7 +1934,7 @@ def generate_audience_error(expected, actual) XML - bomb_data = bomb_prefix + 'A' * (200_000 * 1024) + bomb_suffix + bomb_data = bomb_prefix + ('A' * (200_000 * 1024)) + bomb_suffix bomb = Base64.strict_encode64(Zlib::Deflate.deflate(bomb_data, 9)[2..-5]) assert_raises(RubySaml::ValidationError) do @@ -1799,9 +1942,9 @@ def generate_audience_error(expected, actual) end end - it "rejects Zlib bomb attacks with custom max_bytesize" do + it 'rejects Zlib bomb attacks with custom max_bytesize' do custom_max = 100_000 - custom_settings = RubySaml::Settings.new({:message_max_bytesize => custom_max}) + custom_settings = RubySaml::Settings.new({ message_max_bytesize: custom_max }) bomb_prefix = <<~XML @@ -1809,9 +1952,9 @@ def generate_audience_error(expected, actual) http://idp.example.com/ XML - bomb_suffix = "" + bomb_suffix = '' - bomb_data = bomb_prefix + 'A' * (custom_max + 100) + bomb_suffix + bomb_data = bomb_prefix + ('A' * (custom_max + 100)) + bomb_suffix bomb = Base64.strict_encode64(Zlib::Deflate.deflate(bomb_data, 9)[2..-5]) assert_raises(RubySaml::ValidationError, "SAML Message exceeds #{custom_max} bytes, so was rejected") do @@ -1820,45 +1963,48 @@ def generate_audience_error(expected, actual) end end - describe "signature wrapping attack with encrypted assertion" do - it "should not be valid" do + describe 'signature wrapping attack with encrypted assertion' do + it 'should not be valid' do settings.private_key = ruby_saml_key_text - signature_wrapping_attack = read_invalid_response("encrypted_new_attack.xml.base64") + signature_wrapping_attack = read_invalid_response('encrypted_new_attack.xml.base64') response_wrapped = RubySaml::Response.new(signature_wrapping_attack, settings: settings) response_wrapped.stubs(:conditions).returns(nil) response_wrapped.stubs(:validate_subject_confirmation).returns(true) - settings.idp_cert_fingerprint = "385b1eec71143f00db6af936e2ea12a28771d72c" - assert !response_wrapped.is_valid? - assert_includes response_wrapped.errors, "Found an invalid Signed Element. SAML Response rejected" + settings.idp_cert_fingerprint = '385b1eec71143f00db6af936e2ea12a28771d72c' + + refute response_wrapped.is_valid? + assert_includes response_wrapped.errors, 'Found an invalid Signed Element. SAML Response rejected' end end - describe "signature wrapping attack - concealed SAML response body" do - it "should not be valid" do - signature_wrapping_attack = read_invalid_response("response_with_concealed_signed_assertion.xml") + describe 'signature wrapping attack - concealed SAML response body' do + it 'should not be valid' do + signature_wrapping_attack = read_invalid_response('response_with_concealed_signed_assertion.xml') response_wrapped = RubySaml::Response.new(signature_wrapping_attack, settings: settings) settings.idp_cert_fingerprint = '4b68c453c7d994aad9025c99d5efcf566287fe8d' response_wrapped.stubs(:conditions).returns(nil) response_wrapped.stubs(:validate_subject_confirmation).returns(true) - assert !response_wrapped.is_valid? - assert_includes response_wrapped.errors, "SAML Response must contain 1 assertion" + + refute response_wrapped.is_valid? + assert_includes response_wrapped.errors, 'SAML Response must contain 1 assertion' end end - describe "signature wrapping attack - doubled signed assertion SAML response" do - it "should not be valid" do - signature_wrapping_attack = read_invalid_response("response_with_doubled_signed_assertion.xml") + describe 'signature wrapping attack - doubled signed assertion SAML response' do + it 'should not be valid' do + signature_wrapping_attack = read_invalid_response('response_with_doubled_signed_assertion.xml') response_wrapped = RubySaml::Response.new(signature_wrapping_attack, settings: settings) settings.idp_cert_fingerprint = '4b68c453c7d994aad9025c99d5efcf566287fe8d' response_wrapped.stubs(:conditions).returns(nil) response_wrapped.stubs(:validate_subject_confirmation).returns(true) - assert !response_wrapped.is_valid? - assert_includes response_wrapped.errors, "SAML Response must contain 1 assertion" + + refute response_wrapped.is_valid? + assert_includes response_wrapped.errors, 'SAML Response must contain 1 assertion' end end each_signature_algorithm do |idp_key_algo, idp_hash_algo| - describe "#validate_signature" do + describe '#validate_signature' do let(:xml_signed) do doc = read_response('response_unsigned2.xml') RubySaml::XML::DocumentSigner.sign_document(doc, @pkey, @cert, signature_method(idp_key_algo, idp_hash_algo), digest_method(idp_hash_algo)) @@ -1867,12 +2013,12 @@ def generate_audience_error(expected, actual) before do settings.soft = true - settings.idp_sso_service_url = "http://example.com?field=value" + settings.idp_sso_service_url = 'http://example.com?field=value' @cert, @pkey = CertificateHelper.generate_pair(idp_key_algo) settings.idp_cert = @cert.to_pem end - it "return true when valid signature" do + it 'return true when valid signature' do options = {} options[:settings] = settings response_sign_test = RubySaml::Response.new(xml_signed, options) @@ -1880,7 +2026,7 @@ def generate_audience_error(expected, actual) assert response_sign_test.send(:validate_signature) end - it "return false when no idp_cert is provided and no option :relax_signature_validation is present" do + it 'return false when no idp_cert is provided and no option :relax_signature_validation is present' do settings.idp_cert = nil options = {} options[:settings] = settings @@ -1889,7 +2035,7 @@ def generate_audience_error(expected, actual) refute response_sign_test.send(:validate_signature) end - it "return false when invalid signature" do + it 'return false when invalid signature' do options = {} options[:settings] = settings response = RubySaml::Response.new(xml_signed.gsub('SignatureValue>', 'SignatureValue>Foobar'), options) @@ -1897,39 +2043,39 @@ def generate_audience_error(expected, actual) refute response.send(:validate_signature) end - it "raise when invalid signature" do + it 'raise when invalid signature' do settings.soft = false options = {} options[:settings] = settings response = RubySaml::Response.new(xml_signed.gsub('SignatureValue>', 'SignatureValue>Foobar'), options) assert_raises(RubySaml::ValidationError) { response.send(:validate_signature) } - assert response.errors.include? "Key validation error" + assert_includes response.errors, 'Key validation error' end - describe "with multitple idp certs" do + describe 'with multitple idp certs' do before do settings.idp_cert = nil end - it "return true when at least a idp_cert is valid" do + it 'return true when at least a idp_cert is valid' do options = {} options[:settings] = settings settings.idp_cert_multi = { - :signing => [@cert.to_pem, ruby_saml_cert_text], - :encryption => [] + signing: [@cert.to_pem, ruby_saml_cert_text], + encryption: [] } response_sign_test = RubySaml::Response.new(xml_signed, options) assert response_sign_test.send(:validate_signature) end - it "return false when none cert on idp_cert_multi is valid" do + it 'return false when none cert on idp_cert_multi is valid' do options = {} options[:settings] = settings settings.idp_cert_multi = { - :signing => [ruby_saml_cert_text2, ruby_saml_cert_text2], - :encryption => [] + signing: [ruby_saml_cert_text2, ruby_saml_cert_text2], + encryption: [] } response_sign_test = RubySaml::Response.new(xml_signed, options) diff --git a/test/responses/invalids/status_code_responer_and_msg.xml.base64 b/test/responses/invalids/status_code_responder_and_msg.xml.base64 similarity index 100% rename from test/responses/invalids/status_code_responer_and_msg.xml.base64 rename to test/responses/invalids/status_code_responder_and_msg.xml.base64 diff --git a/test/settings_test.rb b/test/settings_test.rb index da7efd96..8a7a8ec1 100644 --- a/test/settings_test.rb +++ b/test/settings_test.rb @@ -3,334 +3,357 @@ require_relative 'test_helper' class SettingsTest < Minitest::Test - - describe "Settings" do + describe 'Settings' do before do @settings = RubySaml::Settings.new end - it "should provide getters and settings" do - accessors = [ - :idp_entity_id, :idp_sso_target_url, :idp_sso_service_url, :idp_slo_target_url, :idp_slo_service_url, :valid_until, - :idp_cert, :idp_cert_fingerprint, :idp_cert_fingerprint_algorithm, :idp_cert_multi, - :idp_attribute_names, :issuer, :assertion_consumer_service_url, :single_logout_service_url, - :sp_name_qualifier, :name_identifier_format, :name_identifier_value, :name_identifier_value_requested, - :sessionindex, :attributes_index, :passive, :force_authn, :message_max_bytesize, - :security, :certificate, :private_key, :certificate_new, :sp_cert_multi, - :authn_context, :authn_context_comparison, :authn_context_decl_ref, - :assertion_consumer_logout_service_url + it 'should provide getters and settings' do + accessors = %i[ + idp_entity_id idp_sso_target_url idp_sso_service_url idp_slo_target_url idp_slo_service_url valid_until + idp_cert idp_cert_fingerprint idp_cert_fingerprint_algorithm idp_cert_multi + idp_attribute_names issuer assertion_consumer_service_url single_logout_service_url + sp_name_qualifier name_identifier_format name_identifier_value name_identifier_value_requested + sessionindex attributes_index passive force_authn message_max_bytesize + security certificate private_key certificate_new sp_cert_multi + authn_context authn_context_comparison authn_context_decl_ref + assertion_consumer_logout_service_url ] accessors.each do |accessor| value = Kernel.rand - @settings.send("#{accessor}=".to_sym, value) + @settings.send(:"#{accessor}=", value) + assert_equal value, @settings.send(accessor) - @settings.send("#{accessor}=".to_sym, nil) + @settings.send(:"#{accessor}=", nil) + assert_nil @settings.send(accessor) end end - it "should provide getters and settings for binding parameters" do - accessors = [ - :protocol_binding, - :assertion_consumer_service_binding, - :single_logout_service_binding, - :assertion_consumer_logout_service_binding + it 'should provide getters and settings for binding parameters' do + accessors = %i[ + protocol_binding + assertion_consumer_service_binding + single_logout_service_binding + assertion_consumer_logout_service_binding ] accessors.each do |accessor| value = Kernel.rand.to_s - @settings.send("#{accessor}=".to_sym, value) + @settings.send(:"#{accessor}=", value) + assert_equal value, @settings.send(accessor) - @settings.send("#{accessor}=".to_sym, :redirect) - assert_equal "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect", @settings.send(accessor) + @settings.send(:"#{accessor}=", :redirect) - @settings.send("#{accessor}=".to_sym, :post) - assert_equal "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST", @settings.send(accessor) + assert_equal 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect', @settings.send(accessor) + + @settings.send(:"#{accessor}=", :post) + + assert_equal 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST', @settings.send(accessor) + + @settings.send(:"#{accessor}=", nil) - @settings.send("#{accessor}=".to_sym, nil) assert_nil @settings.send(accessor) end end - it "create settings from hash" do + it 'create settings from hash' do config = { - :assertion_consumer_service_url => "http://app.muda.no/sso", - :issuer => "http://muda.no", - :sp_name_qualifier => "http://sso.muda.no", - :idp_sso_service_url => "http://sso.muda.no/sso", - :idp_sso_service_binding => "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST", - :idp_slo_service_url => "http://sso.muda.no/slo", - :idp_slo_service_binding => "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST", - :idp_cert_fingerprint => "00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00", - :message_max_bytesize => 750000, - :valid_until => '2029-04-16T03:35:08.277Z', - :name_identifier_format => "urn:oasis:names:tc:SAML:2.0:nameid-format:transient", - :attributes_index => 30, - :passive => true, - :protocol_binding => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST' + assertion_consumer_service_url: 'http://app.muda.no/sso', + issuer: 'http://muda.no', + sp_name_qualifier: 'http://sso.muda.no', + idp_sso_service_url: 'http://sso.muda.no/sso', + idp_sso_service_binding: 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST', + idp_slo_service_url: 'http://sso.muda.no/slo', + idp_slo_service_binding: 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST', + idp_cert_fingerprint: '00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00', + message_max_bytesize: 750_000, + valid_until: '2029-04-16T03:35:08.277Z', + name_identifier_format: 'urn:oasis:names:tc:SAML:2.0:nameid-format:transient', + attributes_index: 30, + passive: true, + protocol_binding: 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST' } @settings = RubySaml::Settings.new(config) - config.each do |k,v| + config.each do |k, v| assert_equal v, @settings.send(k) end end - it "configure attribute service attributes correctly" do + it 'configure attribute service attributes correctly' do @settings.attribute_consuming_service.configure do - service_name "Test Service" - add_attribute :name => "Name", :name_format => "Name Format", :friendly_name => "Friendly Name" + service_name 'Test Service' + add_attribute name: 'Name', name_format: 'Name Format', friendly_name: 'Friendly Name' end - assert_equal @settings.attribute_consuming_service.configured?, true - assert_equal @settings.attribute_consuming_service.name, "Test Service" - assert_equal @settings.attribute_consuming_service.attributes, [{:name => "Name", :name_format => "Name Format", :friendly_name => "Friendly Name" }] + assert_equal true, @settings.attribute_consuming_service.configured? + assert_equal 'Test Service', @settings.attribute_consuming_service.name + assert_equal [{ name: 'Name', name_format: 'Name Format', friendly_name: 'Friendly Name' }], @settings.attribute_consuming_service.attributes end - it "does not modify default security settings" do + it 'does not modify default security settings' do settings = RubySaml::Settings.new settings.security[:authn_requests_signed] = true settings.security[:digest_method] = RubySaml::XML::SHA512 settings.security[:signature_method] = RubySaml::XML::RSA_SHA512 new_settings = RubySaml::Settings.new - assert_equal new_settings.security[:authn_requests_signed], false + + assert_equal false, new_settings.security[:authn_requests_signed] assert_equal new_settings.get_sp_digest_method, RubySaml::XML::SHA256 assert_equal new_settings.get_sp_signature_method, RubySaml::XML::RSA_SHA256 end - it "overrides only provided security attributes passing a second parameter" do + it 'overrides only provided security attributes passing a second parameter' do config = { - :security => { - :metadata_signed => true + security: { + metadata_signed: true } } @default_attributes = RubySaml::Settings::DEFAULTS @settings = RubySaml::Settings.new(config, true) - assert_equal @settings.security[:metadata_signed], true + + assert_equal true, @settings.security[:metadata_signed] assert_equal @settings.security[:digest_method], @default_attributes[:security][:digest_method] end it "doesn't override only provided security attributes without passing a second parameter" do config = { - :security => { - :metadata_signed => true + security: { + metadata_signed: true } } @default_attributes = RubySaml::Settings::DEFAULTS @settings = RubySaml::Settings.new(config) - assert_equal @settings.security[:metadata_signed], true + + assert_equal true, @settings.security[:metadata_signed] assert_nil @settings.security[:digest_method] end - describe "#single_logout_service_url" do - it "when single_logout_service_url is nil but assertion_consumer_logout_service_url returns its value" do + describe '#single_logout_service_url' do + it 'when single_logout_service_url is nil but assertion_consumer_logout_service_url returns its value' do @settings.single_logout_service_url = nil - @settings.assertion_consumer_logout_service_url = "http://app.muda.no/sls" - assert_equal "http://app.muda.no/sls", @settings.single_logout_service_url + @settings.assertion_consumer_logout_service_url = 'http://app.muda.no/sls' + + assert_equal 'http://app.muda.no/sls', @settings.single_logout_service_url end end - describe "#single_logout_service_binding" do - it "when single_logout_service_binding is nil but assertion_consumer_logout_service_binding returns its value" do + describe '#single_logout_service_binding' do + it 'when single_logout_service_binding is nil but assertion_consumer_logout_service_binding returns its value' do @settings.single_logout_service_binding = nil - @settings.assertion_consumer_logout_service_binding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" - assert_equal "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect", @settings.single_logout_service_binding + @settings.assertion_consumer_logout_service_binding = 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect' + + assert_equal 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect', @settings.single_logout_service_binding end end - describe "#idp_sso_service_url" do - it "when idp_sso_service_url is nil but idp_sso_target_url returns its value" do + describe '#idp_sso_service_url' do + it 'when idp_sso_service_url is nil but idp_sso_target_url returns its value' do @settings.idp_sso_service_url = nil - @settings.idp_sso_target_url = "https://idp.example.com/sso" + @settings.idp_sso_target_url = 'https://idp.example.com/sso' - assert_equal "https://idp.example.com/sso", @settings.idp_sso_service_url + assert_equal 'https://idp.example.com/sso', @settings.idp_sso_service_url end end - describe "#idp_slo_service_url" do - it "when idp_slo_service_url is nil but idp_slo_target_url returns its value" do + describe '#idp_slo_service_url' do + it 'when idp_slo_service_url is nil but idp_slo_target_url returns its value' do @settings.idp_slo_service_url = nil - @settings.idp_slo_target_url = "https://idp.example.com/slo" + @settings.idp_slo_target_url = 'https://idp.example.com/slo' - assert_equal "https://idp.example.com/slo", @settings.idp_slo_service_url + assert_equal 'https://idp.example.com/slo', @settings.idp_slo_service_url end end - describe "#get_idp_cert" do - it "returns nil when the cert is an empty string" do - @settings.idp_cert = "" + describe '#get_idp_cert' do + it 'returns nil when the cert is an empty string' do + @settings.idp_cert = '' + assert_nil @settings.get_idp_cert end - it "returns nil when the cert is nil" do + it 'returns nil when the cert is nil' do @settings.idp_cert = nil + assert_nil @settings.get_idp_cert end - it "returns the certificate when it is valid" do + it 'returns the certificate when it is valid' do @settings.idp_cert = ruby_saml_cert_text - assert @settings.get_idp_cert.kind_of? OpenSSL::X509::Certificate + + assert_kind_of OpenSSL::X509::Certificate, @settings.get_idp_cert end - it "raises when the certificate is not valid" do + it 'raises when the certificate is invalid' do # formatted but invalid cert - @settings.idp_cert = read_certificate("formatted_certificate") - assert_raises(OpenSSL::X509::CertificateError) { + @settings.idp_cert = read_certificate('formatted_certificate') + assert_raises(OpenSSL::X509::CertificateError) do @settings.get_idp_cert - } + end end end - describe "#get_idp_cert_multi" do - it "returns nil when the value is empty" do + describe '#get_idp_cert_multi' do + it 'returns nil when the value is empty' do @settings.idp_cert = {} + assert_nil @settings.get_idp_cert_multi end - it "returns nil when the idp_cert_multi is nil or empty" do + it 'returns nil when the idp_cert_multi is nil or empty' do @settings.idp_cert_multi = nil + assert_nil @settings.get_idp_cert_multi end - it "returns partial hash when contains some values" do + it 'returns partial hash when contains some values' do empty_multi = { - :signing => [], - :encryption => [] + signing: [], + encryption: [] } @settings.idp_cert_multi = { - :signing => [] + signing: [] } + assert_equal empty_multi, @settings.get_idp_cert_multi @settings.idp_cert_multi = { - :encryption => [] + encryption: [] } + assert_equal empty_multi, @settings.get_idp_cert_multi @settings.idp_cert_multi = { - :signing => [], - :encryption => [] + signing: [], + encryption: [] } + assert_equal empty_multi, @settings.get_idp_cert_multi @settings.idp_cert_multi = { - :yyy => [], - :zzz => [] + yyy: [], + zzz: [] } + assert_equal empty_multi, @settings.get_idp_cert_multi end - it "returns partial hash when contains some values with string keys" do + it 'returns partial hash when contains some values with string keys' do empty_multi = { - :signing => [], - :encryption => [] + signing: [], + encryption: [] } @settings.idp_cert_multi = { - "signing" => [] + 'signing' => [] } + assert_equal empty_multi, @settings.get_idp_cert_multi @settings.idp_cert_multi = { - "encryption" => [] + 'encryption' => [] } + assert_equal empty_multi, @settings.get_idp_cert_multi @settings.idp_cert_multi = { - "signing" => [], - "encryption" => [] + 'signing' => [], + 'encryption' => [] } + assert_equal empty_multi, @settings.get_idp_cert_multi @settings.idp_cert_multi = { - "yyy" => [], - "zzz" => [] + 'yyy' => [], + 'zzz' => [] } + assert_equal empty_multi, @settings.get_idp_cert_multi end - it "returns the hash with certificates when values were valid" do + it 'returns the hash with certificates when values were valid' do certificates = [ruby_saml_cert_text] @settings.idp_cert_multi = { - :signing => certificates, - :encryption => certificates, + signing: certificates, + encryption: certificates } - assert @settings.get_idp_cert_multi.kind_of? Hash - assert @settings.get_idp_cert_multi[:signing].kind_of? Array - assert @settings.get_idp_cert_multi[:encryption].kind_of? Array - assert @settings.get_idp_cert_multi[:signing][0].kind_of? OpenSSL::X509::Certificate - assert @settings.get_idp_cert_multi[:encryption][0].kind_of? OpenSSL::X509::Certificate + assert_kind_of Hash, @settings.get_idp_cert_multi + assert_kind_of Array, @settings.get_idp_cert_multi[:signing] + assert_kind_of Array, @settings.get_idp_cert_multi[:encryption] + assert_kind_of OpenSSL::X509::Certificate, @settings.get_idp_cert_multi[:signing][0] + assert_kind_of OpenSSL::X509::Certificate, @settings.get_idp_cert_multi[:encryption][0] end - it "returns the hash with certificates when values were valid and with string keys" do + it 'returns the hash with certificates when values were valid and with string keys' do certificates = [ruby_saml_cert_text] @settings.idp_cert_multi = { - "signing" => certificates, - "encryption" => certificates, + 'signing' => certificates, + 'encryption' => certificates } - assert @settings.get_idp_cert_multi.kind_of? Hash - assert @settings.get_idp_cert_multi[:signing].kind_of? Array - assert @settings.get_idp_cert_multi[:encryption].kind_of? Array - assert @settings.get_idp_cert_multi[:signing][0].kind_of? OpenSSL::X509::Certificate - assert @settings.get_idp_cert_multi[:encryption][0].kind_of? OpenSSL::X509::Certificate + assert_kind_of Hash, @settings.get_idp_cert_multi + assert_kind_of Array, @settings.get_idp_cert_multi[:signing] + assert_kind_of Array, @settings.get_idp_cert_multi[:encryption] + assert_kind_of OpenSSL::X509::Certificate, @settings.get_idp_cert_multi[:signing][0] + assert_kind_of OpenSSL::X509::Certificate, @settings.get_idp_cert_multi[:encryption][0] end - it "raises when there is a cert in idp_cert_multi not valid" do - certificate = read_certificate("formatted_certificate") + it 'raises when a cert in idp_cert_multi is not valid' do + certificate = read_certificate('formatted_certificate') @settings.idp_cert_multi = { - :signing => [], - :encryption => [] + signing: [], + encryption: [] } @settings.idp_cert_multi[:signing].push(certificate) @settings.idp_cert_multi[:encryption].push(certificate) - assert_raises(OpenSSL::X509::CertificateError) { + assert_raises(OpenSSL::X509::CertificateError) do @settings.get_idp_cert_multi - } + end end end - describe "#get_sp_cert" do - it "returns nil when the cert is an empty string" do + describe '#get_sp_cert' do + it 'returns nil when the cert is an empty string' do @settings.certificate = '' assert_nil @settings.get_sp_cert end - it "returns nil when the cert is nil" do + it 'returns nil when the cert is nil' do @settings.certificate = nil assert_nil @settings.get_sp_cert end - it "returns the certificate when it is valid" do + it 'returns the certificate when it is valid' do @settings.certificate = ruby_saml_cert_text - assert @settings.get_sp_cert.kind_of? OpenSSL::X509::Certificate + assert_kind_of OpenSSL::X509::Certificate, @settings.get_sp_cert end - it "raises when the certificate is not valid" do + it 'raises when the certificate is invalid' do # formatted but invalid cert - @settings.certificate = read_certificate("formatted_certificate") + @settings.certificate = read_certificate('formatted_certificate') assert_raises(OpenSSL::X509::CertificateError) { @settings.get_sp_cert } end - it "raises an error if SP certificate expired and check_sp_cert_expiration enabled" do + it 'raises an error if SP certificate expired and check_sp_cert_expiration enabled' do @settings.certificate = ruby_saml_cert_text @settings.security[:check_sp_cert_expiration] = true @@ -339,34 +362,34 @@ class SettingsTest < Minitest::Test each_key_algorithm do |sp_cert_algo| it "allows a certificate with a #{sp_cert_algo.upcase} private key" do - @settings.certificate, _= CertificateHelper.generate_cert(sp_cert_algo).to_pem + @settings.certificate, = CertificateHelper.generate_cert(sp_cert_algo).to_pem - assert @settings.get_sp_cert.kind_of? OpenSSL::X509::Certificate + assert_kind_of OpenSSL::X509::Certificate, @settings.get_sp_cert end end end - describe "#get_sp_cert_new" do - it "returns nil when the cert is an empty string" do + describe '#get_sp_cert_new' do + it 'returns nil when the cert is an empty string' do @settings.certificate_new = '' assert_nil @settings.get_sp_cert_new end - it "returns nil when the cert is nil" do + it 'returns nil when the cert is nil' do @settings.certificate_new = nil assert_nil @settings.get_sp_cert_new end - it "returns the certificate when it is valid" do + it 'returns the certificate when it is valid' do @settings.certificate_new = ruby_saml_cert_text - assert @settings.get_sp_cert_new.kind_of? OpenSSL::X509::Certificate + assert_kind_of OpenSSL::X509::Certificate, @settings.get_sp_cert_new end - it "raises when the certificate is formatted but invalid" do - @settings.certificate_new = read_certificate("formatted_certificate") + it 'raises when the certificate is formatted but invalid' do + @settings.certificate_new = read_certificate('formatted_certificate') assert_raises(OpenSSL::X509::CertificateError) { @settings.get_sp_cert_new } end @@ -375,32 +398,32 @@ class SettingsTest < Minitest::Test it "allows a certificate with a #{sp_cert_algo.upcase} private key" do @settings.certificate_new = CertificateHelper.generate_cert(sp_cert_algo).to_pem - assert @settings.get_sp_cert_new.kind_of? OpenSSL::X509::Certificate + assert_kind_of OpenSSL::X509::Certificate, @settings.get_sp_cert_new end end end - describe "#get_sp_key" do - it "returns nil when the private key is an empty string" do + describe '#get_sp_key' do + it 'returns nil when the private key is an empty string' do @settings.private_key = '' assert_nil @settings.get_sp_key end - it "returns nil when the private key is nil" do + it 'returns nil when the private key is nil' do @settings.private_key = nil assert_nil @settings.get_sp_key end - it "returns the private key when it is valid" do + it 'returns the private key when it is valid' do @settings.private_key = ruby_saml_key_text - assert @settings.get_sp_key.kind_of? OpenSSL::PKey::RSA + assert_kind_of OpenSSL::PKey::RSA, @settings.get_sp_key end - it "raises when the private key is formatted but invalid" do - @settings.private_key = read_certificate("formatted_rsa_private_key") + it 'raises when the private key is formatted but invalid' do + @settings.private_key = read_certificate('formatted_rsa_private_key') assert_raises(OpenSSL::PKey::RSAError) { @settings.get_sp_key } end @@ -409,72 +432,78 @@ class SettingsTest < Minitest::Test it "allows a #{sp_cert_algo.upcase} private key" do @settings.private_key = CertificateHelper.generate_private_key(sp_cert_algo).to_pem - assert @settings.get_sp_key.kind_of? expected_key_class(sp_cert_algo) + assert_kind_of expected_key_class(sp_cert_algo), @settings.get_sp_key end end end - describe "#get_fingerprint" do - it "get the fingerprint value when cert and fingerprint in settings are nil" do + describe '#get_fingerprint' do + it 'gets the fingerprint value when cert and fingerprint in settings are nil' do @settings.idp_cert_fingerprint = nil @settings.idp_cert = nil fingerprint = @settings.get_fingerprint + assert_nil fingerprint end - it "get the fingerprint value when there is a cert at the settings" do + it 'gets the fingerprint value when there is a cert at the settings' do @settings.idp_cert_fingerprint = nil @settings.idp_cert = ruby_saml_cert_text fingerprint = @settings.get_fingerprint - assert fingerprint.downcase == ruby_saml_cert_fingerprint.downcase + + assert_equal fingerprint.downcase, ruby_saml_cert_fingerprint.downcase end - it "get the fingerprint value when there is a fingerprint at the settings" do + it 'gets the fingerprint value when there is a fingerprint at the settings' do @settings.idp_cert_fingerprint = ruby_saml_cert_fingerprint @settings.idp_cert = nil fingerprint = @settings.get_fingerprint - assert fingerprint.downcase == ruby_saml_cert_fingerprint.downcase + + assert_equal fingerprint.downcase, ruby_saml_cert_fingerprint.downcase end - it "get the fingerprint value when there are cert and fingerprint at the settings" do + it 'gets the fingerprint value when there are cert and fingerprint at the settings' do @settings.idp_cert_fingerprint = ruby_saml_cert_fingerprint @settings.idp_cert = ruby_saml_cert_text fingerprint = @settings.get_fingerprint - assert fingerprint.downcase == ruby_saml_cert_fingerprint.downcase + + assert_equal fingerprint.downcase, ruby_saml_cert_fingerprint.downcase end end - describe "#get_sp_certs (base cases)" do + describe '#get_sp_certs (base cases)' do let(:cert_text1) { ruby_saml_cert_text } let(:cert_text2) { ruby_saml_cert2.to_pem } let(:cert_text3) { CertificateHelper.generate_cert.to_pem } let(:key_text1) { ruby_saml_key_text } let(:key_text2) { CertificateHelper.generate_private_key.to_pem } - it "returns certs for single case" do + it 'returns certs for single case' do @settings.certificate = cert_text1 @settings.private_key = key_text1 actual = @settings.get_sp_certs expected = [[cert_text1, key_text1]] - assert_equal [:signing, :encryption], actual.keys - assert_equal expected, actual[:signing].map {|ary| ary.map(&:to_pem) } - assert_equal expected, actual[:encryption].map {|ary| ary.map(&:to_pem) } + + assert_equal %i[signing encryption], actual.keys + assert_equal(expected, actual[:signing].map { |ary| ary.map(&:to_pem) }) + assert_equal(expected, actual[:encryption].map { |ary| ary.map(&:to_pem) }) end - it "returns certs for single case with new cert" do + it 'returns certs for single case with new cert' do @settings.certificate = cert_text1 @settings.certificate_new = cert_text2 @settings.private_key = key_text1 actual = @settings.get_sp_certs expected = [[cert_text1, key_text1], [cert_text2, key_text1]] - assert_equal [:signing, :encryption], actual.keys - assert_equal expected, actual[:signing].map {|ary| ary.map(&:to_pem) } - assert_equal expected, actual[:encryption].map {|ary| ary.map(&:to_pem) } + + assert_equal %i[signing encryption], actual.keys + assert_equal(expected, actual[:signing].map { |ary| ary.map(&:to_pem) }) + assert_equal(expected, actual[:encryption].map { |ary| ary.map(&:to_pem) }) end - it "returns certs for multi case" do + it 'returns certs for multi case' do @settings.sp_cert_multi = { signing: [{ certificate: cert_text1, private_key: key_text1 }, { certificate: cert_text2, private_key: key_text1 }], @@ -485,9 +514,10 @@ class SettingsTest < Minitest::Test actual = @settings.get_sp_certs expected_signing = [[cert_text1, key_text1], [cert_text2, key_text1]] expected_encryption = [[cert_text2, key_text1], [cert_text3, key_text2]] - assert_equal [:signing, :encryption], actual.keys - assert_equal expected_signing, actual[:signing].map { |ary| ary.map(&:to_pem) } - assert_equal expected_encryption, actual[:encryption].map { |ary| ary.map(&:to_pem) } + + assert_equal %i[signing encryption], actual.keys + assert_equal(expected_signing, actual[:signing].map { |ary| ary.map(&:to_pem) }) + assert_equal(expected_encryption, actual[:encryption].map { |ary| ary.map(&:to_pem) }) end # TODO: :encryption should validate only RSA keys are used @@ -505,11 +535,12 @@ class SettingsTest < Minitest::Test } actual = @settings.get_sp_certs - expected_signing = @settings.sp_cert_multi[:signing].map { |pair| pair.values } - expected_encryption = @settings.sp_cert_multi[:encryption].map { |pair| pair.values } - assert_equal [:signing, :encryption], actual.keys - assert_equal expected_signing, actual[:signing].map { |ary| ary.map(&:to_pem) } - assert_equal expected_encryption, actual[:encryption].map { |ary| ary.map(&:to_pem) } + expected_signing = @settings.sp_cert_multi[:signing].map(&:values) + expected_encryption = @settings.sp_cert_multi[:encryption].map(&:values) + + assert_equal %i[signing encryption], actual.keys + assert_equal(expected_signing, actual[:signing].map { |ary| ary.map(&:to_pem) }) + assert_equal(expected_encryption, actual[:encryption].map { |ary| ary.map(&:to_pem) }) end end @@ -519,9 +550,10 @@ class SettingsTest < Minitest::Test actual = @settings.get_sp_certs expected = [[cert_text1, key_text1]] - assert_equal [:signing, :encryption], actual.keys - assert_equal expected, actual[:signing].map { |ary| ary.map(&:to_pem) } - assert_equal expected, actual[:encryption].map { |ary| ary.map(&:to_pem) } + + assert_equal %i[signing encryption], actual.keys + assert_equal(expected, actual[:signing].map { |ary| ary.map(&:to_pem) }) + assert_equal(expected, actual[:encryption].map { |ary| ary.map(&:to_pem) }) end it 'handles OpenSSL::X509::Certificate objects for single case with new cert' do @@ -531,9 +563,10 @@ class SettingsTest < Minitest::Test actual = @settings.get_sp_certs expected = [[cert_text1, key_text1], [cert_text2, key_text1]] - assert_equal [:signing, :encryption], actual.keys - assert_equal expected, actual[:signing].map { |ary| ary.map(&:to_pem) } - assert_equal expected, actual[:encryption].map { |ary| ary.map(&:to_pem) } + + assert_equal %i[signing encryption], actual.keys + assert_equal(expected, actual[:signing].map { |ary| ary.map(&:to_pem) }) + assert_equal(expected, actual[:encryption].map { |ary| ary.map(&:to_pem) }) end it 'handles OpenSSL::X509::Certificate objects for multi case' do @@ -549,9 +582,10 @@ class SettingsTest < Minitest::Test actual = @settings.get_sp_certs expected_signing = [[cert_text1, key_text1], [cert_text2, key_text1]] expected_encryption = [[cert_text2, key_text1], [cert_text3, key_text2]] - assert_equal [:signing, :encryption], actual.keys - assert_equal expected_signing, actual[:signing].map { |ary| ary.map(&:to_pem) } - assert_equal expected_encryption, actual[:encryption].map { |ary| ary.map(&:to_pem) } + + assert_equal %i[signing encryption], actual.keys + assert_equal(expected_signing, actual[:signing].map { |ary| ary.map(&:to_pem) }) + assert_equal(expected_encryption, actual[:encryption].map { |ary| ary.map(&:to_pem) }) end it 'handles OpenSSL::PKey::PKey objects for single case' do @@ -560,9 +594,10 @@ class SettingsTest < Minitest::Test actual = @settings.get_sp_certs expected = [[cert_text1, key_text1]] - assert_equal [:signing, :encryption], actual.keys - assert_equal expected, actual[:signing].map { |ary| ary.map(&:to_pem) } - assert_equal expected, actual[:encryption].map { |ary| ary.map(&:to_pem) } + + assert_equal %i[signing encryption], actual.keys + assert_equal(expected, actual[:signing].map { |ary| ary.map(&:to_pem) }) + assert_equal(expected, actual[:encryption].map { |ary| ary.map(&:to_pem) }) end it 'handles OpenSSL::PKey::PKey objects for multi case' do @@ -579,12 +614,13 @@ class SettingsTest < Minitest::Test actual = @settings.get_sp_certs expected_signing = [[cert_text1, pkey3.to_pem], [cert_text2, pkey4.to_pem]] expected_encryption = [[cert_text2, key_text1], [cert_text3, key_text2]] - assert_equal [:signing, :encryption], actual.keys - assert_equal expected_signing, actual[:signing].map { |ary| ary.map(&:to_pem) } - assert_equal expected_encryption, actual[:encryption].map { |ary| ary.map(&:to_pem) } + + assert_equal %i[signing encryption], actual.keys + assert_equal(expected_signing, actual[:signing].map { |ary| ary.map(&:to_pem) }) + assert_equal(expected_encryption, actual[:encryption].map { |ary| ary.map(&:to_pem) }) end - it "sp_cert_multi allows sending only signing" do + it 'sp_cert_multi allows sending only signing' do @settings.sp_cert_multi = { signing: [{ certificate: cert_text1, private_key: key_text1 }, { certificate: cert_text2, private_key: key_text1 }] @@ -592,12 +628,13 @@ class SettingsTest < Minitest::Test actual = @settings.get_sp_certs expected_signing = [[cert_text1, key_text1], [cert_text2, key_text1]] - assert_equal [:signing, :encryption], actual.keys - assert_equal expected_signing, actual[:signing].map {|ary| ary.map(&:to_pem) } - assert_equal [], actual[:encryption] + + assert_equal %i[signing encryption], actual.keys + assert_equal(expected_signing, actual[:signing].map { |ary| ary.map(&:to_pem) }) + assert_empty actual[:encryption] end - it "raises error when sp_cert_multi is not a Hash" do + it 'raises an error when sp_cert_multi is not a Hash' do @settings.sp_cert_multi = 'invalid_type' error_message = 'sp_cert_multi must be a Hash' @@ -606,7 +643,7 @@ class SettingsTest < Minitest::Test end end - it "raises error when sp_cert_multi does not contain an Array of Hashes" do + it 'raises an error when sp_cert_multi does not contain an Array of Hashes' do @settings.sp_cert_multi = { signing: 'invalid_type' } error_message = 'sp_cert_multi :signing node must be an Array of Hashes' @@ -615,7 +652,7 @@ class SettingsTest < Minitest::Test end end - it "raises error when sp_cert_multi inner node missing :certificate" do + it 'raises an error when sp_cert_multi inner node missing :certificate' do @settings.sp_cert_multi = { signing: [{ private_key: key_text1 }] } error_message = 'sp_cert_multi :signing node Hashes must specify keys :certificate and :private_key' @@ -624,7 +661,7 @@ class SettingsTest < Minitest::Test end end - it "raises error when sp_cert_multi inner node missing :private_key" do + it 'raises an error when sp_cert_multi inner node missing :private_key' do @settings.sp_cert_multi = { signing: [{ certificate: cert_text1 }] } error_message = 'sp_cert_multi :signing node Hashes must specify keys :certificate and :private_key' @@ -633,7 +670,7 @@ class SettingsTest < Minitest::Test end end - it "handles sp_cert_multi with string keys" do + it 'handles sp_cert_multi with string keys' do @settings.sp_cert_multi = { 'signing' => [{ 'certificate' => cert_text1, 'private_key' => key_text1 }], 'encryption' => [{ 'certificate' => cert_text2, 'private_key' => key_text1 }] @@ -642,12 +679,13 @@ class SettingsTest < Minitest::Test actual = @settings.get_sp_certs expected_signing = [[cert_text1, key_text1]] expected_encryption = [[cert_text2, key_text1]] - assert_equal [:signing, :encryption], actual.keys - assert_equal expected_signing, actual[:signing].map {|ary| ary.map(&:to_pem) } - assert_equal expected_encryption, actual[:encryption].map {|ary| ary.map(&:to_pem) } + + assert_equal %i[signing encryption], actual.keys + assert_equal(expected_signing, actual[:signing].map { |ary| ary.map(&:to_pem) }) + assert_equal(expected_encryption, actual[:encryption].map { |ary| ary.map(&:to_pem) }) end - it "handles sp_cert_multi with alternate inner keys :cert and :key" do + it 'handles sp_cert_multi with alternate inner keys :cert and :key' do @settings.sp_cert_multi = { signing: [{ cert: cert_text1, key: key_text1 }], encryption: [{ 'cert' => cert_text2, 'key' => key_text1 }] @@ -656,12 +694,13 @@ class SettingsTest < Minitest::Test actual = @settings.get_sp_certs expected_signing = [[cert_text1, key_text1]] expected_encryption = [[cert_text2, key_text1]] - assert_equal [:signing, :encryption], actual.keys - assert_equal expected_signing, actual[:signing].map {|ary| ary.map(&:to_pem) } - assert_equal expected_encryption, actual[:encryption].map {|ary| ary.map(&:to_pem) } + + assert_equal %i[signing encryption], actual.keys + assert_equal(expected_signing, actual[:signing].map { |ary| ary.map(&:to_pem) }) + assert_equal(expected_encryption, actual[:encryption].map { |ary| ary.map(&:to_pem) }) end - it "raises error when both sp_cert_multi and certificate are specified" do + it 'raises an error when both sp_cert_multi and certificate are specified' do @settings.sp_cert_multi = { signing: [{ certificate: cert_text1, private_key: key_text1 }] } @settings.certificate = cert_text1 @@ -671,7 +710,7 @@ class SettingsTest < Minitest::Test end end - it "raises error when both sp_cert_multi and certificate_new are specified" do + it 'raises an error when both sp_cert_multi and certificate_new are specified' do @settings.sp_cert_multi = { signing: [{ certificate: cert_text1, private_key: key_text1 }] } @settings.certificate_new = cert_text2 @@ -681,7 +720,7 @@ class SettingsTest < Minitest::Test end end - it "raises error when both sp_cert_multi and private_key are specified" do + it 'raises an error when both sp_cert_multi and private_key are specified' do @settings.sp_cert_multi = { signing: [{ certificate: cert_text1, private_key: key_text1 }] } @settings.private_key = key_text1 @@ -692,33 +731,35 @@ class SettingsTest < Minitest::Test end end - describe "#get_sp_certs" do + describe '#get_sp_certs' do let(:valid_pair) { CertificateHelper.generate_pem_hash } let(:early_pair) { CertificateHelper.generate_pem_hash(not_before: Time.now + 60) } let(:expired_pair) { CertificateHelper.generate_pem_hash(not_after: Time.now - 60) } - it "returns all certs when check_sp_cert_expiration is false" do + it 'returns all certs when check_sp_cert_expiration is false' do @settings.security = { check_sp_cert_expiration: false } @settings.sp_cert_multi = { signing: [valid_pair, expired_pair], encryption: [valid_pair, early_pair] } actual = @settings.get_sp_certs expected_signing = [valid_pair, expired_pair].map(&:values) expected_encryption = [valid_pair, early_pair].map(&:values) - assert_equal expected_signing, actual[:signing].map {|ary| ary.map(&:to_pem) } - assert_equal expected_encryption, actual[:encryption].map {|ary| ary.map(&:to_pem) } + + assert_equal(expected_signing, actual[:signing].map { |ary| ary.map(&:to_pem) }) + assert_equal(expected_encryption, actual[:encryption].map { |ary| ary.map(&:to_pem) }) end - it "returns only active certs when check_sp_cert_expiration is true" do + it 'returns only active certs when check_sp_cert_expiration is true' do @settings.security = { check_sp_cert_expiration: true } @settings.sp_cert_multi = { signing: [valid_pair, expired_pair], encryption: [valid_pair, early_pair] } actual = @settings.get_sp_certs expected_active = [valid_pair].map(&:values) - assert_equal expected_active, actual[:signing].map {|ary| ary.map(&:to_pem) } - assert_equal expected_active, actual[:encryption].map {|ary| ary.map(&:to_pem) } + + assert_equal(expected_active, actual[:signing].map { |ary| ary.map(&:to_pem) }) + assert_equal(expected_active, actual[:encryption].map { |ary| ary.map(&:to_pem) }) end - it "raises error when all certificates are expired in signing and check_sp_cert_expiration is true" do + it 'raises an error when all certificates are expired in signing and check_sp_cert_expiration is true' do @settings.security = { check_sp_cert_expiration: true } @settings.sp_cert_multi = { signing: [expired_pair], encryption: [valid_pair] } @@ -727,7 +768,7 @@ class SettingsTest < Minitest::Test end end - it "raises error when all certificates are expired in encryption and check_sp_cert_expiration is true" do + it 'raises an error when all certificates are expired in encryption and check_sp_cert_expiration is true' do @settings.security = { check_sp_cert_expiration: true } @settings.sp_cert_multi = { signing: [valid_pair], encryption: [expired_pair] } @@ -736,28 +777,29 @@ class SettingsTest < Minitest::Test end end - it "returns empty arrays for signing and encryption if no pairs are present" do + it 'returns empty arrays for signing and encryption if no pairs are present' do @settings.sp_cert_multi = { signing: [], encryption: [] } actual = @settings.get_sp_certs + assert_empty actual[:signing] assert_empty actual[:encryption] end end - describe "#get_sp_signing_pair and #get_sp_signing_key" do + describe '#get_sp_signing_pair and #get_sp_signing_key' do let(:valid_pair) { CertificateHelper.generate_pem_hash } let(:early_pair) { CertificateHelper.generate_pem_hash(not_before: Time.now + 60) } - let(:expired) { CertificateHelper.generate_pem_hash(not_after: Time.now - 60) } + let(:expired) { CertificateHelper.generate_pem_hash(not_after: Time.now - 60) } - it "returns nil when no signing pairs are present" do + it 'returns nil when no signing pairs are present' do @settings.sp_cert_multi = { signing: [] } assert_nil @settings.get_sp_signing_pair assert_nil @settings.get_sp_signing_key end - it "returns the first pair if check_sp_cert_expiration is false" do + it 'returns the first pair if check_sp_cert_expiration is false' do @settings.security = { check_sp_cert_expiration: false } @settings.sp_cert_multi = { signing: [early_pair, expired, valid_pair] } @@ -765,7 +807,7 @@ class SettingsTest < Minitest::Test assert_equal early_pair[:private_key], @settings.get_sp_signing_key.to_pem end - it "returns the first active pair when check_sp_cert_expiration is true" do + it 'returns the first active pair when check_sp_cert_expiration is true' do @settings.security = { check_sp_cert_expiration: true } @settings.sp_cert_multi = { signing: [early_pair, expired, valid_pair] } @@ -773,7 +815,7 @@ class SettingsTest < Minitest::Test assert_equal valid_pair[:private_key], @settings.get_sp_signing_key.to_pem end - it "raises error when all certificates are expired and check_sp_cert_expiration is true" do + it 'raises an error when all certificates are expired and check_sp_cert_expiration is true' do @settings.security = { check_sp_cert_expiration: true } @settings.sp_cert_multi = { signing: [early_pair, expired] } @@ -787,36 +829,38 @@ class SettingsTest < Minitest::Test end end - describe "#get_sp_decryption_keys" do + describe '#get_sp_decryption_keys' do let(:valid_pair) { CertificateHelper.generate_pem_hash } let(:early_pair) { CertificateHelper.generate_pem_hash(not_before: Time.now + 60) } let(:expired_pair) { CertificateHelper.generate_pem_hash(not_after: Time.now - 60) } - it "returns an empty array when no decryption pairs are present" do + it 'returns an empty array when no decryption pairs are present' do @settings.sp_cert_multi = { encryption: [] } assert_empty @settings.get_sp_decryption_keys end - it "returns all keys when check_sp_cert_expiration is false" do + it 'returns all keys when check_sp_cert_expiration is false' do @settings.security = { check_sp_cert_expiration: false } @settings.sp_cert_multi = { encryption: [early_pair, expired_pair, valid_pair] } expected_keys = [early_pair, expired_pair, valid_pair].map { |pair| pair[:private_key] } actual_keys = @settings.get_sp_decryption_keys.map(&:to_pem) + assert_equal expected_keys, actual_keys end - it "returns only keys of active certificates when check_sp_cert_expiration is true" do + it 'returns only keys of active certificates when check_sp_cert_expiration is true' do @settings.security = { check_sp_cert_expiration: true } @settings.sp_cert_multi = { encryption: [early_pair, expired_pair, valid_pair] } expected_keys = [valid_pair[:private_key]] actual_keys = @settings.get_sp_decryption_keys.map(&:to_pem) + assert_equal expected_keys, actual_keys end - it "raises error when all certificates are expired and check_sp_cert_expiration is true" do + it 'raises an error when all certificates are expired and check_sp_cert_expiration is true' do @settings.security = { check_sp_cert_expiration: true } @settings.sp_cert_multi = { encryption: [early_pair, expired_pair] } @@ -825,7 +869,7 @@ class SettingsTest < Minitest::Test end end - it "removes duplicates" do + it 'removes duplicates' do @settings.sp_cert_multi = { encryption: [early_pair, valid_pair, early_pair, valid_pair] } expected_keys = [early_pair, valid_pair].map { |pair| pair[:private_key] } @@ -882,7 +926,7 @@ class SettingsTest < Minitest::Test assert_equal RubySaml::XML::RSA_SHA384, @settings.get_sp_signature_method end - it 'raises error when digest method is invalid' do + it 'raises an error when digest method is invalid' do @settings.security[:signature_method] = 'RSA_SHA999' assert_raises(ArgumentError, 'Unsupported signature method: RSA_SHA999') do @@ -947,7 +991,7 @@ class SettingsTest < Minitest::Test assert_equal signature_method(sp_key_algo, :sha1), @settings.get_sp_signature_method end - it 'raises error when digest method is invalid' do + it 'raises an error when digest method is invalid' do @settings.security[:signature_method] = 'RSA_SHA999' assert_raises(ArgumentError, "Unsupported signature method for #{sp_key_algo.to_s.upcase} key: RSA_SHA999") do @@ -981,7 +1025,7 @@ class SettingsTest < Minitest::Test assert_equal RubySaml::XML::SHA384, @settings.get_sp_digest_method end - it 'raises error when digest method is invalid' do + it 'raises an error when digest method is invalid' do @settings.security[:digest_method] = 'SHA999' assert_raises(ArgumentError, 'Unsupported digest method: SHA999') do @@ -993,21 +1037,25 @@ class SettingsTest < Minitest::Test describe '#cert?' do it 'returns true for a valid OpenSSL::X509::Certificate object' do cert = CertificateHelper.generate_cert + assert @settings.send(:cert?, cert) end it 'returns true for a non-empty certificate string' do cert_string = '-----BEGIN CERTIFICATE-----\nVALID_CERTIFICATE\n-----END CERTIFICATE-----' + assert @settings.send(:cert?, cert_string) end it 'returns false for an empty certificate string' do cert_string = '' + refute @settings.send(:cert?, cert_string) end it 'returns false for nil' do cert = nil + refute @settings.send(:cert?, cert) end end @@ -1015,21 +1063,25 @@ class SettingsTest < Minitest::Test describe '#private_key?' do it 'returns true for a valid OpenSSL::PKey::PKey object' do private_key = CertificateHelper.generate_private_key + assert @settings.send(:private_key?, private_key) end it 'returns true for a non-empty private key string' do private_key_string = '-----BEGIN PRIVATE KEY-----\nVALID_PRIVATE_KEY\n-----END PRIVATE KEY-----' + assert @settings.send(:private_key?, private_key_string) end it 'returns false for an empty private key string' do private_key_string = '' + refute @settings.send(:private_key?, private_key_string) end it 'returns false for nil' do private_key = nil + refute @settings.send(:private_key?, private_key) end end diff --git a/test/slo_logoutrequest_test.rb b/test/slo_logoutrequest_test.rb index 2a9c2c6d..1c3a3ae0 100644 --- a/test/slo_logoutrequest_test.rb +++ b/test/slo_logoutrequest_test.rb @@ -3,9 +3,7 @@ require_relative 'test_helper' class RubySamlTest < Minitest::Test - - describe "SloLogoutrequest" do - + describe 'SloLogoutrequest' do let(:settings) { RubySaml::Settings.new } let(:logout_request) { RubySaml::SloLogoutrequest.new(logout_request_document) } let(:logout_request_encrypted_nameid) { RubySaml::SloLogoutrequest.new(logout_request_encrypted_nameid_document) } @@ -18,40 +16,41 @@ class RubySamlTest < Minitest::Test invalid_logout_request.settings = settings end - describe "initiator" do - it "raise an exception when logout request is initialized with nil" do + describe 'initiator' do + it 'raises an exception when logout request is initialized with nil' do assert_raises(ArgumentError) { RubySaml::SloLogoutrequest.new(nil) } end end - describe "#is_valid?" do - it "return false when logout request is initialized with blank data" do + describe '#is_valid?' do + it 'return false when logout request is initialized with blank data' do logout_request_blank = RubySaml::SloLogoutrequest.new('') - refute logout_request_blank.is_valid? + + refute_predicate logout_request_blank, :is_valid? assert_includes logout_request_blank.errors, 'Blank logout request' end - it "return true when the logout request is initialized with valid data" do - assert logout_request.is_valid? + it 'return true when the logout request is initialized with valid data' do + assert_predicate logout_request, :is_valid? assert_empty logout_request.errors assert_equal 'someone@example.org', logout_request.nameid end - it "should be idempotent when the logout request is initialized with invalid data" do - refute invalid_logout_request.is_valid? + it 'should be idempotent when the logout request is initialized with invalid data' do + refute_predicate invalid_logout_request, :is_valid? assert_equal ['Invalid SAML Logout Request. Not match the saml-schema-protocol-2.0.xsd'], invalid_logout_request.errors - refute invalid_logout_request.is_valid? + refute_predicate invalid_logout_request, :is_valid? assert_equal ['Invalid SAML Logout Request. Not match the saml-schema-protocol-2.0.xsd'], invalid_logout_request.errors end - it "should be idempotent when the logout request is initialized with valid data" do - assert logout_request.is_valid? + it 'should be idempotent when the logout request is initialized with valid data' do + assert_predicate logout_request, :is_valid? assert_empty logout_request.errors - assert logout_request.is_valid? + assert_predicate logout_request, :is_valid? assert_empty logout_request.errors end - it "collect errors when collect_errors=true" do + it 'collect errors when collect_errors=true' do settings.idp_entity_id = 'http://idp.example.com/invalid' settings.security[:logout_requests_signed] = true settings.certificate = ruby_saml_cert_text @@ -70,144 +69,153 @@ class RubySamlTest < Minitest::Test logout_request_sign_test.settings = settings collect_errors = true + refute logout_request_sign_test.is_valid?(collect_errors) - assert_includes logout_request_sign_test.errors, "Invalid Signature on Logout Request" + assert_includes logout_request_sign_test.errors, 'Invalid Signature on Logout Request' assert_includes logout_request_sign_test.errors, "Doesn't match the issuer, expected: , but was: " end - it "raise error for invalid xml" do + it 'raises an error for invalid XML' do invalid_logout_request.soft = false assert_raises(RubySaml::ValidationError) { invalid_logout_request.is_valid? } end - end - describe "#nameid" do - it "extract the value of the name id element" do - assert_equal "someone@example.org", logout_request.nameid + describe '#nameid' do + it 'extract the value of the name id element' do + assert_equal 'someone@example.org', logout_request.nameid end it 'is not possible when encryptID but no private key' do - assert_raises(RubySaml::ValidationError, "An EncryptedID found and no SP private key found on the settings to decrypt it") do - assert_equal "someone@example.org", logout_request_encrypted_nameid.nameid + assert_raises(RubySaml::ValidationError, 'An EncryptedID found and no SP private key found on the settings to decrypt it') do + assert_equal 'someone@example.org', logout_request_encrypted_nameid.nameid end end - it "extract the value of the name id element inside an EncryptedId" do + it 'extract the value of the name id element inside an EncryptedId' do settings.private_key = ruby_saml_key_text logout_request_encrypted_nameid.settings = settings - assert_equal "someone@example.org", logout_request_encrypted_nameid.nameid + + assert_equal 'someone@example.org', logout_request_encrypted_nameid.nameid end end - describe "#nameid_format" do + describe '#nameid_format' do let(:logout_request) { RubySaml::SloLogoutrequest.new(logout_request_document_with_name_id_format) } - it "extract the format attribute of the name id element" do - assert_equal "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", logout_request.nameid_format + it 'extract the format attribute of the name id element' do + assert_equal 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress', logout_request.nameid_format end it 'is not possible when encryptID but no private key' do - assert_raises(RubySaml::ValidationError, "An EncryptedID found and no SP private key found on the settings to decrypt it") do - assert_equal "someone@example.org", logout_request_encrypted_nameid.nameid + assert_raises(RubySaml::ValidationError, 'An EncryptedID found and no SP private key found on the settings to decrypt it') do + assert_equal 'someone@example.org', logout_request_encrypted_nameid.nameid end end - it "extract the format attribute of the name id element" do + it 'extract the format attribute of the name id element' do settings.private_key = ruby_saml_key_text logout_request_encrypted_nameid.settings = settings - assert_equal "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", logout_request_encrypted_nameid.nameid_format + + assert_equal 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress', logout_request_encrypted_nameid.nameid_format end end - describe "#issuer" do - it "return the issuer inside the logout request" do - assert_equal "https://app.onelogin.com/saml/metadata/SOMEACCOUNT", logout_request.issuer + describe '#issuer' do + it 'return the issuer inside the logout request' do + assert_equal 'https://app.onelogin.com/saml/metadata/SOMEACCOUNT', logout_request.issuer end end - describe "#id" do - it "extract the value of the ID attribute" do - assert_equal "_c0348950-935b-0131-1060-782bcb56fcaa", logout_request.id + describe '#id' do + it 'extract the value of the ID attribute' do + assert_equal '_c0348950-935b-0131-1060-782bcb56fcaa', logout_request.id end end - describe "#not_on_or_after" do - it "extract the value of the NotOnOrAfter attribute" do + describe '#not_on_or_after' do + it 'extract the value of the NotOnOrAfter attribute' do time_value = '2014-07-17T01:01:48Z' + assert_nil logout_request.not_on_or_after logout_request.document.root['NotOnOrAfter'] = time_value + assert_equal Time.parse(time_value), logout_request.not_on_or_after end end describe '#session_indexes' do - it "return empty array when no SessionIndex" do - assert_equal [], logout_request.session_indexes + it 'return empty array when no SessionIndex' do + assert_empty logout_request.session_indexes end - it "return an Array with one SessionIndex" do + it 'return an Array with one SessionIndex' do logout_request_with_session_index = RubySaml::SloLogoutrequest.new(logout_request_xml_with_session_index) + assert_equal ['_ea853497-c58a-408a-bc23-c849752d9741'], logout_request_with_session_index.session_indexes end end - describe "#validate_id" do - it "return true when there is a valid ID in the logout request" do + describe '#validate_id' do + it 'return true when there is a valid ID in the logout request' do assert logout_request.send(:validate_id) assert_empty logout_request.errors end - it "return false when there is an invalid ID in the logout request" do + it 'return false when there is an invalid ID in the logout request' do logout_request_blank = RubySaml::SloLogoutrequest.new('') + refute logout_request_blank.send(:validate_id) - assert_includes logout_request_blank.errors, "Missing ID attribute on Logout Request" + assert_includes logout_request_blank.errors, 'Missing ID attribute on Logout Request' end end - describe "#validate_version" do - it "return true when the logout request is SAML 2.0 Version" do + describe '#validate_version' do + it 'return true when the logout request is SAML 2.0 Version' do assert logout_request.send(:validate_version) end - it "return false when the logout request is not SAML 2.0 Version" do + it 'return false when the logout request is not SAML 2.0 Version' do logout_request_blank = RubySaml::SloLogoutrequest.new('') + refute logout_request_blank.send(:validate_version) - assert_includes logout_request_blank.errors, "Unsupported SAML version" + assert_includes logout_request_blank.errors, 'Unsupported SAML version' end end - describe "#validate_not_on_or_after" do - it "return true when the logout request has a valid NotOnOrAfter or does not contain any" do + describe '#validate_not_on_or_after' do + it 'return true when the logout request has a valid NotOnOrAfter or does not contain any' do assert logout_request.send(:validate_not_on_or_after) assert_empty logout_request.errors Timecop.freeze Time.parse('2014-07-17T01:01:47Z') do logout_request.document.root['NotOnOrAfter'] = '2014-07-17T01:01:48Z' + assert logout_request.send(:validate_not_on_or_after) assert_empty logout_request.errors end end - it "return false when the logout request has an invalid NotOnOrAfter" do + it 'return false when the logout request has an invalid NotOnOrAfter' do Timecop.freeze Time.parse('2014-07-17T01:01:49Z') do logout_request.document.root['NotOnOrAfter'] = '2014-07-17T01:01:48Z' + refute logout_request.send(:validate_not_on_or_after) assert_match(/Current time is on or after NotOnOrAfter/, logout_request.errors[0]) end end - it "raise when the logout request has an invalid NotOnOrAfter" do + it 'raise when the logout request has an invalid NotOnOrAfter' do Timecop.freeze Time.parse('2014-07-17T01:01:49Z') do logout_request.document.root['NotOnOrAfter'] = '2014-07-17T01:01:48Z' logout_request.soft = false - assert_raises(RubySaml::ValidationError, "Current time is on or after NotOnOrAfter") do + assert_raises(RubySaml::ValidationError, 'Current time is on or after NotOnOrAfter') do logout_request.send(:validate_not_on_or_after) end end end - it "optionally allows for clock drift" do + it 'optionally allows for clock drift' do # Java Floats behave differently than MRI java = jruby? || truffleruby? @@ -215,58 +223,63 @@ class RubySamlTest < Minitest::Test logout_request.document.root['NotOnOrAfter'] = '2011-06-14T18:31:01.516Z' # The NotBefore condition in the document is 2011-06-1418:31:01.516Z - Timecop.freeze(Time.parse("2011-06-14T18:31:02Z")) do + Timecop.freeze(Time.parse('2011-06-14T18:31:02Z')) do logout_request.options[:allowed_clock_drift] = 0.483 + refute logout_request.send(:validate_not_on_or_after) logout_request.options[:allowed_clock_drift] = java ? 0.485 : 0.484 + assert logout_request.send(:validate_not_on_or_after) logout_request.options[:allowed_clock_drift] = '0.483' + refute logout_request.send(:validate_not_on_or_after) logout_request.options[:allowed_clock_drift] = java ? '0.485' : '0.484' + assert logout_request.send(:validate_not_on_or_after) end end end - describe "#validate_request_state" do - it "return true when valid logout request xml" do + describe '#validate_request_state' do + it 'return true when valid logout request xml' do assert logout_request.send(:validate_request_state) assert_empty logout_request.errors assert logout_request.send(:validate_request_state) assert_empty logout_request.errors end - it "return false when invalid logout request xml" do + it 'return false when invalid logout request xml' do logout_request_blank = RubySaml::SloLogoutrequest.new('') logout_request_blank.soft = true + refute logout_request_blank.send(:validate_request_state) - assert_includes logout_request_blank.errors, "Blank logout request" + assert_includes logout_request_blank.errors, 'Blank logout request' end - it "raise error for invalid xml" do + it 'raise error for invalid xml' do logout_request_blank = RubySaml::SloLogoutrequest.new('') logout_request_blank.soft = false - assert_raises(RubySaml::ValidationError, "Blank logout request") do + assert_raises(RubySaml::ValidationError, 'Blank logout request') do logout_request_blank.send(:validate_request_state) end end end - describe "#validate_structure" do - it "return true when encountering a valid Logout Request xml" do + describe '#validate_structure' do + it 'return true when encountering a valid Logout Request xml' do assert logout_request.send(:validate_structure) assert_empty logout_request.errors end - it "return false when encountering a Logout Request bad formatted" do + it 'return false when encountering a Logout Request bad formatted' do refute invalid_logout_request.send(:validate_structure) - assert_includes invalid_logout_request.errors, "Invalid SAML Logout Request. Not match the saml-schema-protocol-2.0.xsd" + assert_includes invalid_logout_request.errors, 'Invalid SAML Logout Request. Not match the saml-schema-protocol-2.0.xsd' end - it "raise when encountering a Logout Request bad formatted" do + it 'raises when encountering a badly formatted Logout Request' do invalid_logout_request.soft = false assert_raises(RubySaml::ValidationError, "Element '{urn:oasis:names:tc:SAML:2.0:assertion}Issuer': This element is not expected") do invalid_logout_request.send(:validate_structure) @@ -274,19 +287,21 @@ class RubySamlTest < Minitest::Test end end - describe "#validate_issuer" do - it "return true when the issuer of the Logout Request matchs the IdP entityId" do + describe '#validate_issuer' do + it 'return true when the issuer of the Logout Request matches the IdP entityId' do logout_request.settings.idp_entity_id = 'https://app.onelogin.com/saml/metadata/SOMEACCOUNT' + assert logout_request.send(:validate_issuer) end - it "return false when the issuer of the Logout Request does not match the IdP entityId" do + it 'return false when the issuer of the Logout Request does not match the IdP entityId' do logout_request.settings.idp_entity_id = 'http://idp.example.com/invalid' + refute logout_request.send(:validate_issuer) assert_includes logout_request.errors, "Doesn't match the issuer, expected: <#{logout_request.settings.idp_entity_id}>, but was: " end - it "raise when the issuer of the Logout Request does not match the IdP entityId" do + it 'raise when the issuer of the Logout Request does not match the IdP entityId' do logout_request.settings.idp_entity_id = 'http://idp.example.com/invalid' logout_request.soft = false assert_raises(RubySaml::ValidationError, "Doesn't match the issuer, expected: <#{logout_request.settings.idp_entity_id}>, but was: ") do @@ -296,7 +311,7 @@ class RubySamlTest < Minitest::Test end each_signature_algorithm do |idp_key_algo, idp_hash_algo| - describe "#validate_signature" do + describe '#validate_signature' do before do @cert, @pkey = CertificateHelper.generate_pair(idp_key_algo) settings.idp_cert = @cert.to_pem @@ -309,9 +324,9 @@ class RubySamlTest < Minitest::Test settings.security[:signature_method] = signature_method(idp_key_algo, idp_hash_algo) end - it "return true when no idp_cert is provided and option :relax_signature_validation is present" do + it 'return true when no idp_cert is provided and option :relax_signature_validation is present' do settings.idp_cert = nil - params = RubySaml::Logoutrequest.new.create_params(settings, :RelayState => 'http://example.com') + params = RubySaml::Logoutrequest.new.create_params(settings, RelayState: 'http://example.com') params['RelayState'] = params[:RelayState] options = {} options[:get_params] = params @@ -322,9 +337,9 @@ class RubySamlTest < Minitest::Test assert logout_request_sign_test.send(:validate_signature) end - it "return false when no idp_cert is provided and no option :relax_signature_validation is present" do + it 'return false when no idp_cert is provided and no option :relax_signature_validation is present' do settings.idp_cert = nil - params = RubySaml::Logoutrequest.new.create_params(settings, :RelayState => 'http://example.com') + params = RubySaml::Logoutrequest.new.create_params(settings, RelayState: 'http://example.com') params['RelayState'] = params[:RelayState] options = {} options[:get_params] = params @@ -334,8 +349,8 @@ class RubySamlTest < Minitest::Test refute logout_request_sign_test.send(:validate_signature) end - it "return true when valid RSA_SHA1 Signature" do - params = RubySaml::Logoutrequest.new.create_params(settings, :RelayState => 'http://example.com') + it 'return true when valid RSA_SHA1 Signature' do + params = RubySaml::Logoutrequest.new.create_params(settings, RelayState: 'http://example.com') params['RelayState'] = params[:RelayState] options = {} options[:get_params] = params @@ -345,8 +360,8 @@ class RubySamlTest < Minitest::Test assert logout_request_sign_test.send(:validate_signature) end - it "return false when invalid signature" do - params = RubySaml::Logoutrequest.new.create_params(settings, :RelayState => 'http://example.com') + it 'return false when invalid signature' do + params = RubySaml::Logoutrequest.new.create_params(settings, RelayState: 'http://example.com') params['RelayState'] = 'http://invalid.example.com' params[:RelayState] = params['RelayState'] options = {} @@ -357,9 +372,9 @@ class RubySamlTest < Minitest::Test refute logout_request_sign_test.send(:validate_signature) end - it "raise when invalid signature" do + it 'raises for an invalid signature' do settings.soft = false - params = RubySaml::Logoutrequest.new.create_params(settings, :RelayState => 'http://example.com') + params = RubySaml::Logoutrequest.new.create_params(settings, RelayState: 'http://example.com') params['RelayState'] = 'http://invalid.example.com' params[:RelayState] = params['RelayState'] options = {} @@ -367,31 +382,31 @@ class RubySamlTest < Minitest::Test options[:settings] = settings logout_request_sign_test = RubySaml::SloLogoutrequest.new(params['SAMLRequest'], options) - assert_raises(RubySaml::ValidationError, "Invalid Signature on Logout Request") do + assert_raises(RubySaml::ValidationError, 'Invalid Signature on Logout Request') do logout_request_sign_test.send(:validate_signature) end end - it "raise when get_params encoding differs from what this library generates" do + it 'raises when get_params encoding differs from what this library generates' do settings.soft = false - params = RubySaml::Logoutrequest.new.create_params(settings, "RelayState" => "http://example.com") + params = RubySaml::Logoutrequest.new.create_params(settings, 'RelayState' => 'http://example.com') query = RubySaml::Utils.build_query( - :type => 'SAMLRequest', - :data => params['SAMLRequest'], - :relay_state => params['RelayState'], - :sig_alg => params['SigAlg'] + type: 'SAMLRequest', + data: params['SAMLRequest'], + relay_state: params['RelayState'], + sig_alg: params['SigAlg'] ) # Modify the query string so that it encodes the same values, # but with different percent-encoding. Sanity-check that they - # really are equialent before moving on. + # really are equivalent before moving on. original_query = query.dup - query.gsub!("example", "ex%61mple") + query.gsub!('example', 'ex%61mple') refute_equal(query, original_query) assert_equal(CGI.unescape(query), CGI.unescape(original_query)) - # Make normalised signature based on our modified params. + # Make normalized signature based on our modified params. sign_algorithm = RubySaml::XML.hash_algorithm(settings.get_sp_signature_method) signature = settings.get_sp_signing_key.sign(sign_algorithm.new, query) @@ -404,26 +419,26 @@ class RubySamlTest < Minitest::Test options[:settings] = settings logout_request_sign_test = RubySaml::SloLogoutrequest.new(params['SAMLRequest'], options) - assert_raises(RubySaml::ValidationError, "Invalid Signature on Logout Request") do + assert_raises(RubySaml::ValidationError, 'Invalid Signature on Logout Request') do logout_request_sign_test.send(:validate_signature) end end - it "return true even if raw_get_params encoding differs from what this library generates" do + it 'return true even if raw_get_params encoding differs from what this library generates' do settings.soft = false - params = RubySaml::Logoutrequest.new.create_params(settings, "RelayState" => "http://example.com") + params = RubySaml::Logoutrequest.new.create_params(settings, 'RelayState' => 'http://example.com') query = RubySaml::Utils.build_query( - :type => 'SAMLRequest', - :data => params['SAMLRequest'], - :relay_state => params['RelayState'], - :sig_alg => params['SigAlg'] + type: 'SAMLRequest', + data: params['SAMLRequest'], + relay_state: params['RelayState'], + sig_alg: params['SigAlg'] ) # Modify the query string so that it encodes the same values, # but with different percent-encoding. Sanity-check that they # really are equialent before moving on. original_query = query.dup - query.gsub!("example", "ex%61mple") + query.gsub!('example', 'ex%61mple') refute_equal(query, original_query) assert_equal(CGI.unescape(query), CGI.unescape(original_query)) @@ -438,15 +453,15 @@ class RubySamlTest < Minitest::Test # so that we don't have to guess the value that contributed to the signature. options = {} options[:get_params] = params - options[:get_params].delete("RelayState") - options[:raw_get_params] = { "RelayState" => "http%3A%2F%2Fex%61mple.com" } + options[:get_params].delete('RelayState') + options[:raw_get_params] = { 'RelayState' => 'http%3A%2F%2Fex%61mple.com' } options[:settings] = settings logout_request_sign_test = RubySaml::SloLogoutrequest.new(params['SAMLRequest'], options) assert logout_request_sign_test.send(:validate_signature) end - it "handles Azure AD downcased request encoding" do + it 'handles Azure AD downcased request encoding' do settings.soft = false # Creating the query manually to tweak it later instead of using @@ -459,7 +474,7 @@ class RubySamlTest < Minitest::Test # send is based on this base64 request. params = { 'SAMLRequest' => downcased_escape(base64_request), - 'SigAlg' => downcased_escape(settings.get_sp_signature_method), + 'SigAlg' => downcased_escape(settings.get_sp_signature_method) } query = "SAMLRequest=#{params['SAMLRequest']}&SigAlg=#{params['SigAlg']}" # Make normalised signature based on our modified params. @@ -468,13 +483,13 @@ class RubySamlTest < Minitest::Test params['Signature'] = downcased_escape(Base64.strict_encode64(signature)) # Then parameters are usually unescaped, like we manage them in rails - params = params.map { |k, v| [k, CGI.unescape(v)] }.to_h + params = params.transform_values { |v| CGI.unescape(v) } # Construct SloLogoutrequest and ask it to validate the signature. # It will fail because the signature is based on the downcased request logout_request_downcased_test = RubySaml::SloLogoutrequest.new( - params['SAMLRequest'], get_params: params, settings: settings, + params['SAMLRequest'], get_params: params, settings: settings ) - assert_raises(RubySaml::ValidationError, "Invalid Signature on Logout Request") do + assert_raises(RubySaml::ValidationError, 'Invalid Signature on Logout Request') do logout_request_downcased_test.send(:validate_signature) end @@ -484,20 +499,21 @@ class RubySamlTest < Minitest::Test logout_request_force_downcasing_test = RubySaml::SloLogoutrequest.new( params['SAMLRequest'], get_params: params, settings: settings ) + assert logout_request_force_downcasing_test.send(:validate_signature) end - describe "with multiple idp certs" do + describe 'with multiple idp certs' do before do settings.idp_cert = nil end - it "return true when at least a idp_cert is valid" do + it 'return true when at least a idp_cert is valid' do settings.idp_cert_multi = { - :signing => [@cert.to_pem, ruby_saml_cert_text], - :encryption => [] + signing: [@cert.to_pem, ruby_saml_cert_text], + encryption: [] } - params = RubySaml::Logoutrequest.new.create_params(settings, :RelayState => 'http://example.com') + params = RubySaml::Logoutrequest.new.create_params(settings, RelayState: 'http://example.com') params['RelayState'] = params[:RelayState] options = {} options[:get_params] = params @@ -507,19 +523,19 @@ class RubySamlTest < Minitest::Test assert logout_request_sign_test.send(:validate_signature) end - it "return false when cert expired and check_idp_cert_expiration expired" do + it 'return false when cert is expired and check_idp_cert_expiration is enabled' do settings.security[:check_idp_cert_expiration] = true settings.idp_cert = nil settings.idp_cert_multi = { - :signing => [ruby_saml_cert_text], - :encryption => [] + signing: [ruby_saml_cert_text], + encryption: [] } # These SP settings are for dummy params generation. settings.certificate = ruby_saml_cert_text settings.private_key = ruby_saml_key_text - params = RubySaml::Logoutrequest.new.create_params(settings, :RelayState => 'http://example.com') + params = RubySaml::Logoutrequest.new.create_params(settings, RelayState: 'http://example.com') params['RelayState'] = params[:RelayState] options = {} options[:get_params] = params @@ -527,17 +543,17 @@ class RubySamlTest < Minitest::Test logout_request_sign_test.settings = settings refute logout_request_sign_test.send(:validate_signature) - assert_includes logout_request_sign_test.errors, "IdP x509 certificate expired" + assert_includes logout_request_sign_test.errors, 'IdP x509 certificate expired' end - it "return false when none cert on idp_cert_multi is valid" do + it 'return false when none cert on idp_cert_multi is valid' do settings.idp_cert_fingerprint = ruby_saml_cert_fingerprint settings.idp_cert_multi = { - :signing => [ruby_saml_cert_text2, ruby_saml_cert_text2], - :encryption => [] + signing: [ruby_saml_cert_text2, ruby_saml_cert_text2], + encryption: [] } - params = RubySaml::Logoutrequest.new.create_params(settings, :RelayState => 'http://example.com') + params = RubySaml::Logoutrequest.new.create_params(settings, RelayState: 'http://example.com') params['RelayState'] = params[:RelayState] options = {} options[:get_params] = params @@ -545,7 +561,7 @@ class RubySamlTest < Minitest::Test logout_request_sign_test.settings = settings refute logout_request_sign_test.send(:validate_signature) - assert_includes logout_request_sign_test.errors, "Invalid Signature on Logout Request" + assert_includes logout_request_sign_test.errors, 'Invalid Signature on Logout Request' end end end diff --git a/test/slo_logoutresponse_test.rb b/test/slo_logoutresponse_test.rb index a4c217db..d923b13c 100644 --- a/test/slo_logoutresponse_test.rb +++ b/test/slo_logoutresponse_test.rb @@ -25,7 +25,7 @@ class SloLogoutresponseTest < Minitest::Test assert_match(/^ nil }) assert_match(/&hello=$/, unauth_url) @@ -149,7 +149,7 @@ class SloLogoutresponseTest < Minitest::Test refute_match(/([a-zA-Z0-9/+]+=?=?)} - end + def signature_value_matcher + %r{([a-zA-Z0-9/+]+=?=?)} + end - def signature_method_matcher(algorithm = :rsa, digest = :sha256) - %r{} - end + def signature_method_matcher(algorithm = :rsa, digest = :sha256) + %r{} + end - def digest_method_matcher(digest = :sha256) - %r{} - end + def digest_method_matcher(digest = :sha256) + %r{} + end - def read_response(response) - File.read(File.join(File.dirname(__FILE__), "responses", response)) - end + def read_response(response) + File.read(File.join(File.dirname(__FILE__), 'responses', response)) + end - def read_invalid_response(response) - File.read(File.join(File.dirname(__FILE__), "responses", "invalids", response)) - end + def read_invalid_response(response) + File.read(File.join(File.dirname(__FILE__), 'responses', 'invalids', response)) + end - def read_logout_request(request) - File.read(File.join(File.dirname(__FILE__), "logout_requests", request)) - end + def read_logout_request(request) + File.read(File.join(File.dirname(__FILE__), 'logout_requests', request)) + end - def read_certificate(certificate) - File.read(File.join(File.dirname(__FILE__), "certificates", certificate)) - end + def read_certificate(certificate) + File.read(File.join(File.dirname(__FILE__), 'certificates', certificate)) + end - def response_document_valid_signed - @response_document_valid_signed ||= read_response("valid_response.xml.base64") - end + def response_document_valid_signed + @response_document_valid_signed ||= read_response('valid_response.xml.base64') + end - def response_document_valid_signed_without_x509certificate - @response_document_valid_signed_without_x509certificate ||= read_response("valid_response_without_x509certificate.xml.base64") - end + def response_document_valid_signed_without_x509certificate + @response_document_valid_signed_without_x509certificate ||= read_response('valid_response_without_x509certificate.xml.base64') + end - def response_document_without_recipient - @response_document_without_recipient ||= read_response("response_with_undefined_recipient.xml.base64") - end + def response_document_without_recipient + @response_document_without_recipient ||= read_response('response_with_undefined_recipient.xml.base64') + end - def signed_response_document_without_recipient - @signed_response_document_without_recipient ||= read_response("signed_response_with_undefined_recipient.xml.base64") - end + def signed_response_document_without_recipient + @signed_response_document_without_recipient ||= read_response('signed_response_with_undefined_recipient.xml.base64') + end - def response_document_without_recipient_with_time_updated - doc = Base64.decode64(response_document_without_recipient) - doc.gsub!(/NotBefore="(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})Z"/, "NotBefore=\"#{(Time.now-300).getutc.strftime("%Y-%m-%dT%XZ")}\"") - doc.gsub!(/NotOnOrAfter="(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})Z"/, "NotOnOrAfter=\"#{(Time.now+300).getutc.strftime("%Y-%m-%dT%XZ")}\"") - Base64.encode64(doc) - end + def response_document_without_recipient_with_time_updated + doc = Base64.decode64(response_document_without_recipient) + doc.gsub!(/NotBefore="(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})Z"/, "NotBefore=\"#{(Time.now - 300).getutc.strftime('%Y-%m-%dT%XZ')}\"") + doc.gsub!(/NotOnOrAfter="(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})Z"/, "NotOnOrAfter=\"#{(Time.now + 300).getutc.strftime('%Y-%m-%dT%XZ')}\"") + Base64.encode64(doc) + end - def response_document_without_attributes - @response_document_without_attributes ||= read_response("response_without_attributes.xml.base64") - end + def response_document_without_attributes + @response_document_without_attributes ||= read_response('response_without_attributes.xml.base64') + end - def response_document_without_reference_uri - @response_document_without_reference_uri ||= read_response("response_without_reference_uri.xml.base64") - end + def response_document_without_reference_uri + @response_document_without_reference_uri ||= read_response('response_without_reference_uri.xml.base64') + end - def response_document_with_signed_assertion - @response_document_with_signed_assertion ||= read_response("response_with_signed_assertion.xml.base64") - end + def response_document_with_signed_assertion + @response_document_with_signed_assertion ||= read_response('response_with_signed_assertion.xml.base64') + end - def response_document_with_signed_assertion_2 - @response_document_with_signed_assertion_2 ||= read_response("response_with_signed_assertion_2.xml.base64") - end + def response_document_with_signed_assertion_2 + @response_document_with_signed_assertion_2 ||= read_response('response_with_signed_assertion_2.xml.base64') + end - def response_document_with_ds_namespace_at_the_root - @response_document_with_ds_namespace_at_the_root ||= read_response("response_with_ds_namespace_at_the_root.xml.base64") - end + def response_document_with_ds_namespace_at_the_root + @response_document_with_ds_namespace_at_the_root ||= read_response('response_with_ds_namespace_at_the_root.xml.base64') + end - def response_document_unsigned - @response_document_unsigned ||= read_response("response_unsigned_xml_base64") - end + def response_document_unsigned + @response_document_unsigned ||= read_response('response_unsigned_xml_base64') + end - def response_document_with_saml2_namespace - @response_document_with_saml2_namespace ||= read_response("response_with_saml2_namespace.xml.base64") - end + def response_document_with_saml2_namespace + @response_document_with_saml2_namespace ||= read_response('response_with_saml2_namespace.xml.base64') + end - def ampersands_document - @ampersands_response ||= read_response("response_with_ampersands.xml.base64") - end + def ampersands_document + @ampersands_document ||= read_response('response_with_ampersands.xml.base64') + end - def response_document_no_cert_and_encrypted_attrs - @response_document_no_cert_and_encrypted_attrs ||= Base64.encode64(read_response("response_no_cert_and_encrypted_attrs.xml")) - end + def response_document_no_cert_and_encrypted_attrs + @response_document_no_cert_and_encrypted_attrs ||= Base64.encode64(read_response('response_no_cert_and_encrypted_attrs.xml')) + end - def response_document_wrapped - @response_document_wrapped ||= read_response("response_wrapped.xml.base64") - end + def response_document_wrapped + @response_document_wrapped ||= read_response('response_wrapped.xml.base64') + end - def response_document_assertion_wrapped - @response_document_assertion_wrapped ||= read_response("response_assertion_wrapped.xml.base64") - end + def response_document_assertion_wrapped + @response_document_assertion_wrapped ||= read_response('response_assertion_wrapped.xml.base64') + end - def response_document_encrypted_nameid - @response_document_encrypted_nameid ||= File.read(File.join(File.dirname(__FILE__), 'responses', 'response_encrypted_nameid.xml.base64')) - end + def response_document_encrypted_nameid + @response_document_encrypted_nameid ||= File.read(File.join(File.dirname(__FILE__), 'responses', 'response_encrypted_nameid.xml.base64')) + end - def signed_message_encrypted_unsigned_assertion - @signed_message_encrypted_unsigned_assertion ||= File.read(File.join(File.dirname(__FILE__), 'responses', 'signed_message_encrypted_unsigned_assertion.xml.base64')) - end + def signed_message_encrypted_unsigned_assertion + @signed_message_encrypted_unsigned_assertion ||= File.read(File.join(File.dirname(__FILE__), 'responses', 'signed_message_encrypted_unsigned_assertion.xml.base64')) + end - def signed_message_encrypted_signed_assertion - @signed_message_encrypted_signed_assertion ||= File.read(File.join(File.dirname(__FILE__), 'responses', 'signed_message_encrypted_signed_assertion.xml.base64')) - end + def signed_message_encrypted_signed_assertion + @signed_message_encrypted_signed_assertion ||= File.read(File.join(File.dirname(__FILE__), 'responses', 'signed_message_encrypted_signed_assertion.xml.base64')) + end - def unsigned_message_encrypted_signed_assertion - @unsigned_message_encrypted_signed_assertion ||= File.read(File.join(File.dirname(__FILE__), 'responses', 'unsigned_message_encrypted_signed_assertion.xml.base64')) - end + def unsigned_message_encrypted_signed_assertion + @unsigned_message_encrypted_signed_assertion ||= File.read(File.join(File.dirname(__FILE__), 'responses', 'unsigned_message_encrypted_signed_assertion.xml.base64')) + end - def unsigned_message_encrypted_unsigned_assertion - @unsigned_message_encrypted_unsigned_assertion ||= File.read(File.join(File.dirname(__FILE__), 'responses', 'unsigned_message_encrypted_unsigned_assertion.xml.base64')) - end + def unsigned_message_encrypted_unsigned_assertion + @unsigned_message_encrypted_unsigned_assertion ||= File.read(File.join(File.dirname(__FILE__), 'responses', 'unsigned_message_encrypted_unsigned_assertion.xml.base64')) + end - def response_document_encrypted_attrs - @response_document_encrypted_attrs ||= File.read(File.join(File.dirname(__FILE__), 'responses', 'response_encrypted_attrs.xml.base64')) - end + def response_document_encrypted_attrs + @response_document_encrypted_attrs ||= File.read(File.join(File.dirname(__FILE__), 'responses', 'response_encrypted_attrs.xml.base64')) + end - def response_document_double_status_code - @response_document_double_status_code ||= File.read(File.join(File.dirname(__FILE__), 'responses', 'response_double_status_code.xml.base64')) - end + def response_document_double_status_code + @response_document_double_status_code ||= File.read(File.join(File.dirname(__FILE__), 'responses', 'response_double_status_code.xml.base64')) + end - def signature_fingerprint_1 - @signature_fingerprint1 ||= "C5:19:85:D9:47:F1:BE:57:08:20:25:05:08:46:EB:27:F6:CA:B7:83" - end + def signature_fingerprint_1 + @signature_fingerprint_1 ||= 'C5:19:85:D9:47:F1:BE:57:08:20:25:05:08:46:EB:27:F6:CA:B7:83' + end - # certificate used on response_with_undefined_recipient - def signature_1 - @signature1 ||= read_certificate("certificate1") - end + # certificate used on response_with_undefined_recipient + def signature_1 + @signature_1 ||= read_certificate('certificate1') + end - # certificate used on response_document_with_signed_assertion_2 - def certificate_without_head_foot - @certificate_without_head_foot ||= read_certificate("certificate_without_head_foot") - end + # certificate used on response_document_with_signed_assertion_2 + def certificate_without_head_foot + @certificate_without_head_foot ||= read_certificate('certificate_without_head_foot') + end - def idp_metadata_descriptor - @idp_metadata_descriptor ||= File.read(File.join(File.dirname(__FILE__), 'metadata', 'idp_descriptor.xml')) - end + def idp_metadata_descriptor + @idp_metadata_descriptor ||= File.read(File.join(File.dirname(__FILE__), 'metadata', 'idp_descriptor.xml')) + end - def idp_metadata_descriptor2 - @idp_metadata_descriptor2 ||= File.read(File.join(File.dirname(__FILE__), 'metadata', 'idp_descriptor_2.xml')) - end + def idp_metadata_descriptor2 + @idp_metadata_descriptor2 ||= File.read(File.join(File.dirname(__FILE__), 'metadata', 'idp_descriptor_2.xml')) + end - def idp_metadata_descriptor3 - @idp_metadata_descriptor3 ||= File.read(File.join(File.dirname(__FILE__), 'metadata', 'idp_descriptor_3.xml')) - end + def idp_metadata_descriptor3 + @idp_metadata_descriptor3 ||= File.read(File.join(File.dirname(__FILE__), 'metadata', 'idp_descriptor_3.xml')) + end - def idp_metadata_descriptor4 - @idp_metadata_descriptor4 ||= File.read(File.join(File.dirname(__FILE__), 'metadata', 'idp_descriptor_4.xml')) - end + def idp_metadata_descriptor4 + @idp_metadata_descriptor4 ||= File.read(File.join(File.dirname(__FILE__), 'metadata', 'idp_descriptor_4.xml')) + end - def idp_metadata_descriptor5 - @idp_metadata_descriptor5 ||= File.read(File.join(File.dirname(__FILE__), 'metadata', 'idp_descriptor_5.xml')) - end + def idp_metadata_descriptor5 + @idp_metadata_descriptor5 ||= File.read(File.join(File.dirname(__FILE__), 'metadata', 'idp_descriptor_5.xml')) + end - def idp_metadata_descriptor6 - @idp_metadata_descriptor6 ||= File.read(File.join(File.dirname(__FILE__), 'metadata', 'idp_descriptor_6.xml')) - end + def idp_metadata_descriptor6 + @idp_metadata_descriptor6 ||= File.read(File.join(File.dirname(__FILE__), 'metadata', 'idp_descriptor_6.xml')) + end - def no_idp_metadata_descriptor - @no_idp_metadata_descriptor ||= File.read(File.join(File.dirname(__FILE__), 'metadata', 'no_idp_descriptor.xml')) - end + def no_idp_metadata_descriptor + @no_idp_metadata_descriptor ||= File.read(File.join(File.dirname(__FILE__), 'metadata', 'no_idp_descriptor.xml')) + end - def idp_metadata_multiple_descriptors - @idp_metadata_multiple_descriptors ||= File.read(File.join(File.dirname(__FILE__), 'metadata', 'idp_multiple_descriptors.xml')) - end + def idp_metadata_multiple_descriptors + @idp_metadata_multiple_descriptors ||= File.read(File.join(File.dirname(__FILE__), 'metadata', 'idp_multiple_descriptors.xml')) + end - def idp_metadata_multiple_descriptors2 - @idp_metadata_multiple_descriptors2 ||= File.read(File.join(File.dirname(__FILE__), 'metadata', 'idp_multiple_descriptors_2.xml')) - end + def idp_metadata_multiple_descriptors2 + @idp_metadata_multiple_descriptors2 ||= File.read(File.join(File.dirname(__FILE__), 'metadata', 'idp_multiple_descriptors_2.xml')) + end - def idp_metadata_multiple_certs - @idp_metadata_multiple_descriptors ||= File.read(File.join(File.dirname(__FILE__), 'metadata', 'idp_metadata_multi_certs.xml')) - end + def idp_metadata_multiple_certs + @idp_metadata_multiple_certs ||= File.read(File.join(File.dirname(__FILE__), 'metadata', 'idp_metadata_multi_certs.xml')) + end - def idp_metadata_multiple_signing_certs - @idp_metadata_multiple_signing_certs ||= File.read(File.join(File.dirname(__FILE__), 'metadata', 'idp_metadata_multi_signing_certs.xml')) - end + def idp_metadata_multiple_signing_certs + @idp_metadata_multiple_signing_certs ||= File.read(File.join(File.dirname(__FILE__), 'metadata', 'idp_metadata_multi_signing_certs.xml')) + end - def idp_metadata_same_sign_and_encrypt_cert - @idp_metadata_same_sign_and_encrypt_cert ||= File.read(File.join(File.dirname(__FILE__), 'metadata', 'idp_metadata_same_sign_and_encrypt_cert.xml')) - end + def idp_metadata_same_sign_and_encrypt_cert + @idp_metadata_same_sign_and_encrypt_cert ||= File.read(File.join(File.dirname(__FILE__), 'metadata', 'idp_metadata_same_sign_and_encrypt_cert.xml')) + end - def idp_metadata_different_sign_and_encrypt_cert - @idp_metadata_different_sign_and_encrypt_cert ||= File.read(File.join(File.dirname(__FILE__), 'metadata', 'idp_metadata_different_sign_and_encrypt_cert.xml')) - end + def idp_metadata_different_sign_and_encrypt_cert + @idp_metadata_different_sign_and_encrypt_cert ||= File.read(File.join(File.dirname(__FILE__), 'metadata', 'idp_metadata_different_sign_and_encrypt_cert.xml')) + end - def idp_different_slo_response_location - @idp_different_slo_response_location ||= File.read(File.join(File.dirname(__FILE__), 'metadata', 'idp_different_slo_response_location.xml')) - end + def idp_different_slo_response_location + @idp_different_slo_response_location ||= File.read(File.join(File.dirname(__FILE__), 'metadata', 'idp_different_slo_response_location.xml')) + end - def idp_without_slo_response_location - @idp_without_slo_response_location ||= File.read(File.join(File.dirname(__FILE__), 'metadata', 'idp_without_slo_response_location.xml')) - end + def idp_without_slo_response_location + @idp_without_slo_response_location ||= File.read(File.join(File.dirname(__FILE__), 'metadata', 'idp_without_slo_response_location.xml')) + end - def logout_request_document - unless @logout_request_document - xml = read_logout_request("slo_request.xml") - deflated = Zlib::Deflate.deflate(xml, 9)[2..-5] - @logout_request_document = Base64.encode64(deflated) + def logout_request_document + unless @logout_request_document + xml = read_logout_request('slo_request.xml') + deflated = Zlib::Deflate.deflate(xml, 9)[2..-5] + @logout_request_document = Base64.encode64(deflated) + end + @logout_request_document end - @logout_request_document - end - def logout_request_document_with_name_id_format - unless @logout_request_document_with_name_id_format - xml = read_logout_request("slo_request_with_name_id_format.xml") - deflated = Zlib::Deflate.deflate(xml, 9)[2..-5] - @logout_request_document_with_name_id_format = Base64.encode64(deflated) + def logout_request_document_with_name_id_format + unless @logout_request_document_with_name_id_format + xml = read_logout_request('slo_request_with_name_id_format.xml') + deflated = Zlib::Deflate.deflate(xml, 9)[2..-5] + @logout_request_document_with_name_id_format = Base64.encode64(deflated) + end + @logout_request_document_with_name_id_format end - @logout_request_document_with_name_id_format - end - def logout_request_encrypted_nameid_document - unless @logout_request_encrypted_nameid_document - xml = read_logout_request("slo_request_encrypted_nameid.xml") - deflated = Zlib::Deflate.deflate(xml, 9)[2..-5] - @logout_request_encrypted_nameid_document = Base64.encode64(deflated) + def logout_request_encrypted_nameid_document + unless @logout_request_encrypted_nameid_document + xml = read_logout_request('slo_request_encrypted_nameid.xml') + deflated = Zlib::Deflate.deflate(xml, 9)[2..-5] + @logout_request_encrypted_nameid_document = Base64.encode64(deflated) + end + @logout_request_encrypted_nameid_document end - @logout_request_encrypted_nameid_document - end - def logout_request_xml_with_session_index - @logout_request_xml_with_session_index ||= File.read(File.join(File.dirname(__FILE__), 'logout_requests', 'slo_request_with_session_index.xml')) - end + def logout_request_xml_with_session_index + @logout_request_xml_with_session_index ||= File.read(File.join(File.dirname(__FILE__), 'logout_requests', 'slo_request_with_session_index.xml')) + end - def invalid_logout_request_document - unless @invalid_logout_request_document - xml = File.read(File.join(File.dirname(__FILE__), 'logout_requests', 'invalid_slo_request.xml')) - deflated = Zlib::Deflate.deflate(xml, 9)[2..-5] - @invalid_logout_request_document = Base64.encode64(deflated) + def invalid_logout_request_document + unless @invalid_logout_request_document + xml = File.read(File.join(File.dirname(__FILE__), 'logout_requests', 'invalid_slo_request.xml')) + deflated = Zlib::Deflate.deflate(xml, 9)[2..-5] + @invalid_logout_request_document = Base64.encode64(deflated) + end + @invalid_logout_request_document end - @invalid_logout_request_document - end - def logout_request_original - @logout_request_original ||= File.read(File.join(File.dirname(__FILE__), 'logout_requests', 'slo_request.xml')).gsub("\n", "\r\n").strip - end + def logout_request_original + @logout_request_original ||= File.read(File.join(File.dirname(__FILE__), 'logout_requests', 'slo_request.xml')).gsub("\n", "\r\n").strip + end - def logout_request_base64 - @logout_request_base64 ||= File.read(File.join(File.dirname(__FILE__), 'logout_requests', 'slo_request.xml.base64')) - end + def logout_request_base64 + @logout_request_base64 ||= File.read(File.join(File.dirname(__FILE__), 'logout_requests', 'slo_request.xml.base64')) + end - def logout_request_deflated_base64 - @logout_request_deflated_base64 ||= File.read(File.join(File.dirname(__FILE__), 'logout_requests', 'slo_request_deflated.xml.base64')) - end + def logout_request_deflated_base64 + @logout_request_deflated_base64 ||= File.read(File.join(File.dirname(__FILE__), 'logout_requests', 'slo_request_deflated.xml.base64')) + end - def ruby_saml_cert - @ruby_saml_cert ||= OpenSSL::X509::Certificate.new(ruby_saml_cert_text) - end + def ruby_saml_cert + @ruby_saml_cert ||= OpenSSL::X509::Certificate.new(ruby_saml_cert_text) + end - def ruby_saml_cert2 - @ruby_saml_cert2 ||= OpenSSL::X509::Certificate.new(ruby_saml_cert_text2) - end + def ruby_saml_cert2 + @ruby_saml_cert2 ||= OpenSSL::X509::Certificate.new(ruby_saml_cert_text2) + end - def ruby_saml_cert_fingerprint - @ruby_saml_cert_fingerprint ||= Digest::SHA256.hexdigest(ruby_saml_cert.to_der).scan(/../).join(":") - end + def ruby_saml_cert_fingerprint + @ruby_saml_cert_fingerprint ||= Digest::SHA256.hexdigest(ruby_saml_cert.to_der).scan(/../).join(':') + end - def ruby_saml_cert_text - read_certificate("ruby-saml.crt") - end + def ruby_saml_cert_text + read_certificate('ruby-saml.crt') + end - def ruby_saml_cert_text2 - read_certificate("ruby-saml-2.crt") - end + def ruby_saml_cert_text2 + read_certificate('ruby-saml-2.crt') + end - def ruby_saml_key - @ruby_saml_key ||= OpenSSL::PKey::RSA.new(ruby_saml_key_text) - end + def ruby_saml_key + @ruby_saml_key ||= OpenSSL::PKey::RSA.new(ruby_saml_key_text) + end - def ruby_saml_key_text - read_certificate("ruby-saml.key") - end + def ruby_saml_key_text + read_certificate('ruby-saml.key') + end - # LogoutResponse fixtures - def random_id - "_#{RubySaml::Utils.generate_uuid}" - end + # LogoutResponse fixtures + def random_id + "_#{RubySaml::Utils.generate_uuid}" + end - # Decodes a base64 encoded SAML response for use in SloLogoutresponse tests - def decode_saml_response_payload(unauth_url) - payload = CGI.unescape(unauth_url.split("SAMLResponse=").last) - decoded = Base64.decode64(payload) + # Decodes a base64 encoded SAML response for use in SloLogoutresponse tests + def decode_saml_response_payload(unauth_url) + payload = CGI.unescape(unauth_url.split('SAMLResponse=').last) + decoded = Base64.decode64(payload) - zstream = Zlib::Inflate.new(-Zlib::MAX_WBITS) - inflated = zstream.inflate(decoded) - zstream.finish - zstream.close - inflated - end + zstream = Zlib::Inflate.new(-Zlib::MAX_WBITS) + inflated = zstream.inflate(decoded) + zstream.finish + zstream.close + inflated + end - # Decodes a base64 encoded SAML request for use in Logoutrequest tests - def decode_saml_request_payload(unauth_url) - payload = CGI.unescape(unauth_url.split("SAMLRequest=").last) - decoded = Base64.decode64(payload) + # Decodes a base64 encoded SAML request for use in Logoutrequest tests + def decode_saml_request_payload(unauth_url) + payload = CGI.unescape(unauth_url.split('SAMLRequest=').last) + decoded = Base64.decode64(payload) - zstream = Zlib::Inflate.new(-Zlib::MAX_WBITS) - inflated = zstream.inflate(decoded) - zstream.finish - zstream.close - inflated - end + zstream = Zlib::Inflate.new(-Zlib::MAX_WBITS) + inflated = zstream.inflate(decoded) + zstream.finish + zstream.close + inflated + end - # Validate an xml document against the given schema - def validate_xml!(document, schema) - result = load_schema(schema).validate(load_xml(document)) - raise "Schema validation failed! XSD validation errors: #{result.join(", ")}" if result.length != 0 - true - end + # Validate an xml document against the given schema + def validate_xml!(document, schema) + result = load_schema(schema).validate(load_xml(document)) + raise "Schema validation failed! XSD validation errors: #{result.join(', ')}" unless result.empty? - # Allows to emulate Azure AD request behavior - def downcased_escape(str) - CGI.escape(str).gsub(/%[A-Fa-f0-9]{2}/) { |match| match.downcase } - end + true + end - def encrypt_xml(assertion_xml, private_key) - # Generate a symmetric key (AES-256) - cipher = OpenSSL::Cipher.new('aes-256-cbc') - cipher.encrypt - symmetric_key = cipher.random_key - public_key = private_key.is_a?(OpenSSL::PKey::EC) ? private_key : private_key.public_key + # Allows to emulate Azure AD request behavior + def downcased_escape(str) + CGI.escape(str).gsub(/%[A-Fa-f0-9]{2}/, &:downcase) + end + + def encrypt_xml(assertion_xml, private_key) + # Generate a symmetric key (AES-256) + cipher = OpenSSL::Cipher.new('aes-256-cbc') + cipher.encrypt + symmetric_key = cipher.random_key + public_key = private_key.is_a?(OpenSSL::PKey::EC) ? private_key : private_key.public_key - # Encrypt the symmetric key with the RSA public key - encrypted_symmetric_key = Base64.encode64(public_key.public_encrypt(symmetric_key)) + # Encrypt the symmetric key with the RSA public key + encrypted_symmetric_key = Base64.encode64(public_key.public_encrypt(symmetric_key)) - # Encrypt the assertion XML with the symmetric key - cipher.key = symmetric_key - iv = cipher.random_iv - cipher.iv = iv - encrypted_assertion = cipher.update(assertion_xml) + cipher.final + # Encrypt the assertion XML with the symmetric key + cipher.key = symmetric_key + iv = cipher.random_iv + cipher.iv = iv + encrypted_assertion = cipher.update(assertion_xml) + cipher.final - # Base64 encode the encrypted assertion and IV - encrypted_assertion_base64 = Base64.encode64(encrypted_assertion) - iv_base64 = Base64.encode64(iv) + # Base64 encode the encrypted assertion and IV + encrypted_assertion_base64 = Base64.encode64(encrypted_assertion) + iv_base64 = Base64.encode64(iv) - # Build the EncryptedAssertion XML - encrypted_assertion_xml = <<-XML + # Build the EncryptedAssertion XML + <<-XML @@ -473,26 +475,25 @@ def encrypt_xml(assertion_xml, private_key) - XML - - encrypted_assertion_xml - end + XML + end - private + private - def load_schema(schema) - return schema if schema.is_a?(Nokogiri::XML::Schema) + def load_schema(schema) + return schema if schema.is_a?(Nokogiri::XML::Schema) - # Dir.chdir is necessary to load related schema files - Dir.chdir(File.expand_path('../lib/ruby_saml/schemas', __dir__)) do - Nokogiri::XML::Schema(File.read(schema)) + # Dir.chdir is necessary to load related schema files + Dir.chdir(File.expand_path('../lib/ruby_saml/schemas', __dir__)) do + Nokogiri::XML::Schema(File.read(schema)) + end end - end - def load_xml(document) - return document if document.is_a?(Nokogiri::XML::Document) + def load_xml(document) + return document if document.is_a?(Nokogiri::XML::Document) - Nokogiri::XML(document, &:strict) + Nokogiri::XML(document, &:strict) + end end end @@ -502,9 +503,9 @@ module JRubyZlibTestExtension def capture_exceptions super - if failures&.reject! { |e| e.error&.is_a?(Zlib::BufError) } # nil if nothing rejected - failures << Minitest::Skip.new('Skipping Zlib::BufError in JRuby. See: https://github.com/jruby/jruby/issues/6613') - end + return unless failures&.reject! { |e| e.error&.is_a?(Zlib::BufError) } # nil if nothing rejected + + failures << Minitest::Skip.new('Skipping Zlib::BufError in JRuby. See: https://github.com/jruby/jruby/issues/6613') end end diff --git a/test/utils_test.rb b/test/utils_test.rb index 02c403a1..9a2b81c4 100644 --- a/test/utils_test.rb +++ b/test/utils_test.rb @@ -276,13 +276,13 @@ def result(duration, reference = 0) end describe '.uri_match?' do - it 'matches two urls' do + it 'matches two URLs' do destination = 'http://www.example.com/test?var=stuff' settings = 'http://www.example.com/test?var=stuff' assert RubySaml::Utils.uri_match?(destination, settings) end - it 'fails to match two urls' do + it 'fails to match two URLs' do destination = 'http://www.example.com/test?var=stuff' settings = 'http://www.example.com/othertest?var=stuff' assert !RubySaml::Utils.uri_match?(destination, settings) @@ -312,13 +312,13 @@ def result(duration, reference = 0) assert !RubySaml::Utils.uri_match?(destination, settings) end - it 'matches two non urls' do + it 'matches two non URLs' do destination = 'stuff' settings = 'stuff' assert RubySaml::Utils.uri_match?(destination, settings) end - it "fails to match two non urls" do + it "fails to match two non URLs" do destination = 'stuff' settings = 'not stuff' assert !RubySaml::Utils.uri_match?(destination, settings) diff --git a/test/xml_security_shim_test.rb b/test/xml_security_shim_test.rb index 9a601a38..e85cab37 100644 --- a/test/xml_security_shim_test.rb +++ b/test/xml_security_shim_test.rb @@ -60,21 +60,21 @@ def test_document_constants end def test_document_sign_document_raises_no_method_error - doc = XMLSecurity::Document.new("") + doc = XMLSecurity::Document.new('') assert_raises(::NoMethodError) { doc.sign_document } end def test_signed_document_inherits_from_base_document - assert_kind_of XMLSecurity::BaseDocument, XMLSecurity::SignedDocument.new("") + assert_kind_of XMLSecurity::BaseDocument, XMLSecurity::SignedDocument.new('') end def test_signed_document_validate_document_raises_no_method_error - doc = XMLSecurity::SignedDocument.new("") + doc = XMLSecurity::SignedDocument.new('') assert_raises(::NoMethodError) { doc.validate_document } end def test_signed_document_extract_inclusive_namespaces_raises_no_method_error - doc = XMLSecurity::SignedDocument.new("") + doc = XMLSecurity::SignedDocument.new('') assert_raises(::NoMethodError) { doc.extract_inclusive_namespaces } end end diff --git a/test/xml_test.rb b/test/xml_test.rb index 8c947ad8..a930ffe0 100644 --- a/test/xml_test.rb +++ b/test/xml_test.rb @@ -383,7 +383,7 @@ class XmlTest < Minitest::Test let(:document_data) { read_invalid_response("response_with_doubled_signed_assertion.xml") } let(:fingerprint) { '6385109dd146a45d4382799491cb2707bd1ebda3738f27a0e4a4a8159c0fe6cd' } - it 'is valid, but the unsigned information is ignored in favour of the signed information' do + it 'is valid, but the unsigned information is ignored in favor of the signed information' do assert RubySaml::XML::SignedDocumentValidator.validate_document(document, fingerprint), 'Document should be valid' assert_equal 'someone@example.org', response.name_id, 'Document should expose only signed, valid details' end @@ -394,7 +394,7 @@ class XmlTest < Minitest::Test let(:document) { RubySaml::Response.new(document_data) } let(:fingerprint) { '6385109dd146a45d4382799491cb2707bd1ebda3738f27a0e4a4a8159c0fe6cd' } - it 'is valid, but the unsigned information is ignored in favour of the signed information' do + it 'is valid, but the unsigned information is ignored in favor of the signed information' do assert RubySaml::XML::SignedDocumentValidator.validate_document(document.document, fingerprint), 'Document should be valid' assert_equal 'someone@example.org', document.name_id, 'Document should expose only signed, valid details' end From a2e1fcfddfe1118556c9cf9b79de5a0592a4fe70 Mon Sep 17 00:00:00 2001 From: Sixto Martin Date: Fri, 21 Nov 2025 20:22:25 +0100 Subject: [PATCH 5/7] Improve the inflate method. Prevent potential DoS vulnerability in Zlib::Inflate by limiting the maximum decompressed size. The data is now inflated in chunks. --- lib/ruby_saml/xml/decoder.rb | 40 +++++++++++++++++++++++++++++++----- 1 file changed, 35 insertions(+), 5 deletions(-) diff --git a/lib/ruby_saml/xml/decoder.rb b/lib/ruby_saml/xml/decoder.rb index 10e12787..c078dc31 100644 --- a/lib/ruby_saml/xml/decoder.rb +++ b/lib/ruby_saml/xml/decoder.rb @@ -23,7 +23,7 @@ def decode_message(message, max_bytesize = nil) return message unless base64_encoded?(message) - message = try_inflate(base64_decode(message)) + message = try_inflate(base64_decode(message), max_bytesize) if message.bytesize > max_bytesize # rubocop:disable Style/IfUnlessModifier raise ValidationError.new("SAML Message exceeds #{max_bytesize} bytes, so was rejected") @@ -66,18 +66,48 @@ def base64_encoded?(string) # Attempt inflating a string, if it fails, return the original string. # @param data [String] The string + # @param max_bytesize [Integer] The maximum allowed size of the SAML Message, + # to prevent a possible DoS attack. # @return [String] The inflated or original string - def try_inflate(data) - inflate(data) + def try_inflate(data, max_bytesize = nil) + inflate(data, max_bytesize) rescue Zlib::Error data end # Inflate method. # @param deflated [String] The string + # @param max_bytesize [Integer] The maximum allowed size of the SAML Message, + # to prevent a possible DoS attack. # @return [String] The inflated string - def inflate(deflated) - Zlib::Inflate.new(-Zlib::MAX_WBITS).inflate(deflated) + def inflate(deflated, max_bytesize = nil) + unless max_bytesize.nil? + inflater = Zlib::Inflate.new(-Zlib::MAX_WBITS) + + # Use a StringIO buffer to build the inflated message incrementally. + buffer = StringIO.new + + inflater.inflate(deflated) do |chunk| + if buffer.length + chunk.bytesize > max_bytesize + inflater.close + raise ValidationError, "SAML Message exceeds #{max_bytesize} bytes during decompression, so was rejected" + end + buffer << chunk + end + + final_chunk = inflater.finish + unless final_chunk.empty? + if buffer.length + final_chunk.bytesize > max_bytesize + raise ValidationError, "SAML Message exceeds #{max_bytesize} bytes during decompression, so was rejected" + end + buffer << final_chunk + end + + inflater.close + buffer.string + else + Zlib::Inflate.new(-Zlib::MAX_WBITS).inflate(deflated) + end end # Deflate method. From 3e2328f5e34d198c5b0630a136b5ac6905af12d6 Mon Sep 17 00:00:00 2001 From: Sixto Martin Date: Sat, 22 Nov 2025 20:00:14 +0100 Subject: [PATCH 6/7] Add to the README how to force SP-Initiate flow and Prevent Reply Attacks --- README.md | 109 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 107 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index af6facef..93954bd9 100644 --- a/README.md +++ b/README.md @@ -687,7 +687,7 @@ advanced usage scenarios: - Specifying separate SP certificates for signing and encryption. The `sp_cert_multi` parameter replaces `certificate` and `private_key` -(you may not specify both pparameters at the same time.) `sp_cert_multi` has the following shape: +(you may not specify both parameters at the same time.) `sp_cert_multi` has the following shape: ```ruby settings.sp_cert_multi = { @@ -910,7 +910,7 @@ end ### Attribute Service -To request attributes from the IdP the SP needs to provide an attribute service within it's metadata and reference the index in the assertion. +To request attributes from the IdP the SP needs to provide an attribute service within its metadata and reference the index in the assertion. ```ruby settings = RubySaml::Settings.new @@ -964,6 +964,111 @@ end MyMetadata.new.generate(settings) ``` +### Preventing Replay Attacks + +A replay attack is when an attacker intercepts a valid SAML assertion and "replays" it at a later time to gain unauthorized access. + +The library only checks the assertion's validity window (`NotBefore` and `NotOnOrAfter` conditions). An attacker can replay a valid assertion as many times as they want within this window. + +A robust defense requires tracking of assertion IDs to ensure any given assertion is only accepted once. + +#### 1. Extract the Assertion ID after Validation + +After a response has been successfully validated, get the assertion ID. The library makes this available via `response.assertion_id`. + + +#### 2. Store the ID with an Expiry + +You must store this ID in a persistent cache (like Redis or Memcached) that is shared across your servers. Do not store it in the user's session, as that is not a secure cache. + +The ID should be stored until the assertion's validity window has passed. You will need to check how long the trusted IdPs consider the assertion valid and then add the allowed_clock_drift. + +You can define a global value, or set this value dinamically based on the `not_on_or_after` value of the re + `allowed_clock_drift`. + +```ruby +# In your `consume` action, after a successful validation: +if response.is_valid? + # Prevent replay of this specific assertion + assertion_id = response.assertion_id + authorize_failure("Assertion ID is mandatory") if assertion_id.nil? + + assertion_not_on_or_after = response.not_on_or_after + # We set a default of 5 min expiration in case is not provided + assertion_expiry = (Time.now.utc + 300) if assertion_not_on_or_after.nil? + + # `is_new_assertion?` is your application's method to check and set the ID + # in a shared, persistent cache (e.g., Redis, Memcached). + if is_new_assertion?(assertion_id, expires_at: assertion_expiry) + # This is a new assertion, so we can proceed + session[:userid] = response.nameid + session[:attributes] = response.attributes + # ... + else + # This assertion ID has been seen before. This is a REPLAY ATTACK. + # Log the security event and reject the user. + authorize_failure("Replay attack detected") + end +else + authorize_failure("Invalid response") +end +``` + +Your `is_new_assertion?` method would look something like this (example for Redis): + +```ruby + +def is_new_assertion?(assertion_id, expires_at) + ttl = (expires_at - Time.now.utc).to_i + return false if ttl <= 0 # The assertion has already expired + + # The 'nx' option tells Redis to only set the key if it does not already exist. + # The command returns `true` if the key was set, `false` otherwise. + $redis.set("saml_assertion_ids:#{assertion_id}", "1", ex: ttl, nx: true) +end +``` + +### Enforce SP-Initiated Flow with `InResponseTo` validation + +This is the best way to prevent IdP-initiated logins and ensure that you only accept assertions that you recently requested. + +#### 1. Store the `AuthnRequest` ID + +When you create an `AuthnRequest`, the library assigns it a unique ID. You must store this ID, for example in the user's session *before* redirecting them to the IdP. + +```ruby +def init + request = OneLogin::RubySaml::Authrequest.new + # The unique ID of the request is in request.uuid + session[:saml_request_id] = request.uuid + redirect_to(request.create(saml_settings)) +end +``` + +#### 2. Validate the `InResponseTo` value of the `Response` with the Stored ID + +When you process the `SAMLResponse`, retrieve the ID from the session and pass it to the `Response` constructor. Use `session.delete` to ensure the ID can only be used once. + +```ruby +def consume + request_id = session.delete(:saml_request_id) # Use delete to prevent re-use + + # You can reject the response if no previous saml_request_id was stored + raise "IdP-initiaited detected" if request_id.nil? + + response = OneLogin::RubySaml::Response.new( + params[:SAMLResponse], + settings: saml_settings, + matches_request_id: request_id + ) + + if response.is_valid? + # ... authorize user + else + # Response is invalid, errors in response.errors + end +end +``` + ## Contributing ### Pay it Forward: Support RubySAML and Strengthen Open-Source Security From d28bb3aca53e7a6dc3072f5e3aa4fb3ec82c16b9 Mon Sep 17 00:00:00 2001 From: Sixto Martin Date: Sat, 22 Nov 2025 20:06:12 +0100 Subject: [PATCH 7/7] Fix RuboCop --- lib/ruby_saml/xml/decoder.rb | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/lib/ruby_saml/xml/decoder.rb b/lib/ruby_saml/xml/decoder.rb index c078dc31..917ead3c 100644 --- a/lib/ruby_saml/xml/decoder.rb +++ b/lib/ruby_saml/xml/decoder.rb @@ -81,7 +81,9 @@ def try_inflate(data, max_bytesize = nil) # to prevent a possible DoS attack. # @return [String] The inflated string def inflate(deflated, max_bytesize = nil) - unless max_bytesize.nil? + if max_bytesize.nil? + Zlib::Inflate.new(-Zlib::MAX_WBITS).inflate(deflated) + else inflater = Zlib::Inflate.new(-Zlib::MAX_WBITS) # Use a StringIO buffer to build the inflated message incrementally. @@ -90,23 +92,20 @@ def inflate(deflated, max_bytesize = nil) inflater.inflate(deflated) do |chunk| if buffer.length + chunk.bytesize > max_bytesize inflater.close - raise ValidationError, "SAML Message exceeds #{max_bytesize} bytes during decompression, so was rejected" + raise ValidationError.new("SAML Message exceeds #{max_bytesize} bytes during decompression, so was rejected") end buffer << chunk end final_chunk = inflater.finish unless final_chunk.empty? - if buffer.length + final_chunk.bytesize > max_bytesize - raise ValidationError, "SAML Message exceeds #{max_bytesize} bytes during decompression, so was rejected" - end + raise ValidationError.new("SAML Message exceeds #{max_bytesize} bytes during decompression, so was rejected") if buffer.length + final_chunk.bytesize > max_bytesize + buffer << final_chunk end inflater.close buffer.string - else - Zlib::Inflate.new(-Zlib::MAX_WBITS).inflate(deflated) end end