adal 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +5 -0
- data/.rubocop.yml +7 -0
- data/.travis.yml +7 -0
- data/Gemfile +25 -0
- data/LICENSE.txt +21 -0
- data/README.md +97 -0
- data/Rakefile +39 -0
- data/adal.gemspec +52 -0
- data/contributing.md +127 -0
- data/lib/adal.rb +24 -0
- data/lib/adal/authentication_context.rb +202 -0
- data/lib/adal/authentication_parameters.rb +126 -0
- data/lib/adal/authority.rb +165 -0
- data/lib/adal/cache_driver.rb +171 -0
- data/lib/adal/cached_token_response.rb +190 -0
- data/lib/adal/client_assertion.rb +63 -0
- data/lib/adal/client_assertion_certificate.rb +89 -0
- data/lib/adal/client_credential.rb +46 -0
- data/lib/adal/core_ext.rb +26 -0
- data/lib/adal/core_ext/hash.rb +34 -0
- data/lib/adal/jwt_parameters.rb +39 -0
- data/lib/adal/logger.rb +90 -0
- data/lib/adal/logging.rb +98 -0
- data/lib/adal/memory_cache.rb +95 -0
- data/lib/adal/mex_request.rb +52 -0
- data/lib/adal/mex_response.rb +141 -0
- data/lib/adal/noop_cache.rb +38 -0
- data/lib/adal/oauth_request.rb +76 -0
- data/lib/adal/request_parameters.rb +48 -0
- data/lib/adal/self_signed_jwt_factory.rb +96 -0
- data/lib/adal/templates/rst.13.xml.erb +35 -0
- data/lib/adal/templates/rst.2005.xml.erb +32 -0
- data/lib/adal/token_request.rb +231 -0
- data/lib/adal/token_response.rb +144 -0
- data/lib/adal/user_assertion.rb +57 -0
- data/lib/adal/user_credential.rb +152 -0
- data/lib/adal/user_identifier.rb +83 -0
- data/lib/adal/user_information.rb +49 -0
- data/lib/adal/util.rb +49 -0
- data/lib/adal/version.rb +36 -0
- data/lib/adal/wstrust_request.rb +100 -0
- data/lib/adal/wstrust_response.rb +168 -0
- data/lib/adal/xml_namespaces.rb +64 -0
- data/samples/authorization_code_example/README.md +10 -0
- data/samples/authorization_code_example/web_app.rb +139 -0
- data/samples/client_assertion_certificate_example/README.md +42 -0
- data/samples/client_assertion_certificate_example/app.rb +55 -0
- data/samples/on_behalf_of_example/README.md +35 -0
- data/samples/on_behalf_of_example/native_app.rb +52 -0
- data/samples/on_behalf_of_example/web_api.rb +71 -0
- data/samples/user_credentials_example/README.md +7 -0
- data/samples/user_credentials_example/app.rb +52 -0
- data/spec/adal/authentication_context_spec.rb +186 -0
- data/spec/adal/authentication_parameters_spec.rb +107 -0
- data/spec/adal/authority_spec.rb +122 -0
- data/spec/adal/cache_driver_spec.rb +191 -0
- data/spec/adal/cached_token_response_spec.rb +148 -0
- data/spec/adal/client_assertion_certificate_spec.rb +113 -0
- data/spec/adal/client_assertion_spec.rb +38 -0
- data/spec/adal/core_ext/hash_spec.rb +47 -0
- data/spec/adal/logging_spec.rb +48 -0
- data/spec/adal/memory_cache_spec.rb +107 -0
- data/spec/adal/mex_request_spec.rb +57 -0
- data/spec/adal/mex_response_spec.rb +143 -0
- data/spec/adal/self_signed_jwt_factory_spec.rb +63 -0
- data/spec/adal/token_request_spec.rb +150 -0
- data/spec/adal/token_response_spec.rb +102 -0
- data/spec/adal/user_credential_spec.rb +125 -0
- data/spec/adal/user_identifier_spec.rb +115 -0
- data/spec/adal/wstrust_request_spec.rb +51 -0
- data/spec/adal/wstrust_response_spec.rb +152 -0
- data/spec/fixtures/mex/insecureaddress.xml +924 -0
- data/spec/fixtures/mex/invalid_namespaces.xml +916 -0
- data/spec/fixtures/mex/malformed.xml +914 -0
- data/spec/fixtures/mex/microsoft.xml +916 -0
- data/spec/fixtures/mex/multiple_endpoints.xml +922 -0
- data/spec/fixtures/mex/no_matching_bindings.xml +916 -0
- data/spec/fixtures/mex/no_username_token_policies.xml +914 -0
- data/spec/fixtures/mex/no_wstrust_endpoints.xml +838 -0
- data/spec/fixtures/mex/only_13.xml +842 -0
- data/spec/fixtures/mex/only_2005.xml +842 -0
- data/spec/fixtures/oauth/error.json +1 -0
- data/spec/fixtures/oauth/success.json +1 -0
- data/spec/fixtures/oauth/success_with_id_token.json +1 -0
- data/spec/fixtures/wstrust/error.xml +24 -0
- data/spec/fixtures/wstrust/invalid_namespaces.xml +136 -0
- data/spec/fixtures/wstrust/missing_security_tokens.xml +90 -0
- data/spec/fixtures/wstrust/success.xml +136 -0
- data/spec/fixtures/wstrust/token.xml +1 -0
- data/spec/fixtures/wstrust/too_many_security_tokens.xml +219 -0
- data/spec/fixtures/wstrust/unrecognized_token_type.xml +136 -0
- data/spec/fixtures/wstrust/wstrust.13.xml +1 -0
- data/spec/fixtures/wstrust/wstrust.2005.xml +89 -0
- data/spec/spec_helper.rb +53 -0
- data/spec/support/fake_data.rb +40 -0
- data/spec/support/fake_token_endpoint.rb +108 -0
- metadata +265 -0
@@ -0,0 +1,107 @@
|
|
1
|
+
#-------------------------------------------------------------------------------
|
2
|
+
# Copyright (c) 2015 Micorosft Corporation
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
5
|
+
# of this software and associated documentation files (the "Software"), to deal
|
6
|
+
# in the Software without restriction, including without limitation the rights
|
7
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
8
|
+
# copies of the Software, and to permit persons to whom the Software is
|
9
|
+
# furnished to do so, subject to the following conditions:
|
10
|
+
#
|
11
|
+
# The above copyright notice and this permission notice shall be included in
|
12
|
+
# all copies or substantial portions of the Software.
|
13
|
+
#
|
14
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
15
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
16
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
17
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
18
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
19
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
20
|
+
# THE SOFTWARE.
|
21
|
+
#-------------------------------------------------------------------------------
|
22
|
+
|
23
|
+
require_relative '../spec_helper'
|
24
|
+
|
25
|
+
require 'uri'
|
26
|
+
|
27
|
+
describe ADAL::AuthenticationParameters do
|
28
|
+
describe '::create_from_resource_url' do
|
29
|
+
it 'should make an request to the resource server and parse the response' do
|
30
|
+
expect(Net::HTTP).to receive(:post_form).once.and_return(
|
31
|
+
'www-authenticate' => 'Bearer authorization_uri="sample.com"')
|
32
|
+
params = ADAL::AuthenticationParameters.create_from_resource_url('a.io')
|
33
|
+
expect(params.authority_uri).to eq(URI.parse('sample.com'))
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'should fail if the url is not a valid url' do
|
37
|
+
bad_url = 'this is not a valid url'
|
38
|
+
expect do
|
39
|
+
ADAL::AuthenticationParameters.create_from_resource_url(bad_url)
|
40
|
+
end.to raise_error(URI::InvalidURIError)
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'should fail if the response does not contain an authenticate header' do
|
44
|
+
allow(Net::HTTP).to receive(:post_form).and_return(
|
45
|
+
body: 'abc', 'content-type' => 'application/json')
|
46
|
+
expect do
|
47
|
+
ADAL::AuthenticationParameters.create_from_resource_url('myurl.com')
|
48
|
+
end.to raise_error(ArgumentError)
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'should fail if the resource server does not response' do
|
52
|
+
allow(Net::HTTP).to receive(:post_form).and_raise(Timeout::Error)
|
53
|
+
expect do
|
54
|
+
ADAL::AuthenticationParameters.create_from_resource_url('myurl.com')
|
55
|
+
end.to raise_error(Timeout::Error)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
describe '::create_from_authenticate_header' do
|
60
|
+
it 'should successfully parse a valid authentication header' do
|
61
|
+
params = ADAL::AuthenticationParameters.create_from_authenticate_header(
|
62
|
+
'Bearer authorization_uri="foobar,lj,;l,", resource="a( res&*^ource"')
|
63
|
+
expect(params.authority_uri).to eq(URI.parse('foobar,lj,;l,'))
|
64
|
+
expect(params.resource).to eq('a( res&*^ource')
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'should return nil if the input is nil' do
|
68
|
+
expect(
|
69
|
+
ADAL::AuthenticationParameters.create_from_authenticate_header(nil)
|
70
|
+
).to be_nil
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'should return nil if the header does not contain an authority key' do
|
74
|
+
expect(
|
75
|
+
ADAL::AuthenticationParameters.create_from_authenticate_header(
|
76
|
+
'Bearer: resource="http://graph.windows.net"')
|
77
|
+
).to be_nil
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
describe '#create_context' do
|
82
|
+
before(:each) do
|
83
|
+
auth_uri = 'https://login.windows.net/mytenant.onmicrosoft.com'
|
84
|
+
@auth_params = ADAL::AuthenticationParameters.new(auth_uri)
|
85
|
+
end
|
86
|
+
|
87
|
+
it 'should return an AuthenticationContext object' do
|
88
|
+
expect(@auth_params.create_context).to be_a(ADAL::AuthenticationContext)
|
89
|
+
end
|
90
|
+
|
91
|
+
it 'should extract the host and tenant from the authorize uri' do
|
92
|
+
# @authority does not have a getter, so we have to use Ruby voodoo.
|
93
|
+
authority = @auth_params.create_context.instance_variable_get(:@authority)
|
94
|
+
expect(authority.host).to eq('login.windows.net')
|
95
|
+
expect(authority.tenant).to eq('mytenant.onmicrosoft.com')
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
describe '#initialize' do
|
100
|
+
it 'should fail if the authorize uri is not a valid uri' do
|
101
|
+
bad_auth_uri = 'not a real parsable uri'
|
102
|
+
expect do
|
103
|
+
ADAL::AuthenticationParameters.new(bad_auth_uri)
|
104
|
+
end.to raise_error(URI::InvalidURIError)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
#-------------------------------------------------------------------------------
|
2
|
+
# Copyright (c) 2015 Micorosft Corporation
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
5
|
+
# of this software and associated documentation files (the "Software"), to deal
|
6
|
+
# in the Software without restriction, including without limitation the rights
|
7
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
8
|
+
# copies of the Software, and to permit persons to whom the Software is
|
9
|
+
# furnished to do so, subject to the following conditions:
|
10
|
+
#
|
11
|
+
# The above copyright notice and this permission notice shall be included in
|
12
|
+
# all copies or substantial portions of the Software.
|
13
|
+
#
|
14
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
15
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
16
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
17
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
18
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
19
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
20
|
+
# THE SOFTWARE.
|
21
|
+
#-------------------------------------------------------------------------------
|
22
|
+
|
23
|
+
require_relative '../spec_helper'
|
24
|
+
|
25
|
+
describe ADAL::Authority do
|
26
|
+
let(:auth_host) { 'login.windows.net' }
|
27
|
+
let(:tenant) { 'atenant.onmicrosoft.com' }
|
28
|
+
|
29
|
+
describe '#token_endpoint' do
|
30
|
+
it 'should correctly construct token endpoints' do
|
31
|
+
auth = ADAL::Authority.new(auth_host, tenant)
|
32
|
+
expect(auth.token_endpoint.to_s).to eq(
|
33
|
+
"https://#{auth_host}/#{tenant}/oauth2/token")
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
describe '#authorize_endpoint' do
|
38
|
+
context 'with additional params' do
|
39
|
+
it 'should correctly construct the authorize endpoint' do
|
40
|
+
auth = ADAL::Authority.new(auth_host, tenant)
|
41
|
+
expect(auth.authorize_endpoint(foo: :bar).to_s).to eq(
|
42
|
+
"https://#{auth_host}/#{tenant}/oauth2/authorize?foo=bar")
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
context 'with no additional params' do
|
47
|
+
it 'should correctly construct the authorize endpoint' do
|
48
|
+
auth = ADAL::Authority.new(auth_host, tenant)
|
49
|
+
expect(auth.authorize_endpoint.to_s).to eq(
|
50
|
+
"https://#{auth_host}/#{tenant}/oauth2/authorize")
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
describe '#validate' do
|
56
|
+
it 'should not do anything if validate_authority was set to false in the ' \
|
57
|
+
' constructor' do
|
58
|
+
auth = ADAL::Authority.new(auth_host, tenant)
|
59
|
+
expect(auth).to_not receive(:validated_statically?)
|
60
|
+
expect(auth).to_not receive(:validated_dynamically?)
|
61
|
+
expect(auth.validate).to be_truthy
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'should attempt static validation before dynamic validation' do
|
65
|
+
auth = ADAL::Authority.new(
|
66
|
+
auth_host, tenant, true)
|
67
|
+
expect(auth).to receive(:validated_statically?).once.and_return true
|
68
|
+
expect(auth).to_not receive(:validated_dynamically?)
|
69
|
+
expect(auth.validate).to be_truthy
|
70
|
+
end
|
71
|
+
|
72
|
+
it 'should successfully validate statically with a well known host' do
|
73
|
+
auth = ADAL::Authority.new(
|
74
|
+
auth_host, tenant, true)
|
75
|
+
expect(auth).to_not receive(:validated_dynamically?)
|
76
|
+
expect(auth.validate).to be_truthy
|
77
|
+
end
|
78
|
+
|
79
|
+
it 'should successully validate dynamically with the discovery endpoint' do
|
80
|
+
auth = ADAL::Authority.new(
|
81
|
+
'someothersite.net', tenant, true)
|
82
|
+
expect(Net::HTTP).to receive(:get).once.and_return('{"tenant_discovery_' \
|
83
|
+
'endpoint": "https://login.windows.net/atenant.onmicrosoft.com/.well-' \
|
84
|
+
'known/openid-configuration"}')
|
85
|
+
expect(auth.validate).to be_truthy
|
86
|
+
end
|
87
|
+
|
88
|
+
it 'should not make a network connection after it validates once' do
|
89
|
+
auth = ADAL::Authority.new(
|
90
|
+
'someothersite.net', tenant, true)
|
91
|
+
expect(Net::HTTP).to receive(:get).once.and_return(
|
92
|
+
'{"tenant_discovery_endpoint": "endpoint"}')
|
93
|
+
expect(auth.validate).to be_truthy
|
94
|
+
expect(auth.validate).to be_truthy
|
95
|
+
expect(auth.validate).to be_truthy
|
96
|
+
expect(auth.validate).to be_truthy
|
97
|
+
end
|
98
|
+
|
99
|
+
it 'should be false if dynamic validation does not respond' do
|
100
|
+
auth_host = 'notvalid.com'
|
101
|
+
dynamic_validation_endpoint =
|
102
|
+
'https://login.microsoftonline.com/common/discovery/instance?api-vers' \
|
103
|
+
"ion=1.0&authorization_endpoint=https://#{auth_host}/#{tenant}/oauth2" \
|
104
|
+
'/authorize'
|
105
|
+
auth = ADAL::Authority.new('notvalid.com', tenant, true)
|
106
|
+
stub_request(:get, dynamic_validation_endpoint).to_return(status: 500)
|
107
|
+
expect(auth.validate).to be_falsey
|
108
|
+
end
|
109
|
+
|
110
|
+
it 'should be false if dynamic validation response is invalid' do
|
111
|
+
auth_host = 'notvalid.com'
|
112
|
+
dynamic_validation_endpoint =
|
113
|
+
'https://login.microsoftonline.com/common/discovery/instance?api-vers' \
|
114
|
+
"ion=1.0&authorization_endpoint=https://#{auth_host}/#{tenant}/oauth2" \
|
115
|
+
'/authorize'
|
116
|
+
auth = ADAL::Authority.new('notvalid.com', tenant, true)
|
117
|
+
stub_request(:get, dynamic_validation_endpoint)
|
118
|
+
.to_return(status: 200, body: '{}')
|
119
|
+
expect(auth.validate).to be_falsey
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
@@ -0,0 +1,191 @@
|
|
1
|
+
#-------------------------------------------------------------------------------
|
2
|
+
# Copyright (c) 2015 Micorosft Corporation
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
5
|
+
# of this software and associated documentation files (the "Software"), to deal
|
6
|
+
# in the Software without restriction, including without limitation the rights
|
7
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
8
|
+
# copies of the Software, and to permit persons to whom the Software is
|
9
|
+
# furnished to do so, subject to the following conditions:
|
10
|
+
#
|
11
|
+
# The above copyright notice and this permission notice shall be included in
|
12
|
+
# all copies or substantial portions of the Software.
|
13
|
+
#
|
14
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
15
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
16
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
17
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
18
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
19
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
20
|
+
# THE SOFTWARE.
|
21
|
+
#-------------------------------------------------------------------------------
|
22
|
+
|
23
|
+
require_relative '../spec_helper'
|
24
|
+
require_relative '../support/fake_data'
|
25
|
+
|
26
|
+
include FakeData
|
27
|
+
|
28
|
+
describe ADAL::CacheDriver do
|
29
|
+
let(:authority) { ADAL::Authority.new(AUTHORITY, TENANT) }
|
30
|
+
let(:client_id) { CLIENT_ID }
|
31
|
+
let(:client) { ADAL::ClientCredential.new(client_id) }
|
32
|
+
let(:driver) { ADAL::CacheDriver.new(authority, client, token_cache) }
|
33
|
+
let(:success_response) { ADAL::SuccessResponse.new }
|
34
|
+
|
35
|
+
describe '#add' do
|
36
|
+
let(:token_cache) { ADAL::MemoryCache.new }
|
37
|
+
|
38
|
+
context 'with an empty memory cache' do
|
39
|
+
before(:each) { driver.add(success_response) }
|
40
|
+
|
41
|
+
it 'should leave the cache with exactly one entry' do
|
42
|
+
expect(token_cache.entries.size).to eq 1
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'should put the token response in the token cache' do
|
46
|
+
expect(token_cache.entries.map(&:token_response))
|
47
|
+
.to include success_response
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
context 'when the cache already contains the entry' do
|
52
|
+
before(:each) do
|
53
|
+
driver.add(success_response)
|
54
|
+
driver.add(success_response)
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'should not put a duplicate in the cache' do
|
58
|
+
expect(token_cache.entries.uniq).to match_array(token_cache.entries)
|
59
|
+
end
|
60
|
+
|
61
|
+
it 'should leave the cache with the same number of entries' do
|
62
|
+
expect(token_cache.entries.size).to eq 1
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
context 'when the cache contains non-matching entries' do
|
67
|
+
let(:idtoken1) { JWT.encode({ upn: 'user1' }, '') }
|
68
|
+
let(:idtoken2) { JWT.encode({ upn: 'user2' }, '') }
|
69
|
+
let(:token1) { ADAL::SuccessResponse.new(id_token: idtoken1) }
|
70
|
+
let(:token2) { ADAL::SuccessResponse.new(id_token: idtoken2) }
|
71
|
+
before(:each) do
|
72
|
+
driver.add(token1)
|
73
|
+
driver.add(token2)
|
74
|
+
driver.add(ADAL::SuccessResponse.new(refresh_token: REFRESH_TOKEN,
|
75
|
+
resource: RESOURCE,
|
76
|
+
id_token: idtoken1))
|
77
|
+
end
|
78
|
+
|
79
|
+
it 'should update the refresh tokens of the matching entries' do
|
80
|
+
expect(token1.refresh_token).to eq(REFRESH_TOKEN)
|
81
|
+
end
|
82
|
+
|
83
|
+
it 'should not update the refresh tokens of the non-matching entries' do
|
84
|
+
expect(token2.refresh_token).to be nil
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
describe '#find' do
|
90
|
+
let(:token_cache) { ADAL::MemoryCache.new }
|
91
|
+
let(:resource1) { 'resource1' }
|
92
|
+
let(:resource2) { 'resource2' }
|
93
|
+
let(:user1) { 'user1' }
|
94
|
+
let(:user2) { 'user2' }
|
95
|
+
let(:user3) { 'user3' }
|
96
|
+
let(:expiry) { 100 }
|
97
|
+
let(:response1) do
|
98
|
+
ADAL::SuccessResponse.new(resource: resource1,
|
99
|
+
user_info: user1,
|
100
|
+
expires_in: expiry)
|
101
|
+
end
|
102
|
+
let(:response2) do
|
103
|
+
ADAL::SuccessResponse.new(resource: resource1,
|
104
|
+
user_info: user2,
|
105
|
+
expires_in: expiry,
|
106
|
+
refresh_token: REFRESH_TOKEN)
|
107
|
+
end
|
108
|
+
before(:each) do
|
109
|
+
driver.add(response1)
|
110
|
+
driver.add(response2)
|
111
|
+
end
|
112
|
+
|
113
|
+
let(:query) { { user_info: user, resource: resource } }
|
114
|
+
subject { driver.find(query) }
|
115
|
+
|
116
|
+
context 'with a user that is not in the cache' do
|
117
|
+
let(:resource) { resource1 }
|
118
|
+
let(:user) { user3 }
|
119
|
+
it { is_expected.to be nil }
|
120
|
+
end
|
121
|
+
|
122
|
+
context 'with a user that is in the cache' do
|
123
|
+
let(:user) { user2 }
|
124
|
+
|
125
|
+
context 'with a resource that is in the cache' do
|
126
|
+
let(:resource) { resource1 }
|
127
|
+
|
128
|
+
context 'which is expired' do
|
129
|
+
let(:expiry) { -10 }
|
130
|
+
let(:updated_access_token) { 'some access token' }
|
131
|
+
let(:refresh_response) do
|
132
|
+
double(body: "{\"access_token\": \"#{updated_access_token}\", \"r" \
|
133
|
+
"esource\": \"#{resource1}\"}")
|
134
|
+
end
|
135
|
+
before(:each) do
|
136
|
+
allow_any_instance_of(Net::HTTP).to receive(:request)
|
137
|
+
.and_return(refresh_response)
|
138
|
+
end
|
139
|
+
|
140
|
+
it { is_expected.to_not be_nil }
|
141
|
+
it 'should refresh the access token' do
|
142
|
+
expect(subject.access_token).to eq(updated_access_token)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
context 'which is not expired' do
|
147
|
+
it { is_expected.to be response2 }
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
context 'with a resource that is not in the cache' do
|
152
|
+
let(:resource) { resource2 }
|
153
|
+
|
154
|
+
context 'without an mrrt' do
|
155
|
+
let(:user) { user1 }
|
156
|
+
it { is_expected.to be nil }
|
157
|
+
end
|
158
|
+
|
159
|
+
context 'with an mrrt' do
|
160
|
+
it { is_expected.to_not be nil }
|
161
|
+
it 'should fetch a new access token from OAuth' do
|
162
|
+
expect(subject.access_token).to eq(RETURNED_TOKEN)
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
context 'with no cache' do
|
169
|
+
let(:token_cache) { ADAL::NoopCache.new }
|
170
|
+
let(:user) { user1 }
|
171
|
+
let(:resource) { resource1 }
|
172
|
+
|
173
|
+
it { is_expected.to be_nil }
|
174
|
+
it { expect { subject }.to_not raise_error }
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
context 'with a nonmatching client' do
|
179
|
+
describe '#find' do
|
180
|
+
let(:query) { { resource: RESOURCE, client_id: client.client_id } }
|
181
|
+
subject { driver.find(query) }
|
182
|
+
|
183
|
+
let(:token_cache) { ADAL::MemoryCache.new }
|
184
|
+
before(:each) do
|
185
|
+
driver.add(ADAL::SuccessResponse.new(client_id: 'different client id'))
|
186
|
+
end
|
187
|
+
|
188
|
+
it { is_expected.to be_nil }
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
@@ -0,0 +1,148 @@
|
|
1
|
+
#-------------------------------------------------------------------------------
|
2
|
+
# Copyright (c) 2015 Micorosft Corporation
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
5
|
+
# of this software and associated documentation files (the "Software"), to deal
|
6
|
+
# in the Software without restriction, including without limitation the rights
|
7
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
8
|
+
# copies of the Software, and to permit persons to whom the Software is
|
9
|
+
# furnished to do so, subject to the following conditions:
|
10
|
+
#
|
11
|
+
# The above copyright notice and this permission notice shall be included in
|
12
|
+
# all copies or substantial portions of the Software.
|
13
|
+
#
|
14
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
15
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
16
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
17
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
18
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
19
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
20
|
+
# THE SOFTWARE.
|
21
|
+
#-------------------------------------------------------------------------------
|
22
|
+
|
23
|
+
require_relative '../support/fake_data'
|
24
|
+
require_relative '../spec_helper'
|
25
|
+
|
26
|
+
include FakeData
|
27
|
+
|
28
|
+
describe ADAL::CachedTokenResponse do
|
29
|
+
let(:authority) { ADAL::Authority.new(AUTHORITY, TENANT) }
|
30
|
+
let(:client) { ADAL::ClientCredential.new(CLIENT_ID) }
|
31
|
+
let(:expires_in) { 100 }
|
32
|
+
let(:resource) { RESOURCE }
|
33
|
+
let(:refresh_token) { REFRESH_TOKEN }
|
34
|
+
let(:response) do
|
35
|
+
ADAL::SuccessResponse.new(
|
36
|
+
resource: resource, refresh_token: refresh_token, expires_in: expires_in)
|
37
|
+
end
|
38
|
+
|
39
|
+
describe '#initialize' do
|
40
|
+
subject { -> { ADAL::CachedTokenResponse.new(client, authority, resp) } }
|
41
|
+
|
42
|
+
context 'with a SuccessResponse' do
|
43
|
+
let(:resp) { ADAL::SuccessResponse.new }
|
44
|
+
|
45
|
+
it { is_expected.to_not raise_error }
|
46
|
+
end
|
47
|
+
|
48
|
+
context 'with an ErrorResponse' do
|
49
|
+
let(:resp) { ADAL::ErrorResponse.new }
|
50
|
+
|
51
|
+
it { is_expected.to raise_error ArgumentError }
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
describe '#validate' do
|
56
|
+
subject do
|
57
|
+
ADAL::CachedTokenResponse.new(client, authority, response).validate
|
58
|
+
end
|
59
|
+
|
60
|
+
context 'with a non expired token' do
|
61
|
+
let(:expires_in) { 100 }
|
62
|
+
|
63
|
+
it { is_expected.to be_truthy }
|
64
|
+
end
|
65
|
+
|
66
|
+
context 'with an expired token' do
|
67
|
+
let(:expires_in) { -100 }
|
68
|
+
|
69
|
+
context 'with no refresh token' do
|
70
|
+
let(:refresh_token) { nil }
|
71
|
+
|
72
|
+
it { is_expected.to be_falsey }
|
73
|
+
end
|
74
|
+
|
75
|
+
context 'with a refresh token that fails to refresh' do
|
76
|
+
let(:refresh_token) { REFRESH_TOKEN }
|
77
|
+
before(:each) { stub_request(:post, /.*/).and_return(status: 500) }
|
78
|
+
|
79
|
+
it { is_expected.to be_falsey }
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
describe '#mrrt?' do
|
85
|
+
subject { ADAL::CachedTokenResponse.new(client, authority, response) }
|
86
|
+
|
87
|
+
context 'with a refresh_token' do
|
88
|
+
let(:refresh_token) { REFRESH_TOKEN }
|
89
|
+
|
90
|
+
context 'with a resource' do
|
91
|
+
let(:resource) { RESOURCE }
|
92
|
+
it { is_expected.to be_mrrt }
|
93
|
+
end
|
94
|
+
|
95
|
+
context 'without a resource' do
|
96
|
+
let(:resource) { nil }
|
97
|
+
it { is_expected.to_not be_mrrt }
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
context 'wihout a refresh_token' do
|
102
|
+
let(:refresh_token) { nil }
|
103
|
+
|
104
|
+
context 'with a resource' do
|
105
|
+
let(:resource) { RESOURCE }
|
106
|
+
it { is_expected.to_not be_mrrt }
|
107
|
+
end
|
108
|
+
|
109
|
+
context 'without a resource' do
|
110
|
+
let(:resource) { nil }
|
111
|
+
it { is_expected.to_not be_mrrt }
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
describe '#can_refresh?' do
|
117
|
+
let(:other) { ADAL::CachedTokenResponse.new(client, oauthority, response) }
|
118
|
+
subject do
|
119
|
+
ADAL::CachedTokenResponse.new(client, authority, response)
|
120
|
+
.can_refresh?(other)
|
121
|
+
end
|
122
|
+
|
123
|
+
context 'when not an mrrt' do
|
124
|
+
let(:oauthority) { authority }
|
125
|
+
let(:refresh_token) { nil }
|
126
|
+
|
127
|
+
it { is_expected.to be_falsey }
|
128
|
+
end
|
129
|
+
|
130
|
+
context 'when an mrrt' do
|
131
|
+
context 'when fields match' do
|
132
|
+
let(:oauthority) { authority }
|
133
|
+
it { is_expected.to be_truthy }
|
134
|
+
end
|
135
|
+
|
136
|
+
context 'when fields do not match' do
|
137
|
+
let(:oauthority) { 'some other authority' }
|
138
|
+
it { is_expected.to be_falsey }
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
it 'provides proxy access to token response fields' do
|
144
|
+
expect(
|
145
|
+
ADAL::CachedTokenResponse.new(client, AUTHORITY, response).resource
|
146
|
+
).to eq RESOURCE
|
147
|
+
end
|
148
|
+
end
|