Commit fadbb7a

mo <mo@mokhan.ca>
2017-11-16 23:12:58
deserialize http redirect bound data.
1 parent 4ebfdd6
lib/saml/kit/binding.rb
@@ -32,6 +32,15 @@ module Saml
         end
       end
 
+      def deserialize(params)
+        if params['SAMLRequest'].present?
+          Saml::Kit::Request.deserialize(CGI.unescape(params['SAMLRequest']))
+        elsif params['SAMLResponse'].present?
+          Saml::Kit::Response.deserialize(CGI.unescape(params['SAMLResponse']))
+        else
+        end
+      end
+
       def http_redirect?
         binding == Namespaces::HTTP_REDIRECT
       end
lib/saml/kit/invalid_request.rb
@@ -1,6 +1,6 @@
 module Saml
   module Kit
-    class InvalidRequest
+    class InvalidDocument
       include ActiveModel::Validations
       include XsdValidatable
       attr_reader :raw, :name
@@ -9,14 +9,26 @@ module Saml
         model.errors[:base] << model.error_message(:invalid)
       end
 
-      def initialize(raw)
+      def initialize(raw, name)
         @raw = raw
-        @name = "InvalidRequest"
       end
 
       def to_xml
         raw
       end
+
+    end
+
+    class InvalidRequest < InvalidDocument
+      def initialize(raw)
+        super raw, "InvalidRequest"
+      end
+    end
+
+    class InvalidResponse < InvalidDocument
+      def initialize(raw)
+        super raw, "InvalidResponse"
+      end
     end
   end
 end
lib/saml/kit/response.rb
@@ -122,7 +122,17 @@ module Saml
 
       class << self
         def deserialize(saml_response)
-          new(Saml::Kit::Content.deserialize(saml_response))
+          xml = Saml::Kit::Content.deserialize(saml_response)
+          hash = Hash.from_xml(xml)
+          if hash['Response'].present?
+            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
 
@@ -209,7 +219,7 @@ module Saml
         attr_reader :user, :request
         attr_accessor :id, :reference_id, :now
         attr_accessor :version, :status_code
-        attr_accessor :issuer
+        attr_accessor :issuer, :sign, :destination
 
         def initialize(user, request)
           @user = user
@@ -220,6 +230,8 @@ module Saml
           @version = "2.0"
           @status_code = Namespaces::SUCCESS
           @issuer = configuration.issuer
+          @destination = request.acs_url
+          @sign = want_assertions_signed
         end
 
         def want_assertions_signed
@@ -230,7 +242,7 @@ module Saml
         end
 
         def to_xml
-          Signature.sign(id, sign: want_assertions_signed) do |xml, signature|
+          Signature.sign(id, sign: sign) do |xml, signature|
             xml.Response response_options do
               xml.Issuer(issuer, xmlns: Namespaces::ASSERTION)
               signature.template(xml)
@@ -285,7 +297,7 @@ module Saml
             ID: id.present? ? "_#{id}" : nil,
             Version: version,
             IssueInstant: now.iso8601,
-            Destination: request.acs_url,
+            Destination: destination,
             Consent: Namespaces::UNSPECIFIED,
             InResponseTo: request.id,
             xmlns: Namespaces::PROTOCOL,
spec/saml/binding_spec.rb
@@ -1,9 +1,10 @@
 require 'spec_helper'
 
 RSpec.describe Saml::Kit::Binding do
+  let(:location) { FFaker::Internet.http_url }
+
   describe "#serialize" do
     let(:relay_state) { "ECHO" }
-    let(:location) { FFaker::Internet.http_url }
 
     describe "HTTP-REDIRECT BINDING" do
       let(:subject) { Saml::Kit::Binding.new(binding: Saml::Kit::Namespaces::HTTP_REDIRECT, location: location) }
@@ -77,4 +78,49 @@ RSpec.describe Saml::Kit::Binding do
       expect(subject.serialize(Saml::Kit::AuthenticationRequest)).to be_empty
     end
   end
+
+  describe "#deserialize" do
+    describe "HTTP-Redirect binding" do
+      let(:subject) { Saml::Kit::Binding.new(binding: Saml::Kit::Namespaces::HTTP_REDIRECT, location: location) }
+
+      it 'deserializes the SAMLRequest to an AuthnRequest' do
+        url, _ = subject.serialize(Saml::Kit::AuthenticationRequest::Builder.new)
+        result = subject.deserialize(query_params_from(url))
+        expect(result).to be_instance_of(Saml::Kit::AuthenticationRequest)
+      end
+
+      it 'deserializes the SAMLRequest to a LogoutRequest' do
+        user = double(:user, name_id_for: SecureRandom.uuid)
+        url, _ = subject.serialize(Saml::Kit::LogoutRequest::Builder.new(user))
+        result = subject.deserialize(query_params_from(url))
+        expect(result).to be_instance_of(Saml::Kit::LogoutRequest)
+      end
+
+      it 'returns an invalid request when the SAMLRequest is invalid' do
+        result = subject.deserialize({ 'SAMLRequest' => "nonsense" })
+        expect(result).to be_instance_of(Saml::Kit::InvalidRequest)
+      end
+
+      it 'deserializes the SAMLResponse to a Response' do
+        user = double(:user, name_id_for: SecureRandom.uuid, assertion_attributes_for: [])
+        request = double(:request, id: SecureRandom.uuid, provider: nil, acs_url: FFaker::Internet.http_url, name_id_format: Saml::Kit::Namespaces::PERSISTENT, issuer: FFaker::Internet.http_url)
+        url, _ = subject.serialize(Saml::Kit::Response::Builder.new(user, request))
+        result = subject.deserialize(query_params_from(url))
+        expect(result).to be_instance_of(Saml::Kit::Response)
+      end
+
+      it 'deserializes the SAMLResponse to a LogoutResponse' do
+        user = double(:user, name_id_for: SecureRandom.uuid, assertion_attributes_for: [])
+        request = double(:request, id: SecureRandom.uuid, provider: nil, acs_url: FFaker::Internet.http_url, name_id_format: Saml::Kit::Namespaces::PERSISTENT, issuer: FFaker::Internet.http_url)
+        url, _ = subject.serialize(Saml::Kit::LogoutResponse::Builder.new(user, request))
+        result = subject.deserialize(query_params_from(url))
+        expect(result).to be_instance_of(Saml::Kit::LogoutResponse)
+      end
+
+      it 'returns an invalid response when the SAMLResponse is invalid' do
+        result = subject.deserialize({ 'SAMLResponse' => "nonsense" })
+        expect(result).to be_instance_of(Saml::Kit::InvalidResponse)
+      end
+    end
+  end
 end
spec/support/matchers/have_query_param.rb
@@ -1,13 +1,5 @@
 RSpec::Matchers.define :have_query_param do |key|
   match do |url|
-    query_params(url)['SAMLRequest'].present?
-  end
-
-  def query_params(url)
-    Hash[uri_for(url).query.split("&").map { |x| x.split('=', 2) }]
-  end
-
-  def uri_for(url)
-    URI.parse(url)
+    query_params_from(url)['SAMLRequest'].present?
   end
 end
spec/support/test_helpers.rb
@@ -0,0 +1,9 @@
+module TestHelpers
+  def query_params_from(url)
+    Hash[uri_for(url).query.split("&").map { |x| x.split('=', 2) }]
+  end
+
+  def uri_for(url)
+    URI.parse(url)
+  end
+end
spec/spec_helper.rb
@@ -9,6 +9,7 @@ Saml::Kit.configuration.logger.level = :fatal
 Dir[File.join(Dir.pwd, 'spec/support/**/*.rb')].each { |f| require f }
 RSpec.configure do |config|
   config.include ActiveSupport::Testing::TimeHelpers
+  config.include TestHelpers
   # Enable flags like --only-failures and --next-failure
   config.example_status_persistence_file_path = ".rspec_status"