adal 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +5 -0
  3. data/.rubocop.yml +7 -0
  4. data/.travis.yml +7 -0
  5. data/Gemfile +25 -0
  6. data/LICENSE.txt +21 -0
  7. data/README.md +97 -0
  8. data/Rakefile +39 -0
  9. data/adal.gemspec +52 -0
  10. data/contributing.md +127 -0
  11. data/lib/adal.rb +24 -0
  12. data/lib/adal/authentication_context.rb +202 -0
  13. data/lib/adal/authentication_parameters.rb +126 -0
  14. data/lib/adal/authority.rb +165 -0
  15. data/lib/adal/cache_driver.rb +171 -0
  16. data/lib/adal/cached_token_response.rb +190 -0
  17. data/lib/adal/client_assertion.rb +63 -0
  18. data/lib/adal/client_assertion_certificate.rb +89 -0
  19. data/lib/adal/client_credential.rb +46 -0
  20. data/lib/adal/core_ext.rb +26 -0
  21. data/lib/adal/core_ext/hash.rb +34 -0
  22. data/lib/adal/jwt_parameters.rb +39 -0
  23. data/lib/adal/logger.rb +90 -0
  24. data/lib/adal/logging.rb +98 -0
  25. data/lib/adal/memory_cache.rb +95 -0
  26. data/lib/adal/mex_request.rb +52 -0
  27. data/lib/adal/mex_response.rb +141 -0
  28. data/lib/adal/noop_cache.rb +38 -0
  29. data/lib/adal/oauth_request.rb +76 -0
  30. data/lib/adal/request_parameters.rb +48 -0
  31. data/lib/adal/self_signed_jwt_factory.rb +96 -0
  32. data/lib/adal/templates/rst.13.xml.erb +35 -0
  33. data/lib/adal/templates/rst.2005.xml.erb +32 -0
  34. data/lib/adal/token_request.rb +231 -0
  35. data/lib/adal/token_response.rb +144 -0
  36. data/lib/adal/user_assertion.rb +57 -0
  37. data/lib/adal/user_credential.rb +152 -0
  38. data/lib/adal/user_identifier.rb +83 -0
  39. data/lib/adal/user_information.rb +49 -0
  40. data/lib/adal/util.rb +49 -0
  41. data/lib/adal/version.rb +36 -0
  42. data/lib/adal/wstrust_request.rb +100 -0
  43. data/lib/adal/wstrust_response.rb +168 -0
  44. data/lib/adal/xml_namespaces.rb +64 -0
  45. data/samples/authorization_code_example/README.md +10 -0
  46. data/samples/authorization_code_example/web_app.rb +139 -0
  47. data/samples/client_assertion_certificate_example/README.md +42 -0
  48. data/samples/client_assertion_certificate_example/app.rb +55 -0
  49. data/samples/on_behalf_of_example/README.md +35 -0
  50. data/samples/on_behalf_of_example/native_app.rb +52 -0
  51. data/samples/on_behalf_of_example/web_api.rb +71 -0
  52. data/samples/user_credentials_example/README.md +7 -0
  53. data/samples/user_credentials_example/app.rb +52 -0
  54. data/spec/adal/authentication_context_spec.rb +186 -0
  55. data/spec/adal/authentication_parameters_spec.rb +107 -0
  56. data/spec/adal/authority_spec.rb +122 -0
  57. data/spec/adal/cache_driver_spec.rb +191 -0
  58. data/spec/adal/cached_token_response_spec.rb +148 -0
  59. data/spec/adal/client_assertion_certificate_spec.rb +113 -0
  60. data/spec/adal/client_assertion_spec.rb +38 -0
  61. data/spec/adal/core_ext/hash_spec.rb +47 -0
  62. data/spec/adal/logging_spec.rb +48 -0
  63. data/spec/adal/memory_cache_spec.rb +107 -0
  64. data/spec/adal/mex_request_spec.rb +57 -0
  65. data/spec/adal/mex_response_spec.rb +143 -0
  66. data/spec/adal/self_signed_jwt_factory_spec.rb +63 -0
  67. data/spec/adal/token_request_spec.rb +150 -0
  68. data/spec/adal/token_response_spec.rb +102 -0
  69. data/spec/adal/user_credential_spec.rb +125 -0
  70. data/spec/adal/user_identifier_spec.rb +115 -0
  71. data/spec/adal/wstrust_request_spec.rb +51 -0
  72. data/spec/adal/wstrust_response_spec.rb +152 -0
  73. data/spec/fixtures/mex/insecureaddress.xml +924 -0
  74. data/spec/fixtures/mex/invalid_namespaces.xml +916 -0
  75. data/spec/fixtures/mex/malformed.xml +914 -0
  76. data/spec/fixtures/mex/microsoft.xml +916 -0
  77. data/spec/fixtures/mex/multiple_endpoints.xml +922 -0
  78. data/spec/fixtures/mex/no_matching_bindings.xml +916 -0
  79. data/spec/fixtures/mex/no_username_token_policies.xml +914 -0
  80. data/spec/fixtures/mex/no_wstrust_endpoints.xml +838 -0
  81. data/spec/fixtures/mex/only_13.xml +842 -0
  82. data/spec/fixtures/mex/only_2005.xml +842 -0
  83. data/spec/fixtures/oauth/error.json +1 -0
  84. data/spec/fixtures/oauth/success.json +1 -0
  85. data/spec/fixtures/oauth/success_with_id_token.json +1 -0
  86. data/spec/fixtures/wstrust/error.xml +24 -0
  87. data/spec/fixtures/wstrust/invalid_namespaces.xml +136 -0
  88. data/spec/fixtures/wstrust/missing_security_tokens.xml +90 -0
  89. data/spec/fixtures/wstrust/success.xml +136 -0
  90. data/spec/fixtures/wstrust/token.xml +1 -0
  91. data/spec/fixtures/wstrust/too_many_security_tokens.xml +219 -0
  92. data/spec/fixtures/wstrust/unrecognized_token_type.xml +136 -0
  93. data/spec/fixtures/wstrust/wstrust.13.xml +1 -0
  94. data/spec/fixtures/wstrust/wstrust.2005.xml +89 -0
  95. data/spec/spec_helper.rb +53 -0
  96. data/spec/support/fake_data.rb +40 -0
  97. data/spec/support/fake_token_endpoint.rb +108 -0
  98. 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