Commit e9bf0d8

mo <mo.khan@gmail.com>
2017-10-22 23:06:27
move classes to gem.
1 parent 7237f83
app/models/authentication_request.rb
@@ -1,56 +0,0 @@
-require 'builder'
-
-class AuthenticationRequest
-  def initialize(xml, registry = ServiceProviderRegistry.new)
-    @xml = xml
-    @registry = registry
-    @hash = Hash.from_xml(@xml)
-  end
-
-  def id
-    @hash['AuthnRequest']['ID']
-  end
-
-  def acs_url
-    @hash['AuthnRequest']['AssertionConsumerServiceURL']
-  end
-
-  def issuer
-    @hash['AuthnRequest']['Issuer']
-  end
-
-  def valid?
-    @registry.registered?(issuer)
-  end
-
-  def to_xml
-    @xml
-  end
-
-  def response_for(user)
-    SamlResponse::Builder.new(user, self).build
-  end
-
-  class Builder
-    attr_accessor :id, :issued_at, :issuer, :acs_url
-
-    def to_xml(xml = ::Builder::XmlMarkup.new)
-      xml.tag!('samlp:AuthnRequest',
-               "xmlns:samlp" => "urn:oasis:names:tc:SAML:2.0:protocol",
-               "xmlns:saml" => "urn:oasis:names:tc:SAML:2.0:assertion",
-               ID: id,
-               Version: "2.0",
-               IssueInstant: issued_at.strftime("%Y-%m-%dT%H:%M:%SZ"),
-               AssertionConsumerServiceURL: acs_url,
-              ) do
-        xml.tag!('saml:Issuer', issuer)
-        xml.tag!('samlp:NameIDPolicy', Format: "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress")
-      end
-      xml.target!
-    end
-
-    def build
-      AuthenticationRequest.new(to_xml)
-    end
-  end
-end
app/models/namespaces.rb
@@ -1,38 +0,0 @@
-module Namespaces
-  METADATA = "urn:oasis:names:tc:SAML:2.0:metadata"
-  ASSERTION = "urn:oasis:names:tc:SAML:2.0:assertion"
-  SIGNATURE = "http://www.w3.org/2000/09/xmldsig#"
-  PROTOCOL = "urn:oasis:names:tc:SAML:2.0:protocol"
-
-  module Statuses
-    SUCCESS = "urn:oasis:names:tc:SAML:2.0:status:Success"
-  end
-
-  module Consents
-    UNSPECIFIED = "urn:oasis:names:tc:SAML:2.0:consent:unspecified"
-  end
-
-  module AuthnContext
-    module ClassRef
-      PASSWORD = "urn:oasis:names:tc:SAML:2.0:ac:classes:Password"
-      PASSWORD_PROTECTED = "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport"
-    end
-  end
-
-  module Methods
-    BEARER = "urn:oasis:names:tc:SAML:2.0:cm:bearer"
-  end
-
-  module Formats
-    module Attr
-      URI = "urn:oasis:names:tc:SAML:2.0:attrname-format:uri"
-      BASIC = "urn:oasis:names:tc:SAML:2.0:attrname-format:basic"
-    end
-
-    module NameId
-      EMAIL_ADDRESS = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
-      TRANSIENT = "urn:oasis:names:tc:SAML:2.0:nameid-format:transient"
-      PERSISTENT = "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent"
-    end
-  end
-end
app/models/saml_request.rb
@@ -1,5 +0,0 @@
-class SamlRequest
-  def self.decode(raw_request)
-    AuthenticationRequest.new(Base64.decode64(raw_request))
-  end
-end
app/models/saml_response.rb
@@ -1,125 +0,0 @@
-require 'builder'
-
-class SamlResponse
-  def initialize(xml)
-    @xml = xml
-  end
-
-  def acs_url
-    Hash.from_xml(@xml)['Response']['Destination']
-  end
-
-  def to_xml
-    @xml
-  end
-
-  def encode
-    Base64.strict_encode64(to_xml)
-  end
-
-  class Builder
-    attr_reader :user, :request, :id, :reference_id, :now
-
-    def initialize(user, request)
-      @user = user
-      @request = request
-      @id = SecureRandom.uuid
-      @reference_id = SecureRandom.uuid
-      @now = Time.now.utc
-    end
-
-    def to_xml
-      xml = ::Builder::XmlMarkup.new
-      xml.tag!("samlp:Response", response_options) do
-        xml.Issuer(configuration.issuer, xmlns: Namespaces::ASSERTION)
-        xml.tag!("samlp:Status") do
-          xml.tag!('samlp:StatusCode', Value: Namespaces::Statuses::SUCCESS)
-        end
-        xml.Assertion(assertion_options) do
-          xml.Issuer configuration.issuer
-          xml.Subject do
-            xml.NameID user.uuid, Format: name_id_format
-            xml.SubjectConfirmation Method: Namespaces::Methods::BEARER do
-              xml.SubjectConfirmationData "", subject_confirmation_data_options
-            end
-          end
-          xml.Conditions conditions_options do
-            xml.AudienceRestriction do
-              xml.Audience request.issuer
-            end
-          end
-          xml.AuthnStatement authn_statement_options do
-            xml.AuthnContext do
-              xml.AuthnContextClassRef Namespaces::AuthnContext::ClassRef::PASSWORD
-            end
-          end
-          xml.AttributeStatement do
-            user.assertion_attributes.each do |key, value|
-              xml.Attribute Name: key, NameFormat: Namespaces::Formats::Attr::URI, FriendlyName: key do
-                xml.AttributeValue value.to_s
-              end
-            end
-          end
-        end
-      end
-      xml.target!
-    end
-
-    def build
-      SamlResponse.new(to_xml)
-    end
-
-    private
-
-    def configuration
-      Rails.configuration.x
-    end
-
-    def response_options
-      {
-        ID: "_#{id}",
-        Version: "2.0",
-        IssueInstant: now.iso8601,
-        Destination: request.acs_url,
-        Consent: Namespaces::Consents::UNSPECIFIED,
-        InResponseTo: request.id,
-        "xmlns:samlp" => Namespaces::PROTOCOL,
-      }
-    end
-
-    def assertion_options
-      {
-        ID: "_#{reference_id}",
-        IssueInstant: now.iso8601,
-        Version: "2.0",
-      }
-    end
-
-    def subject_confirmation_data_options
-      {
-        InResponseTo: request.id,
-        NotOnOrAfter: 3.hours.from_now.utc.iso8601,
-        Recipient: request.acs_url,
-      }
-    end
-
-    def conditions_options
-      {
-        NotBefore: 5.seconds.ago.utc.iso8601,
-        NotOnOrAfter: 3.hours.from_now.utc.iso8601,
-      }
-    end
-
-    def authn_statement_options
-      {
-        AuthnInstant: now.iso8601,
-        SessionIndex: assertion_options[:ID],
-        SessionNotOnOrAfter: 3.hours.from_now.utc.iso8601,
-      }
-    end
-
-    def name_id_format
-      "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent"
-    end
-  end
-end
app/models/service_provider_registry.rb
@@ -1,5 +0,0 @@
-class ServiceProviderRegistry
-  def registered?(issuer)
-    true
-  end
-end
spec/models/authentication_request_spec.rb
@@ -1,33 +0,0 @@
-require 'rails_helper'
-
-describe AuthenticationRequest do
-  subject { described_class.new(raw_xml, registry) }
-  let(:registry) { double }
-  let(:id) { SecureRandom.uuid }
-  let(:acs_url) { "https://#{FFaker::Internet.domain_name}/acs" }
-  let(:issuer) { FFaker::Movie.title }
-  let(:raw_xml) do
-    builder = AuthenticationRequest::Builder.new
-    builder.id = id
-    builder.issued_at = Time.now.utc
-    builder.issuer = issuer
-    builder.acs_url = acs_url
-    builder.to_xml
-  end
-
-  it { expect(subject.issuer).to eql(issuer) }
-  it { expect(subject.id).to eql(id) }
-  it { expect(subject.acs_url).to eql(acs_url) }
-
-  describe "#valid?" do
-    it 'returns false when the service provider is not known' do
-      allow(registry).to receive(:registered?).with(issuer).and_return(false)
-      expect(subject).to_not be_valid
-    end
-
-    it 'returns true when the service provider is registered' do
-      allow(registry).to receive(:registered?).with(issuer).and_return(true)
-      expect(subject).to be_valid
-    end
-  end
-end
spec/models/saml_response_spec.rb
@@ -1,115 +0,0 @@
-require 'rails_helper'
-
-describe SamlResponse do
-  describe "#acs_url" do
-    let(:acs_url) { "https://#{FFaker::Internet.domain_name}/acs" }
-    let(:user) { double(:user, uuid: SecureRandom.uuid, assertion_attributes: { }) }
-    let(:request) { double(id: SecureRandom.uuid, acs_url: acs_url, issuer: FFaker::Movie.title) }
-    subject { SamlResponse::Builder.new(user, request).build }
-
-    it 'returns the acs_url' do
-      expect(subject.acs_url).to eql(acs_url)
-    end
-  end
-
-  describe "#to_xml" do
-    subject { SamlResponse::Builder.new(user, request) }
-    let(:user) { double(:user, uuid: SecureRandom.uuid, assertion_attributes: { email: email, created_at: Time.now.utc.iso8601 }) }
-    let(:request) { double(id: SecureRandom.uuid, acs_url: acs_url, issuer: FFaker::Movie.title) }
-    let(:acs_url) { "https://#{FFaker::Internet.domain_name}/acs" }
-    let(:issuer) { FFaker::Movie.title }
-    let(:email) { FFaker::Internet.email }
-
-    <<-XML
-<samlp:Response
-  xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
-  xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
-  ID="_8e8dc5f69a98cc4c1ff3427e5ce34606fd672f91e6"
-  Version="2.0"
-  IssueInstant="2014-07-17T01:01:48Z"
-  Destination="http://sp.example.com/demo1/index.php?acs"
-  InResponseTo="ONELOGIN_4fee3b046395c4e751011e97f8900b5273d56685">
-  <saml:Issuer>http://idp.example.com/metadata.php</saml:Issuer>
-  <samlp:Status>
-    <samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/>
-  </samlp:Status>
-  <saml:Assertion
-    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-    xmlns:xs="http://www.w3.org/2001/XMLSchema"
-    ID="_d71a3a8e9fcc45c9e9d248ef7049393fc8f04e5f75"
-    Version="2.0"
-    IssueInstant="2014-07-17T01:01:48Z">
-    <saml:Issuer>http://idp.example.com/metadata.php</saml:Issuer>
-    <saml:Subject>
-      <saml:NameID Format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient">_ce3d2948b4cf20146dee0a0b3dd6f69b6cf86f62d7</saml:NameID>
-      <saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
-        <saml:SubjectConfirmationData NotOnOrAfter="2024-01-18T06:21:48Z" Recipient="http://sp.example.com/demo1/index.php?acs" InResponseTo="ONELOGIN_4fee3b046395c4e751011e97f8900b5273d56685"/>
-      </saml:SubjectConfirmation>
-    </saml:Subject>
-    <saml:Conditions NotBefore="2014-07-17T01:01:18Z" NotOnOrAfter="2024-01-18T06:21:48Z">
-      <saml:AudienceRestriction>
-        <saml:Audience>http://sp.example.com/demo1/metadata.php</saml:Audience>
-      </saml:AudienceRestriction>
-    </saml:Conditions>
-    <saml:AuthnStatement AuthnInstant="2014-07-17T01:01:48Z" SessionNotOnOrAfter="2024-07-17T09:01:48Z" SessionIndex="_be9967abd904ddcae3c0eb4189adbe3f71e327cf93">
-      <saml:AuthnContext>
-        <saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:Password</saml:AuthnContextClassRef>
-      </saml:AuthnContext>
-    </saml:AuthnStatement>
-    <saml:AttributeStatement>
-      <saml:Attribute Name="uid" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
-        <saml:AttributeValue xsi:type="xs:string">test</saml:AttributeValue>
-      </saml:Attribute>
-      <saml:Attribute Name="mail" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
-        <saml:AttributeValue xsi:type="xs:string">test@example.com</saml:AttributeValue>
-      </saml:Attribute>
-    </saml:AttributeStatement>
-  </saml:Assertion>
-</samlp:Response>
-    XML
-    it 'returns a proper response for the user' do
-      travel_to 1.second.from_now
-      allow(Rails.configuration.x).to receive(:issuer).and_return(issuer)
-      result = subject.to_xml
-      hash = Hash.from_xml(result)
-
-      expect(hash['Response']['ID']).to be_present
-      expect(hash['Response']['Version']).to eql('2.0')
-      expect(hash['Response']['IssueInstant']).to eql(Time.now.utc.iso8601)
-      expect(hash['Response']['Destination']).to eql(acs_url)
-      expect(hash['Response']['InResponseTo']).to eql(request.id)
-      expect(hash['Response']['Issuer']).to eql(issuer)
-      expect(hash['Response']['Status']['StatusCode']['Value']).to eql("urn:oasis:names:tc:SAML:2.0:status:Success")
-
-      expect(hash['Response']['Assertion']['ID']).to be_present
-      expect(hash['Response']['Assertion']['IssueInstant']).to eql(Time.now.utc.iso8601)
-      expect(hash['Response']['Assertion']['Version']).to eql("2.0")
-      expect(hash['Response']['Assertion']['Issuer']).to eql(issuer)
-
-      expect(hash['Response']['Assertion']['Subject']['NameID']).to eql(user.uuid)
-      expect(hash['Response']['Assertion']['Subject']['SubjectConfirmation']['Method']).to eql("urn:oasis:names:tc:SAML:2.0:cm:bearer")
-      expect(hash['Response']['Assertion']['Subject']['SubjectConfirmation']['SubjectConfirmationData']['NotOnOrAfter']).to eql(3.hours.from_now.utc.iso8601)
-      expect(hash['Response']['Assertion']['Subject']['SubjectConfirmation']['SubjectConfirmationData']['Recipient']).to eql(acs_url)
-      expect(hash['Response']['Assertion']['Subject']['SubjectConfirmation']['SubjectConfirmationData']['InResponseTo']).to eql(request.id)
-
-      expect(hash['Response']['Assertion']['Conditions']['NotBefore']).to eql(5.seconds.ago.utc.iso8601)
-      expect(hash['Response']['Assertion']['Conditions']['NotOnOrAfter']).to eql(3.hours.from_now.utc.iso8601)
-      expect(hash['Response']['Assertion']['Conditions']['AudienceRestriction']['Audience']).to eql(request.issuer)
-
-      expect(hash['Response']['Assertion']['AuthnStatement']['AuthnInstant']).to eql(Time.now.utc.iso8601)
-      expect(hash['Response']['Assertion']['AuthnStatement']['SessionNotOnOrAfter']).to eql(3.hours.from_now.utc.iso8601)
-      expect(hash['Response']['Assertion']['AuthnStatement']['SessionIndex']).to eql(hash['Response']['Assertion']['ID'])
-      expect(hash['Response']['Assertion']['AuthnStatement']['AuthnContext']['AuthnContextClassRef']).to eql('urn:oasis:names:tc:SAML:2.0:ac:classes:Password')
-
-      expect(hash['Response']['Assertion']['AttributeStatement']['Attribute'][0]['Name']).to eql('email')
-      expect(hash['Response']['Assertion']['AttributeStatement']['Attribute'][0]['FriendlyName']).to eql('email')
-      expect(hash['Response']['Assertion']['AttributeStatement']['Attribute'][0]['NameFormat']).to eql('urn:oasis:names:tc:SAML:2.0:attrname-format:uri')
-      expect(hash['Response']['Assertion']['AttributeStatement']['Attribute'][0]['AttributeValue']).to eql(email)
-
-      expect(hash['Response']['Assertion']['AttributeStatement']['Attribute'][1]['Name']).to eql('created_at')
-      expect(hash['Response']['Assertion']['AttributeStatement']['Attribute'][1]['FriendlyName']).to eql('created_at')
-      expect(hash['Response']['Assertion']['AttributeStatement']['Attribute'][1]['NameFormat']).to eql('urn:oasis:names:tc:SAML:2.0:attrname-format:uri')
-      expect(hash['Response']['Assertion']['AttributeStatement']['Attribute'][1]['AttributeValue']).to be_present
-    end
-  end
-end