Commit ef22f59

mo <mo.khan@gmail.com>
2017-12-10 21:33:37
use tilt to compile builder templates
1 parent 8a6f103
lib/saml/kit/builders/templates/authn_request.builder
@@ -0,0 +1,5 @@
+xml.tag!('samlp:AuthnRequest', request_options) do
+  xml.tag!('saml:Issuer', issuer)
+  signature.template(id)
+  xml.tag!('samlp:NameIDPolicy', Format: name_id_format)
+end
lib/saml/kit/builders/templates/identity_provider_metadata.builder
@@ -0,0 +1,44 @@
+xml.instruct!
+xml.EntityDescriptor entity_descriptor_options do
+  signature.template(id)
+  xml.IDPSSODescriptor idp_sso_descriptor_options do
+    if configuration.signing_certificate_pem.present?
+      xml.KeyDescriptor use: "signing" do
+        xml.KeyInfo "xmlns": Saml::Kit::Namespaces::XMLDSIG do
+          xml.X509Data do
+            xml.X509Certificate configuration.stripped_signing_certificate
+          end
+        end
+      end
+    end
+    if configuration.encryption_certificate_pem.present?
+      xml.KeyDescriptor use: "encryption" do
+        xml.KeyInfo "xmlns": Saml::Kit::Namespaces::XMLDSIG do
+          xml.X509Data do
+            xml.X509Certificate configuration.stripped_encryption_certificate
+          end
+        end
+      end
+    end
+    logout_urls.each do |item|
+      xml.SingleLogoutService Binding: item[:binding], Location: item[:location]
+    end
+    name_id_formats.each do |format|
+      xml.NameIDFormat format
+    end
+    single_sign_on_urls.each do |item|
+      xml.SingleSignOnService Binding: item[:binding], Location: item[:location]
+    end
+    attributes.each do |attribute|
+      xml.tag! 'saml:Attribute', Name: attribute
+    end
+  end
+  xml.Organization do
+    xml.OrganizationName organization_name, 'xml:lang': "en"
+    xml.OrganizationDisplayName organization_name, 'xml:lang': "en"
+    xml.OrganizationURL organization_url, 'xml:lang': "en"
+  end
+  xml.ContactPerson contactType: "technical" do
+    xml.Company "mailto:#{contact_email}"
+  end
+end
lib/saml/kit/builders/templates/logout_request.builder
@@ -0,0 +1,6 @@
+xml.instruct!
+xml.LogoutRequest logout_request_options do
+  xml.Issuer({ xmlns: Saml::Kit::Namespaces::ASSERTION }, issuer)
+  signature.template(id)
+  xml.NameID name_id_options, user.name_id_for(name_id_format)
+end
lib/saml/kit/builders/templates/logout_response.builder
@@ -0,0 +1,7 @@
+xml.LogoutResponse logout_response_options do
+  xml.Issuer(issuer, xmlns: Saml::Kit::Namespaces::ASSERTION)
+  signature.template(id)
+  xml.Status do
+    xml.StatusCode Value: status_code
+  end
+end
lib/saml/kit/builders/templates/service_provider_metadata.builder
@@ -0,0 +1,41 @@
+xml.instruct!
+xml.EntityDescriptor entity_descriptor_options do
+  signature.template(id)
+  xml.SPSSODescriptor descriptor_options do
+    if configuration.signing_certificate_pem.present?
+      xml.KeyDescriptor use: "signing" do
+        xml.KeyInfo "xmlns": Saml::Kit::Namespaces::XMLDSIG do
+          xml.X509Data do
+            xml.X509Certificate configuration.stripped_signing_certificate
+          end
+        end
+      end
+    end
+    if configuration.encryption_certificate_pem.present?
+      xml.KeyDescriptor use: "encryption" do
+        xml.KeyInfo "xmlns": Saml::Kit::Namespaces::XMLDSIG do
+          xml.X509Data do
+            xml.X509Certificate configuration.stripped_encryption_certificate
+          end
+        end
+      end
+    end
+    logout_urls.each do |item|
+      xml.SingleLogoutService Binding: item[:binding], Location: item[:location]
+    end
+    name_id_formats.each do |format|
+      xml.NameIDFormat format
+    end
+    acs_urls.each_with_index do |item, index|
+      xml.AssertionConsumerService Binding: item[:binding], Location: item[:location], index: index, isDefault: index == 0 ? true : false
+    end
+  end
+  xml.Organization do
+    xml.OrganizationName organization_name, 'xml:lang': "en"
+    xml.OrganizationDisplayName organization_name, 'xml:lang': "en"
+    xml.OrganizationURL organization_url, 'xml:lang': "en"
+  end
+  xml.ContactPerson contactType: "technical" do
+    xml.Company "mailto:#{contact_email}"
+  end
+end
lib/saml/kit/builders/authentication_request.rb
@@ -2,16 +2,20 @@ module Saml
   module Kit
     module Builders
       class AuthenticationRequest
+        include Saml::Kit::Templatable
         attr_accessor :id, :now, :issuer, :assertion_consumer_service_url, :name_id_format, :sign, :destination
         attr_accessor :version
+        attr_reader :template_name, :configuration
 
         def initialize(configuration: Saml::Kit.configuration, sign: true)
+          @configuration = configuration
           @id = Id.generate
           @issuer = configuration.issuer
           @name_id_format = Namespaces::PERSISTENT
           @now = Time.now.utc
-          @version = "2.0"
           @sign = sign
+          @template_name = 'authn_request'
+          @version = "2.0"
         end
 
         def acs_url
@@ -24,16 +28,6 @@ module Saml
           self.assertion_consumer_service_url = value
         end
 
-        def to_xml
-          Signature.sign(sign: sign) do |xml, signature|
-            xml.tag!('samlp:AuthnRequest', request_options) do
-              xml.tag!('saml:Issuer', issuer)
-              signature.template(id)
-              xml.tag!('samlp:NameIDPolicy', Format: name_id_format)
-            end
-          end
-        end
-
         def build
           Saml::Kit::AuthenticationRequest.new(to_xml)
         end
lib/saml/kit/builders/identity_provider_metadata.rb
@@ -2,19 +2,22 @@ module Saml
   module Kit
     module Builders
       class IdentityProviderMetadata
+        include Saml::Kit::Templatable
         attr_accessor :id, :organization_name, :organization_url, :contact_email, :entity_id, :attributes, :name_id_formats
         attr_accessor :want_authn_requests_signed, :sign
         attr_reader :logout_urls, :single_sign_on_urls
+        attr_reader :template_name, :configuration
 
         def initialize(configuration = Saml::Kit.configuration)
-          @id = Id.generate
-          @entity_id = configuration.issuer
           @attributes = []
-          @name_id_formats = [Namespaces::PERSISTENT]
-          @single_sign_on_urls = []
-          @logout_urls = []
           @configuration = configuration
+          @entity_id = configuration.issuer
+          @id = Id.generate
+          @logout_urls = []
+          @name_id_formats = [Namespaces::PERSISTENT]
           @sign = true
+          @single_sign_on_urls = []
+          @template_name = 'identity_provider_metadata'
           @want_authn_requests_signed = true
         end
 
@@ -26,55 +29,6 @@ module Saml
           @logout_urls.push(location: url, binding: Bindings.binding_for(binding))
         end
 
-        def to_xml
-          Signature.sign(sign: sign) do |xml, signature|
-            xml.instruct!
-            xml.EntityDescriptor entity_descriptor_options do
-              signature.template(id)
-              xml.IDPSSODescriptor idp_sso_descriptor_options do
-                if @configuration.signing_certificate_pem.present?
-                  xml.KeyDescriptor use: "signing" do
-                    xml.KeyInfo "xmlns": Namespaces::XMLDSIG do
-                      xml.X509Data do
-                        xml.X509Certificate @configuration.stripped_signing_certificate
-                      end
-                    end
-                  end
-                end
-                if @configuration.encryption_certificate_pem.present?
-                  xml.KeyDescriptor use: "encryption" do
-                    xml.KeyInfo "xmlns": Namespaces::XMLDSIG do
-                      xml.X509Data do
-                        xml.X509Certificate @configuration.stripped_encryption_certificate
-                      end
-                    end
-                  end
-                end
-                logout_urls.each do |item|
-                  xml.SingleLogoutService Binding: item[:binding], Location: item[:location]
-                end
-                name_id_formats.each do |format|
-                  xml.NameIDFormat format
-                end
-                single_sign_on_urls.each do |item|
-                  xml.SingleSignOnService Binding: item[:binding], Location: item[:location]
-                end
-                attributes.each do |attribute|
-                  xml.tag! 'saml:Attribute', Name: attribute
-                end
-              end
-              xml.Organization do
-                xml.OrganizationName organization_name, 'xml:lang': "en"
-                xml.OrganizationDisplayName organization_name, 'xml:lang': "en"
-                xml.OrganizationURL organization_url, 'xml:lang': "en"
-              end
-              xml.ContactPerson contactType: "technical" do
-                xml.Company "mailto:#{contact_email}"
-              end
-            end
-          end
-        end
-
         def build
           Saml::Kit::IdentityProviderMetadata.new(to_xml)
         end
lib/saml/kit/builders/logout_request.rb
@@ -2,11 +2,13 @@ module Saml
   module Kit
     module Builders
       class LogoutRequest
+        include Saml::Kit::Templatable
         attr_accessor :id, :destination, :issuer, :name_id_format, :now
         attr_accessor :sign, :version
-        attr_reader :user
+        attr_reader :user, :configuration, :template_name
 
         def initialize(user, configuration: Saml::Kit.configuration, sign: true)
+          @configuration = configuration
           @user = user
           @id = "_#{SecureRandom.uuid}"
           @issuer = configuration.issuer
@@ -14,17 +16,7 @@ module Saml
           @now = Time.now.utc
           @version = "2.0"
           @sign = sign
-        end
-
-        def to_xml
-          Signature.sign(sign: sign) do |xml, signature|
-            xml.instruct!
-            xml.LogoutRequest logout_request_options do
-              xml.Issuer({ xmlns: Namespaces::ASSERTION }, issuer)
-              signature.template(id)
-              xml.NameID name_id_options, user.name_id_for(name_id_format)
-            end
-          end
+          @template_name = 'logout_request'
         end
 
         def build
lib/saml/kit/builders/logout_response.rb
@@ -2,30 +2,22 @@ module Saml
   module Kit
     module Builders
       class LogoutResponse
+        include Saml::Kit::Templatable
         attr_accessor :id, :issuer, :version, :status_code, :sign, :now, :destination
         attr_reader :request
+        attr_reader :configuration, :template_name
 
         def initialize(user, request, configuration: Saml::Kit.configuration, sign: true)
-          @user = user
+          @configuration = configuration
+          @id = Id.generate
+          @issuer = configuration.issuer
           @now = Time.now.utc
           @request = request
-          @id = Id.generate
-          @version = "2.0"
-          @status_code = Namespaces::SUCCESS
           @sign = sign
-          @issuer = configuration.issuer
-        end
-
-        def to_xml
-          Signature.sign(sign: sign) do |xml, signature|
-            xml.LogoutResponse logout_response_options do
-              xml.Issuer(issuer, xmlns: Namespaces::ASSERTION)
-              signature.template(id)
-              xml.Status do
-                xml.StatusCode Value: status_code
-              end
-            end
-          end
+          @status_code = Namespaces::SUCCESS
+          @template_name = 'logout_response'
+          @user = user
+          @version = "2.0"
         end
 
         def build
lib/saml/kit/builders/response.rb
@@ -6,8 +6,9 @@ module Saml
         attr_accessor :id, :reference_id, :now
         attr_accessor :version, :status_code
         attr_accessor :issuer, :sign, :destination, :encrypt
+        attr_reader :configuration
 
-        def initialize(user, request)
+        def initialize(user, request, configuration: Saml::Kit.configuration)
           @user = user
           @request = request
           @id = Id.generate
@@ -19,6 +20,7 @@ module Saml
           @destination = destination_for(request)
           @sign = want_assertions_signed
           @encrypt = false
+          @configuration = configuration
         end
 
         def want_assertions_signed
@@ -129,10 +131,6 @@ module Saml
           end
         end
 
-        def configuration
-          Saml::Kit.configuration
-        end
-
         def response_options
           {
             ID: id,
@@ -165,7 +163,7 @@ module Saml
         def conditions_options
           {
             NotBefore: now.utc.iso8601,
-            NotOnOrAfter: Saml::Kit.configuration.session_timeout.from_now.utc.iso8601,
+            NotOnOrAfter: configuration.session_timeout.from_now.utc.iso8601,
           }
         end
 
lib/saml/kit/builders/service_provider_metadata.rb
@@ -2,18 +2,21 @@ module Saml
   module Kit
     module Builders
       class ServiceProviderMetadata
+        include Saml::Kit::Templatable
         attr_accessor :id, :entity_id, :acs_urls, :logout_urls, :name_id_formats, :sign
         attr_accessor :organization_name, :organization_url, :contact_email
         attr_accessor :want_assertions_signed
+        attr_reader :configuration, :template_name
 
         def initialize(configuration = Saml::Kit.configuration)
-          @id = Id.generate
+          @acs_urls = []
           @configuration = configuration
           @entity_id = configuration.issuer
-          @acs_urls = []
+          @id = Id.generate
           @logout_urls = []
           @name_id_formats = [Namespaces::PERSISTENT]
           @sign = true
+          @template_name = 'service_provider_metadata'
           @want_assertions_signed = true
         end
 
@@ -25,52 +28,6 @@ module Saml
           @logout_urls.push(location: url, binding: Bindings.binding_for(binding))
         end
 
-        def to_xml
-          Signature.sign(sign: sign) do |xml, signature|
-            xml.instruct!
-            xml.EntityDescriptor entity_descriptor_options do
-              signature.template(id)
-              xml.SPSSODescriptor descriptor_options do
-                if @configuration.signing_certificate_pem.present?
-                  xml.KeyDescriptor use: "signing" do
-                    xml.KeyInfo "xmlns": Namespaces::XMLDSIG do
-                      xml.X509Data do
-                        xml.X509Certificate @configuration.stripped_signing_certificate
-                      end
-                    end
-                  end
-                end
-                if @configuration.encryption_certificate_pem.present?
-                  xml.KeyDescriptor use: "encryption" do
-                    xml.KeyInfo "xmlns": Namespaces::XMLDSIG do
-                      xml.X509Data do
-                        xml.X509Certificate @configuration.stripped_encryption_certificate
-                      end
-                    end
-                  end
-                end
-                logout_urls.each do |item|
-                  xml.SingleLogoutService Binding: item[:binding], Location: item[:location]
-                end
-                name_id_formats.each do |format|
-                  xml.NameIDFormat format
-                end
-                acs_urls.each_with_index do |item, index|
-                  xml.AssertionConsumerService Binding: item[:binding], Location: item[:location], index: index, isDefault: index == 0 ? true : false
-                end
-              end
-              xml.Organization do
-                xml.OrganizationName organization_name, 'xml:lang': "en"
-                xml.OrganizationDisplayName organization_name, 'xml:lang': "en"
-                xml.OrganizationURL organization_url, 'xml:lang': "en"
-              end
-              xml.ContactPerson contactType: "technical" do
-                xml.Company "mailto:#{contact_email}"
-              end
-            end
-          end
-        end
-
         def build
           Saml::Kit::ServiceProviderMetadata.new(to_xml)
         end
lib/saml/kit/signature.rb
@@ -53,9 +53,12 @@ module Saml
       end
 
       def finalize
-        return xml.target! unless sign
+        sign ? apply_to(xml.target!) : xml.target!
+      end
+
+      def apply_to(raw_xml)
+        return raw_xml unless sign
 
-        raw_xml = xml.target!
         @reference_ids.each do |reference_id|
           raw_xml = Xmldsig::SignedDocument.new(raw_xml).sign(private_key)
         end
lib/saml/kit/templatable.rb
@@ -0,0 +1,24 @@
+module Saml
+  module Kit
+    module Templatable
+      def template_path
+        File.join(File.expand_path(File.dirname(__FILE__)), "builders/templates/#{template_name}.builder")
+      end
+
+      def template
+        Tilt.new(template_path)
+      end
+
+      def to_xml(xml: ::Builder::XmlMarkup.new)
+        signature = Saml::Kit::Signature.new(
+          xml,
+          configuration: configuration,
+          sign: sign
+        )
+        signature.apply_to(
+          template.render(self, xml: xml, signature: signature)
+        )
+      end
+    end
+  end
+end
lib/saml/kit.rb
@@ -12,9 +12,11 @@ require "logger"
 require "net/http"
 require "nokogiri"
 require "securerandom"
+require "tilt"
 require "xmldsig"
 
 require "saml/kit/buildable"
+require "saml/kit/templatable"
 require "saml/kit/builders"
 require "saml/kit/namespaces"
 require "saml/kit/serializable"
spec/saml/builders/logout_response_spec.rb
@@ -1,8 +1,7 @@
 require 'spec_helper'
 
 RSpec.describe Saml::Kit::Builders::LogoutResponse do
-  subject { described_class.new(user, request, configuration: configuration) }
-  let(:configuration) { double(issuer: issuer)  }
+  subject { described_class.new(user, request) }
   let(:user) { double(:user, name_id_for: SecureRandom.uuid) }
   let(:request) { Saml::Kit::Builders::LogoutRequest.new(user).build }
   let(:issuer) { FFaker::Internet.http_url }
@@ -12,6 +11,7 @@ RSpec.describe Saml::Kit::Builders::LogoutResponse do
     it 'builds a logout response' do
       travel_to 1.second.from_now
 
+      subject.issuer = issuer
       subject.destination = destination
       result = subject.build
       expect(result.id).to be_present
saml-kit.gemspec
@@ -25,6 +25,7 @@ Gem::Specification.new do |spec|
   spec.add_dependency "activemodel", ">= 4.2.0"
   spec.add_dependency "builder", "~> 3.2"
   spec.add_dependency "nokogiri", "~> 1.8"
+  spec.add_dependency "tilt", "~> 2.0"
   spec.add_dependency "xmldsig", "~> 0.6"
   spec.add_development_dependency "bundler", "~> 1.15"
   spec.add_development_dependency "ffaker", "~> 2.7"