Skip to content

Commit

Permalink
Merge pull request #16 from mak-it/feature-saml-1
Browse files Browse the repository at this point in the history
Add support for SAML 1 Tokens
  • Loading branch information
Keith Beckman committed Jan 18, 2015
2 parents dfe79bb + adbe67b commit f8c1a28
Show file tree
Hide file tree
Showing 8 changed files with 204 additions and 42 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ posted. **Required**
* `:id_claim` - Name of the authentication claim that you want to use as OmniAuth's
**uid** property.

* `:saml_version` - The version of SAML tokens. **Defaults to 2**.


## Authors and Credits ##

Expand Down
7 changes: 6 additions & 1 deletion lib/omniauth/strategies/wsfed.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,14 @@ class WSFed
autoload :AuthRequest, 'omniauth/strategies/wsfed/auth_request'
autoload :AuthCallback, 'omniauth/strategies/wsfed/auth_callback'
autoload :AuthCallbackValidator, 'omniauth/strategies/wsfed/auth_callback_validator'
autoload :SAML2Token, 'omniauth/strategies/wsfed/saml_2_token'
autoload :SAML1Token, 'omniauth/strategies/wsfed/saml_1_token'
autoload :ValidationError, 'omniauth/strategies/wsfed/validation_error'
autoload :XMLSecurity, 'omniauth/strategies/wsfed/xml_security'

WS_TRUST = 'http://schemas.xmlsoap.org/ws/2005/02/trust'
WS_POLICY = 'http://schemas.xmlsoap.org/ws/2004/09/policy'

# Issues passive WS-Federation redirect for authentication...
def request_phase
auth_request = OmniAuth::Strategies::WSFed::AuthRequest.new(options, :whr => @request.params['whr'])
Expand All @@ -25,7 +30,7 @@ def callback_phase

wsfed_callback = request.params['wresult']

signed_document = OmniAuth::Strategies::WSFed::XMLSecurity::SignedDocument.new(wsfed_callback)
signed_document = OmniAuth::Strategies::WSFed::XMLSecurity::SignedDocument.new(wsfed_callback, options)
signed_document.validate(get_fingerprint, false)

auth_callback = OmniAuth::Strategies::WSFed::AuthCallback.new(wsfed_callback, options)
Expand Down
48 changes: 16 additions & 32 deletions lib/omniauth/strategies/wsfed/auth_callback.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,7 @@ class WSFed

class AuthCallback

WS_TRUST = 'http://schemas.xmlsoap.org/ws/2005/02/trust'
WS_UTILITY = 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd'
WS_POLICY = 'http://schemas.xmlsoap.org/ws/2004/09/policy'

attr_accessor :options, :raw_callback, :settings

Expand All @@ -27,17 +25,14 @@ def initialize(raw_callback, settings, options = {})
# TODO: remove reference to SignedDocument (document) and move it to validation
# use response variable instead...
def document
@document ||= OmniAuth::Strategies::WSFed::XMLSecurity::SignedDocument.new(raw_callback)
@document ||= OmniAuth::Strategies::WSFed::XMLSecurity::SignedDocument.new(raw_callback, settings)
end


# WS-Trust Envelope and WS* Element Values

def audience
@audience ||= begin
applies_to = REXML::XPath.first(document, '//t:RequestSecurityTokenResponse/wsp:AppliesTo', { 't' => WS_TRUST, 'wsp' => WS_POLICY })
REXML::XPath.first(applies_to, '//EndpointReference/Address').text
end
@audience ||= token.audience
end

def created_at
Expand All @@ -49,36 +44,14 @@ def expires_at
end


# SAML 2.0 Assertion [Token] Values
# Note: If/When future development warrants additional token types, these items should be refactored into a
# token abstraction...
# Token Values

def issuer
@issuer ||= begin
REXML::XPath.first(document, '//Assertion/Issuer').text
end
@issuer ||= token.issuer
end

def claims
@attr_statements ||= begin
stmt_element = REXML::XPath.first(document, '//Assertion/AttributeStatement')
return {} if stmt_element.nil?

{}.tap do |result|
stmt_element.elements.each do |attr_element|
name = attr_element.attributes['Name']

if attr_element.elements.count > 1
value = []
attr_element.elements.each { |element| value << element.text }
else
value = attr_element.elements.first.text.lstrip.rstrip
end

result[name] = value
end
end
end
@claims ||= token.claims
end
alias :attributes :claims

Expand All @@ -92,6 +65,17 @@ def name_id

private

def token
@token ||= begin
case settings[:saml_version].to_s
when '1'
SAML1Token.new(document)
else
SAML2Token.new(document)
end
end
end


# WS-Trust token lifetime element
def wstrust_lifetime
Expand Down
45 changes: 45 additions & 0 deletions lib/omniauth/strategies/wsfed/saml_1_token.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
module OmniAuth
module Strategies
class WSFed
class SAML1Token

attr_accessor :document

def initialize(document)
@document = document
end

def audience
applies_to = REXML::XPath.first(document, '//t:RequestSecurityTokenResponse/wsp:AppliesTo', { 't' => WS_TRUST, 'wsp' => WS_POLICY })
REXML::XPath.first(applies_to, '//wsa:EndpointReference/wsa:Address').text
end

def issuer
REXML::XPath.first(document, '//saml:Assertion').attributes['Issuer']
end

def claims
stmt_element = REXML::XPath.first(document, '//saml:Assertion/saml:AttributeStatement')

return {} if stmt_element.nil?

{}.tap do |result|
stmt_element.each_element('saml:Attribute') do |attr_element|
name = attr_element.attributes['AttributeName']

if attr_element.elements.count > 1
value = []
attr_element.elements.each { |element| value << element.text }
else
value = attr_element.elements.first.text.lstrip.rstrip
end

result[name] = value
end
end
end

end
end
end
end
45 changes: 45 additions & 0 deletions lib/omniauth/strategies/wsfed/saml_2_token.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
module OmniAuth
module Strategies
class WSFed
class SAML2Token

attr_accessor :document

def initialize(document)
@document = document
end

def audience
applies_to = REXML::XPath.first(document, '//t:RequestSecurityTokenResponse/wsp:AppliesTo', { 't' => WS_TRUST, 'wsp' => WS_POLICY })
REXML::XPath.first(applies_to, '//EndpointReference/Address').text
end

def issuer
REXML::XPath.first(document, '//Assertion/Issuer').text
end

def claims
stmt_element = REXML::XPath.first(document, '//Assertion/AttributeStatement')

return {} if stmt_element.nil?

{}.tap do |result|
stmt_element.elements.each do |attr_element|
name = attr_element.attributes['Name']

if attr_element.elements.count > 1
value = []
attr_element.elements.each { |element| value << element.text }
else
value = attr_element.elements.first.text.lstrip.rstrip
end

result[name] = value
end
end
end

end
end
end
end
10 changes: 7 additions & 3 deletions lib/omniauth/strategies/wsfed/xml_security.rb
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,13 @@ module XMLSecurity
class SignedDocument < REXML::Document
DSIG = "http://www.w3.org/2000/09/xmldsig#"

attr_accessor :signed_element_id
attr_accessor :signed_element_id, :settings

def initialize(response)
def initialize(response, settings = {})
super(response)
extract_signed_element_id

self.settings = settings
end

def validate(idp_cert_fingerprint, soft = true)
Expand Down Expand Up @@ -80,9 +82,11 @@ def validate_doc(base64_cert, soft = true)
sig_element.remove

# check digests
saml_version = settings[:saml_version]
REXML::XPath.each(sig_element, "//ds:Reference", {"ds"=>DSIG}) do |ref|
uri = ref.attributes.get_attribute("URI").value
hashed_element = REXML::XPath.first(self, "//[@ID='#{uri[1,uri.size]}']")
hashed_element = REXML::XPath.first(self, "//[@ID='#{uri[1,uri.size]}']") ||
REXML::XPath.first(self, "//[@AssertionID='#{uri[1,uri.size]}']")
canoner = XML::Util::XmlCanonicalizer.new(false, true)
canoner.inclusive_namespaces = inclusive_namespaces if canoner.respond_to?(:inclusive_namespaces) && !inclusive_namespaces.empty?
canon_hashed_element = canoner.canonicalize(hashed_element)
Expand Down
23 changes: 17 additions & 6 deletions spec/omniauth/strategies/wsfed/auth_callback_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,16 +36,13 @@
auth_callback.expires_at.should == Time.parse('2012-06-29T21:17:14.766Z')
end

end

shared_examples_for 'SAML token' do
it 'should extract the token audience' do
auth_callback.audience.should == 'http://rp.coding4streetcred.com/sample'
end

end

context 'SAML 2.0 Assertion [Token] Values' do

let(:auth_callback) { described_class.new(load_support_xml(:acs_example), @wsfed_settings) }

it 'should extract the issuer' do
auth_callback.issuer.should == 'https://c4sc-identity.accesscontrol.windows.net/'
end
Expand All @@ -59,6 +56,20 @@

auth_callback.attributes.should == expected_claims
end
end

context 'SAML 1.0 Assertion [Token] Values' do

let(:auth_callback) { described_class.new(load_support_xml(:saml1_example), @wsfed_settings.merge(saml_version: '1')) }

it_behaves_like 'SAML token'
end

context 'SAML 2.0 Assertion [Token] Values' do

let(:auth_callback) { described_class.new(load_support_xml(:acs_example), @wsfed_settings) }

it_behaves_like 'SAML token'

it 'should load the proper value from various id_claim settings' do
id_claims = [
Expand Down
66 changes: 66 additions & 0 deletions spec/support/saml1_example.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<t:RequestSecurityTokenResponse xmlns:t="http://schemas.xmlsoap.org/ws/2005/02/trust">
<t:Lifetime>
<wsu:Created xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">2014-06-27T19:45:38.263Z</wsu:Created>
<wsu:Expires xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">2014-06-27T20:45:38.263Z</wsu:Expires>
</t:Lifetime>
<wsp:AppliesTo xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy">
<wsa:EndpointReference xmlns:wsa="http://www.w3.org/2005/08/addressing">
<wsa:Address>http://rp.coding4streetcred.com/sample</wsa:Address>
</wsa:EndpointReference>
</wsp:AppliesTo>
<t:RequestedSecurityToken>
<saml:Assertion MajorVersion="1" MinorVersion="1" AssertionID="_fa0de02b-b5a1-49c5-a8c0-4b391295a789" Issuer="https://c4sc-identity.accesscontrol.windows.net/" IssueInstant="2014-06-27T19:45:38.263Z" xmlns:saml="urn:oasis:names:tc:SAML:1.0:assertion">
<saml:Conditions NotBefore="2014-06-27T19:45:38.263Z" NotOnOrAfter="2014-06-27T20:45:38.263Z">
<saml:AudienceRestrictionCondition>
<saml:Audience>https://c4sc-identity.accesscontrol.windows.net</saml:Audience>
</saml:AudienceRestrictionCondition>
</saml:Conditions>
<saml:AttributeStatement>
<saml:Subject>
<saml:SubjectConfirmation>
<saml:ConfirmationMethod>urn:oasis:names:tc:SAML:1.0:cm:bearer</saml:ConfirmationMethod>
</saml:SubjectConfirmation>
</saml:Subject>
<saml:Attribute AttributeName="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress" AttributeNamespace="http://schemas.xmlsoap.org/ws/2005/05/identity/claims">
<saml:AttributeValue>[email protected]</saml:AttributeValue>
</saml:Attribute>
<saml:Attribute AttributeName="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name" AttributeNamespace="http://schemas.microsoft.com/ws/2008/06/identity/claims">
<saml:AttributeValue>kbeckman.c4sc</saml:AttributeValue>
</saml:Attribute>
<saml:Attribute AttributeName="http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider" AttributeNamespace="http://schemas.xmlsoap.org/ws/2005/05/identity/claims">
<saml:AttributeValue>http://identity.c4sc.com/trust/</saml:AttributeValue>
</saml:Attribute>
</saml:AttributeStatement>
<saml:AuthenticationStatement AuthenticationMethod="urn:oasis:names:tc:SAML:1.0:am:password" AuthenticationInstant="2014-06-27T19:45:38.232Z">
<saml:Subject>
<saml:SubjectConfirmation>
<saml:ConfirmationMethod>urn:oasis:names:tc:SAML:1.0:cm:bearer</saml:ConfirmationMethod>
</saml:SubjectConfirmation>
</saml:Subject>
</saml:AuthenticationStatement>
<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:SignedInfo>
<ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
<ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256" />
<ds:Reference URI="#_fa0de02b-b5a1-49c5-a8c0-4b391295a789">
<ds:Transforms>
<ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" />
<ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
</ds:Transforms>
<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256" />
<ds:DigestValue>bdwpOR25Tiw03Y5gZsz/NDSrN2T1XAEUQl9/B2aDVjs=</ds:DigestValue>
</ds:Reference>
</ds:SignedInfo>
<ds:SignatureValue>O3dJ5YtFIJJHk8SKAqdI2goSJUj7/oZebGwrm5yjVz8WT9TdHfJT2e/rygKLz9MBujZoZ13oGaVq6NVJLvmvR+IrKsUIuUeXwk4X2UexYxJL9VGZD6RnXR+p0Jne+jGUIlVOb2zMr29Ew27wLfnw3za+Zf5ravQZ/bv3LoL/LFIYFb7iR4XlJ5bjlMhO41euUp/6NTntIC90utugpjqcPryxNbIto6nk3w57IrKmw9rFpRJudoXbw7BsA3t69dmzu2MQzjILbFcfmkUgtEXDQyGM/ziXqxNFEGNHkycEsO37NO4/t5Hk1zPufBbbhSm+5K6tVqZ2Nl1e5yNciBwo6g==</ds:SignatureValue>
<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
<X509Data>
<X509Certificate>MIIDHDCCAgSgAwIBAgIQXMOBsrQ1QJpNmYFiiW5+PjANBgkqhkiG9w0BAQUFADAyMTAwLgYDVQQDEydjNHNjLWlkZW50aXR5LmFjY2Vzc2NvbnRyb2wud2luZG93cy5uZXQwHhcNMTIwNjI5MjAzNTA2WhcNMTMwNjMwMDIzNTA2WjAyMTAwLgYDVQQDEydjNHNjLWlkZW50aXR5LmFjY2Vzc2NvbnRyb2wud2luZG93cy5uZXQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCrieSwMYpW73fJtHiBw1yQFWcFZwvDbltFfdb4xzC+MuC8KU/QJzKGBzxixwmvTUKTbH4W4gKNzi7+/gGk9my1UFnDLsnJBooGjx6lCXMU9HoOQA0tXVGkyYeD11Lb2KYWoivvGFI6dun84JY1n1hbAnuyqr+8VUfKpBk3bO6s3xLf2eojAHKhUiw2E+ZBFZWqlTMtSoNupe8I4Zs5Kkp5Xe1hrDjCzHWTHRf880y8f6KOvieQuGO2a2dBSYJVY3IHr1cLlk/o9Dwks4zSjYDABE8NDKer7aq2pnXl0/0XeXeYsDsZFrwfuRtf06pqGgMuqo1aFRiQ2+gm4naZn7D3AgMBAAGjLjAsMAsGA1UdDwQEAwIE8DAdBgNVHQ4EFgQUIBxPC4SJIAWTt6Q4htDcvHDgatAwDQYJKoZIhvcNAQEFBQADggEBAB2RNPpJMNotdKMKtQkV/tEhttOOq+bXlMa42UQu6r+ikgJ6WcSecBxOs4KHw3lj7wO0l8CIOfvXy5KBePLQsUuk8tWCdKdpa+7uuGntIHdlTAvjlcVhXXAQQvyS+wUbnwj8rCN85e76EWMWCAVXzqwMURt5Rzmb+SdU40hRi+7u+HmZaQW5kDXD3CIm+1eOU7oyWpz6Ltx1DEJ88qt4xB2e6IGQUTdvq2jUnyzC1f9jdtRtZ3LiiVkA29rfRROJQb/PeEinUON0hP7/6VS9LZPL4vB1EIOBNahY1V4/75Hb+NGzb05w71hMUiJK2eu8w1WwqcRqUlu7GI921Od1oFQ=</X509Certificate>
</X509Data>
</KeyInfo>
</ds:Signature>
</saml:Assertion>
</t:RequestedSecurityToken>
<t:TokenType>urn:oasis:names:tc:SAML:1.0:assertion</t:TokenType>
<t:RequestType>http://schemas.xmlsoap.org/ws/2005/02/trust/Issue</t:RequestType>
<t:KeyType>http://schemas.xmlsoap.org/ws/2005/05/identity/NoProofKey</t:KeyType>
</t:RequestSecurityTokenResponse>

0 comments on commit f8c1a28

Please sign in to comment.