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 CHANGED
@@ -2,4 +2,9 @@
2
2
  .idea
3
3
 
4
4
  #RVM
5
- .rvmrc
5
+ .rvmrc
6
+
7
+ #Other
8
+ .DS_Store
9
+ *.gem
10
+
data/Gemfile.lock CHANGED
@@ -1,36 +1,30 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- omniauth-wsfed (0.1.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.1)
17
+ rack-test (0.6.2)
21
18
  rack (>= 1.0)
22
- rake (0.9.2.2)
23
- rspec (2.10.0)
24
- rspec-core (~> 2.10.0)
25
- rspec-expectations (~> 2.10.0)
26
- rspec-mocks (~> 2.10.0)
27
- rspec-core (2.10.1)
28
- rspec-expectations (2.10.0)
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.10.1)
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 (~> 0.6.1)
42
- rake (~> 0.9.2)
43
- rspec (~> 2.10.0)
35
+ rack-test (>= 0.6.2)
36
+ rake (>= 10.0.3)
37
+ rspec (>= 2.12.0)
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2011-2012 Keith Beckman, http://www.coding4streetcred.com/blog
1
+ Copyright (c) 2011-2013 Keith Beckman, http://coding4streetcred.com/blog
2
2
  All rights reserved. Released under the MIT license.
3
3
 
4
4
  Portions Copyright (c) 2007 Sun Microsystems Inc.
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). This option is not required,
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.
@@ -1,2 +1 @@
1
1
  require 'omniauth/strategies/wsfed'
2
- require 'azure_acs/idp_feed'
@@ -1,5 +1,5 @@
1
1
  module OmniAuth
2
2
  module WSFed
3
- VERSION = "0.1.0"
3
+ VERSION = '0.2.0'
4
4
  end
5
5
  end
@@ -6,10 +6,11 @@ module OmniAuth
6
6
  class WSFed
7
7
  include OmniAuth::Strategy
8
8
 
9
- autoload :AuthRequest, 'omniauth/strategies/wsfed/auth_request'
10
- autoload :AuthResponse, 'omniauth/strategies/wsfed/auth_response'
11
- autoload :ValidationError, 'omniauth/strategies/wsfed/validation_error'
12
- autoload :XMLSecurity, 'omniauth/strategies/wsfed/xml_security'
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
- response = OmniAuth::Strategies::WSFed::AuthResponse.new(request.params['wresult'], options)
34
+ wsfed_callback = request.params['wresult']
34
35
 
35
- @name_id = response.name_id
36
- @claims = response.attributes
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!(:invalid_ticket, 'Invalid WSFed Response')
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
@@ -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 = "omniauth-wsfed"
6
+ gem.name = 'omniauth-wsfed'
7
7
  gem.version = OmniAuth::WSFed::VERSION
8
- gem.description = %q{A WS-Federation + WS-Trust strategy for OmniAuth.}
9
- gem.summary = %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.}
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 = ["Keith Beckman"]
12
- gem.email = ["kbeckman.c4sc@gmail.com"]
13
- gem.homepage = "https://github.com/kbeckman/omniauth-wsfed"
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', '~> 2.10.0'
20
- gem.add_development_dependency 'rake', '~> 0.9.2'
21
- gem.add_development_dependency 'rack-test', '~> 0.6.1'
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 = ["lib"]
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.1.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: 2012-09-07 00:00:00.000000000 Z
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.10.0
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.10.0
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.9.2
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.9.2
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.1
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.1
110
- description: A WS-Federation + WS-Trust strategy for OmniAuth.
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/azure_acs/idp_feed_spec.rb
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.24
145
+ rubygems_version: 1.8.25
159
146
  signing_key:
160
147
  specification_version: 3
161
- summary: OmniAuth WS-Federation strategy enabling integration with Windows Azure Access
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/azure_acs/idp_feed_spec.rb
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
@@ -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