Commit 19c1e76
Changed files (6)
app
models
db
spec
models
app/models/namespaces.rb
@@ -0,0 +1,37 @@
+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"
+ 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_response.rb
@@ -15,29 +15,30 @@ class SamlResponse
end
class Builder
- attr_reader :user, :request, :id
+ attr_reader :user, :request, :id, :reference_id
def initialize(user, request)
@user = user
@request = request
@id = SecureRandom.uuid
+ @reference_id = SecureRandom.uuid
end
def to_xml
xml = ::Builder::XmlMarkup.new
- options = {
- "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: Time.now.utc.iso8601,
- Destination: request.acs_url,
- InResponseTo: request.id,
- }
- xml.tag!("samlp:Response", options) do
- xml.tag!('saml:Issuer', configuration.issuer)
+ xml.tag!("samlp:Response", response_options) do
+ xml.Issuer(configuration.issuer, xmlns: Namespaces::ASSERTION)
xml.tag!("samlp:Status") do
- xml.tag!('samlp:StatusCode', Value: "urn:oasis:names:tc:SAML:2.0:status:Success")
+ 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
end
end
xml.target!
@@ -52,5 +53,37 @@ class SamlResponse
def configuration
Rails.configuration.x
end
+
+ def response_options
+ {
+ ID: "_#{id}",
+ Version: "2.0",
+ IssueInstant: Time.now.utc.iso8601,
+ Destination: request.acs_url,
+ Consent: Namespaces::Consents::UNSPECIFIED,
+ InResponseTo: request.id,
+ "xmlns:samlp" => Namespaces::PROTOCOL,
+ }
+ end
+
+ def assertion_options
+ {
+ ID: "_#{reference_id}",
+ IssueInstant: Time.now.utc.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 name_id_format
+ "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent"
+ end
end
end
app/models/user.rb
@@ -1,3 +1,6 @@
class User < ApplicationRecord
has_secure_password
+ after_initialize do
+ self.uuid = SecureRandom.uuid unless self.uuid
+ end
end
db/migrate/20171021193946_create_users.rb
@@ -2,7 +2,9 @@ class CreateUsers < ActiveRecord::Migration[5.1]
def change
create_table :users do |t|
t.string :email
+ t.string :uuid, null: false, index: true
t.string :password_digest
+ t.timestamps null: false
end
end
end
db/schema.rb
@@ -14,7 +14,11 @@ ActiveRecord::Schema.define(version: 20171021193946) do
create_table "users", force: :cascade do |t|
t.string "email"
+ t.string "uuid", null: false
t.string "password_digest"
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ t.index ["uuid"], name: "index_users_on_uuid"
end
end
spec/models/saml_response_spec.rb
@@ -3,7 +3,7 @@ require 'rails_helper'
describe SamlResponse do
describe ".for" do
subject { described_class }
- let(:user) { double(:user) }
+ let(:user) { double(:user, uuid: SecureRandom.uuid) }
let(:request) { double(id: SecureRandom.uuid, acs_url: acs_url) }
let(:acs_url) { "https://#{FFaker::Internet.domain_name}/acs" }
let(:issuer) { FFaker::Movie.title }
@@ -21,10 +21,15 @@ describe SamlResponse do
<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: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 SPNameQualifier="http://sp.example.com/demo1/metadata.php" Format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient">_ce3d2948b4cf20146dee0a0b3dd6f69b6cf86f62d7</saml:NameID>
+ <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>
@@ -46,10 +51,6 @@ describe SamlResponse do
<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:Attribute Name="eduPersonAffiliation" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
- <saml:AttributeValue xsi:type="xs:string">users</saml:AttributeValue>
- <saml:AttributeValue xsi:type="xs:string">examplerole1</saml:AttributeValue>
- </saml:Attribute>
</saml:AttributeStatement>
</saml:Assertion>
</samlp:Response>
@@ -67,6 +68,17 @@ describe SamlResponse do
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)
end
end
end