Commit 6f026a3
Changed files (8)
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