custom-adal 1.0.1

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 +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