Commit 12f4348
2017-10-23 00:42:41
1 parent
bda3928
Changed files (24)
airport
app
config
initializers
spec
fixtures
proof
config
initializers
saml-kit
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