custom-adal 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (98) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +6 -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 +106 -0
  8. data/Rakefile +39 -0
  9. data/adal.gemspec +52 -0
  10. data/contributing.md +127 -0
  11. data/lib/adal/authentication_context.rb +202 -0
  12. data/lib/adal/authentication_parameters.rb +126 -0
  13. data/lib/adal/authority.rb +165 -0
  14. data/lib/adal/cache_driver.rb +171 -0
  15. data/lib/adal/cached_token_response.rb +190 -0
  16. data/lib/adal/client_assertion.rb +63 -0
  17. data/lib/adal/client_assertion_certificate.rb +89 -0
  18. data/lib/adal/client_credential.rb +46 -0
  19. data/lib/adal/core_ext/hash.rb +34 -0
  20. data/lib/adal/core_ext.rb +26 -0
  21. data/lib/adal/jwt_parameters.rb +39 -0
  22. data/lib/adal/logger.rb +90 -0
  23. data/lib/adal/logging.rb +98 -0
  24. data/lib/adal/memory_cache.rb +95 -0
  25. data/lib/adal/mex_request.rb +52 -0
  26. data/lib/adal/mex_response.rb +141 -0
  27. data/lib/adal/noop_cache.rb +38 -0
  28. data/lib/adal/oauth_request.rb +76 -0
  29. data/lib/adal/request_parameters.rb +48 -0
  30. data/lib/adal/self_signed_jwt_factory.rb +96 -0
  31. data/lib/adal/templates/rst.13.xml.erb +35 -0
  32. data/lib/adal/templates/rst.2005.xml.erb +32 -0
  33. data/lib/adal/token_request.rb +231 -0
  34. data/lib/adal/token_response.rb +144 -0
  35. data/lib/adal/user_assertion.rb +57 -0
  36. data/lib/adal/user_credential.rb +152 -0
  37. data/lib/adal/user_identifier.rb +83 -0
  38. data/lib/adal/user_information.rb +49 -0
  39. data/lib/adal/util.rb +49 -0
  40. data/lib/adal/version.rb +36 -0
  41. data/lib/adal/wstrust_request.rb +100 -0
  42. data/lib/adal/wstrust_response.rb +168 -0
  43. data/lib/adal/xml_namespaces.rb +64 -0
  44. data/lib/adal.rb +24 -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 +264 -0
@@ -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
@@ -0,0 +1,113 @@
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 'jwt'
26
+
27
+ include FakeData
28
+
29
+ describe ADAL::ClientAssertionCertificate do
30
+ describe '#initialize' do
31
+ let(:auth) { ADAL::Authority.new(AUTHORITY, TENANT) }
32
+ let(:cert) { OpenSSL::X509::Certificate.new }
33
+
34
+ it "should fail if the public key isn't large enough" do
35
+ # The key is an integer number of bytes so we have to subtract at least 8.
36
+ too_few_bits = ADAL::ClientAssertionCertificate::MIN_KEY_SIZE_BITS - 8
37
+ key = OpenSSL::PKey::RSA.new(too_few_bits)
38
+ cert.public_key = key.public_key
39
+ pfx = OpenSSL::PKCS12.create('', '', key, cert)
40
+ expect do
41
+ ADAL::ClientAssertionCertificate.new(auth, CLIENT_ID, pfx)
42
+ end.to raise_error(ArgumentError)
43
+ end
44
+
45
+ it 'should succeed if the public key is the minimum size' do
46
+ just_enough_bits = ADAL::ClientAssertionCertificate::MIN_KEY_SIZE_BITS
47
+ key = OpenSSL::PKey::RSA.new(just_enough_bits)
48
+ cert.public_key = key.public_key
49
+ pfx = OpenSSL::PKCS12.create('', '', key, cert)
50
+ expect do
51
+ ADAL::ClientAssertionCertificate.new(auth, CLIENT_ID, pfx)
52
+ end.to_not raise_error
53
+ end
54
+
55
+ it 'should fail if the certificate is not PKCS12' do
56
+ pfx = 'Not an OpenSSL::PKCS12'
57
+ expect { ADAL::ClientAssertionCertificate.new(auth, CLIENT_ID, pfx) }
58
+ .to raise_error ArgumentError
59
+ end
60
+
61
+ it 'should fail if the pkcs12 does not use valid rsa' do
62
+ key = OpenSSL::PKey::DSA.new 2048
63
+ cert.public_key = key.public_key
64
+ pfx = OpenSSL::PKCS12.create('', '', key, cert)
65
+ expect { ADAL::ClientAssertionCertificate.new(auth, CLIENT_ID, pfx) }
66
+ .to raise_error ArgumentError
67
+ end
68
+
69
+ it 'should fail if the pkcs12 does not use valid x509' do
70
+ key = OpenSSL::PKey::RSA.new 2048
71
+ cert.public_key = key.public_key
72
+ pfx = OpenSSL::PKCS12.create('', '', key, cert)
73
+
74
+ # In practice, no one would ever do this. But we do check for it just in
75
+ # case.
76
+ pfx.instance_variable_set(:@certificate, 'Not an x509 certificate')
77
+ expect { ADAL::ClientAssertionCertificate.new(auth, CLIENT_ID, pfx) }
78
+ .to raise_error ArgumentError
79
+ end
80
+ end
81
+
82
+ describe '#request_params' do
83
+ ONE_YEAR_IN_SECONDS = 60 * 60 * 24 * 365
84
+ let(:cert) { OpenSSL::X509::Certificate.new }
85
+ before(:each) do
86
+ key = OpenSSL::PKey::RSA.new 2048
87
+ cert.public_key = key.public_key
88
+ @pfx = OpenSSL::PKCS12.create('', '', key, cert)
89
+ @assertion_cert = ADAL::ClientAssertionCertificate.new(
90
+ ADAL::Authority.new(AUTHORITY, TENANT), CLIENT_ID, @pfx)
91
+ end
92
+
93
+ it 'should contain client id, client assertion and client assertion type' do
94
+ params = @assertion_cert.request_params
95
+ expect(params.keys).to contain_exactly(
96
+ :client_id, :client_assertion, :client_assertion_type)
97
+ end
98
+
99
+ it 'should have client assertion type be JWT_BEARER' do
100
+ expect(
101
+ @assertion_cert.request_params[:client_assertion_type]
102
+ ).to eq('urn:ietf:params:oauth:grant-type:jwt-bearer')
103
+ end
104
+
105
+ it 'should have an assertion that is a decodable JWT' do
106
+ expect do
107
+ JWT.decode(@assertion_cert.request_params[:client_assertion],
108
+ cert.public_key,
109
+ options: { verify_not_before: false })
110
+ end.to_not raise_error
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,38 @@
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
+ include FakeData
26
+
27
+ describe ADAL::ClientAssertion do
28
+ describe '#initialize' do
29
+ it 'should fail if any parameters are nil' do
30
+ expect do
31
+ ADAL::ClientAssertion.new(nil, ASSERTION)
32
+ end.to raise_error(ArgumentError)
33
+ expect do
34
+ ADAL::ClientAssertion.new(CLIENT_ID, nil)
35
+ end.to raise_error(ArgumentError)
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,47 @@
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 'spec_helper'
24
+
25
+ using ADAL::CoreExt
26
+
27
+ describe Hash do
28
+ describe '#reverse_merge' do
29
+ it 'should work on empty hashes' do
30
+ expect({}.reverse_merge({})).to eq({})
31
+ end
32
+
33
+ it 'should work just like merge if the keys do not conflict' do
34
+ hash1 = { a: 5, b: 10 }
35
+ hash2 = { c: 8, d: 12 }
36
+ expect(hash1.reverse_merge(hash2)).to eq(hash1.merge(hash2))
37
+ expect(hash2.reverse_merge(hash1)).to eq(hash2.merge(hash1))
38
+ end
39
+
40
+ it "should prefer self's values to other_hash's values" do
41
+ hash1 = { a: 5, c: 10 }
42
+ hash2 = { a: 6, b: 15 }
43
+ expect(hash1.reverse_merge(hash2)).to eq(a: 5, b: 15, c: 10)
44
+ expect(hash2.reverse_merge(hash1)).to eq(a: 6, b: 15, c: 10)
45
+ end
46
+ end
47
+ end