Comparing changes

v0.2.12 v0.2.13
5 commits 9 files changed
lib/saml/kit/locales/en.yml
@@ -5,6 +5,7 @@ en:
       Assertion:
         expired: "must not be expired."
         must_match_issuer: "must match entityId."
+        must_contain_single_assertion: "must contain single Assertion."
       AuthnRequest:
         invalid: "must contain AuthnRequest."
         invalid_fingerprint: "does not match."
lib/saml/kit/assertion.rb
@@ -70,12 +70,16 @@ module Saml
       private
 
       def assertion
-        if encrypted?
+        @assertion ||= if encrypted?
           decrypted = XmlDecryption.new(configuration: @configuration).decrypt(@xml_hash['Response']['EncryptedAssertion'])
           Saml::Kit.logger.debug(decrypted)
           Hash.from_xml(decrypted)['Assertion']
         else
-          @xml_hash.fetch('Response', {}).fetch('Assertion', {})
+          result = @xml_hash.fetch('Response', {}).fetch('Assertion', {})
+          return result if result.is_a?(Hash)
+
+          errors[:assertion] << error_message(:must_contain_single_assertion)
+          {}
         end
       end
 
lib/saml/kit/metadata.rb
@@ -29,6 +29,18 @@ module Saml
         document.find_all("/md:EntityDescriptor/md:#{name}/md:NameIDFormat").map(&:text)
       end
 
+      def organization_name
+        document.find_by("/md:EntityDescriptor/md:Organization/md:OrganizationName").try(:text)
+      end
+
+      def organization_url
+        document.find_by("/md:EntityDescriptor/md:Organization/md:OrganizationURL").try(:text)
+      end
+
+      def contact_person_company
+        document.find_by("/md:EntityDescriptor/md:ContactPerson/md:Company").try(:text)
+      end
+
       def certificates
         @certificates ||= document.find_all("/md:EntityDescriptor/md:#{name}/md:KeyDescriptor").map do |item|
           cert = item.at_xpath("./ds:KeyInfo/ds:X509Data/ds:X509Certificate", Xml::NAMESPACES).text
lib/saml/kit/version.rb
@@ -1,5 +1,5 @@
 module Saml
   module Kit
-    VERSION = "0.2.12"
+    VERSION = "0.2.13"
   end
 end
spec/saml/builders/metadata_spec.rb
@@ -54,18 +54,21 @@ RSpec.describe Saml::Kit::Builders::Metadata do
 
     it 'generates signed idp and sp metadata' do
       configuration = Saml::Kit::Configuration.new do |config|
-        config.generate_key_pair_for(use: :signing)
+        3.times { config.generate_key_pair_for(use: :signing) }
       end
       metadata = Saml::Kit::Metadata.build(configuration: configuration) do |builder|
         builder.entity_id = FFaker::Internet.uri("https")
         builder.build_identity_provider do |x|
           x.embed_signature = true
+          x.add_single_sign_on_service(url, binding: :http_post)
         end
         builder.build_service_provider do |x|
           x.embed_signature = true
+          x.add_assertion_consumer_service(url, binding: :http_post)
         end
       end
       expect(metadata).to be_present
+      expect(metadata).to be_valid
     end
   end
 end
spec/saml/certificate_spec.rb
@@ -1,7 +1,6 @@
 require 'spec_helper'
 
 RSpec.describe Saml::Kit::Certificate do
-  #subject { Saml::Kit.configuration.certificates(use: :signing).last }
   subject { described_class.new(certificate, use: :signing) }
   let(:certificate) do
     cert, _ = Saml::Kit::SelfSignedCertificate.new('password').create
spec/saml/metadata_spec.rb
@@ -44,6 +44,9 @@ RSpec.describe Saml::Kit::Metadata do
       expect(result.single_sign_on_services.count).to eql(2)
       expect(result.assertion_consumer_services.count).to eql(1)
       expect(result.single_logout_services.count).to eql(2)
+      expect(result.organization_name).to eql("Acme, Inc")
+      expect(result.organization_url).to eql("http://localhost:5000/")
+      expect(result.contact_person_company).to eql("mailto:hi@example.com")
     end
   end
 end
spec/saml/response_spec.rb
@@ -160,6 +160,80 @@ RSpec.describe Saml::Kit::Response do
       subject = described_class.new(raw_xml)
       expect(subject).to be_invalid
     end
+
+    it 'is invalid when there are 2 assertions' do
+      id = Saml::Kit::Id.generate
+      issuer = FFaker::Internet.uri("https")
+      configuration = Saml::Kit::Configuration.new do |config|
+        config.generate_key_pair_for(use: :signing)
+      end
+      response_options = {
+        ID: id,
+        Version: "2.0",
+        IssueInstant: Time.now.iso8601,
+        Consent: Saml::Kit::Namespaces::UNSPECIFIED,
+        InResponseTo: request.id,
+        xmlns: Saml::Kit::Namespaces::PROTOCOL,
+      }
+      assertion_options = {
+        ID: Saml::Kit::Id.generate,
+        IssueInstant: Time.now.iso8601,
+        Version: "2.0",
+        xmlns: Saml::Kit::Namespaces::ASSERTION,
+      }
+      xml = Saml::Kit::Signatures.sign(configuration: configuration) do |xml, signature|
+        xml.instruct!
+        xml.Response response_options do
+          xml.Issuer(issuer, xmlns: Saml::Kit::Namespaces::ASSERTION)
+          xml.Status do
+            xml.StatusCode Value: Saml::Kit::Namespaces::SUCCESS
+          end
+          xml.Assertion(assertion_options) do
+            xml.Issuer issuer
+            signature.template(assertion_options[:ID])
+            xml.Subject do
+              xml.NameID FFaker::Internet.email, Format: Saml::Kit::Namespaces::EMAIL_ADDRESS
+              xml.SubjectConfirmation Method: Saml::Kit::Namespaces::BEARER do
+                xml.SubjectConfirmationData "", InResponseTo: request.id, NotOnOrAfter: 3.hours.from_now.utc.iso8601, Recipient: FFaker::Internet.uri("https")
+              end
+            end
+            xml.Conditions NotBefore: Time.now.utc.iso8601, NotOnOrAfter: 3.hours.from_now.utc.iso8601 do
+              xml.AudienceRestriction do
+                xml.Audience request.issuer
+              end
+            end
+            xml.AuthnStatement AuthnInstant: Time.now.iso8601, SessionIndex: assertion_options[:ID], SessionNotOnOrAfter: 3.hours.from_now.utc.iso8601 do
+              xml.AuthnContext do
+                xml.AuthnContextClassRef Saml::Kit::Namespaces::PASSWORD
+              end
+            end
+          end
+          new_options = assertion_options.merge(ID: Saml::Kit::Id.generate)
+          xml.Assertion(new_options) do
+            xml.Issuer issuer
+            xml.Subject do
+              xml.NameID FFaker::Internet.email, Format: Saml::Kit::Namespaces::EMAIL_ADDRESS
+              xml.SubjectConfirmation Method: Saml::Kit::Namespaces::BEARER do
+                xml.SubjectConfirmationData "", InResponseTo: request.id, NotOnOrAfter: 3.hours.from_now.utc.iso8601, Recipient: FFaker::Internet.uri("https")
+              end
+            end
+            xml.Conditions NotBefore: Time.now.utc.iso8601, NotOnOrAfter: 3.hours.from_now.utc.iso8601 do
+              xml.AudienceRestriction do
+                xml.Audience request.issuer
+              end
+            end
+            xml.AuthnStatement AuthnInstant: Time.now.iso8601, SessionIndex: new_options[:ID], SessionNotOnOrAfter: 3.hours.from_now.utc.iso8601 do
+              xml.AuthnContext do
+                xml.AuthnContextClassRef Saml::Kit::Namespaces::PASSWORD
+              end
+            end
+          end
+        end
+      end
+      subject = described_class.new(xml)
+      expect(subject).to_not be_valid
+      expect(subject.errors[:assertion]).to be_present
+    end
   end
 
   describe "#signed?" do
spec/saml/xml_spec.rb
@@ -45,5 +45,16 @@ RSpec.describe Saml::Kit::Xml do
       expect(subject).to_not be_valid
       expect(subject.errors[:signature]).to be_present
     end
+
+    it 'is valid' do
+      configuration = Saml::Kit::Configuration.new do |config|
+        5.times { config.generate_key_pair_for(use: :signing) }
+      end
+      signed_xml = Saml::Kit::Metadata.build_xml(configuration: configuration) do |builder|
+        builder.build_identity_provider
+        builder.build_service_provider
+      end
+      expect(described_class.new(signed_xml)).to be_valid
+    end
   end
 end