Commit 6f026a3

mo <mo.khan@gmail.com>
2017-10-23 00:42:41
migrate code from sp to saml-kit.
1 parent 76d83dd
lib/saml/kit/authentication_request.rb
@@ -34,6 +34,13 @@ module Saml
       class Builder
         attr_accessor :id, :issued_at, :issuer, :acs_url
 
+        def initialize(configuration = Saml::Kit.configuration)
+          @id = SecureRandom.uuid
+          @issued_at = Time.now.utc
+          @acs_url = configuration.acs_url
+          @issuer = configuration.issuer
+        end
+
         def to_xml(xml = ::Builder::XmlMarkup.new)
           xml.tag!('samlp:AuthnRequest',
                    "xmlns:samlp" => "urn:oasis:names:tc:SAML:2.0:protocol",
lib/saml/kit/configuration.rb
@@ -1,7 +1,7 @@
 module Saml
   module Kit
     class Configuration
-      attr_accessor :issuer
+      attr_accessor :issuer, :acs_url
     end
   end
 end
lib/saml/kit/saml_request.rb
@@ -1,6 +1,15 @@
 module Saml
   module Kit
     class SamlRequest
+      def self.encode(document)
+        Base64.encode64(compress(document.to_xml))
+      end
+
+      def self.compress(content)
+        content
+        #Zlib::Deflate.deflate(xml, 9)[2..-5]
+      end
+
       def self.decode(raw_request)
         AuthenticationRequest.new(Base64.decode64(raw_request))
       end
lib/saml/kit/saml_response.rb
@@ -3,10 +3,22 @@ module Saml
     class SamlResponse
       def initialize(xml)
         @xml = xml
+        @hash = Hash.from_xml(xml)
+      end
+
+      def name_id
+        @hash['Response']['Assertion']['Subject']['NameID']
+      end
+
+      def [](key)
+        item = @hash['Response']['Assertion']['AttributeStatement']['Attribute'].find do |x|
+          x['Name'] == key.to_s
+        end
+        item['AttributeValue']
       end
 
       def acs_url
-        Hash.from_xml(@xml)['Response']['Destination']
+        @hash['Response']['Destination']
       end
 
       def to_xml
@@ -17,6 +29,10 @@ module Saml
         Base64.strict_encode64(to_xml)
       end
 
+      def self.parse(saml_response)
+        new(Base64.decode64(saml_response))
+      end
+
       class Builder
         attr_reader :user, :request, :id, :reference_id, :now
 
spec/fixtures/encoded_response.txt
@@ -0,0 +1,1 @@
+PHNhbWxwOlJlc3BvbnNlIElEPSJfNzUzNThjZDktZjM1Ny00YjJkLTk5OWYtZjUzMzgyYmE4MjYzIiBWZXJzaW9uPSIyLjAiIElzc3VlSW5zdGFudD0iMjAxNy0xMC0yMlQyMzozNjo0NFoiIERlc3RpbmF0aW9uPSJodHRwOi8vbG9jYWxob3N0OjQwMDAvc2Vzc2lvbiIgQ29uc2VudD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmNvbnNlbnQ6dW5zcGVjaWZpZWQiIEluUmVzcG9uc2VUbz0iZTJmNjQyYzEtMjVlOC00NjMwLWJkYTctMzRhMzAyNzhiNDNlIiB4bWxuczpzYW1scD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnByb3RvY29sIj48SXNzdWVyIHhtbG5zPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXNzZXJ0aW9uIj5wcm9vZi5kZXY8L0lzc3Vlcj48c2FtbHA6U3RhdHVzPjxzYW1scDpTdGF0dXNDb2RlIFZhbHVlPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6c3RhdHVzOlN1Y2Nlc3MiLz48L3NhbWxwOlN0YXR1cz48QXNzZXJ0aW9uIElEPSJfNzhjYWNmNzYtMjQzZS00NTA5LTlhY2UtZDE5ODUzNTNiM2ZlIiBJc3N1ZUluc3RhbnQ9IjIwMTctMTAtMjJUMjM6MzY6NDRaIiBWZXJzaW9uPSIyLjAiPjxJc3N1ZXI+cHJvb2YuZGV2PC9Jc3N1ZXI+PFN1YmplY3Q+PE5hbWVJRCBGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpuYW1laWQtZm9ybWF0OnBlcnNpc3RlbnQiPmVhNjRjMjM1LWUxOGQtNGI5YS04NjcyLTA2ZWY4NGRhYmRlYzwvTmFtZUlEPjxTdWJqZWN0Q29uZmlybWF0aW9uIE1ldGhvZD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmNtOmJlYXJlciI+PFN1YmplY3RDb25maXJtYXRpb25EYXRhIEluUmVzcG9uc2VUbz0iZTJmNjQyYzEtMjVlOC00NjMwLWJkYTctMzRhMzAyNzhiNDNlIiBOb3RPbk9yQWZ0ZXI9IjIwMTctMTAtMjNUMDI6MzY6NDRaIiBSZWNpcGllbnQ9Imh0dHA6Ly9sb2NhbGhvc3Q6NDAwMC9zZXNzaW9uIj48L1N1YmplY3RDb25maXJtYXRpb25EYXRhPjwvU3ViamVjdENvbmZpcm1hdGlvbj48L1N1YmplY3Q+PENvbmRpdGlvbnMgTm90QmVmb3JlPSIyMDE3LTEwLTIyVDIzOjM2OjM5WiIgTm90T25PckFmdGVyPSIyMDE3LTEwLTIzVDAyOjM2OjQ0WiI+PEF1ZGllbmNlUmVzdHJpY3Rpb24+PEF1ZGllbmNlPmFpcnBvcnQuZGV2PC9BdWRpZW5jZT48L0F1ZGllbmNlUmVzdHJpY3Rpb24+PC9Db25kaXRpb25zPjxBdXRoblN0YXRlbWVudCBBdXRobkluc3RhbnQ9IjIwMTctMTAtMjJUMjM6MzY6NDRaIiBTZXNzaW9uSW5kZXg9Il83OGNhY2Y3Ni0yNDNlLTQ1MDktOWFjZS1kMTk4NTM1M2IzZmUiIFNlc3Npb25Ob3RPbk9yQWZ0ZXI9IjIwMTctMTAtMjNUMDI6MzY6NDRaIj48QXV0aG5Db250ZXh0PjxBdXRobkNvbnRleHRDbGFzc1JlZj51cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YWM6Y2xhc3NlczpQYXNzd29yZDwvQXV0aG5Db250ZXh0Q2xhc3NSZWY+PC9BdXRobkNvbnRleHQ+PC9BdXRoblN0YXRlbWVudD48QXR0cmlidXRlU3RhdGVtZW50PjxBdHRyaWJ1dGUgTmFtZT0iaWQiIE5hbWVGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphdHRybmFtZS1mb3JtYXQ6dXJpIiBGcmllbmRseU5hbWU9ImlkIj48QXR0cmlidXRlVmFsdWU+ZWE2NGMyMzUtZTE4ZC00YjlhLTg2NzItMDZlZjg0ZGFiZGVjPC9BdHRyaWJ1dGVWYWx1ZT48L0F0dHJpYnV0ZT48QXR0cmlidXRlIE5hbWU9ImVtYWlsIiBOYW1lRm9ybWF0PSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXR0cm5hbWUtZm9ybWF0OnVyaSIgRnJpZW5kbHlOYW1lPSJlbWFpbCI+PEF0dHJpYnV0ZVZhbHVlPm1vQG1va2hhbi5jYTwvQXR0cmlidXRlVmFsdWU+PC9BdHRyaWJ1dGU+PEF0dHJpYnV0ZSBOYW1lPSJjcmVhdGVkX2F0IiBOYW1lRm9ybWF0PSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXR0cm5hbWUtZm9ybWF0OnVyaSIgRnJpZW5kbHlOYW1lPSJjcmVhdGVkX2F0Ij48QXR0cmlidXRlVmFsdWU+MjAxNy0xMC0yMiAyMToxMjoxNCBVVEM8L0F0dHJpYnV0ZVZhbHVlPjwvQXR0cmlidXRlPjwvQXR0cmlidXRlU3RhdGVtZW50PjwvQXNzZXJ0aW9uPjwvc2FtbHA6UmVzcG9uc2U+
spec/saml/authentication_request_spec.rb
@@ -30,4 +30,34 @@ RSpec.describe Saml::Kit::AuthenticationRequest do
       expect(subject).to be_valid
     end
   end
+
+<<-EXAMPLE
+<samlp:AuthnRequest
+  xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
+  xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
+  ID="ONELOGIN_809707f0030a5d00620c9d9df97f627afe9dcc24"
+  Version="2.0"
+  IssueInstant="2014-07-16T23:52:45Z"
+  AssertionConsumerServiceURL="http://sp.example.com/demo1/index.php?acs">
+  <saml:Issuer>http://sp.example.com/demo1/metadata.php</saml:Issuer>
+  <samlp:NameIDPolicy Format="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"/>
+</samlp:AuthnRequest>
+EXAMPLE
+  describe "#to_xml" do
+    subject { described_class::Builder.new(double(issuer: issuer, acs_url: acs_url)) }
+    let(:issuer) { FFaker::Movie.title }
+    let(:acs_url) { "https://airport.dev/session/acs" }
+
+    it 'returns a valid authentication request' do
+      travel_to DateTime.new(2014, 7, 16, 23, 52, 45)
+      result = Hash.from_xml(subject.to_xml)
+
+      expect(result['AuthnRequest']['ID']).to be_present
+      expect(result['AuthnRequest']['Version']).to eql('2.0')
+      expect(result['AuthnRequest']['IssueInstant']).to eql('2014-07-16T23:52:45Z')
+      expect(result['AuthnRequest']['AssertionConsumerServiceURL']).to eql(acs_url)
+      expect(result['AuthnRequest']['Issuer']).to eql(issuer)
+      expect(result['AuthnRequest']['NameIDPolicy']['Format']).to eql("urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress")
+    end
+  end
 end
spec/saml/saml_request_spec.rb
@@ -0,0 +1,13 @@
+require 'spec_helper'
+
+RSpec.describe Saml::Kit::SamlRequest do
+  describe ".encode" do
+    subject { described_class }
+
+    it 'returns a compressed and base64 encoded document' do
+      xml = "<xml></xml>"
+      document = double(to_xml: xml)
+      expect(subject.encode(document)).to eql(Base64.encode64(xml))
+    end
+  end
+end
spec/saml/saml_response_spec.rb
@@ -112,4 +112,29 @@ RSpec.describe Saml::Kit::SamlResponse do
       expect(hash['Response']['Assertion']['AttributeStatement']['Attribute'][1]['AttributeValue']).to be_present
     end
   end
+
+  describe ".parse" do
+    subject { described_class }
+    let(:raw_response) { IO.read('spec/fixtures/encoded_response.txt') }
+
+    it 'decodes the response to the raw xml' do
+      xml = subject.parse(raw_response).to_xml
+      result = Hash.from_xml(xml)
+      expect(result['Response']['ID']).to eql('_75358cd9-f357-4b2d-999f-f53382ba8263')
+      expect(result['Response']['Version']).to eql('2.0')
+      expect(result['Response']['IssueInstant']).to eql("2017-10-22T23:36:44Z")
+      expect(result['Response']['Destination']).to eql('http://localhost:4000/session')
+      expect(result['Response']['Issuer']).to eql('proof.dev')
+      expect(result['Response']['Status']['StatusCode']['Value']).to eql('urn:oasis:names:tc:SAML:2.0:status:Success')
+      expect(result['Response']['Assertion']['ID']).to eql("_78cacf76-243e-4509-9ace-d1985353b3fe")
+      expect(result['Response']['Assertion']['IssueInstant']).to eql("2017-10-22T23:36:44Z")
+      expect(result['Response']['Assertion']['Issuer']).to eql("proof.dev")
+      expect(result['Response']['Assertion']['Subject']['NameID']).to eql("ea64c235-e18d-4b9a-8672-06ef84dabdec")
+      expect(result['Response']['Assertion']['Conditions']['NotBefore']).to eql("2017-10-22T23:36:39Z")
+      expect(result['Response']['Assertion']['Conditions']['NotOnOrAfter']).to eql("2017-10-23T02:36:44Z")
+      expect(result['Response']['Assertion']['Conditions']['AudienceRestriction']['Audience']).to eql('airport.dev')
+      expect(result['Response']['Assertion']['AttributeStatement']['Attribute'][0]['Name']).to eql('id')
+      expect(result['Response']['Assertion']['AttributeStatement']['Attribute'][0]['AttributeValue']).to eql("ea64c235-e18d-4b9a-8672-06ef84dabdec")
+    end
+  end
 end