omniauth-wsfed 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +6 -1
- data/Gemfile.lock +13 -19
- data/LICENSE +1 -1
- data/README.md +1 -31
- data/lib/omniauth-wsfed.rb +0 -1
- data/lib/omniauth-wsfed/version.rb +1 -1
- data/lib/omniauth/strategies/wsfed.rb +32 -9
- data/lib/omniauth/strategies/wsfed/auth_callback.rb +107 -0
- data/lib/omniauth/strategies/wsfed/auth_callback_validator.rb +49 -0
- data/omniauth-wsfed.gemspec +10 -11
- data/spec/omniauth/strategies/wsfed/auth_callback_spec.rb +81 -0
- data/spec/omniauth/strategies/wsfed/auth_callback_validator_spec.rb +100 -0
- metadata +26 -42
- data/lib/azure_acs/idp_feed.rb +0 -16
- data/lib/omniauth/strategies/wsfed/auth_response.rb +0 -159
- data/spec/azure_acs/idp_feed_spec.rb +0 -18
- data/spec/omniauth/strategies/wsfed/auth_response_spec.rb +0 -67
data/.gitignore
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,36 +1,30 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
omniauth-wsfed (0.
|
4
|
+
omniauth-wsfed (0.2.0)
|
5
5
|
omniauth (~> 1.1.0)
|
6
|
-
typhoeus (~> 0.4.2)
|
7
6
|
xmlcanonicalizer (= 0.1.1)
|
8
7
|
|
9
8
|
GEM
|
10
9
|
remote: https://rubygems.org/
|
11
10
|
specs:
|
12
11
|
diff-lcs (1.1.3)
|
13
|
-
ffi (1.1.0)
|
14
12
|
hashie (1.2.0)
|
15
|
-
mime-types (1.19)
|
16
13
|
omniauth (1.1.0)
|
17
14
|
hashie (~> 1.2)
|
18
15
|
rack
|
19
16
|
rack (1.4.1)
|
20
|
-
rack-test (0.6.
|
17
|
+
rack-test (0.6.2)
|
21
18
|
rack (>= 1.0)
|
22
|
-
rake (0.
|
23
|
-
rspec (2.
|
24
|
-
rspec-core (~> 2.
|
25
|
-
rspec-expectations (~> 2.
|
26
|
-
rspec-mocks (~> 2.
|
27
|
-
rspec-core (2.
|
28
|
-
rspec-expectations (2.
|
19
|
+
rake (10.0.3)
|
20
|
+
rspec (2.12.0)
|
21
|
+
rspec-core (~> 2.12.0)
|
22
|
+
rspec-expectations (~> 2.12.0)
|
23
|
+
rspec-mocks (~> 2.12.0)
|
24
|
+
rspec-core (2.12.2)
|
25
|
+
rspec-expectations (2.12.1)
|
29
26
|
diff-lcs (~> 1.1.3)
|
30
|
-
rspec-mocks (2.
|
31
|
-
typhoeus (0.4.2)
|
32
|
-
ffi (~> 1.0)
|
33
|
-
mime-types (~> 1.18)
|
27
|
+
rspec-mocks (2.12.1)
|
34
28
|
xmlcanonicalizer (0.1.1)
|
35
29
|
|
36
30
|
PLATFORMS
|
@@ -38,6 +32,6 @@ PLATFORMS
|
|
38
32
|
|
39
33
|
DEPENDENCIES
|
40
34
|
omniauth-wsfed!
|
41
|
-
rack-test (
|
42
|
-
rake (
|
43
|
-
rspec (
|
35
|
+
rack-test (>= 0.6.2)
|
36
|
+
rake (>= 10.0.3)
|
37
|
+
rspec (>= 2.12.0)
|
data/LICENSE
CHANGED
data/README.md
CHANGED
@@ -1,7 +1,5 @@
|
|
1
1
|
# OmniAuth WS-Fed #
|
2
2
|
|
3
|
-
#### This gem is currently under construction... Expect an official v1 release by the the end of July 2012. ####
|
4
|
-
|
5
3
|
The OmniAuth-WSFed authentication strategy can be used with the following technologies
|
6
4
|
under scenarios requiring the [WS-Federation protocol](http://msdn.microsoft.com/en-us/library/bb498017.aspx)
|
7
5
|
for authentication. These services are typically used for Identity Federation and Single
|
@@ -71,9 +69,7 @@ end
|
|
71
69
|
|
72
70
|
## Configuration Options ##
|
73
71
|
|
74
|
-
* `:issuer_name` - The name of your Identity Provider (IdP).
|
75
|
-
but can be a nice way to differentiate your IdP configurations if you are testing with
|
76
|
-
multiple providers in multiple enviornments. **Optional**
|
72
|
+
* `:issuer_name` - The URI name of your Identity Provider (IdP). **Required**
|
77
73
|
|
78
74
|
* `:issuer` - The IdP web endpoint (URL) to which the authentication request should be
|
79
75
|
sent. **Required**.
|
@@ -103,29 +99,3 @@ Special thanks to the developers of the following projects from which I borrowed
|
|
103
99
|
|
104
100
|
* [PracticallyGreen / omniauth-saml](https://github.com/PracticallyGreen/omniauth-saml)
|
105
101
|
* [onelogin / ruby-saml](https://github.com/onelogin/ruby-saml)
|
106
|
-
|
107
|
-
|
108
|
-
## License ##
|
109
|
-
|
110
|
-
Copyright (c) 2011-2012 Keith Beckman, [Coding4StreetCred.com](http://www.coding4streetcred.com/blog)
|
111
|
-
All rights reserved. Released under the MIT license.
|
112
|
-
|
113
|
-
Portions Copyright (c) 2007 Sun Microsystems Inc.
|
114
|
-
|
115
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
116
|
-
of this software and associated documentation files (the "Software"), to deal
|
117
|
-
in the Software without restriction, including without limitation the rights
|
118
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
119
|
-
copies of the Software, and to permit persons to whom the Software is
|
120
|
-
furnished to do so, subject to the following conditions:
|
121
|
-
|
122
|
-
The above copyright notice and this permission notice shall be included in
|
123
|
-
all copies or substantial portions of the Software.
|
124
|
-
|
125
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
126
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
127
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
128
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
129
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
130
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
131
|
-
THE SOFTWARE.
|
data/lib/omniauth-wsfed.rb
CHANGED
@@ -6,10 +6,11 @@ module OmniAuth
|
|
6
6
|
class WSFed
|
7
7
|
include OmniAuth::Strategy
|
8
8
|
|
9
|
-
autoload :AuthRequest,
|
10
|
-
autoload :
|
11
|
-
autoload :
|
12
|
-
autoload :
|
9
|
+
autoload :AuthRequest, 'omniauth/strategies/wsfed/auth_request'
|
10
|
+
autoload :AuthCallback, 'omniauth/strategies/wsfed/auth_callback'
|
11
|
+
autoload :AuthCallbackValidator, 'omniauth/strategies/wsfed/auth_callback_validator'
|
12
|
+
autoload :ValidationError, 'omniauth/strategies/wsfed/validation_error'
|
13
|
+
autoload :XMLSecurity, 'omniauth/strategies/wsfed/xml_security'
|
13
14
|
|
14
15
|
# Issues passive WS-Federation redirect for authentication...
|
15
16
|
def request_phase
|
@@ -30,16 +31,27 @@ module OmniAuth
|
|
30
31
|
# Parse SAML token...
|
31
32
|
def callback_phase
|
32
33
|
begin
|
33
|
-
|
34
|
+
wsfed_callback = request.params['wresult']
|
34
35
|
|
35
|
-
|
36
|
-
|
36
|
+
signed_document = OmniAuth::Strategies::WSFed::XMLSecurity::SignedDocument.new(wsfed_callback)
|
37
|
+
signed_document.validate(get_fingerprint, false)
|
38
|
+
|
39
|
+
auth_callback = OmniAuth::Strategies::WSFed::AuthCallback.new(wsfed_callback, options)
|
40
|
+
validator = OmniAuth::Strategies::WSFed::AuthCallbackValidator.new(auth_callback, options)
|
41
|
+
|
42
|
+
validator.validate!
|
43
|
+
|
44
|
+
@name_id = auth_callback.name_id
|
45
|
+
@claims = auth_callback.attributes
|
37
46
|
|
38
|
-
return fail!(:invalid_ticket, OmniAuth::Strategies::WSFed::ValidationError('Invalid SAML Token') ) if @claims.nil? || @claims.empty? || !response.valid?
|
39
47
|
super
|
48
|
+
|
40
49
|
rescue ArgumentError => e
|
41
|
-
fail!(:
|
50
|
+
fail!(:invalid_response, e)
|
51
|
+
rescue OmniAuth::Strategies::WSFed::ValidationError => e
|
52
|
+
fail!(:invalid_authn_token, e)
|
42
53
|
end
|
54
|
+
|
43
55
|
end
|
44
56
|
|
45
57
|
# OmniAuth DSL methods...
|
@@ -49,6 +61,17 @@ module OmniAuth
|
|
49
61
|
|
50
62
|
extra { { :wresult => request.params['wresult'] } }
|
51
63
|
|
64
|
+
private
|
65
|
+
|
66
|
+
def get_fingerprint
|
67
|
+
if options[:idp_cert_fingerprint]
|
68
|
+
options[:idp_cert_fingerprint]
|
69
|
+
else
|
70
|
+
cert = OpenSSL::X509::Certificate.new(options[:idp_cert].gsub(/^ +/, ''))
|
71
|
+
Digest::SHA1.hexdigest(cert.to_der).upcase.scan(/../).join(":")
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
52
75
|
end
|
53
76
|
end
|
54
77
|
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
require 'time'
|
2
|
+
require 'hashie'
|
3
|
+
require 'rexml/xpath'
|
4
|
+
|
5
|
+
module OmniAuth
|
6
|
+
module Strategies
|
7
|
+
class WSFed
|
8
|
+
|
9
|
+
class AuthCallback
|
10
|
+
|
11
|
+
WS_TRUST = 'http://schemas.xmlsoap.org/ws/2005/02/trust'
|
12
|
+
WS_UTILITY = 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd'
|
13
|
+
WS_POLICY = 'http://schemas.xmlsoap.org/ws/2004/09/policy'
|
14
|
+
|
15
|
+
attr_accessor :options, :raw_callback, :settings
|
16
|
+
|
17
|
+
def initialize(raw_callback, settings, options = {})
|
18
|
+
raise ArgumentError.new('Response cannot be nil.') if raw_callback.nil?
|
19
|
+
raise ArgumentError.new('WSFed settings cannot be nil.') if settings.nil?
|
20
|
+
|
21
|
+
self.options = options
|
22
|
+
self.raw_callback = raw_callback
|
23
|
+
self.settings = settings
|
24
|
+
end
|
25
|
+
|
26
|
+
|
27
|
+
# TODO: remove reference to SignedDocument (document) and move it to validation
|
28
|
+
# use response variable instead...
|
29
|
+
def document
|
30
|
+
@document ||= OmniAuth::Strategies::WSFed::XMLSecurity::SignedDocument.new(raw_callback)
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
# WS-Trust Envelope and WS* Element Values
|
35
|
+
|
36
|
+
def audience
|
37
|
+
@audience ||= begin
|
38
|
+
applies_to = REXML::XPath.first(document, '//t:RequestSecurityTokenResponse/wsp:AppliesTo', { 't' => WS_TRUST, 'wsp' => WS_POLICY })
|
39
|
+
REXML::XPath.first(applies_to, '//EndpointReference/Address').text
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def created_at
|
44
|
+
Time.parse(REXML::XPath.first(wstrust_lifetime, '//wsu:Created', { 'wsu' => WS_UTILITY }).text)
|
45
|
+
end
|
46
|
+
|
47
|
+
def expires_at
|
48
|
+
Time.parse(REXML::XPath.first(wstrust_lifetime, '//wsu:Expires', { 'wsu' => WS_UTILITY }).text)
|
49
|
+
end
|
50
|
+
|
51
|
+
|
52
|
+
# SAML 2.0 Assertion [Token] Values
|
53
|
+
# Note: If/When future development warrants additional token types, these items should be refactored into a
|
54
|
+
# token abstraction...
|
55
|
+
|
56
|
+
def issuer
|
57
|
+
@issuer ||= begin
|
58
|
+
REXML::XPath.first(document, '//Assertion/Issuer').text
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def claims
|
63
|
+
@attr_statements ||= begin
|
64
|
+
stmt_element = REXML::XPath.first(document, '//Assertion/AttributeStatement')
|
65
|
+
return {} if stmt_element.nil?
|
66
|
+
|
67
|
+
{}.tap do |result|
|
68
|
+
stmt_element.elements.each do |attr_element|
|
69
|
+
name = attr_element.attributes['Name']
|
70
|
+
|
71
|
+
if attr_element.elements.count > 1
|
72
|
+
value = []
|
73
|
+
attr_element.elements.each { |element| value << element.text }
|
74
|
+
else
|
75
|
+
value = attr_element.elements.first.text.lstrip.rstrip
|
76
|
+
end
|
77
|
+
|
78
|
+
result[name] = value
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
alias :attributes :claims
|
84
|
+
|
85
|
+
# The value of the user identifier as defined by the id_claim configuration setting...
|
86
|
+
def name_id
|
87
|
+
@name_id ||= begin
|
88
|
+
claims.has_key?(settings[:id_claim]) ? claims.fetch(settings[:id_claim]) : nil
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
|
93
|
+
private
|
94
|
+
|
95
|
+
|
96
|
+
# WS-Trust token lifetime element
|
97
|
+
def wstrust_lifetime
|
98
|
+
@wstrust_lifetime ||= begin
|
99
|
+
REXML::XPath.first(document, '//t:RequestSecurityTokenResponse/t:Lifetime', { 't' => WS_TRUST })
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module OmniAuth
|
2
|
+
module Strategies
|
3
|
+
class WSFed
|
4
|
+
|
5
|
+
class AuthCallbackValidator
|
6
|
+
|
7
|
+
attr_accessor :auth_callback, :wsfed_settings
|
8
|
+
|
9
|
+
ISSUER_MISMATCH = 'AuthN token issuer does not match configured issuer.'
|
10
|
+
AUDIENCE_MISMATCH = 'AuthN token audience does not match configured realm.'
|
11
|
+
FUTURE_CREATED_AT = 'AuthN token created timestamp occurs in the future.'
|
12
|
+
TOKEN_EXPIRED = 'AuthN token has expired.'
|
13
|
+
NO_CLAIMS = 'AuthN token contains no claims.'
|
14
|
+
NO_USER_IDENTIFIER = 'AuthN token contains no user identifier. Verify that configured :id_claim setting is correct.'
|
15
|
+
|
16
|
+
def initialize(auth_callback, wsfed_settings)
|
17
|
+
self.auth_callback = auth_callback
|
18
|
+
self.wsfed_settings = wsfed_settings
|
19
|
+
end
|
20
|
+
|
21
|
+
def validate!
|
22
|
+
raise OmniAuth::Strategies::WSFed::ValidationError.new(ISSUER_MISMATCH) unless
|
23
|
+
auth_callback.issuer == wsfed_settings[:issuer_name]
|
24
|
+
|
25
|
+
raise OmniAuth::Strategies::WSFed::ValidationError.new(AUDIENCE_MISMATCH) unless
|
26
|
+
auth_callback.audience == wsfed_settings[:realm]
|
27
|
+
|
28
|
+
raise OmniAuth::Strategies::WSFed::ValidationError.new(FUTURE_CREATED_AT) unless
|
29
|
+
auth_callback.created_at < Time.now.utc
|
30
|
+
|
31
|
+
raise OmniAuth::Strategies::WSFed::ValidationError.new(TOKEN_EXPIRED) unless
|
32
|
+
auth_callback.expires_at > Time.now.utc
|
33
|
+
|
34
|
+
if auth_callback.claims.nil? || auth_callback.claims.empty?
|
35
|
+
raise OmniAuth::Strategies::WSFed::ValidationError.new(NO_CLAIMS)
|
36
|
+
end
|
37
|
+
|
38
|
+
if auth_callback.name_id.nil? || auth_callback.name_id.empty?
|
39
|
+
raise OmniAuth::Strategies::WSFed::ValidationError.new(NO_USER_IDENTIFIER)
|
40
|
+
end
|
41
|
+
|
42
|
+
true
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
data/omniauth-wsfed.gemspec
CHANGED
@@ -3,26 +3,25 @@ require File.expand_path('../lib/omniauth-wsfed/version', __FILE__)
|
|
3
3
|
|
4
4
|
Gem::Specification.new do |gem|
|
5
5
|
|
6
|
-
gem.name =
|
6
|
+
gem.name = 'omniauth-wsfed'
|
7
7
|
gem.version = OmniAuth::WSFed::VERSION
|
8
|
-
gem.
|
9
|
-
gem.
|
8
|
+
gem.summary = %q{A WS-Federation + WS-Trust strategy for OmniAuth.}
|
9
|
+
gem.description = %q{OmniAuth WS-Federation strategy enabling integration with Windows Azure Access Control Service (ACS), Active Directory Federation Services (ADFS) 2.0, custom Identity Providers built with Windows Identity Foundation (WIF) or any other Identity Provider supporting the WS-Federation protocol.}
|
10
10
|
|
11
|
-
gem.authors = [
|
12
|
-
gem.email = [
|
13
|
-
gem.homepage =
|
11
|
+
gem.authors = ['Keith Beckman']
|
12
|
+
gem.email = ['kbeckman.c4sc@gmail.com']
|
13
|
+
gem.homepage = 'https://github.com/kbeckman/omniauth-wsfed'
|
14
14
|
|
15
15
|
gem.add_runtime_dependency 'omniauth', '~> 1.1.0'
|
16
16
|
gem.add_runtime_dependency 'xmlcanonicalizer', '0.1.1'
|
17
|
-
gem.add_runtime_dependency 'typhoeus', '~> 0.4.2'
|
18
17
|
|
19
|
-
gem.add_development_dependency 'rspec', '
|
20
|
-
gem.add_development_dependency 'rake', '
|
21
|
-
gem.add_development_dependency 'rack-test', '
|
18
|
+
gem.add_development_dependency 'rspec', '>= 2.12.0'
|
19
|
+
gem.add_development_dependency 'rake', '>= 10.0.3'
|
20
|
+
gem.add_development_dependency 'rack-test', '>= 0.6.2'
|
22
21
|
|
23
22
|
gem.files = `git ls-files`.split($\)
|
24
23
|
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
25
24
|
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
26
|
-
gem.require_paths = [
|
25
|
+
gem.require_paths = ['lib']
|
27
26
|
|
28
27
|
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe OmniAuth::Strategies::WSFed::AuthCallback do
|
4
|
+
|
5
|
+
describe 'Initialization' do
|
6
|
+
|
7
|
+
context 'with Invalid Context' do
|
8
|
+
|
9
|
+
it 'should raise an exception when raw callback is nil' do
|
10
|
+
expect { described_class.new(nil, {}) }.to raise_error ArgumentError
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'should raise an exception when settings are nil' do
|
14
|
+
expect { described_class.new({}, nil) }.to raise_error ArgumentError
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
describe 'Callback Parsing' do
|
22
|
+
|
23
|
+
before(:each) do
|
24
|
+
@wsfed_settings = {}
|
25
|
+
end
|
26
|
+
|
27
|
+
context 'WS-Trust Envelope and WS* Values' do
|
28
|
+
|
29
|
+
let(:auth_callback) { described_class.new(load_support_xml(:acs_example), @wsfed_settings) }
|
30
|
+
|
31
|
+
it 'should extract the creation timestamp' do
|
32
|
+
auth_callback.created_at.should == Time.parse('2012-06-29T21:07:14.766Z')
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'should extract the expiration limit' do
|
36
|
+
auth_callback.expires_at.should == Time.parse('2012-06-29T21:17:14.766Z')
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'should extract the token audience' do
|
40
|
+
auth_callback.audience.should == 'http://rp.coding4streetcred.com/sample'
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
context 'SAML 2.0 Assertion [Token] Values' do
|
46
|
+
|
47
|
+
let(:auth_callback) { described_class.new(load_support_xml(:acs_example), @wsfed_settings) }
|
48
|
+
|
49
|
+
it 'should extract the issuer' do
|
50
|
+
auth_callback.issuer.should == 'https://c4sc-identity.accesscontrol.windows.net/'
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'should extract the authentication claims' do
|
54
|
+
expected_claims = {
|
55
|
+
'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress' => 'kbeckman.c4sc@gmail.com',
|
56
|
+
'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name' => 'kbeckman.c4sc',
|
57
|
+
'http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider' => 'http://identity.c4sc.com/trust/'
|
58
|
+
}
|
59
|
+
|
60
|
+
auth_callback.attributes.should == expected_claims
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'should load the proper value from various id_claim settings' do
|
64
|
+
id_claims = [
|
65
|
+
{ :id_claim => 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress', :value => 'kbeckman.c4sc@gmail.com' },
|
66
|
+
{ :id_claim => 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name', :value => 'kbeckman.c4sc' }
|
67
|
+
]
|
68
|
+
|
69
|
+
id_claims.each do |claim_setting|
|
70
|
+
@wsfed_settings.merge!(claim_setting.select { |k| k == :id_claim })
|
71
|
+
auth_callback = described_class.new(load_support_xml(:acs_example), @wsfed_settings)
|
72
|
+
|
73
|
+
auth_callback.name_id.should == claim_setting[:value]
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe OmniAuth::Strategies::WSFed::AuthCallbackValidator do
|
4
|
+
|
5
|
+
describe 'Response Validation Rules' do
|
6
|
+
|
7
|
+
let(:auth_callback) { OmniAuth::Strategies::WSFed::AuthCallback.new({}, {})}
|
8
|
+
|
9
|
+
before(:each) do
|
10
|
+
@wsfed_settings = {
|
11
|
+
:issuer_name => 'https://identity-wwf.accesscontrol.windows.net/',
|
12
|
+
:realm => 'http://rp.wwf.com/wsfed-sample',
|
13
|
+
:id_claim => 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name'
|
14
|
+
}
|
15
|
+
|
16
|
+
@claims = {
|
17
|
+
'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress' => 'ravishing_rick@wwf.com',
|
18
|
+
'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name' => 'rick.rude',
|
19
|
+
'http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider' => 'http://sso.wwf.com'
|
20
|
+
}
|
21
|
+
|
22
|
+
auth_callback.stub(:issuer).and_return(@wsfed_settings[:issuer_name])
|
23
|
+
auth_callback.stub(:audience).and_return(@wsfed_settings[:realm])
|
24
|
+
auth_callback.stub(:claims).and_return(@claims)
|
25
|
+
auth_callback.stub(:name_id).and_return(@claims[@wsfed_settings[:id_claim]])
|
26
|
+
auth_callback.stub(:created_at).and_return(Time.now.utc - 1) # 1 second ago
|
27
|
+
auth_callback.stub(:expires_at).and_return(Time.now.utc + 300) # 5 minutes from now
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'should pass validation with....' do
|
31
|
+
validator = described_class.new(auth_callback, @wsfed_settings)
|
32
|
+
|
33
|
+
validator.validate!.should == true
|
34
|
+
end
|
35
|
+
|
36
|
+
context 'with Invalid Response' do
|
37
|
+
|
38
|
+
it 'should throw an exception when issuers do not match' do
|
39
|
+
auth_callback.stub(:issuer).and_return('https://c4sc-federation-nomatch.accesscontrol.windows.net/')
|
40
|
+
|
41
|
+
validator = described_class.new(auth_callback, @wsfed_settings)
|
42
|
+
|
43
|
+
lambda { validator.validate! }.should raise_error OmniAuth::Strategies::WSFed::ValidationError,
|
44
|
+
OmniAuth::Strategies::WSFed::AuthCallbackValidator::ISSUER_MISMATCH
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'should throw an exception when realm/audience do not match' do
|
48
|
+
auth_callback.stub(:audience).and_return('http://rp.c4sc.com/wsfed-sample-nomatch')
|
49
|
+
|
50
|
+
validator = described_class.new(auth_callback, @wsfed_settings)
|
51
|
+
|
52
|
+
lambda { validator.validate! }.should raise_error OmniAuth::Strategies::WSFed::ValidationError,
|
53
|
+
OmniAuth::Strategies::WSFed::AuthCallbackValidator::AUDIENCE_MISMATCH
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'should throw an exception when the created_at timestamp is in the future' do
|
57
|
+
auth_callback.stub(:created_at).and_return(Time.now.utc + 2)
|
58
|
+
|
59
|
+
validator = described_class.new(auth_callback, @wsfed_settings)
|
60
|
+
|
61
|
+
lambda { validator.validate! }.should raise_error OmniAuth::Strategies::WSFed::ValidationError,
|
62
|
+
OmniAuth::Strategies::WSFed::AuthCallbackValidator::FUTURE_CREATED_AT
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'should throw an exception when the expires_at timestamp limit has been exceeded' do
|
66
|
+
auth_callback.stub(:expires_at).and_return(Time.now.utc - 1)
|
67
|
+
|
68
|
+
validator = described_class.new(auth_callback, @wsfed_settings)
|
69
|
+
|
70
|
+
lambda { validator.validate! }.should raise_error OmniAuth::Strategies::WSFed::ValidationError,
|
71
|
+
OmniAuth::Strategies::WSFed::AuthCallbackValidator::TOKEN_EXPIRED
|
72
|
+
end
|
73
|
+
|
74
|
+
it 'should throw an exception when claims are empty or nil' do
|
75
|
+
[nil, {}].each do |val|
|
76
|
+
auth_callback.stub(:claims).and_return(val)
|
77
|
+
|
78
|
+
validator = described_class.new(auth_callback, @wsfed_settings)
|
79
|
+
|
80
|
+
lambda { validator.validate! }.should raise_error OmniAuth::Strategies::WSFed::ValidationError,
|
81
|
+
OmniAuth::Strategies::WSFed::AuthCallbackValidator::NO_CLAIMS
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
it 'should throw an exception when the name_id is empty or nil' do
|
86
|
+
[nil, ""].each do |val|
|
87
|
+
auth_callback.stub(:name_id).and_return(val)
|
88
|
+
|
89
|
+
validator = described_class.new(auth_callback, @wsfed_settings)
|
90
|
+
|
91
|
+
lambda { validator.validate! }.should raise_error OmniAuth::Strategies::WSFed::ValidationError,
|
92
|
+
OmniAuth::Strategies::WSFed::AuthCallbackValidator::NO_USER_IDENTIFIER
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: omniauth-wsfed
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2013-02-22 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: omniauth
|
@@ -43,71 +43,58 @@ dependencies:
|
|
43
43
|
- - '='
|
44
44
|
- !ruby/object:Gem::Version
|
45
45
|
version: 0.1.1
|
46
|
-
- !ruby/object:Gem::Dependency
|
47
|
-
name: typhoeus
|
48
|
-
requirement: !ruby/object:Gem::Requirement
|
49
|
-
none: false
|
50
|
-
requirements:
|
51
|
-
- - ~>
|
52
|
-
- !ruby/object:Gem::Version
|
53
|
-
version: 0.4.2
|
54
|
-
type: :runtime
|
55
|
-
prerelease: false
|
56
|
-
version_requirements: !ruby/object:Gem::Requirement
|
57
|
-
none: false
|
58
|
-
requirements:
|
59
|
-
- - ~>
|
60
|
-
- !ruby/object:Gem::Version
|
61
|
-
version: 0.4.2
|
62
46
|
- !ruby/object:Gem::Dependency
|
63
47
|
name: rspec
|
64
48
|
requirement: !ruby/object:Gem::Requirement
|
65
49
|
none: false
|
66
50
|
requirements:
|
67
|
-
- -
|
51
|
+
- - ! '>='
|
68
52
|
- !ruby/object:Gem::Version
|
69
|
-
version: 2.
|
53
|
+
version: 2.12.0
|
70
54
|
type: :development
|
71
55
|
prerelease: false
|
72
56
|
version_requirements: !ruby/object:Gem::Requirement
|
73
57
|
none: false
|
74
58
|
requirements:
|
75
|
-
- -
|
59
|
+
- - ! '>='
|
76
60
|
- !ruby/object:Gem::Version
|
77
|
-
version: 2.
|
61
|
+
version: 2.12.0
|
78
62
|
- !ruby/object:Gem::Dependency
|
79
63
|
name: rake
|
80
64
|
requirement: !ruby/object:Gem::Requirement
|
81
65
|
none: false
|
82
66
|
requirements:
|
83
|
-
- -
|
67
|
+
- - ! '>='
|
84
68
|
- !ruby/object:Gem::Version
|
85
|
-
version: 0.
|
69
|
+
version: 10.0.3
|
86
70
|
type: :development
|
87
71
|
prerelease: false
|
88
72
|
version_requirements: !ruby/object:Gem::Requirement
|
89
73
|
none: false
|
90
74
|
requirements:
|
91
|
-
- -
|
75
|
+
- - ! '>='
|
92
76
|
- !ruby/object:Gem::Version
|
93
|
-
version: 0.
|
77
|
+
version: 10.0.3
|
94
78
|
- !ruby/object:Gem::Dependency
|
95
79
|
name: rack-test
|
96
80
|
requirement: !ruby/object:Gem::Requirement
|
97
81
|
none: false
|
98
82
|
requirements:
|
99
|
-
- -
|
83
|
+
- - ! '>='
|
100
84
|
- !ruby/object:Gem::Version
|
101
|
-
version: 0.6.
|
85
|
+
version: 0.6.2
|
102
86
|
type: :development
|
103
87
|
prerelease: false
|
104
88
|
version_requirements: !ruby/object:Gem::Requirement
|
105
89
|
none: false
|
106
90
|
requirements:
|
107
|
-
- -
|
91
|
+
- - ! '>='
|
108
92
|
- !ruby/object:Gem::Version
|
109
|
-
version: 0.6.
|
110
|
-
description:
|
93
|
+
version: 0.6.2
|
94
|
+
description: OmniAuth WS-Federation strategy enabling integration with Windows Azure
|
95
|
+
Access Control Service (ACS), Active Directory Federation Services (ADFS) 2.0, custom
|
96
|
+
Identity Providers built with Windows Identity Foundation (WIF) or any other Identity
|
97
|
+
Provider supporting the WS-Federation protocol.
|
111
98
|
email:
|
112
99
|
- kbeckman.c4sc@gmail.com
|
113
100
|
executables: []
|
@@ -120,18 +107,18 @@ files:
|
|
120
107
|
- LICENSE
|
121
108
|
- README.md
|
122
109
|
- Rakefile
|
123
|
-
- lib/azure_acs/idp_feed.rb
|
124
110
|
- lib/omniauth-wsfed.rb
|
125
111
|
- lib/omniauth-wsfed/version.rb
|
126
112
|
- lib/omniauth/strategies/wsfed.rb
|
113
|
+
- lib/omniauth/strategies/wsfed/auth_callback.rb
|
114
|
+
- lib/omniauth/strategies/wsfed/auth_callback_validator.rb
|
127
115
|
- lib/omniauth/strategies/wsfed/auth_request.rb
|
128
|
-
- lib/omniauth/strategies/wsfed/auth_response.rb
|
129
116
|
- lib/omniauth/strategies/wsfed/validation_error.rb
|
130
117
|
- lib/omniauth/strategies/wsfed/xml_security.rb
|
131
118
|
- omniauth-wsfed.gemspec
|
132
|
-
- spec/
|
119
|
+
- spec/omniauth/strategies/wsfed/auth_callback_spec.rb
|
120
|
+
- spec/omniauth/strategies/wsfed/auth_callback_validator_spec.rb
|
133
121
|
- spec/omniauth/strategies/wsfed/auth_request_spec.rb
|
134
|
-
- spec/omniauth/strategies/wsfed/auth_response_spec.rb
|
135
122
|
- spec/omniauth/strategies/wsfed_spec.rb
|
136
123
|
- spec/spec_helper.rb
|
137
124
|
- spec/support/acs_example.xml
|
@@ -155,17 +142,14 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
155
142
|
version: '0'
|
156
143
|
requirements: []
|
157
144
|
rubyforge_project:
|
158
|
-
rubygems_version: 1.8.
|
145
|
+
rubygems_version: 1.8.25
|
159
146
|
signing_key:
|
160
147
|
specification_version: 3
|
161
|
-
summary:
|
162
|
-
Control Service (ACS), Active Directory Federation Services (ADFS) 2.0, custom Identity
|
163
|
-
Providers built with Windows Identity Foundation (WIF) or any other Identity Provider
|
164
|
-
supporting the WS-Federation protocol.
|
148
|
+
summary: A WS-Federation + WS-Trust strategy for OmniAuth.
|
165
149
|
test_files:
|
166
|
-
- spec/
|
150
|
+
- spec/omniauth/strategies/wsfed/auth_callback_spec.rb
|
151
|
+
- spec/omniauth/strategies/wsfed/auth_callback_validator_spec.rb
|
167
152
|
- spec/omniauth/strategies/wsfed/auth_request_spec.rb
|
168
|
-
- spec/omniauth/strategies/wsfed/auth_response_spec.rb
|
169
153
|
- spec/omniauth/strategies/wsfed_spec.rb
|
170
154
|
- spec/spec_helper.rb
|
171
155
|
- spec/support/acs_example.xml
|
data/lib/azure_acs/idp_feed.rb
DELETED
@@ -1,16 +0,0 @@
|
|
1
|
-
module AzureACS
|
2
|
-
|
3
|
-
class IdPFeed
|
4
|
-
|
5
|
-
def initialize(idp_json_feed_url)
|
6
|
-
raise ArgumentError.new("Azure ACS JSON feed URL cannot be null.") if idp_json_feed_url.nil?
|
7
|
-
@json_feed_url = idp_json_feed_url
|
8
|
-
end
|
9
|
-
|
10
|
-
def identity_providers
|
11
|
-
@json_feed ||= Typhoeus::Request.get(@json_feed_url).body
|
12
|
-
end
|
13
|
-
|
14
|
-
end
|
15
|
-
|
16
|
-
end
|
@@ -1,159 +0,0 @@
|
|
1
|
-
require "time"
|
2
|
-
require "hashie"
|
3
|
-
|
4
|
-
module OmniAuth
|
5
|
-
module Strategies
|
6
|
-
class WSFed
|
7
|
-
|
8
|
-
class AuthResponse
|
9
|
-
|
10
|
-
ASSERTION = "urn:oasis:names:tc:SAML:2.0:assertion"
|
11
|
-
PROTOCOL = "urn:oasis:names:tc:SAML:2.0:protocol"
|
12
|
-
DSIG = "http://www.w3.org/2000/09/xmldsig#"
|
13
|
-
|
14
|
-
attr_accessor :options, :response, :document, :settings
|
15
|
-
|
16
|
-
def initialize(response, settings, options = {})
|
17
|
-
raise ArgumentError.new("Response cannot be nil.") if response.nil?
|
18
|
-
raise ArgumentError.new("WSFed settings cannot be nil.") if settings.nil?
|
19
|
-
|
20
|
-
self.options = options
|
21
|
-
self.response = response
|
22
|
-
self.settings = settings
|
23
|
-
self.document = OmniAuth::Strategies::WSFed::XMLSecurity::SignedDocument.new(response)
|
24
|
-
end
|
25
|
-
|
26
|
-
def valid?
|
27
|
-
validate(soft = true)
|
28
|
-
end
|
29
|
-
|
30
|
-
def validate!
|
31
|
-
validate(soft = false)
|
32
|
-
end
|
33
|
-
|
34
|
-
# The value of the user identifier as defined by the id_claim setting...
|
35
|
-
def name_id
|
36
|
-
@name_id ||= begin
|
37
|
-
attributes.has_key?(settings[:id_claim]) ? attributes.fetch(settings[:id_claim]) : nil
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
# A hash of all the claims provided by the response.
|
42
|
-
def attributes
|
43
|
-
@attr_statements ||= begin
|
44
|
-
stmt_element = REXML::XPath.first(document, "//Assertion/AttributeStatement")
|
45
|
-
return {} if stmt_element.nil?
|
46
|
-
|
47
|
-
{}.tap do |result|
|
48
|
-
stmt_element.elements.each do |attr_element|
|
49
|
-
name = attr_element.attributes["Name"]
|
50
|
-
|
51
|
-
if attr_element.elements.count > 1
|
52
|
-
value = []
|
53
|
-
attr_element.elements.each { |element| value << element.text }
|
54
|
-
else
|
55
|
-
value = attr_element.elements.first.text.lstrip.rstrip
|
56
|
-
end
|
57
|
-
|
58
|
-
result[name] = value
|
59
|
-
end
|
60
|
-
end
|
61
|
-
end
|
62
|
-
end
|
63
|
-
|
64
|
-
# When this user session should expire at latest
|
65
|
-
def session_expires_at
|
66
|
-
@expires_at ||= begin
|
67
|
-
node = xpath("/p:Response/a:Assertion/a:AuthnStatement")
|
68
|
-
parse_time(node, "SessionNotOnOrAfter")
|
69
|
-
end
|
70
|
-
end
|
71
|
-
|
72
|
-
# Conditions (if any) for the assertion to run
|
73
|
-
def conditions
|
74
|
-
@conditions ||= begin
|
75
|
-
xpath("/p:Response/a:Assertion[@ID='#{signed_element_id}']/a:Conditions")
|
76
|
-
end
|
77
|
-
end
|
78
|
-
|
79
|
-
private
|
80
|
-
|
81
|
-
def validation_error(message)
|
82
|
-
raise OmniAuth::Strategies::WSFed::ValidationError.new(message)
|
83
|
-
end
|
84
|
-
|
85
|
-
def validate(soft = true)
|
86
|
-
validate_response_state(soft) &&
|
87
|
-
validate_conditions(soft) &&
|
88
|
-
document.validate(get_fingerprint, soft)
|
89
|
-
end
|
90
|
-
|
91
|
-
def validate_response_state(soft = true)
|
92
|
-
if response.empty?
|
93
|
-
return soft ? false : validation_error("Blank response")
|
94
|
-
end
|
95
|
-
|
96
|
-
if settings.nil?
|
97
|
-
return soft ? false : validation_error("No settings on response")
|
98
|
-
end
|
99
|
-
|
100
|
-
if settings[:idp_cert_fingerprint].nil? && settings[:idp_cert].nil?
|
101
|
-
return soft ? false : validation_error("No fingerprint or certificate on settings")
|
102
|
-
end
|
103
|
-
|
104
|
-
true
|
105
|
-
end
|
106
|
-
|
107
|
-
def get_fingerprint
|
108
|
-
if settings[:idp_cert_fingerprint]
|
109
|
-
settings[:idp_cert_fingerprint]
|
110
|
-
else
|
111
|
-
cert = OpenSSL::X509::Certificate.new(settings[:idp_cert].gsub(/^ +/, ''))
|
112
|
-
Digest::SHA1.hexdigest(cert.to_der).upcase.scan(/../).join(":")
|
113
|
-
end
|
114
|
-
end
|
115
|
-
|
116
|
-
def validate_conditions(soft = true)
|
117
|
-
return true if conditions.nil?
|
118
|
-
return true if options[:skip_conditions]
|
119
|
-
|
120
|
-
if not_before = parse_time(conditions, "NotBefore")
|
121
|
-
if Time.now.utc < not_before
|
122
|
-
return soft ? false : validation_error("Current time is earlier than NotBefore condition")
|
123
|
-
end
|
124
|
-
end
|
125
|
-
|
126
|
-
if not_on_or_after = parse_time(conditions, "NotOnOrAfter")
|
127
|
-
if Time.now.utc >= not_on_or_after
|
128
|
-
return soft ? false : validation_error("Current time is on or after NotOnOrAfter condition")
|
129
|
-
end
|
130
|
-
end
|
131
|
-
|
132
|
-
true
|
133
|
-
end
|
134
|
-
|
135
|
-
def parse_time(node, attribute)
|
136
|
-
if node && node.attributes[attribute]
|
137
|
-
Time.parse(node.attributes[attribute])
|
138
|
-
end
|
139
|
-
end
|
140
|
-
|
141
|
-
#def strip(string)
|
142
|
-
# return string unless string
|
143
|
-
# string.gsub(/^\s+/, '').gsub(/\s+$/, '')
|
144
|
-
#end
|
145
|
-
|
146
|
-
def xpath(path)
|
147
|
-
#REXML::XPath.first(document, path, { "p" => PROTOCOL, "a" => ASSERTION })
|
148
|
-
REXML::XPath.first(document, path, { "saml" => ASSERTION })
|
149
|
-
end
|
150
|
-
|
151
|
-
def signed_element_id
|
152
|
-
doc_id = document.signed_element_id
|
153
|
-
doc_id[1, doc_id.size]
|
154
|
-
end
|
155
|
-
end
|
156
|
-
|
157
|
-
end
|
158
|
-
end
|
159
|
-
end
|
@@ -1,18 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
require 'erb'
|
3
|
-
|
4
|
-
describe AzureACS::IdPFeed do
|
5
|
-
|
6
|
-
describe "Initialization" do
|
7
|
-
|
8
|
-
context "with Invalid Context" do
|
9
|
-
|
10
|
-
it "should raise an exception when IdP JSON feed URL is nil" do
|
11
|
-
expect { described_class.new(nil) }.to raise_error ArgumentError
|
12
|
-
end
|
13
|
-
|
14
|
-
end
|
15
|
-
|
16
|
-
end
|
17
|
-
|
18
|
-
end
|
@@ -1,67 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
|
-
describe OmniAuth::Strategies::WSFed::AuthResponse do
|
4
|
-
|
5
|
-
describe "Initialization" do
|
6
|
-
|
7
|
-
context "with Invalid Context" do
|
8
|
-
|
9
|
-
it "should raise an exception when response is nil" do
|
10
|
-
expect { described_class.new(nil, {}) }.to raise_error ArgumentError
|
11
|
-
end
|
12
|
-
|
13
|
-
it "should raise an exception when settings are nil" do
|
14
|
-
expect { described_class.new({}, nil) }.to raise_error ArgumentError
|
15
|
-
end
|
16
|
-
|
17
|
-
end
|
18
|
-
|
19
|
-
end
|
20
|
-
|
21
|
-
describe "Response Parsing" do
|
22
|
-
|
23
|
-
before(:each) do
|
24
|
-
@wsfed_settings = {
|
25
|
-
issuer_name: "http://identity.c4sc.com/trust/",
|
26
|
-
issuer: "https://identity.c4sc.com/issue/wsfed",
|
27
|
-
realm: "http://rp.c4sc/security_realm",
|
28
|
-
reply: "http://rp.c4sc/callback",
|
29
|
-
}
|
30
|
-
end
|
31
|
-
|
32
|
-
context "name_id" do
|
33
|
-
|
34
|
-
it "should load the proper value from various id_claim settings" do
|
35
|
-
id_claims = [
|
36
|
-
{ id_claim: "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress", value: "kbeckman.c4sc@gmail.com" },
|
37
|
-
{ id_claim: "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name", value: "kbeckman.c4sc" }
|
38
|
-
]
|
39
|
-
|
40
|
-
id_claims.each do |claim_setting|
|
41
|
-
@wsfed_settings.merge!(claim_setting.select { |k,v| k == :id_claim })
|
42
|
-
response = described_class.new(load_support_xml(:acs_example), @wsfed_settings)
|
43
|
-
|
44
|
-
response.name_id.should == claim_setting[:value]
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
|
-
end
|
49
|
-
|
50
|
-
context "attributes" do
|
51
|
-
|
52
|
-
it "should extract the authentication claims" do
|
53
|
-
expected_claims = {
|
54
|
-
"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress" => "kbeckman.c4sc@gmail.com",
|
55
|
-
"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name" => "kbeckman.c4sc",
|
56
|
-
"http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider" => "http://identity.c4sc.com/trust/"
|
57
|
-
}
|
58
|
-
response = described_class.new(load_support_xml(:acs_example), @wsfed_settings)
|
59
|
-
|
60
|
-
response.attributes.should == expected_claims
|
61
|
-
end
|
62
|
-
|
63
|
-
end
|
64
|
-
|
65
|
-
end
|
66
|
-
|
67
|
-
end
|