Commit 4bf714a

mo <mo.khan@gmail.com>
2017-11-14 02:35:34
generate logout response.
1 parent 9a03a92
lib/saml/kit/logout_request.rb
@@ -19,6 +19,10 @@ module Saml
         @xml_hash = Hash.from_xml(xml)
       end
 
+      def id
+        to_h[name]['ID']
+      end
+
       def issuer
         to_h[name]['Issuer']
       end
@@ -85,6 +89,10 @@ module Saml
         Saml::Kit::Content.encode_raw_saml(to_xml)
       end
 
+      def response_for(user)
+        LogoutResponse::Builder.new(user, self).build
+      end
+
       private
 
       def registry
lib/saml/kit/logout_response.rb
@@ -0,0 +1,95 @@
+
+module Saml
+  module Kit
+    class LogoutResponse
+      attr_reader :content, :name
+
+      def initialize(xml)
+        @content = xml
+        @name = 'LogoutResponse'
+        @xml_hash = Hash.from_xml(xml)
+      end
+
+      def id
+        to_h[name]['ID']
+      end
+
+      def issue_instant
+        to_h[name]['IssueInstant']
+      end
+
+      def version
+        to_h[name]['Version']
+      end
+
+      def issuer
+        to_h[name]['Issuer']
+      end
+
+      def status_code
+        to_h[name]['Status']['StatusCode']['Value']
+      end
+
+      def in_response_to
+        to_h[name]['InResponseTo']
+      end
+
+      def destination
+        to_h[name]['Destination']
+      end
+
+      def to_h
+        @xml_hash
+      end
+
+      def to_xml
+        content
+      end
+
+      class Builder
+        attr_accessor :id, :issuer, :version, :status_code, :sign, :now
+        attr_reader :request
+
+        def initialize(user, request, configuration: Saml::Kit.configuration, sign: true)
+          @user = user
+          @now = Time.now.utc
+          @request = request
+          @id = SecureRandom.uuid
+          @version = "2.0"
+          @status_code = Namespaces::SUCCESS
+          @sign = sign
+          @issuer = configuration.issuer
+        end
+
+        def to_xml
+          Signature.sign(id, sign: sign) do |xml, signature|
+            xml.LogoutResponse logout_response_options do
+              xml.Issuer(issuer, xmlns: Namespaces::ASSERTION)
+              signature.template(xml)
+              xml.Status do
+                xml.StatusCode Value: status_code
+              end
+            end
+          end
+        end
+
+        def build
+          LogoutResponse.new(to_xml)
+        end
+
+        private
+
+        def logout_response_options
+          {
+            xmlns: Namespaces::PROTOCOL,
+            ID: "_#{id}",
+            Version: "2.0",
+            IssueInstant: now.utc.iso8601,
+            Destination: "",
+            InResponseTo: request.id,
+          }
+        end
+      end
+    end
+  end
+end
lib/saml/kit/response.rb
@@ -56,7 +56,7 @@ module Saml
         end].with_indifferent_access
       end
 
-      def acs_url
+      def destination
         to_h.fetch(name, {}).fetch('Destination', nil)
       end
 
lib/saml/kit.rb
@@ -18,6 +18,7 @@ require "saml/kit/configuration"
 require "saml/kit/content"
 require "saml/kit/default_registry"
 require "saml/kit/fingerprint"
+require "saml/kit/logout_response"
 require "saml/kit/logout_request"
 require "saml/kit/namespaces"
 require "saml/kit/metadata"
spec/saml/authentication_request_spec.rb
@@ -9,7 +9,7 @@ RSpec.describe Saml::Kit::AuthenticationRequest do
   let(:raw_xml) do
     builder = described_class::Builder.new
     builder.id = id
-    builder.issued_at = Time.now.utc
+    builder.now = Time.now.utc
     builder.issuer = issuer
     builder.acs_url = acs_url
     builder.name_id_format = name_id_format
spec/saml/logout_request_spec.rb
@@ -157,4 +157,11 @@ RSpec.describe Saml::Kit::LogoutRequest do
       expect(result.to_xml).to eql(subject.to_xml)
     end
   end
+
+  describe "#response_for" do
+    it 'returns a logout response for a particular user' do
+      user = double(:user)
+      expect(subject.response_for(user)).to be_instance_of(Saml::Kit::LogoutResponse)
+    end
+  end
 end
spec/saml/logout_response_spec.rb
@@ -0,0 +1,26 @@
+require 'spec_helper'
+
+RSpec.describe Saml::Kit::LogoutResponse do
+  describe described_class::Builder do
+    subject { described_class.new(user, request, configuration: configuration) }
+    let(:configuration) { double(issuer: issuer)  }
+    let(:user) { double(:user, name_id_for: SecureRandom.uuid) }
+    let(:request) { Saml::Kit::LogoutRequest::Builder.new(user).build }
+    let(:issuer) { FFaker::Internet.http_url }
+
+    describe "#build" do
+      it 'builds a logout response' do
+        travel_to 1.second.from_now
+
+        result = subject.build
+        expect(result.id).to be_present
+        expect(result.issue_instant).to eql(Time.now.utc.iso8601)
+        expect(result.version).to eql("2.0")
+        expect(result.issuer).to eql(issuer)
+        expect(result.status_code).to eql(Saml::Kit::Namespaces::SUCCESS)
+        expect(result.in_response_to).to eql(request.id)
+        expect(result.destination).to be_present
+      end
+    end
+  end
+end
spec/saml/response_spec.rb
@@ -1,14 +1,14 @@
 require 'spec_helper'
 
 RSpec.describe Saml::Kit::Response do
-  describe "#acs_url" do
+  describe "#destination" do
     let(:acs_url) { "https://#{FFaker::Internet.domain_name}/acs" }
     let(:user) { double(:user, name_id_for: SecureRandom.uuid, assertion_attributes_for: { }) }
     let(:request) { instance_double(Saml::Kit::AuthenticationRequest, id: SecureRandom.uuid, acs_url: acs_url, issuer: FFaker::Movie.title, name_id_format: Saml::Kit::Namespaces::EMAIL_ADDRESS, provider: nil) }
     subject { described_class::Builder.new(user, request).build }
 
     it 'returns the acs_url' do
-      expect(subject.acs_url).to eql(acs_url)
+      expect(subject.destination).to eql(acs_url)
     end
   end