Commit 12f4348

mo <mo.khan@gmail.com>
2017-10-23 00:42:41
migrate code from sp to saml-kit.
1 parent bda3928
airport/app/controllers/sessions_controller.rb
@@ -8,7 +8,7 @@ class SessionsController < ApplicationController
   end
 
   def create
-    saml_response = SamlResponse.parse(params[:SAMLResponse])
+    saml_response = Saml::Kit::SamlResponse.parse(params[:SAMLResponse])
     session[:user_id] = saml_response.name_id
     session[:email] = saml_response[:email]
     redirect_to dashboard_path
@@ -18,10 +18,14 @@ class SessionsController < ApplicationController
 
   def query_params
     {
-      'SAMLRequest' => SamlRequest.build(AuthenticationRequest.new),
+      'SAMLRequest' => Saml::Kit::SamlRequest.encode(authentication_request),
       'RelayState' => JSON.generate(inbound_path: '/'),
     }.map do |(x, y)|
       "#{x}=#{CGI.escape(y)}"
     end.join('&')
   end
+
+  def authentication_request
+    Saml::Kit::AuthenticationRequest::Builder.new
+  end
 end
airport/app/models/authentication_request.rb
@@ -1,26 +0,0 @@
-require 'builder'
-
-class AuthenticationRequest
-  attr_reader :id, :issued_at
-
-  def initialize(configuration = Configuration.new)
-    @id = SecureRandom.uuid
-    @issued_at = Time.now.utc
-    @configuration = configuration
-  end
-
-  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: @configuration.acs_url,
-    ) do
-      xml.tag!('saml:Issuer', @configuration.issuer)
-      xml.tag!('samlp:NameIDPolicy', Format: "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress")
-    end
-    xml.target!
-  end
-end
airport/app/models/configuration.rb
@@ -1,15 +0,0 @@
-class Configuration
-  def issuer
-    configuration.issuer
-  end
-
-  def acs_url
-    configuration.acs_url
-  end
-
-  private
-
-  def configuration
-    Rails.configuration.x
-  end
-end
airport/app/models/saml_request.rb
@@ -1,24 +0,0 @@
-class SamlRequest
-  def self.build(document)
-    new(document.to_xml).to_s
-  end
-
-  def initialize(raw_xml)
-    @xml = encode(compress(raw_xml))
-  end
-
-  def to_s
-    @xml
-  end
-
-  private
-
-  def encode(xml)
-    Base64.encode64(xml)
-  end
-
-  def compress(xml)
-    xml
-    #Zlib::Deflate.deflate(xml, 9)[2..-5]
-  end
-end
airport/app/models/saml_response.rb
@@ -1,25 +0,0 @@
-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 to_xml
-    @xml
-  end
-
-  def self.parse(saml_response)
-    new(Base64.decode64(saml_response))
-  end
-end
airport/config/initializers/configuration.rb
@@ -1,5 +1,1 @@
-Rails.application.config.x.acs_url = ENV['ACS_URL']
 Rails.application.config.x.authentication_host = ENV['AUTHENTICATION_HOST']
-Rails.application.config.x.issuer = ENV['ISSUER']
-Rails.configuration.x.api_client_id = ENV['API_CLIENT_ID']
-Rails.configuration.x.api_client_secret = ENV['API_CLIENT_SECRET']
airport/config/initializers/saml_kit.rb
@@ -0,0 +1,4 @@
+Saml::Kit.configure do |configuration|
+  configuration.issuer = ENV['ISSUER']
+  configuration.acs_url = ENV['ACS_URL']
+end
airport/spec/controllers/computers_controller_spec.rb
@@ -1,49 +0,0 @@
-require 'rails_helper'
-
-describe ComputersController do
-  describe "#index" do
-    let(:access_token) { SecureRandom.uuid }
-    let(:computer) do
-      {
-        "connector_guid": "ad29d359-dac9-4940-9c7e-c50e6d32ee6f",
-        "hostname": "Demo_CozyDuke",
-        "active": true,
-        "links": {
-          "computer": "https://portal.dev/v1/computers/ad29d359-dac9-4940-9c7e-c50e6d32ee6f",
-          "trajectory": "https://portal.dev/v1/computers/ad29d359-dac9-4940-9c7e-c50e6d32ee6f/trajectory",
-          "group": "https://portal.dev/v1/groups/b077d6bc-bbdf-42f7-8838-a06053fbd98a"
-        },
-        "connector_version": "4.1.7.10201",
-        "operating_system": "Windows 7, SP 1.0",
-        "internal_ips": [ "87.27.44.37" ],
-        "external_ip": "93.111.140.204",
-        "group_guid": "b077d6bc-bbdf-42f7-8838-a06053fbd98a",
-        "install_date": "2016-05-20T19:20:00Z",
-        "network_addresses": [ { "mac": "09:de:6b:a8:74:10", "ip": "87.27.44.37" } ],
-        "policy": { "guid": "89912c9e-8dbd-4c2b-a1d8-dee8a0c2bb29", "name": "Audit Policy" }
-      }
-    end
-
-    it 'fetches all the computers' do
-      response_body = {
-        "version": "v1.2.0",
-        "metadata": {
-          "links": { "self": "https://portal.dev/v1/computers" },
-          "results": { "total": 1, "current_item_count": 1, "index": 0, "items_per_page": 500 }
-        },
-        "data": [ computer ]
-      }
-
-      stub_request(:get, "https://portal.dev/v1/computers/").
-        with(headers: { 'Authorization' => "Bearer #{access_token}", 'Accept'=>'application/json', 'Content-Type'=>'application/json'}).
-        to_return(status: 201, body: response_body.to_json)
-
-      session[:access_token] = access_token
-
-      get :index
-
-      expect(response).to have_http_status(:ok)
-      expect(assigns(:computers)).to match_array([computer])
-    end
-  end
-end
airport/spec/controllers/sessions_controller_spec.rb
@@ -8,7 +8,7 @@ describe SessionsController do
 
     it 'generates a saml request and redirects to the auth host' do
       travel_to 1.seconds.from_now
-      allow(SamlRequest).to receive(:build).and_return(saml_request)
+      allow(Saml::Kit::SamlRequest).to receive(:encode).and_return(saml_request)
       allow(Rails.configuration.x).to receive(:authentication_host).and_return(auth_host)
 
       get :new
@@ -26,35 +26,19 @@ describe SessionsController do
   end
 
   describe "#create" do
-    let(:saml_response) { IO.read('spec/fixtures/encoded_response.txt') }
-    let(:email) { 'mokha@cisco.com' }
-    let(:bearer_token) { SecureRandom.uuid }
-    let(:user_id) { '760a54e2-31ba-4dfa-9303-fa6887270980' }
-    let(:username) { Rails.configuration.x.api_client_id }
-    let(:password) { Rails.configuration.x.api_client_secret }
+    let(:saml_response) do
+      Saml::Kit::SamlResponse::Builder.new(user, auth_request).build.encode
+    end
+    let(:auth_request) { double(id: '1', issuer: 'issuer', acs_url: '')  }
+    let(:user) { double(uuid: user_id, assertion_attributes: { email: email, blah: 'blah' }) }
+    let(:email) { FFaker::Internet.email }
+    let(:user_id) { SecureRandom.uuid }
 
     it 'logs the correct user in' do
-      expected_code = ActionController::HttpAuthentication::Basic.encode_credentials(username, password).split(' ', 2).second
-
-      response_body = {
-        "version":"v1.2.0",
-        "metadata":{ "links":{ "self":"http://test.host/v1/users/#{user_id}/api_credentials" } },
-        "data":{
-          "access_token": bearer_token,
-          "token_type":"Bearer",
-          "expires_in":1799,
-          "expires_at":"2017-10-03T19:38:26Z",
-        }
-      }
-      stub_request(:post, "https://portal.dev/v1/users/#{user_id}/api_credentials").
-        with(body: "{\"grant_type\":\"authorization_code\",\"code\":\"#{expected_code}\"}", headers: {'Accept'=>'application/json', 'Content-Type'=>'application/json'}).
-        to_return(status: 201, body: response_body.to_json)
-
       post :create, params: { SAMLResponse: saml_response }
 
       expect(session[:email]).to eql(email)
       expect(session[:user_id]).to eql(user_id)
-      expect(session[:access_token]).to eql(bearer_token)
       expect(response).to redirect_to(dashboard_path)
     end
   end
airport/spec/fixtures/encoded_response.txt
@@ -1,1 +0,0 @@
-PHNhbWxwOlJlc3BvbnNlIElEPSJfYjA0MTczNTAtOTY3MS0wMTM1LTU1ZmMtMjA5OTliMDllNWU3IiBWZXJzaW9uPSIyLjAiIElzc3VlSW5zdGFudD0iMjAxNy0xMC0xOFQyMDozNDozOVoiIERlc3RpbmF0aW9uPSJodHRwOi8vbG9jYWxob3N0OjMwMDAvc2Vzc2lvbiIgQ29uc2VudD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmNvbnNlbnQ6dW5zcGVjaWZpZWQiIEluUmVzcG9uc2VUbz0iNzM5MTAyZjUtZmFmOS00OTY3LWJlNzQtY2U0NWVjYjRmNzUzIiB4bWxuczpzYW1scD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnByb3RvY29sIj48SXNzdWVyIHhtbG5zPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXNzZXJ0aW9uIj5odHRwOi8vYXV0aC5kZXYvYXV0aC9tZXRhZGF0YTwvSXNzdWVyPjxzYW1scDpTdGF0dXM+PHNhbWxwOlN0YXR1c0NvZGUgVmFsdWU9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpzdGF0dXM6U3VjY2VzcyIvPjwvc2FtbHA6U3RhdHVzPjxBc3NlcnRpb24geG1sbnM9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphc3NlcnRpb24iIElEPSJfYjA0MTc0MDAtOTY3MS0wMTM1LTU1ZmMtMjA5OTliMDllNWU3IiBJc3N1ZUluc3RhbnQ9IjIwMTctMTAtMThUMjA6MzQ6MzlaIiBWZXJzaW9uPSIyLjAiPjxJc3N1ZXI+aHR0cDovL2F1dGguZGV2L2F1dGgvbWV0YWRhdGE8L0lzc3Vlcj48ZHM6U2lnbmF0dXJlIHhtbG5zOmRzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjIj48ZHM6U2lnbmVkSW5mbyB4bWxuczpkcz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnIyI+PGRzOkNhbm9uaWNhbGl6YXRpb25NZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzEwL3htbC1leGMtYzE0biMiPjwvZHM6Q2Fub25pY2FsaXphdGlvbk1ldGhvZD48ZHM6U2lnbmF0dXJlTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8wNC94bWxkc2lnLW1vcmUjcnNhLXNoYTI1NiI+PC9kczpTaWduYXR1cmVNZXRob2Q+PGRzOlJlZmVyZW5jZSBVUkk9IiNfYjA0MTc0MDAtOTY3MS0wMTM1LTU1ZmMtMjA5OTliMDllNWU3Ij48ZHM6VHJhbnNmb3Jtcz48ZHM6VHJhbnNmb3JtIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnI2VudmVsb3BlZC1zaWduYXR1cmUiPjwvZHM6VHJhbnNmb3JtPjxkczpUcmFuc2Zvcm0gQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzEwL3htbC1leGMtYzE0biMiPjwvZHM6VHJhbnNmb3JtPjwvZHM6VHJhbnNmb3Jtcz48ZHM6RGlnZXN0TWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8wNC94bWxlbmMjc2hhMjU2Ij48L2RzOkRpZ2VzdE1ldGhvZD48ZHM6RGlnZXN0VmFsdWU+SVJEcGszM2hmWlh4RDluWnNsUEs0L0JWNWkrQkYwZmZ1WFY2R3I1ZzhGQT08L2RzOkRpZ2VzdFZhbHVlPjwvZHM6UmVmZXJlbmNlPjwvZHM6U2lnbmVkSW5mbz48ZHM6U2lnbmF0dXJlVmFsdWU+ZGVkSkRieG0xcFJHcEJJT2l3M1BMZHJkd0Z1QjdxZGhVTXZiUXkvN1ZDNEloQ25BNDE4Rmh0M1FLeFN1Z3Q2cXpkU2Rnam8yMmVJRVY0ajRyVnZJUmZLVVJwMWZqdTNkWnVrcFJpTCtWNngvSjRhajgrbHhpSEx1VWN1T1FRSytmRGtMbE4rZkdHQjBaUVpsbGw4LzY4NkxkZW1iYlVSTTR2TmJ6Si9DcnFzWG9NdlJpaWVVc2tGdFdUMS80ckJTdmJ6OUF5YW1Zd3NKalNKaFFsNXg3OW5SdzhtQzNaNGorNWlVR3M2eE9Sa0ljQ0hxZ3puZUZYemtVK2FnTTVzcGZGNDVZem9xQ0UzR2xHdkk3cEtDSkZRUGxKU2loWm5OS2kwSmtCMlhQOXBRSWpvMG1NaHhWVFdwckl3T3UveWlFMGJKWm1BRnh5aFdwSkNhNTBJMnd3PT08L2RzOlNpZ25hdHVyZVZhbHVlPjxLZXlJbmZvIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjIj48ZHM6WDUwOURhdGE+PGRzOlg1MDlDZXJ0aWZpY2F0ZT5NSUlEMnpDQ0FzT2dBd0lCQWdJSkFORXdkdFB2MkNjZk1BMEdDU3FHU0liM0RRRUJDd1VBTUlHRE1Rc3dDUVlEVlFRR0V3SkRRVEVRTUE0R0ExVUVDQXdIUVd4aVpYSjBZVEVRTUE0R0ExVUVCd3dIUTJGc1oyRnllVEVPTUF3R0ExVUVDZ3dGUTJselkyOHhEREFLQmdOVkJBc01BMEZOVURFU01CQUdBMVVFQXd3SlkybHpZMjh1WTI5dE1SNHdIQVlKS29aSWh2Y05BUWtCRmc5d2FtbHpiRUJqYVhOamJ5NWpiMjB3SGhjTk1UWXdNakl6TVRneU1ERXpXaGNOTWpZd01qSXlNVGd5TURFeldqQ0JnekVMTUFrR0ExVUVCaE1DUTBFeEVEQU9CZ05WQkFnTUIwRnNZbVZ5ZEdFeEVEQU9CZ05WQkFjTUIwTmhiR2RoY25reERqQU1CZ05WQkFvTUJVTnBjMk52TVF3d0NnWURWUVFMREFOQlRWQXhFakFRQmdOVkJBTU1DV05wYzJOdkxtTnZiVEVlTUJ3R0NTcUdTSWIzRFFFSkFSWVBjR3BwYzJ4QVkybHpZMjh1WTI5dE1JSUJJakFOQmdrcWhraUc5dzBCQVFFRkFBT0NBUThBTUlJQkNnS0NBUUVBcFk2QmFMMUhabjNPUU0welBWKzUrS2twSmM5MGZVS0dJMG1mNFdtYlBCM05QRjFxS2hvRlZTOWNaY2x5TDR5NnZSaEw4OGxaQnA4N3hrejNNMXB6MTRITFRkbHM0TnNPR3p3MXhLTWl6RnpQL3NFQm1uNWdMMmZXSGtrYjVtRnRyV2ExUHFFTTVjVFdQWXZlai81ZktNNHFkUXRXM2taRnVNZ2hhY0NWOVk5eEdPYmt1Y1pNUm92MVl5OEhlYTN2RUgvU1E1YS83TkpORmlIekNRMjEwOU9Da3hwVFYwbmpjWWVJQlF4VUZrUjJVS1dJRDF3YmlFcXZqQXVydEE4c2lxL0NQcHdNamtBR2lxaWFSTURyb0hDUnB5YzNaSURpZXc0OWVzM3R4d3FKclpKUGdpWHk3SGRJZENBYWlnRnNXQXZpNllkVkVFeU5FeGViM2dpT2J3SURBUUFCbzFBd1RqQWRCZ05WSFE0RUZnUVVicHk5c0R1R3ljdDB1SXUzT1lreUk3R1ZkNEV3SHdZRFZSMGpCQmd3Rm9BVWJweTlzRHVHeWN0MHVJdTNPWWt5STdHVmQ0RXdEQVlEVlIwVEJBVXdBd0VCL3pBTkJna3Foa2lHOXcwQkFRc0ZBQU9DQVFFQVhUVU9xYXoxYTdNY3NnaVU2dTA5aVE0SFVaS2s1V2dzb0lqa01KWS9qRTFJOEdzT2xobHkxdEhnUG1PVkhlLzBReFpnK1lCNE1WL2Q4MzcwOE5wQmlLK3FHekU5bWpDQzQ1N3RjZnpkNzYyVmNwZXhtTG81U1IrSldPQkZYU0ZnZGRyRWhCeThlV1hrMDc1dmRRdC9mbUI4UzRNd1dTaUlDY3dZNHJUZGR3eTRMQlE0eXFzTGg3dTd5MVhwdFpHMjBqZDFWSUJzRlAxa2NyeGxQVER4aHVsSEprZ0FEV2YrTi9ueWhVT1RTT21GbGFYSzdMSkZKRGpxTDR1c2xQODVpUG5FempDUnJOUkNySTZlUWxlVHJyVGRldjF1MzJSTWo1bnlyY3AxaDlHM0p0NS9neUpDaE03QmFyM2J3Sk1acHY3Y21kR21ZYTNMc3g2bVJnTzZHZz09PC9kczpYNTA5Q2VydGlmaWNhdGU+PC9kczpYNTA5RGF0YT48L0tleUluZm8+PC9kczpTaWduYXR1cmU+PFN1YmplY3Q+PE5hbWVJRCBGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjEuMTpuYW1laWQtZm9ybWF0OmVtYWlsQWRkcmVzcyI+bW9raGFAY2lzY28uY29tPC9OYW1lSUQ+PFN1YmplY3RDb25maXJtYXRpb24gTWV0aG9kPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6Y206YmVhcmVyIj48U3ViamVjdENvbmZpcm1hdGlvbkRhdGEgSW5SZXNwb25zZVRvPSI3MzkxMDJmNS1mYWY5LTQ5NjctYmU3NC1jZTQ1ZWNiNGY3NTMiIE5vdE9uT3JBZnRlcj0iMjAxNy0xMC0xOFQyMDozNzozOVoiIFJlY2lwaWVudD0iaHR0cDovL2xvY2FsaG9zdDozMDAwL3Nlc3Npb24iPjwvU3ViamVjdENvbmZpcm1hdGlvbkRhdGE+PC9TdWJqZWN0Q29uZmlybWF0aW9uPjwvU3ViamVjdD48Q29uZGl0aW9ucyBOb3RCZWZvcmU9IjIwMTctMTAtMThUMjA6MzQ6MzRaIiBOb3RPbk9yQWZ0ZXI9IjIwMTctMTAtMThUMjE6MzQ6MzlaIj48QXVkaWVuY2VSZXN0cmljdGlvbj48QXVkaWVuY2U+YWlycG9ydC5kZXY8L0F1ZGllbmNlPjwvQXVkaWVuY2VSZXN0cmljdGlvbj48L0NvbmRpdGlvbnM+PEF0dHJpYnV0ZVN0YXRlbWVudD48QXR0cmlidXRlIE5hbWU9InVzZXJfaWQiIE5hbWVGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphdHRybmFtZS1mb3JtYXQ6dXJpIiBGcmllbmRseU5hbWU9InVzZXJfaWQiPjxBdHRyaWJ1dGVWYWx1ZT43NjBhNTRlMi0zMWJhLTRkZmEtOTMwMy1mYTY4ODcyNzA5ODA8L0F0dHJpYnV0ZVZhbHVlPjwvQXR0cmlidXRlPjxBdHRyaWJ1dGUgTmFtZT0ic3VjY2Vzc19ub3RpY2UiIE5hbWVGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphdHRybmFtZS1mb3JtYXQ6dXJpIiBGcmllbmRseU5hbWU9InN1Y2Nlc3Nfbm90aWNlIj48L0F0dHJpYnV0ZT48QXR0cmlidXRlIE5hbWU9ImJ1c2luZXNzX2d1aWQiIE5hbWVGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphdHRybmFtZS1mb3JtYXQ6dXJpIiBGcmllbmRseU5hbWU9ImJ1c2luZXNzX2d1aWQiPjxBdHRyaWJ1dGVWYWx1ZT5lMTJkYzJhNi02ZjE4LTRkMTEtODIwNC1hOTg0OTA4OTZkZTg8L0F0dHJpYnV0ZVZhbHVlPjwvQXR0cmlidXRlPjxBdHRyaWJ1dGUgTmFtZT0iZXZlbnRfaW50YWtlX3VybCIgTmFtZUZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmF0dHJuYW1lLWZvcm1hdDp1cmkiIEZyaWVuZGx5TmFtZT0iZXZlbnRfaW50YWtlX3VybCI+PEF0dHJpYnV0ZVZhbHVlPjwvQXR0cmlidXRlVmFsdWU+PC9BdHRyaWJ1dGU+PEF0dHJpYnV0ZSBOYW1lPSJjb25zb2xlX2Jhc2VfdXJsIiBOYW1lRm9ybWF0PSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXR0cm5hbWUtZm9ybWF0OnVyaSIgRnJpZW5kbHlOYW1lPSJjb25zb2xlX2Jhc2VfdXJsIj48QXR0cmlidXRlVmFsdWU+PC9BdHRyaWJ1dGVWYWx1ZT48L0F0dHJpYnV0ZT48QXR0cmlidXRlIE5hbWU9ImF1dGhfdG9rZW4iIE5hbWVGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphdHRybmFtZS1mb3JtYXQ6dXJpIiBGcmllbmRseU5hbWU9ImF1dGhfdG9rZW4iPjxBdHRyaWJ1dGVWYWx1ZT5iMjU0Y2VhYWI2MzRkZGM2MDUzZDYyZjg3MTIyNjMzYzllZDQ3ODU4NzhkY2Y0OTg8L0F0dHJpYnV0ZVZhbHVlPjwvQXR0cmlidXRlPjxBdHRyaWJ1dGUgTmFtZT0iYW1wX3VzZXJfYWRtaW4iIE5hbWVGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphdHRybmFtZS1mb3JtYXQ6dXJpIiBGcmllbmRseU5hbWU9ImFtcF91c2VyX3JvbGUiPjxBdHRyaWJ1dGVWYWx1ZT50cnVlPC9BdHRyaWJ1dGVWYWx1ZT48L0F0dHJpYnV0ZT48QXR0cmlidXRlIE5hbWU9ImFtcF9idXNpbmVzc19uYW1lIiBOYW1lRm9ybWF0PSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXR0cm5hbWUtZm9ybWF0OnVyaSIgRnJpZW5kbHlOYW1lPSJhbXBfYnVzaW5lc3NfbmFtZSI+PEF0dHJpYnV0ZVZhbHVlPkJ1c2luZXNzIGZvciBtb2toYUBjaXNjby5jb208L0F0dHJpYnV0ZVZhbHVlPjwvQXR0cmlidXRlPjwvQXR0cmlidXRlU3RhdGVtZW50PjxBdXRoblN0YXRlbWVudCBBdXRobkluc3RhbnQ9IjIwMTctMTAtMThUMjA6MzQ6MzlaIiBTZXNzaW9uSW5kZXg9Il9iMDQxNzQwMC05NjcxLTAxMzUtNTVmYy0yMDk5OWIwOWU1ZTciPjxBdXRobkNvbnRleHQ+PEF1dGhuQ29udGV4dENsYXNzUmVmPnVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphYzpjbGFzc2VzOlBhc3N3b3JkPC9BdXRobkNvbnRleHRDbGFzc1JlZj48L0F1dGhuQ29udGV4dD48L0F1dGhuU3RhdGVtZW50PjwvQXNzZXJ0aW9uPjwvc2FtbHA6UmVzcG9uc2U+
\ No newline at end of file
airport/spec/models/authentication_request_spec.rb
@@ -1,33 +0,0 @@
-require 'rails_helper'
-
-describe AuthenticationRequest do
-  subject { AuthenticationRequest.new(double(issuer: issuer, acs_url: acs_url)) }
-  let(:issuer) { FFaker::Movie.title }
-  let(:acs_url) { "https://airport.dev/session/acs" }
-
-<<-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
-    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
airport/spec/models/saml_response_spec.rb
@@ -1,60 +0,0 @@
-require 'rails_helper'
-
-describe SamlResponse do
-  describe ".parse" do
-    subject { described_class }
-    let(:raw_response) { IO.read('spec/fixtures/encoded_response.txt') }
-
-<<-XML
-<samlp:Response ID="_b0417350-9671-0135-55fc-20999b09e5e7" Version="2.0"
-  IssueInstant="2017-10-18T20:34:39Z"
-  Destination="http://localhost:3000/session"
-  Consent="urn:oasis:names:tc:SAML:2.0:consent:unspecified"
-  InResponseTo="739102f5-faf9-4967-be74-ce45ecb4f753"
-  xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol">
-  <Issuer xmlns="urn:oasis:names:tc:SAML:2.0:assertion">http://auth.dev/auth/metadata</Issuer>
-  <samlp:Status>
-    <samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/>
-  </samlp:Status>
-  <Assertion xmlns="urn:oasis:names:tc:SAML:2.0:assertion"
-    ID="_b0417400-9671-0135-55fc-20999b09e5e7"
-    IssueInstant="2017-10-18T20:34:39Z" Version="2.0">
-    <Issuer>http://auth.dev/auth/metadata</Issuer>
-    <Subject>
-      <NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress">mokha@cisco.com</NameID>
-      <SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
-        <SubjectConfirmationData InResponseTo="739102f5-faf9-4967-be74-ce45ecb4f753" NotOnOrAfter="2017-10-18T20:37:39Z" Recipient="http://localhost:3000/session"></SubjectConfirmationData>
-      </SubjectConfirmation>
-    </Subject>
-    <Conditions NotBefore="2017-10-18T20:34:34Z" NotOnOrAfter="2017-10-18T21:34:39Z">
-      <AudienceRestriction><Audience>airport.dev</Audience></AudienceRestriction>
-    </Conditions>
-    <AttributeStatement>
-      <Attribute Name="user_id" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri" FriendlyName="user_id"><AttributeValue>760a54e2-31ba-4dfa-9303-fa6887270980</AttributeValue></Attribute>
-      <Attribute Name="business_guid" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri" FriendlyName="business_guid"><AttributeValue>e12dc2a6-6f18-4d11-8204-a98490896de8</AttributeValue></Attribute>
-    </AttributeStatement>
-    <AuthnStatement AuthnInstant="2017-10-18T20:34:39Z" SessionIndex="_b0417400-9671-0135-55fc-20999b09e5e7"><AuthnContext><AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:Password</AuthnContextClassRef></AuthnContext></AuthnStatement>
-  </Assertion>
-</samlp:Response>"
-XML
-    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('_b0417350-9671-0135-55fc-20999b09e5e7')
-      expect(result['Response']['Version']).to eql('2.0')
-      expect(result['Response']['IssueInstant']).to eql('2017-10-18T20:34:39Z')
-      expect(result['Response']['Destination']).to eql('http://localhost:3000/session')
-      expect(result['Response']['Issuer']).to eql('http://auth.dev/auth/metadata')
-      expect(result['Response']['Status']['StatusCode']['Value']).to eql('urn:oasis:names:tc:SAML:2.0:status:Success')
-      expect(result['Response']['Assertion']['ID']).to eql('_b0417400-9671-0135-55fc-20999b09e5e7')
-      expect(result['Response']['Assertion']['IssueInstant']).to eql('2017-10-18T20:34:39Z')
-      expect(result['Response']['Assertion']['Issuer']).to eql('http://auth.dev/auth/metadata')
-      expect(result['Response']['Assertion']['Subject']['NameID']).to eql('mokha@cisco.com')
-      expect(result['Response']['Assertion']['Conditions']['NotBefore']).to eql('2017-10-18T20:34:34Z')
-      expect(result['Response']['Assertion']['Conditions']['NotOnOrAfter']).to eql('2017-10-18T21:34:39Z')
-      expect(result['Response']['Assertion']['Conditions']['AudienceRestriction']['Audience']).to eql('airport.dev')
-      expect(result['Response']['Assertion']['AttributeStatement']['Attribute'][0]['Name']).to eql('user_id')
-      expect(result['Response']['Assertion']['AttributeStatement']['Attribute'][0]['AttributeValue']).to eql('760a54e2-31ba-4dfa-9303-fa6887270980')
-    end
-  end
-end
airport/Gemfile
@@ -58,4 +58,4 @@ end
 gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]
 gem 'dotenv-rails'
 gem "factory_girl_rails", "~> 4.0"
-gem "rest-client"
+gem "saml-kit", path: "../saml-kit"
airport/Gemfile.lock
@@ -1,3 +1,10 @@
+PATH
+  remote: ../saml-kit
+  specs:
+    saml-kit (0.1.0)
+      activesupport (~> 5.1)
+      builder (~> 3.2)
+
 GEM
   remote: https://rubygems.org/
   specs:
@@ -65,8 +72,6 @@ GEM
       safe_yaml (~> 1.0.0)
     crass (1.0.2)
     diff-lcs (1.3)
-    domain_name (0.5.20170404)
-      unf (>= 0.0.5, < 1.0.0)
     dotenv (2.2.1)
     dotenv-rails (2.2.1)
       dotenv (= 2.2.1)
@@ -83,8 +88,6 @@ GEM
     globalid (0.4.0)
       activesupport (>= 4.2.0)
     hashdiff (0.3.7)
-    http-cookie (1.0.3)
-      domain_name (~> 0.5)
     i18n (0.9.0)
       concurrent-ruby (~> 1.0)
     jbuilder (2.7.0)
@@ -107,7 +110,6 @@ GEM
     mini_portile2 (2.3.0)
     minitest (5.10.3)
     multi_json (1.12.2)
-    netrc (0.11.0)
     nio4r (2.1.0)
     nokogiri (1.8.1)
       mini_portile2 (~> 2.3.0)
@@ -147,10 +149,6 @@ GEM
     rb-fsevent (0.10.2)
     rb-inotify (0.9.10)
       ffi (>= 0.5.0, < 2)
-    rest-client (2.0.2)
-      http-cookie (>= 1.0.2, < 2.0)
-      mime-types (>= 1.16, < 4.0)
-      netrc (~> 0.8)
     rspec-core (3.7.0)
       rspec-support (~> 3.7.0)
     rspec-expectations (3.7.0)
@@ -208,9 +206,6 @@ GEM
       thread_safe (~> 0.1)
     uglifier (3.2.0)
       execjs (>= 0.3.0, < 3)
-    unf (0.1.4)
-      unf_ext
-    unf_ext (0.0.7.4)
     web-console (3.5.1)
       actionview (>= 5.0)
       activemodel (>= 5.0)
@@ -241,8 +236,8 @@ DEPENDENCIES
   puma (~> 3.7)
   rails (~> 5.1.4)
   rails-controller-testing
-  rest-client
   rspec-rails (~> 3.6)
+  saml-kit!
   sass-rails (~> 5.0)
   selenium-webdriver
   spring
proof/config/initializers/configuration.rb
@@ -1,1 +0,0 @@
-Rails.application.config.x.issuer = ENV['ISSUER']
proof/config/initializers/saml_kit.rb
@@ -0,0 +1,3 @@
+Saml::Kit.configure do |configuration|
+  configuration.issuer = ENV['ISSUER']
+end
saml-kit/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",
saml-kit/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
saml-kit/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
saml-kit/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
 
saml-kit/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+
saml-kit/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
airport/spec/models/saml_request_spec.rb → saml-kit/spec/saml/saml_request_spec.rb
@@ -1,13 +1,13 @@
 require 'spec_helper'
 
-describe SamlRequest do
-  describe ".build" do
+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.build(document)).to eql(Base64.encode64(xml))
+      expect(subject.encode(document)).to eql(Base64.encode64(xml))
     end
   end
 end
saml-kit/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