Comparing changes

v1.0.14 v1.0.15
22 commits 47 files changed

Commits

d06af18 bump version. mo 2018-03-12 01:44:21
1c1a203 use block instead. mo 2018-03-12 01:39:27
fba39b9 extract variable. mo 2018-03-10 18:32:50
fef2c2c remove duplicate method calls. mo 2018-03-10 18:30:48
0a9afb1 fix reek warnings. mo 2018-03-10 18:26:05
7f7e18d increase max_methods to 30. mo 2018-03-10 18:13:47
e33aee1 increase max_instance_variables. mo 2018-03-10 18:11:23
c699b88 remove ! mo 2018-03-10 18:08:22
86c847b fix reek warning. mo 2018-03-10 18:07:39
e2109a1 fix reek warning. mo 2018-03-10 18:05:55
fc948ea fix reek warnings. mo 2018-03-10 18:03:17
7887115 fix reek warning. mo 2018-03-10 17:47:34
cc6302d fix some reek warnings. mo 2018-03-10 17:42:30
3a88047 create the reek todo list. mo 2018-03-10 17:33:18
8ec03e9 describe module. mo 2018-03-10 17:15:23
f523e77 add reek linter. mo 2018-03-10 17:15:14
0858b2b try to parse the request id. mo 2018-03-09 21:50:15
lib/saml/kit/bindings/binding.rb
@@ -3,6 +3,7 @@
 module Saml
   module Kit
     module Bindings
+      # This class is a base class for SAML bindings.
       # {include:file:spec/saml/kit/bindings/binding_spec.rb}
       class Binding
         attr_reader :binding, :location
lib/saml/kit/bindings/http_post.rb
@@ -3,6 +3,11 @@
 module Saml
   module Kit
     module Bindings
+      # This class is responsible for
+      # serializing/deserializing SAML
+      # documents using the HTTP Post
+      # binding specification.
+      # https://docs.oasis-open.org/security/saml/v2.0/saml-bindings-2.0-os.pdf
       # {include:file:spec/saml/kit/bindings/http_post_spec.rb}
       class HttpPost < Binding
         include Serializable
lib/saml/kit/bindings/http_redirect.rb
@@ -3,6 +3,11 @@
 module Saml
   module Kit
     module Bindings
+      # This class is responsible for
+      # serializing/deserializing SAML
+      # documents using the HTTP Redirect
+      # binding specification.
+      # https://docs.oasis-open.org/security/saml/v2.0/saml-bindings-2.0-os.pdf
       # {include:file:spec/saml/kit/bindings/http_redirect_spec.rb}
       class HttpRedirect < Binding
         include Serializable
@@ -20,31 +25,31 @@ module Saml
 
         def deserialize(params, configuration: Saml::Kit.configuration)
           parameters = normalize(params_to_hash(params))
-          document = deserialize_document_from!(parameters, configuration)
-          ensure_valid_signature!(parameters, document)
+          document = deserialize_document_from(parameters, configuration)
+          ensure_valid_signature(parameters, document)
           document
         end
 
         private
 
-        def deserialize_document_from!(params, configuration)
+        def deserialize_document_from(params, configuration)
           xml = inflate(decode(unescape(saml_param_from(params))))
           Saml::Kit::Document.to_saml_document(xml, configuration: configuration)
         end
 
-        def ensure_valid_signature!(params, document)
-          return if params[:Signature].blank? || params[:SigAlg].blank?
-          return if document.provider.nil?
+        def ensure_valid_signature(params, document)
+          signature = params[:Signature]
+          algorithm = params[:SigAlg]
+          provider = document.provider
+          return if signature.blank? || algorithm.blank?
+          return if provider.nil?
 
-          if document.provider.verify(
-            algorithm_for(params[:SigAlg]),
-            decode(params[:Signature]),
+          return document.signature_verified! if provider.verify(
+            algorithm_for(algorithm),
+            decode(signature),
             canonicalize(params)
           )
-            document.signature_verified!
-          else
-            raise ArgumentError, 'Invalid Signature'
-          end
+          raise ArgumentError, 'Invalid Signature'
         end
 
         def canonicalize(params)
@@ -79,7 +84,7 @@ module Saml
 
         def params_to_hash(value)
           return value unless value.is_a?(String)
-          Hash[URI.parse(value).query.split('&').map { |x| x.split('=', 2) }]
+          Hash[URI.parse(value).query.split('&').map { |xx| xx.split('=', 2) }]
         end
       end
     end
lib/saml/kit/bindings/url_builder.rb
@@ -3,6 +3,11 @@
 module Saml
   module Kit
     module Bindings
+      # This class is responsible for
+      # generating a url as per the
+      # rules for the HTTP redirect binding
+      # specification.
+      # https://docs.oasis-open.org/security/saml/v2.0/saml-bindings-2.0-os.pdf
       # {include:file:spec/saml/kit/bindings/url_builder_spec.rb}
       class UrlBuilder
         include Serializable
@@ -13,15 +18,16 @@ module Saml
         end
 
         def build(saml_document, relay_state: nil)
+          destination = saml_document.destination
           if configuration.sign?
             payload = canonicalize(saml_document, relay_state)
-            "#{saml_document.destination}?#{payload}&Signature=#{signature_for(payload)}"
+            "#{destination}?#{payload}&Signature=#{signature_for(payload)}"
           else
             payload = to_query_string(
               saml_document.query_string_parameter => serialize(saml_document.to_xml),
               'RelayState' => relay_state
             )
-            "#{saml_document.destination}?#{payload}"
+            "#{destination}?#{payload}"
           end
         end
 
lib/saml/kit/builders/templates/null.builder
@@ -0,0 +1,1 @@
+# frozen_string_literal: true
lib/saml/kit/builders/templates/response.builder
@@ -6,6 +6,7 @@ xml.Response response_options do
   signature_for(reference_id: id, xml: xml)
   xml.Status do
     xml.StatusCode Value: status_code
+    xml.StatusMessage(status_message) if status_message.present?
   end
   render assertion, xml: xml
 end
lib/saml/kit/builders/assertion.rb
@@ -3,6 +3,7 @@
 module Saml
   module Kit
     module Builders
+      # This class is responsible for building a SAML Assertion
       # {include:file:lib/saml/kit/builders/templates/assertion.builder}
       class Assertion
         include XmlTemplatable
lib/saml/kit/builders/encrypted_assertion.rb
@@ -3,6 +3,7 @@
 module Saml
   module Kit
     module Builders
+      # This class is responsible for encrypting an Assertion.
       # {include:file:lib/saml/kit/builders/templates/encrypted_assertion.builder}
       class EncryptedAssertion
         include XmlTemplatable
lib/saml/kit/builders/metadata.rb
@@ -22,14 +22,14 @@ module Saml
         end
 
         def build_service_provider
-          @service_provider = Saml::Kit::ServiceProviderMetadata.builder(configuration: configuration) do |x|
-            yield x if block_given?
+          @service_provider = Saml::Kit::ServiceProviderMetadata.builder(configuration: configuration) do |xx|
+            yield xx if block_given?
           end
         end
 
         def build_identity_provider
-          @identity_provider = Saml::Kit::IdentityProviderMetadata.builder(configuration: configuration) do |x|
-            yield x if block_given?
+          @identity_provider = Saml::Kit::IdentityProviderMetadata.builder(configuration: configuration) do |xx|
+            yield xx if block_given?
           end
         end
 
lib/saml/kit/builders/null.rb
@@ -0,0 +1,13 @@
+
+# frozen_string_literal: true
+
+module Saml
+  module Kit
+    module Builders
+      # This class is responsible for rendering an empty xml element
+      class Null
+        include XmlTemplatable
+      end
+    end
+  end
+end
lib/saml/kit/builders/response.rb
@@ -9,7 +9,7 @@ module Saml
         include XmlTemplatable
         attr_reader :user, :request
         attr_accessor :id, :reference_id, :now
-        attr_accessor :version, :status_code
+        attr_accessor :version, :status_code, :status_message
         attr_accessor :issuer, :destination
         attr_reader :configuration
 
@@ -21,6 +21,7 @@ module Saml
           @now = Time.now.utc
           @version = '2.0'
           @status_code = Namespaces::SUCCESS
+          @status_message = nil
           @issuer = configuration.entity_id
           @encryption_certificate = request.try(:provider).try(:encryption_certificates).try(:last)
           @encrypt = encryption_certificate.present?
@@ -31,6 +32,10 @@ module Saml
           Saml::Kit::Response.new(to_xml, request_id: request.try(:id), configuration: configuration)
         end
 
+        def assertion=(value)
+          @assertion = value ? value : Null.new
+        end
+
         def assertion
           @assertion ||=
             begin
@@ -54,7 +59,7 @@ module Saml
             xmlns: Namespaces::PROTOCOL,
           }
           options[:Destination] = destination if destination.present?
-          options[:InResponseTo] = request.id if request.present?
+          options[:InResponseTo] = request.try(:id) if request.present?
           options
         end
       end
lib/saml/kit/rspec/have_query_param.rb
@@ -8,7 +8,7 @@ RSpec::Matchers.define :have_query_param do |key|
   end
 
   def query_params_from(url)
-    Hash[query_for(url).split('&').map { |x| x.split('=', 2) }]
+    Hash[query_for(url).split('&').map { |xxx| xxx.split('=', 2) }]
   end
 
   def uri_for(url)
lib/saml/kit/assertion.rb
@@ -2,6 +2,9 @@
 
 module Saml
   module Kit
+    # This class validates the Assertion
+    # element nested in a Response element
+    # of a SAML document.
     class Assertion
       include ActiveModel::Validations
       include Translatable
@@ -22,8 +25,10 @@ module Saml
         @node = node
         @configuration = configuration
         @occurred_at = Time.current
+        @cannot_decrypt = false
+        @encrypted = false
         private_keys = (configuration.private_keys(use: :encryption) + private_keys).uniq
-        decrypt!(::Xml::Kit::Decryption.new(private_keys: private_keys))
+        decrypt(::Xml::Kit::Decryption.new(private_keys: private_keys))
       end
 
       def issuer
@@ -83,15 +88,19 @@ module Saml
         @node.present?
       end
 
-      def to_xml(pretty: false)
-        pretty ? @node.to_xml(indent: 2) : @node.to_s
+      def to_xml(pretty: nil)
+        pretty ? @node.to_xml(indent: 2) : to_s
+      end
+
+      def to_s
+        @node.to_s
       end
 
       private
 
       attr_reader :configuration
 
-      def decrypt!(decryptor)
+      def decrypt(decryptor)
         encrypted_assertion = at_xpath('./xmlenc:EncryptedData')
         @encrypted = encrypted_assertion.present?
         return unless @encrypted
lib/saml/kit/authentication_request.rb
@@ -54,11 +54,11 @@ module Saml
       # @param configuration [Saml::Kit::Configuration] the configuration to use to build the response.
       def response_for(user, binding:, relay_state: nil, configuration: Saml::Kit.configuration)
         response_binding = provider.assertion_consumer_service_for(binding: binding)
-        builder = Saml::Kit::Response.builder(user, self, configuration: configuration) do |x|
-          x.embed_signature = provider.want_assertions_signed
-          yield x if block_given?
+        response = Saml::Kit::Response.builder(user, self, configuration: configuration) do |builder|
+          builder.embed_signature = provider.want_assertions_signed
+          yield builder if block_given?
         end
-        response_binding.serialize(builder, relay_state: relay_state)
+        response_binding.serialize(response, relay_state: relay_state)
       end
     end
   end
lib/saml/kit/bindings.rb
@@ -7,6 +7,9 @@ require 'saml/kit/bindings/url_builder'
 
 module Saml
   module Kit
+    # This module is responsible for exposing
+    # the different SAML bindings that are
+    # supported by this gem.
     module Bindings
       HTTP_ARTIFACT = 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact'.freeze
       HTTP_POST = 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST'.freeze
lib/saml/kit/buildable.rb
@@ -2,6 +2,9 @@
 
 module Saml
   module Kit
+    # This module is responsible for
+    # providing an API to build a
+    # document object, xml, or builder class.
     module Buildable
       extend ActiveSupport::Concern
 
lib/saml/kit/builders.rb
@@ -8,11 +8,14 @@ require 'saml/kit/builders/identity_provider_metadata'
 require 'saml/kit/builders/logout_request'
 require 'saml/kit/builders/logout_response'
 require 'saml/kit/builders/metadata'
+require 'saml/kit/builders/null'
 require 'saml/kit/builders/response'
 require 'saml/kit/builders/service_provider_metadata'
 
 module Saml
   module Kit
+    # This module contains all the builders classes
+    # that are used to create SAML documents.
     module Builders
     end
   end
lib/saml/kit/composite_metadata.rb
@@ -2,6 +2,11 @@
 
 module Saml
   module Kit
+    # This class implements the Composite
+    # design pattern to allow client
+    # component to work with a metadata
+    # that provides an IDPSSODescriptor
+    # and SPSSODescriptor element.
     class CompositeMetadata < Metadata # :nodoc:
       include Enumerable
       attr_reader :service_provider, :identity_provider
@@ -15,7 +20,7 @@ module Saml
       end
 
       def services(type)
-        xpath = map { |x| "//md:EntityDescriptor/md:#{x.name}/md:#{type}" }.join('|')
+        xpath = map { |xxx| "//md:EntityDescriptor/md:#{xxx.name}/md:#{type}" }.join('|')
         search(xpath).map do |item|
           binding = item.attribute('Binding').value
           location = item.attribute('Location').value
@@ -32,7 +37,7 @@ module Saml
       end
 
       def method_missing(name, *args)
-        if (target = find { |x| x.respond_to?(name) })
+        if (target = find { |xxx| xxx.respond_to?(name) })
           target.public_send(name, *args)
         else
           super
@@ -40,7 +45,7 @@ module Saml
       end
 
       def respond_to_missing?(method, *)
-        find { |x| x.respond_to?(method) }
+        find { |xxx| xxx.respond_to?(method) }
       end
     end
   end
lib/saml/kit/configuration.rb
@@ -56,7 +56,7 @@ module Saml
       # @param passphrase [String] the password to decrypt the private key.
       # @param use [Symbol] the type of key pair, `:signing` or `:encryption`
       def add_key_pair(certificate, private_key, passphrase: nil, use: :signing)
-        ensure_proper_use!(use)
+        ensure_proper_use(use)
         @key_pairs.push(::Xml::Kit::KeyPair.new(certificate, private_key, passphrase, use.to_sym))
       end
 
@@ -65,7 +65,7 @@ module Saml
       # @param use [Symbol] the type of key pair, `:signing` or `:encryption`
       # @param passphrase [String] the private key passphrase to use.
       def generate_key_pair_for(use:, passphrase: SecureRandom.uuid)
-        ensure_proper_use!(use)
+        ensure_proper_use(use)
         certificate, private_key = ::Xml::Kit::SelfSignedCertificate.new.create(passphrase: passphrase)
         add_key_pair(certificate, private_key, passphrase: passphrase, use: use)
       end
@@ -74,7 +74,7 @@ module Saml
       #
       # @param use [Symbol] the type of key pair to return `nil`, `:signing` or `:encryption`
       def key_pairs(use: nil)
-        use.present? ? @key_pairs.find_all { |x| x.for?(use) } : @key_pairs
+        use.present? ? @key_pairs.find_all { |xxx| xxx.for?(use) } : @key_pairs
       end
 
       # Return each certificate for a specific use.
@@ -93,12 +93,12 @@ module Saml
 
       # Returns true if there is at least one signing certificate registered.
       def sign?
-        certificates(use: :signing).any?
+        @sign ||= certificates(use: :signing).any?
       end
 
       private
 
-      def ensure_proper_use!(use)
+      def ensure_proper_use(use)
         return if USES.include?(use)
 
         error_message = 'Use must be either :signing or :encryption'
lib/saml/kit/default_registry.rb
@@ -69,6 +69,9 @@ module Saml
         end
       end
 
+      # This class is responsible for
+      # making HTTP requests to fetch metadata
+      # from remote locations.
       class HttpApi # :nodoc:
         def initialize(url, verify_ssl: true)
           @uri = URI.parse(url)
lib/saml/kit/document.rb
@@ -2,6 +2,7 @@
 
 module Saml
   module Kit
+    # This class is a base class for SAML documents.
     class Document
       include ActiveModel::Validations
       include XsdValidatable
@@ -65,8 +66,8 @@ module Saml
       # Returns the SAML document as an XML string.
       #
       # @param pretty [Boolean] formats the xml or returns the raw xml.
-      def to_xml(pretty: false)
-        pretty ? to_nokogiri.to_xml(indent: 2) : content
+      def to_xml(pretty: nil)
+        pretty ? to_nokogiri.to_xml(indent: 2) : to_s
       end
 
       # Returns the SAML document as an XHTML string.
@@ -77,7 +78,7 @@ module Saml
 
       # @!visibility private
       def to_nokogiri
-        @to_nokogiri ||= Nokogiri::XML(content)
+        @to_nokogiri ||= Nokogiri::XML(to_s)
       end
 
       # @!visibility private
@@ -91,7 +92,7 @@ module Saml
       end
 
       def to_s
-        to_xml
+        content
       end
 
       class << self
lib/saml/kit/identity_provider_metadata.rb
@@ -74,9 +74,9 @@ module Saml
       # @param configuration [Saml::Kit::Configuration] the configuration to use for generating the request.
       # @return [Array] The url and saml params encoded using the rules for the specified binding.
       def login_request_for(binding:, relay_state: nil, configuration: Saml::Kit.configuration)
-        builder = Saml::Kit::AuthenticationRequest.builder(configuration: configuration) do |x|
-          x.embed_signature = want_authn_requests_signed
-          yield x if block_given?
+        builder = Saml::Kit::AuthenticationRequest.builder(configuration: configuration) do |xxx|
+          xxx.embed_signature = want_authn_requests_signed
+          yield xxx if block_given?
         end
         request_binding = single_sign_on_service_for(binding: binding)
         request_binding.serialize(builder, relay_state: relay_state)
lib/saml/kit/invalid_document.rb
@@ -2,6 +2,8 @@
 
 module Saml
   module Kit
+    # This class represents an invalid SAML
+    # document that could not be parsed.
     # {include:file:spec/saml/kit/invalid_document_spec.rb}
     class InvalidDocument < Document
       validate do |model|
lib/saml/kit/logout_request.rb
@@ -51,8 +51,8 @@ module Saml
       # @param relay_state [Object] The RelayState to include in the RelayState param.
       # @return [Array] Returns an array with a url and Hash of parameters to return to the requestor.
       def response_for(binding:, relay_state: nil)
-        builder = Saml::Kit::LogoutResponse.builder(self) do |x|
-          yield x if block_given?
+        builder = Saml::Kit::LogoutResponse.builder(self) do |xxx|
+          yield xxx if block_given?
         end
         response_binding = provider.single_logout_service_for(binding: binding)
         response_binding.serialize(builder, relay_state: relay_state)
lib/saml/kit/metadata.rb
@@ -110,7 +110,7 @@ module Saml
       # @param type [Symbol] can be on the service element like `AssertionConsumerServiceURL`, `SingleSignOnService` or `SingleLogoutService`.
       def service_for(binding:, type:)
         binding = Saml::Kit::Bindings.binding_for(binding)
-        services(type).find { |x| x.binding?(binding) }
+        services(type).find { |xxx| xxx.binding?(binding) }
       end
 
       # Returns each of the SingleLogoutService bindings
@@ -132,7 +132,7 @@ module Saml
       # @param relay_state [String] the relay state to have echo'd back.
       # @return [Array] Returns an array with a url and Hash of parameters to send to the other party.
       def logout_request_for(user, binding: :http_post, relay_state: nil)
-        builder = Saml::Kit::LogoutRequest.builder(user) { |x| yield x if block_given? }
+        builder = Saml::Kit::LogoutRequest.builder(user) { |xxx| yield xxx if block_given? }
         request_binding = single_logout_service_for(binding: binding)
         request_binding.serialize(builder, relay_state: relay_state)
       end
@@ -143,7 +143,7 @@ module Saml
       # @param use [Symbol] the type of certificates to look at. Can be `:signing` or `:encryption`.
       # @return [Xml::Kit::Certificate] returns the matching `{Xml::Kit::Certificate}`
       def matches?(fingerprint, use: :signing)
-        certificates.find { |x| x.for?(use) && x.fingerprint == fingerprint }
+        certificates.find { |xxx| xxx.for?(use) && xxx.fingerprint == fingerprint }
       end
 
       # Returns the XML document converted to a Hash.
@@ -153,14 +153,14 @@ module Saml
 
       # Returns the XML document as a String.
       #
-      # @param pretty [Symbol] true to return a human friendly version of the XML.
-      def to_xml(pretty: false)
-        pretty ? to_nokogiri.to_xml(indent: 2) : @xml
+      # @param pretty [Boolean] true to return a human friendly version of the XML.
+      def to_xml(pretty: nil)
+        pretty ? to_nokogiri.to_xml(indent: 2) : to_s
       end
 
       # Returns the XML document as a [String].
       def to_s
-        to_xml
+        @xml
       end
 
       # Verifies the signature and data using the signing certificates.
lib/saml/kit/null_assertion.rb
@@ -2,6 +2,9 @@
 
 module Saml
   module Kit
+    # This class is an implementation of the
+    # Null Object pattern for when a Response
+    # is missing an Assertion.
     class NullAssertion
       include ActiveModel::Validations
       include Translatable
lib/saml/kit/requestable.rb
@@ -2,6 +2,10 @@
 
 module Saml
   module Kit
+    # This module is responsible for providing
+    # the functionality available to all
+    # SAML request documents.
+    # e.g. AuthnRequest, LogoutRequest.
     module Requestable
       extend ActiveSupport::Concern
 
lib/saml/kit/respondable.rb
@@ -2,6 +2,9 @@
 
 module Saml
   module Kit
+    # This module provides the behaviours
+    # associated with SAML Response documents.
+    # .e.g. Response, LogoutResponse
     module Respondable
       extend ActiveSupport::Concern
       attr_reader :request_id
@@ -21,6 +24,11 @@ module Saml
         at_xpath('./*/samlp:Status/samlp:StatusCode/@Value').try(:value)
       end
 
+      # Returns the /Status/StatusMessage
+      def status_message
+        at_xpath('./*/samlp:Status/samlp:StatusMessage').try(:text)
+      end
+
       # Returns the /InResponseTo attribute.
       def in_response_to
         at_xpath('./*/@InResponseTo').try(:value)
lib/saml/kit/response.rb
@@ -2,6 +2,8 @@
 
 module Saml
   module Kit
+    # This class is responsible for validating and
+    # parsing a SAML Response document.
     # {include:file:spec/examples/response_spec.rb}
     class Response < Document
       include Respondable
lib/saml/kit/serializable.rb
@@ -2,6 +2,7 @@
 
 module Saml
   module Kit
+    # This module is responsible for serializing/deserialing values.
     module Serializable
       # Base 64 decodes the value.
       #
lib/saml/kit/service_provider_metadata.rb
@@ -2,6 +2,9 @@
 
 module Saml
   module Kit
+    # This class represents a
+    # SPSSODescriptor element in a
+    # SAML metadata document.
     # {include:file:spec/examples/service_provider_metadata_spec.rb}
     class ServiceProviderMetadata < Metadata
       def initialize(xml)
lib/saml/kit/signature.rb
@@ -2,6 +2,9 @@
 
 module Saml
   module Kit
+    # This class is responsible for
+    # validating an xml digital signature
+    # in an xml document.
     class Signature
       include ActiveModel::Validations
       include Translatable
@@ -34,8 +37,8 @@ module Saml
       end
 
       def expected_digest_value
-        digests = dsignature.references.map do |x|
-          Base64.encode64(x.calculate_digest_value).chomp
+        digests = dsignature.references.map do |xxx|
+          Base64.encode64(xxx.calculate_digest_value).chomp
         end
         digests.count > 1 ? digests : digests[0]
       end
@@ -69,8 +72,12 @@ module Saml
         node.present?
       end
 
-      def to_xml(pretty: false)
-        pretty ? node.to_xml(indent: 2) : node.to_s
+      def to_xml(pretty: nil)
+        pretty ? node.to_xml(indent: 2) : to_s
+      end
+
+      def to_s
+        node.to_s
       end
 
       private
lib/saml/kit/translatable.rb
@@ -2,6 +2,9 @@
 
 module Saml
   module Kit
+    # This module is responsible
+    # for translating error messages
+    # to the current locale.
     module Translatable
       # @!visibility private
       def error_message(attribute, options = {})
lib/saml/kit/trustable.rb
@@ -2,6 +2,9 @@
 
 module Saml
   module Kit
+    # This module is responsible for
+    # validating the trustworthiness
+    # of a saml document.
     module Trustable
       extend ActiveSupport::Concern
 
lib/saml/kit/version.rb
@@ -2,6 +2,6 @@
 
 module Saml
   module Kit
-    VERSION = '1.0.14'.freeze
+    VERSION = '1.0.15'.freeze
   end
 end
lib/saml/kit/xml_templatable.rb
@@ -2,6 +2,8 @@
 
 module Saml
   module Kit
+    # This module is responsible for
+    # generating converting templates to xml.
     module XmlTemplatable
       TEMPLATES_DIR = Pathname.new(File.join(__dir__, 'builders/templates/'))
       include ::Xml::Kit::Templatable
lib/saml/kit/xsd_validatable.rb
@@ -2,6 +2,8 @@
 
 module Saml
   module Kit
+    # This module is responsible for validating
+    # xml documents against the SAML XSD's
     module XsdValidatable
       # @!visibility private
       def matches_xsd?(xsd)
lib/saml/kit.rb
@@ -48,7 +48,11 @@ I18n.load_path +=
   Dir[File.expand_path('kit/locales/*.yml', File.dirname(__FILE__))]
 
 module Saml
+  # This module is the container for all classes/modules in this gem.
   module Kit
+    # This class provides a global access to the
+    # default SAML configuration. This is useful
+    # for long running processes.
     class << self
       def configuration
         @configuration ||= Saml::Kit::Configuration.new
spec/fixtures/no_nameid.saml_response.erb
@@ -0,0 +1,25 @@
+<saml2p:Response xmlns:saml2p="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:xsd="http://www.w3.org/2001/XMLSchema" Destination="<%= fetch(:destination, 'https://www.example.com') %>" ID="_d2b481a92e895f1436189403b7d5ccd1" InResponseTo="<%= fetch(:in_response_to, "_4db60150-227a-439b-a686-e7c57a9b5f9a") %>" IssueInstant="<%= issue_instant.iso8601 %>" Version="2.0">
+  <saml2:Issuer xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion"><%= fetch(:issuer, 'https://www.example.com') %></saml2:Issuer>
+  <saml2p:Status>
+    <saml2p:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"></saml2p:StatusCode>
+  </saml2p:Status>
+  <saml2:Assertion xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:xsd="http://www.w3.org/2001/XMLSchema" ID="_637d9c5c732418d7cef10586f21c21b0" IssueInstant="<%= issue_instant.iso8601 %>" Version="2.0">
+    <saml2:Issuer><%= fetch(:issuer, 'https://www.example.com') %></saml2:Issuer>
+    <saml2:Subject>
+      <saml2:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
+        <saml2:SubjectConfirmationData Address="10.38.131.134" InResponseTo="<%= fetch(:in_response_to, "_4db60150-227a-439b-a686-e7c57a9b5f9a") %>" NotOnOrAfter="<%= fetch(:not_on_or_after, Time.now).iso8601 %>" Recipient="<%= fetch(:recipient, 'https://www.example.com/') %>"></saml2:SubjectConfirmationData>
+      </saml2:SubjectConfirmation>
+    </saml2:Subject>
+    <saml2:Conditions NotBefore="<%= fetch(:not_before, Time.now).iso8601 %>" NotOnOrAfter="<%= fetch(:not_on_or_after, Time.now).iso8601 %>">
+      <saml2:AudienceRestriction>
+        <saml2:Audience><%= fetch(:audience, 'https://www.example.com') %></saml2:Audience>
+      </saml2:AudienceRestriction>
+    </saml2:Conditions>
+    <saml2:AuthnStatement AuthnInstant="<%= issue_instant.iso8601 %>" SessionIndex="_99d6ac14d6ad70a23c89f82ba75c4ae0">
+      <saml2:SubjectLocality Address="10.38.131.134"></saml2:SubjectLocality>
+      <saml2:AuthnContext>
+        <saml2:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:Password</saml2:AuthnContextClassRef>
+      </saml2:AuthnContext>
+    </saml2:AuthnStatement>
+  </saml2:Assertion>
+</saml2p:Response>
spec/fixtures/requester_error.saml_response.erb
@@ -0,0 +1,7 @@
+<saml2p:Response xmlns:saml2p="urn:oasis:names:tc:SAML:2.0:protocol" ID="<%= Xml::Kit::Id.generate %>" InResponseTo="<%= Xml::Kit::Id.generate %>" IssueInstant="<%= fetch(:issue_instant, Time.now).iso8601 %>" Destination="<%= fetch(:destination, 'https://www.example.com') %>" Version="2.0">
+  <saml2:Issuer xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion"><%= fetch(:issuer, 'https://www.example.com') %></saml2:Issuer>
+  <saml2p:Status>
+    <saml2p:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Requester"></saml2p:StatusCode>
+    <saml2p:StatusMessage><%= fetch(:status_message, "Invalid message signature") %></saml2p:StatusMessage>
+  </saml2p:Status>
+</saml2p:Response>
spec/saml/kit/builders/response_spec.rb
@@ -38,6 +38,19 @@ RSpec.describe Saml::Kit::Builders::Response do
       expect(result.issuer).to eql(issuer)
       expect(result.assertion.issuer).to eql(issuer)
     end
+
+    it 'builds a response with a status code' do
+      subject.status_code = Saml::Kit::Namespaces::REQUESTER_ERROR
+      subject.status_message = 'Invalid message signature'
+      result = subject.build
+      expect(result.status_message).to eql('Invalid message signature')
+    end
+
+    it 'builds a response without an assertion' do
+      subject.assertion = nil
+      result = subject.build
+      expect(result.assertion).not_to be_present
+    end
   end
 
   describe '#to_xml' do
spec/saml/kit/response_spec.rb
@@ -554,6 +554,19 @@ XML
       subject = described_class.build(user, request)
       expect(subject.attributes).to eql('name' => 'mo', 'age' => '33')
     end
+
+    it 'can parse an assertion without a name id' do
+      xml = expand_template('no_nameid.saml_response', issue_instant: Time.now)
+      subject = described_class.new(xml)
+      expect(subject.name_id).to be_nil
+    end
+
+    it 'parses a response with a status code of Requester' do
+      message = FFaker::Lorem.sentence
+      subject = described_class.new(expand_template('requester_error.saml_response', status_message: message))
+      expect(subject.status_code).to eql(Saml::Kit::Namespaces::REQUESTER_ERROR)
+      expect(subject.status_message).to eql(message)
+    end
   end
 
   describe '#build' do
spec/support/erb_templating.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+module ErbTemplating
+  class Template
+    def initialize(template_name, data)
+      @erb = ERB.new(IO.read(File.join('spec/fixtures', "#{template_name}.erb")))
+      @data = data
+    end
+
+    def fetch(key, default)
+      @data.fetch(key) { default }
+    end
+
+    def __expand
+      @erb.result(binding)
+    end
+
+    def method_missing(method, *)
+      @data[method] || super
+    end
+
+    def respond_to_missing?(method, *)
+      @data.key?(method)
+    end
+  end
+
+  def expand_template(template_name, data = {})
+    Template.new(template_name, data).__expand
+  end
+end
+
+RSpec.configure do |config|
+  config.include ErbTemplating
+end
spec/support/test_helpers.rb
@@ -2,7 +2,7 @@
 
 module TestHelpers
   def query_params_from(url)
-    Hash[query_for(url).split('&').map { |x| x.split('=', 2) }]
+    Hash[query_for(url).split('&').map { |xxx| xxx.split('=', 2) }]
   end
 
   def uri_for(url)
.reek
@@ -0,0 +1,125 @@
+---
+Attribute:
+  exclude:
+  - Saml::Kit::Assertion#occurred_at
+  - Saml::Kit::Builders::AuthenticationRequest#assertion_consumer_service_url
+  - Saml::Kit::Builders::AuthenticationRequest#destination
+  - Saml::Kit::Builders::AuthenticationRequest#id
+  - Saml::Kit::Builders::AuthenticationRequest#issuer
+  - Saml::Kit::Builders::AuthenticationRequest#name_id_format
+  - Saml::Kit::Builders::AuthenticationRequest#now
+  - Saml::Kit::Builders::AuthenticationRequest#version
+  - Saml::Kit::Builders::IdentityProviderMetadata#attributes
+  - Saml::Kit::Builders::IdentityProviderMetadata#name_id_formats
+  - Saml::Kit::Builders::IdentityProviderMetadata#want_authn_requests_signed
+  - Saml::Kit::Builders::LogoutRequest#destination
+  - Saml::Kit::Builders::LogoutRequest#id
+  - Saml::Kit::Builders::LogoutRequest#issuer
+  - Saml::Kit::Builders::LogoutRequest#name_id_format
+  - Saml::Kit::Builders::LogoutRequest#now
+  - Saml::Kit::Builders::LogoutRequest#version
+  - Saml::Kit::Builders::LogoutResponse#destination
+  - Saml::Kit::Builders::LogoutResponse#id
+  - Saml::Kit::Builders::LogoutResponse#issuer
+  - Saml::Kit::Builders::LogoutResponse#now
+  - Saml::Kit::Builders::LogoutResponse#status_code
+  - Saml::Kit::Builders::LogoutResponse#version
+  - Saml::Kit::Builders::Metadata#contact_email
+  - Saml::Kit::Builders::Metadata#entity_id
+  - Saml::Kit::Builders::Metadata#id
+  - Saml::Kit::Builders::Metadata#identity_provider
+  - Saml::Kit::Builders::Metadata#organization_name
+  - Saml::Kit::Builders::Metadata#organization_url
+  - Saml::Kit::Builders::Metadata#service_provider
+  - Saml::Kit::Builders::Response#destination
+  - Saml::Kit::Builders::Response#id
+  - Saml::Kit::Builders::Response#issuer
+  - Saml::Kit::Builders::Response#now
+  - Saml::Kit::Builders::Response#reference_id
+  - Saml::Kit::Builders::Response#status_code
+  - Saml::Kit::Builders::Response#status_message
+  - Saml::Kit::Builders::Response#version
+  - Saml::Kit::Builders::ServiceProviderMetadata#acs_urls
+  - Saml::Kit::Builders::ServiceProviderMetadata#logout_urls
+  - Saml::Kit::Builders::ServiceProviderMetadata#name_id_formats
+  - Saml::Kit::Builders::ServiceProviderMetadata#want_assertions_signed
+  - Saml::Kit::Configuration#clock_drift
+  - Saml::Kit::Configuration#digest_method
+  - Saml::Kit::Configuration#entity_id
+  - Saml::Kit::Configuration#logger
+  - Saml::Kit::Configuration#registry
+  - Saml::Kit::Configuration#session_timeout
+  - Saml::Kit::Configuration#signature_method
+  - Saml::Kit::Document#registry
+BooleanParameter:
+  exclude:
+  - Saml::Kit::DefaultRegistry#register_url
+  - Saml::Kit::DefaultRegistry::HttpApi#initialize
+ControlParameter:
+  exclude:
+  - Saml::Kit::Assertion#to_xml
+  - Saml::Kit::Bindings::HttpRedirect#algorithm_for
+  - Saml::Kit::Document#to_xml
+  - Saml::Kit::Metadata#to_xml
+  - Saml::Kit::Signature#to_xml
+TooManyInstanceVariables:
+  max_instance_variables: 15
+TooManyMethods:
+  max_methods: 30
+TooManyStatements:
+  max_statements: 10
+UtilityFunction:
+  exclude:
+  - Saml::Kit::Assertion#parse_date
+  - Saml::Kit::Bindings::HttpRedirect#algorithm_for
+  - Saml::Kit::Bindings::HttpRedirect#canonicalize
+  - Saml::Kit::Bindings::HttpRedirect#normalize
+  - Saml::Kit::Bindings::HttpRedirect#params_to_hash
+  - Saml::Kit::NullAssertion#expired_at
+  - Saml::Kit::NullAssertion#started_at
+  - uri_for
+  - xml_document
+  - Saml::Kit::Serializable#decode
+  - Saml::Kit::Serializable#deflate
+  - Saml::Kit::Serializable#encode
+  - Saml::Kit::Serializable#escape
+  - Saml::Kit::Serializable#inflate
+  - Saml::Kit::Serializable#unescape
+LongParameterList:
+  max_params: 4
+DuplicateMethodCall:
+  exclude:
+  - Saml::Kit::Bindings::Binding#saml_param_from
+  - Saml::Kit::Builders::Response#initialize
+  - Saml::Kit::XmlTemplatable#sign?
+FeatureEnvy:
+  exclude:
+  - Saml::Kit::Bindings::Binding#saml_param_from
+  - Saml::Kit::Bindings::HttpRedirect#serialize
+  - Saml::Kit::DefaultRegistry#register
+  - Saml::Kit::Metadata#matches?
+  - Saml::Kit::Signature#expected_digest_value
+  - Saml::Kit::Signature#trusted?
+NilCheck:
+  exclude:
+  - Saml::Kit::Bindings::HttpRedirect#ensure_valid_signature
+  - Saml::Kit::IdentityProviderMetadata#want_authn_requests_signed
+  - Saml::Kit::LogoutRequest#single_logout_service
+  - Saml::Kit::Respondable#must_match_request_id
+  - Saml::Kit::Response#assertion
+  - Saml::Kit::ServiceProviderMetadata#want_assertions_signed
+  - Saml::Kit::Signature#certificate
+  - Saml::Kit::Signature#trusted?
+  - Saml::Kit::Signature#validate_signature
+  - Saml::Kit::XmlTemplatable#sign?
+ManualDispatch:
+  exclude:
+  - Saml::Kit::Builders::Assertion#assertion_attributes
+  - Saml::Kit::CompositeMetadata#method_missing
+  - Saml::Kit::CompositeMetadata#respond_to_missing?
+UnusedParameters:
+  exclude:
+  - Saml::Kit::InvalidDocument#initialize
+TooManyConstants:
+  exclude:
+  - Saml::Kit::Namespaces
Rakefile
@@ -1,15 +1,17 @@
 # frozen_string_literal: true
 
+require 'bundler/audit/task'
 require 'bundler/gem_tasks'
+require 'reek/rake/task'
 require 'rspec/core/rake_task'
+require 'rubocop/rake_task'
 
 RSpec::Core::RakeTask.new(:spec)
-task default: :spec
-
-require 'rubocop/rake_task'
 RuboCop::RakeTask.new(:rubocop)
-
-require 'bundler/audit/task'
 Bundler::Audit::Task.new
+Reek::Rake::Task.new(:reek) do |task|
+  task.config_file = '.reek'
+end
 
-task lint: [:rubocop, 'bundle:audit']
+task lint: [:rubocop, :reek, 'bundle:audit']
+task default: :spec
saml-kit.gemspec
@@ -34,6 +34,7 @@ Gem::Specification.new do |spec|
   spec.add_development_dependency 'bundler-audit', '~> 0.6'
   spec.add_development_dependency 'ffaker', '~> 2.7'
   spec.add_development_dependency 'rake', '~> 10.0'
+  spec.add_development_dependency 'reek', '~> 4.8'
   spec.add_development_dependency 'rspec', '~> 3.0'
   spec.add_development_dependency 'rspec-benchmark', '~> 0.3'
   spec.add_development_dependency 'rubocop', '~> 0.52'