Commit e34d28a

mo <mo.khan@gmail.com>
2017-11-17 22:50:01
declare binding subclasses.
1 parent 8f9a2b3
lib/saml/kit/binding.rb
@@ -13,57 +13,97 @@ module Saml
       end
 
       def serialize(builder, relay_state: nil)
-        if http_redirect?
-          builder.sign = false
-          builder.destination = location
-          document = builder.build
-          [UrlBuilder.new.build(document, relay_state: relay_state), {}]
-        elsif post?
-          builder.sign = true
-          builder.destination = location
-          document = builder.build
-          saml_params = {
-            document.query_string_parameter => Base64.strict_encode64(document.to_xml),
-          }
-          saml_params['RelayState'] = relay_state if relay_state.present?
-          [location, saml_params]
+        []
+      end
+
+      def deserialize(params)
+        raise ArgumentError.new("Unsupported binding")
+      end
+
+      def to_h
+        { binding: binding, location: location }
+      end
+
+      protected
+
+      def deserialize_request(raw_request)
+        xml = Saml::Kit::Content.deserialize(raw_request)
+        hash = Hash.from_xml(xml)
+        if hash['AuthnRequest'].present?
+          AuthenticationRequest.new(xml)
         else
-          []
+          LogoutRequest.new(xml)
         end
+      rescue => error
+        Saml::Kit.logger.error(error)
+        Saml::Kit.logger.error(error.backtrace.join("\n"))
+        InvalidRequest.new(raw_request)
       end
 
-      def deserialize(params)
-        if http_redirect?
-          document = deserialize_document_from!(params)
-          ensure_valid_signature!(params, document)
-          document
-        elsif post?
-          if params['SAMLRequest'].present?
-            deserialize_request(params['SAMLRequest'])
-          elsif params['SAMLResponse'].present?
-            deserialize_response(params['SAMLResponse'])
-          else
-            raise ArgumentError.new("Missing SAMLRequest or SAMLResponse")
-          end
+      def deserialize_response(saml_response)
+        xml = Saml::Kit::Content.deserialize(saml_response)
+        hash = Hash.from_xml(xml)
+        if hash['Response'].present?
+          Response.new(xml)
         else
-          raise ArgumentError.new("Unsupported binding")
+          LogoutResponse.new(xml)
         end
+      rescue => error
+        Saml::Kit.logger.error(error)
+        Saml::Kit.logger.error(error.backtrace.join("\n"))
+        InvalidResponse.new(saml_response)
       end
+    end
 
-      def http_redirect?
-        binding == Namespaces::HTTP_REDIRECT
+    class HttpPostBinding < Binding
+      def serialize(builder, relay_state: nil)
+        builder.sign = true
+        builder.destination = location
+        document = builder.build
+        saml_params = {
+          document.query_string_parameter => Base64.strict_encode64(document.to_xml),
+        }
+        saml_params['RelayState'] = relay_state if relay_state.present?
+        [location, saml_params]
       end
 
-      def post?
-        binding == Namespaces::POST
+      def deserialize(params)
+        if params['SAMLRequest'].present?
+          deserialize_request(params['SAMLRequest'])
+        elsif params['SAMLResponse'].present?
+          deserialize_response(params['SAMLResponse'])
+        else
+          raise ArgumentError.new("Missing SAMLRequest or SAMLResponse")
+        end
       end
+    end
 
-      def to_h
-        { binding: binding, location: location }
+    class HttpRedirectBinding < Binding
+      def serialize(builder, relay_state: nil)
+        builder.sign = false
+        builder.destination = location
+        document = builder.build
+        [UrlBuilder.new.build(document, relay_state: relay_state), {}]
+      end
+
+      def deserialize(params)
+        document = deserialize_document_from!(params)
+        ensure_valid_signature!(params, document)
+        document
       end
 
       private
 
+      def deserialize_document_from!(params)
+        if params['SAMLRequest'].present?
+          deserialize_request(CGI.unescape(params['SAMLRequest']))
+        elsif params['SAMLResponse'].present?
+          deserialize_response(CGI.unescape(params['SAMLResponse']))
+        else
+          raise ArgumentError.new("SAMLRequest or SAMLResponse parameter is required.")
+        end
+      end
+
       def ensure_valid_signature!(params, document)
         return if params['Signature'].blank? || params['SigAlg'].blank?
 
@@ -77,15 +117,6 @@ module Saml
         raise ArgumentError.new("Invalid Signature") unless valid
       end
 
-      def deserialize_document_from!(params)
-        if params['SAMLRequest'].present?
-          deserialize_request(CGI.unescape(params['SAMLRequest']))
-        elsif params['SAMLResponse'].present?
-          deserialize_response(CGI.unescape(params['SAMLResponse']))
-        else
-          raise ArgumentError.new("SAMLRequest or SAMLResponse parameter is required.")
-        end
-      end
 
       def algorithm_for(algorithm)
         case algorithm =~ /(rsa-)?sha(.*?)$/i && $2.to_i
@@ -100,33 +131,6 @@ module Saml
         end
       end
 
-      def deserialize_request(raw_request)
-        xml = Saml::Kit::Content.deserialize(raw_request)
-        hash = Hash.from_xml(xml)
-        if hash['AuthnRequest'].present?
-          AuthenticationRequest.new(xml)
-        else
-          LogoutRequest.new(xml)
-        end
-      rescue => error
-        Saml::Kit.logger.error(error)
-        Saml::Kit.logger.error(error.backtrace.join("\n"))
-        InvalidRequest.new(raw_request)
-      end
-
-      def deserialize_response(saml_response)
-        xml = Saml::Kit::Content.deserialize(saml_response)
-        hash = Hash.from_xml(xml)
-        if hash['Response'].present?
-          Response.new(xml)
-        else
-          LogoutResponse.new(xml)
-        end
-      rescue => error
-        Saml::Kit.logger.error(error)
-        Saml::Kit.logger.error(error.backtrace.join("\n"))
-        InvalidResponse.new(saml_response)
-      end
     end
   end
 end
lib/saml/kit/identity_provider_metadata.rb
@@ -15,10 +15,8 @@ module Saml
       def single_sign_on_services
         xpath = "/md:EntityDescriptor/md:#{name}/md:SingleSignOnService"
         find_all(xpath).map do |item|
-          Saml::Kit::Binding.new(
-            binding: item.attribute("Binding").value,
-            location: item.attribute("Location").value,
-          )
+          binding = item.attribute("Binding").value
+          binding_type_for(binding).new(binding: binding, location: item.attribute("Location").value)
         end
       end
 
@@ -38,6 +36,19 @@ module Saml
         end
       end
 
+      private
+
+      def binding_type_for(binding)
+        case binding
+        when Namespaces::HTTP_REDIRECT
+          Saml::Kit::HttpRedirectBinding
+        when Namespaces::POST
+          Saml::Kit::HttpPostBinding
+        else
+          Saml::Kit::Binding
+        end
+      end
+
       class Builder
         attr_accessor :id, :organization_name, :organization_url, :contact_email, :entity_id, :attributes, :name_id_formats
         attr_accessor :want_authn_requests_signed, :sign
spec/saml/binding_spec.rb
@@ -7,7 +7,7 @@ RSpec.describe Saml::Kit::Binding do
     let(:relay_state) { "ECHO" }
 
     describe "HTTP-REDIRECT BINDING" do
-      let(:subject) { Saml::Kit::Binding.new(binding: Saml::Kit::Namespaces::HTTP_REDIRECT, location: location) }
+      let(:subject) { Saml::Kit::HttpRedirectBinding.new(binding: Saml::Kit::Namespaces::HTTP_REDIRECT, location: location) }
 
       it 'encodes the request using the HTTP-Redirect encoding' do
         builder = Saml::Kit::AuthenticationRequest::Builder.new
@@ -20,7 +20,7 @@ RSpec.describe Saml::Kit::Binding do
     end
 
     describe "HTTP-POST Binding" do
-      let(:subject) { Saml::Kit::Binding.new(binding: Saml::Kit::Namespaces::POST, location: location) }
+      let(:subject) { Saml::Kit::HttpPostBinding.new(binding: Saml::Kit::Namespaces::POST, location: location) }
 
       it 'encodes the request using the HTTP-POST encoding for a AuthenticationRequest' do
         builder = Saml::Kit::AuthenticationRequest::Builder.new
@@ -81,7 +81,7 @@ RSpec.describe Saml::Kit::Binding do
 
   describe "#deserialize" do
     describe "HTTP-Redirect binding" do
-      let(:subject) { Saml::Kit::Binding.new(binding: Saml::Kit::Namespaces::HTTP_REDIRECT, location: location) }
+      let(:subject) { Saml::Kit::HttpRedirectBinding.new(binding: Saml::Kit::Namespaces::HTTP_REDIRECT, location: location) }
       let(:issuer) { FFaker::Internet.http_url }
       let(:provider) { Saml::Kit::IdentityProviderMetadata::Builder.new.build }
 
@@ -146,7 +146,7 @@ RSpec.describe Saml::Kit::Binding do
     end
 
     describe "HTTP Post binding" do
-      let(:subject) { Saml::Kit::Binding.new(binding: Saml::Kit::Namespaces::POST, location: location) }
+      let(:subject) { Saml::Kit::HttpPostBinding.new(binding: Saml::Kit::Namespaces::POST, location: location) }
 
       it 'deserializes to an AuthnRequest' do
         builder = Saml::Kit::AuthenticationRequest::Builder.new