@webex/webex-core 2.59.2 → 2.59.3-next.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 (189) hide show
  1. package/.eslintrc.js +6 -6
  2. package/README.md +79 -79
  3. package/babel.config.js +3 -3
  4. package/dist/config.js +24 -24
  5. package/dist/config.js.map +1 -1
  6. package/dist/credentials-config.js +56 -56
  7. package/dist/credentials-config.js.map +1 -1
  8. package/dist/index.js.map +1 -1
  9. package/dist/interceptors/auth.js +28 -28
  10. package/dist/interceptors/auth.js.map +1 -1
  11. package/dist/interceptors/default-options.js +24 -24
  12. package/dist/interceptors/default-options.js.map +1 -1
  13. package/dist/interceptors/embargo.js +9 -9
  14. package/dist/interceptors/embargo.js.map +1 -1
  15. package/dist/interceptors/network-timing.js +19 -19
  16. package/dist/interceptors/network-timing.js.map +1 -1
  17. package/dist/interceptors/payload-transformer.js +19 -19
  18. package/dist/interceptors/payload-transformer.js.map +1 -1
  19. package/dist/interceptors/rate-limit.js +40 -40
  20. package/dist/interceptors/rate-limit.js.map +1 -1
  21. package/dist/interceptors/redirect.js +13 -13
  22. package/dist/interceptors/redirect.js.map +1 -1
  23. package/dist/interceptors/request-event.js +23 -23
  24. package/dist/interceptors/request-event.js.map +1 -1
  25. package/dist/interceptors/request-logger.js +13 -13
  26. package/dist/interceptors/request-logger.js.map +1 -1
  27. package/dist/interceptors/request-timing.js +23 -23
  28. package/dist/interceptors/request-timing.js.map +1 -1
  29. package/dist/interceptors/response-logger.js +19 -19
  30. package/dist/interceptors/response-logger.js.map +1 -1
  31. package/dist/interceptors/user-agent.js +29 -29
  32. package/dist/interceptors/user-agent.js.map +1 -1
  33. package/dist/interceptors/webex-tracking-id.js +15 -15
  34. package/dist/interceptors/webex-tracking-id.js.map +1 -1
  35. package/dist/interceptors/webex-user-agent.js +13 -13
  36. package/dist/interceptors/webex-user-agent.js.map +1 -1
  37. package/dist/lib/batcher.js +83 -83
  38. package/dist/lib/batcher.js.map +1 -1
  39. package/dist/lib/credentials/credentials.js +103 -103
  40. package/dist/lib/credentials/credentials.js.map +1 -1
  41. package/dist/lib/credentials/grant-errors.js +17 -17
  42. package/dist/lib/credentials/grant-errors.js.map +1 -1
  43. package/dist/lib/credentials/index.js +2 -2
  44. package/dist/lib/credentials/index.js.map +1 -1
  45. package/dist/lib/credentials/scope.js +11 -11
  46. package/dist/lib/credentials/scope.js.map +1 -1
  47. package/dist/lib/credentials/token-collection.js +2 -2
  48. package/dist/lib/credentials/token-collection.js.map +1 -1
  49. package/dist/lib/credentials/token.js +145 -145
  50. package/dist/lib/credentials/token.js.map +1 -1
  51. package/dist/lib/page.js +49 -49
  52. package/dist/lib/page.js.map +1 -1
  53. package/dist/lib/services/constants.js.map +1 -1
  54. package/dist/lib/services/index.js +2 -2
  55. package/dist/lib/services/index.js.map +1 -1
  56. package/dist/lib/services/interceptors/server-error.js +9 -9
  57. package/dist/lib/services/interceptors/server-error.js.map +1 -1
  58. package/dist/lib/services/interceptors/service.js +24 -24
  59. package/dist/lib/services/interceptors/service.js.map +1 -1
  60. package/dist/lib/services/metrics.js.map +1 -1
  61. package/dist/lib/services/service-catalog.js +104 -104
  62. package/dist/lib/services/service-catalog.js.map +1 -1
  63. package/dist/lib/services/service-fed-ramp.js.map +1 -1
  64. package/dist/lib/services/service-host.js +134 -134
  65. package/dist/lib/services/service-host.js.map +1 -1
  66. package/dist/lib/services/service-registry.js +175 -175
  67. package/dist/lib/services/service-registry.js.map +1 -1
  68. package/dist/lib/services/service-state.js +38 -38
  69. package/dist/lib/services/service-state.js.map +1 -1
  70. package/dist/lib/services/service-url.js +31 -31
  71. package/dist/lib/services/service-url.js.map +1 -1
  72. package/dist/lib/services/services.js +245 -245
  73. package/dist/lib/services/services.js.map +1 -1
  74. package/dist/lib/stateless-webex-plugin.js +28 -28
  75. package/dist/lib/stateless-webex-plugin.js.map +1 -1
  76. package/dist/lib/storage/decorators.js +27 -27
  77. package/dist/lib/storage/decorators.js.map +1 -1
  78. package/dist/lib/storage/errors.js +4 -4
  79. package/dist/lib/storage/errors.js.map +1 -1
  80. package/dist/lib/storage/index.js.map +1 -1
  81. package/dist/lib/storage/make-webex-plugin-store.js +44 -44
  82. package/dist/lib/storage/make-webex-plugin-store.js.map +1 -1
  83. package/dist/lib/storage/make-webex-store.js +40 -40
  84. package/dist/lib/storage/make-webex-store.js.map +1 -1
  85. package/dist/lib/storage/memory-store-adapter.js +9 -9
  86. package/dist/lib/storage/memory-store-adapter.js.map +1 -1
  87. package/dist/lib/webex-core-plugin-mixin.js +13 -13
  88. package/dist/lib/webex-core-plugin-mixin.js.map +1 -1
  89. package/dist/lib/webex-http-error.js +9 -9
  90. package/dist/lib/webex-http-error.js.map +1 -1
  91. package/dist/lib/webex-internal-core-plugin-mixin.js +13 -13
  92. package/dist/lib/webex-internal-core-plugin-mixin.js.map +1 -1
  93. package/dist/lib/webex-plugin.js +36 -36
  94. package/dist/lib/webex-plugin.js.map +1 -1
  95. package/dist/plugins/logger.js +9 -9
  96. package/dist/plugins/logger.js.map +1 -1
  97. package/dist/webex-core.js +104 -104
  98. package/dist/webex-core.js.map +1 -1
  99. package/dist/webex-internal-core.js +12 -12
  100. package/dist/webex-internal-core.js.map +1 -1
  101. package/jest.config.js +3 -3
  102. package/package.json +20 -19
  103. package/process +1 -1
  104. package/src/config.js +90 -90
  105. package/src/credentials-config.js +212 -212
  106. package/src/index.js +62 -62
  107. package/src/interceptors/auth.js +186 -186
  108. package/src/interceptors/default-options.js +55 -55
  109. package/src/interceptors/embargo.js +43 -43
  110. package/src/interceptors/network-timing.js +54 -54
  111. package/src/interceptors/payload-transformer.js +55 -55
  112. package/src/interceptors/rate-limit.js +169 -169
  113. package/src/interceptors/redirect.js +106 -106
  114. package/src/interceptors/request-event.js +93 -93
  115. package/src/interceptors/request-logger.js +78 -78
  116. package/src/interceptors/request-timing.js +65 -65
  117. package/src/interceptors/response-logger.js +98 -98
  118. package/src/interceptors/user-agent.js +77 -77
  119. package/src/interceptors/webex-tracking-id.js +73 -73
  120. package/src/interceptors/webex-user-agent.js +79 -79
  121. package/src/lib/batcher.js +307 -307
  122. package/src/lib/credentials/credentials.js +552 -552
  123. package/src/lib/credentials/grant-errors.js +92 -92
  124. package/src/lib/credentials/index.js +16 -16
  125. package/src/lib/credentials/scope.js +34 -34
  126. package/src/lib/credentials/token-collection.js +17 -17
  127. package/src/lib/credentials/token.js +559 -559
  128. package/src/lib/page.js +159 -159
  129. package/src/lib/services/constants.js +9 -9
  130. package/src/lib/services/index.js +26 -26
  131. package/src/lib/services/interceptors/server-error.js +48 -48
  132. package/src/lib/services/interceptors/service.js +101 -101
  133. package/src/lib/services/metrics.js +4 -4
  134. package/src/lib/services/service-catalog.js +435 -435
  135. package/src/lib/services/service-fed-ramp.js +4 -4
  136. package/src/lib/services/service-host.js +267 -267
  137. package/src/lib/services/service-registry.js +465 -465
  138. package/src/lib/services/service-state.js +78 -78
  139. package/src/lib/services/service-url.js +124 -124
  140. package/src/lib/services/services.js +1018 -1018
  141. package/src/lib/stateless-webex-plugin.js +98 -98
  142. package/src/lib/storage/decorators.js +220 -220
  143. package/src/lib/storage/errors.js +15 -15
  144. package/src/lib/storage/index.js +10 -10
  145. package/src/lib/storage/make-webex-plugin-store.js +211 -211
  146. package/src/lib/storage/make-webex-store.js +140 -140
  147. package/src/lib/storage/memory-store-adapter.js +79 -79
  148. package/src/lib/webex-core-plugin-mixin.js +114 -114
  149. package/src/lib/webex-http-error.js +61 -61
  150. package/src/lib/webex-internal-core-plugin-mixin.js +107 -107
  151. package/src/lib/webex-plugin.js +222 -222
  152. package/src/plugins/logger.js +60 -60
  153. package/src/webex-core.js +745 -745
  154. package/src/webex-internal-core.js +46 -46
  155. package/test/integration/spec/credentials/credentials.js +139 -139
  156. package/test/integration/spec/credentials/token.js +102 -102
  157. package/test/integration/spec/services/service-catalog.js +838 -838
  158. package/test/integration/spec/services/services.js +1221 -1221
  159. package/test/integration/spec/webex-core.js +178 -178
  160. package/test/unit/spec/_setup.js +44 -44
  161. package/test/unit/spec/credentials/credentials.js +1017 -1017
  162. package/test/unit/spec/credentials/token.js +441 -441
  163. package/test/unit/spec/interceptors/auth.js +521 -521
  164. package/test/unit/spec/interceptors/default-options.js +84 -84
  165. package/test/unit/spec/interceptors/embargo.js +144 -144
  166. package/test/unit/spec/interceptors/network-timing.js +49 -49
  167. package/test/unit/spec/interceptors/payload-transformer.js +155 -155
  168. package/test/unit/spec/interceptors/rate-limit.js +302 -302
  169. package/test/unit/spec/interceptors/redirect.js +102 -102
  170. package/test/unit/spec/interceptors/request-timing.js +92 -92
  171. package/test/unit/spec/interceptors/user-agent.js +76 -76
  172. package/test/unit/spec/interceptors/webex-tracking-id.js +76 -76
  173. package/test/unit/spec/interceptors/webex-user-agent.js +159 -159
  174. package/test/unit/spec/lib/batcher.js +330 -330
  175. package/test/unit/spec/lib/page.js +148 -148
  176. package/test/unit/spec/lib/webex-plugin.js +48 -48
  177. package/test/unit/spec/services/interceptors/server-error.js +204 -204
  178. package/test/unit/spec/services/interceptors/service.js +188 -188
  179. package/test/unit/spec/services/service-catalog.js +194 -194
  180. package/test/unit/spec/services/service-host.js +260 -260
  181. package/test/unit/spec/services/service-registry.js +747 -747
  182. package/test/unit/spec/services/service-state.js +60 -60
  183. package/test/unit/spec/services/service-url.js +258 -258
  184. package/test/unit/spec/services/services.js +348 -348
  185. package/test/unit/spec/storage/persist.js +50 -50
  186. package/test/unit/spec/storage/storage-adapter.js +12 -12
  187. package/test/unit/spec/storage/wait-for-value.js +81 -81
  188. package/test/unit/spec/webex-core.js +253 -253
  189. package/test/unit/spec/webex-internal-core.js +91 -91
@@ -1,521 +1,521 @@
1
- /*!
2
- * Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.
3
- */
4
-
5
- /* eslint-disable camelcase */
6
-
7
- import chai from 'chai';
8
- import chaiAsPromised from 'chai-as-promised';
9
- import sinon from 'sinon';
10
- import {browserOnly, nodeOnly} from '@webex/test-helper-mocha';
11
- import Logger from '@webex/plugin-logger';
12
- import MockWebex from '@webex/test-helper-mock-webex';
13
- import {AuthInterceptor, config, Credentials, WebexHttpError, Token} from '@webex/webex-core';
14
- import {cloneDeep, merge} from 'lodash';
15
-
16
- const {assert} = chai;
17
-
18
- chai.use(chaiAsPromised);
19
- sinon.assert.expose(chai.assert, {prefix: ''});
20
-
21
- describe('webex-core', () => {
22
- describe('Interceptors', () => {
23
- describe('AuthInterceptor', () => {
24
- let interceptor, webex;
25
-
26
- beforeEach(() => {
27
- webex = new MockWebex({
28
- children: {
29
- credentials: Credentials,
30
- logger: Logger,
31
- },
32
- config: merge(cloneDeep(config), {credentials: {client_secret: 'fake'}}),
33
- });
34
-
35
- webex.credentials.supertoken = new Token(
36
- {
37
- access_token: 'ST1',
38
- token_type: 'Bearer',
39
- },
40
- {parent: webex}
41
- );
42
-
43
- interceptor = Reflect.apply(AuthInterceptor.create, webex, []);
44
- });
45
-
46
- describe('#onRequest()', () => {
47
- it('does not replace the auth header if one has been provided', () =>
48
- interceptor
49
- .onRequest({
50
- uri: `${config.services.discovery.hydra}/ping`,
51
- headers: {
52
- authorization: 'Bearer Alternate',
53
- },
54
- })
55
- .then((result) =>
56
- assert.deepEqual(result, {
57
- uri: `${config.services.discovery.hydra}/ping`,
58
- headers: {
59
- authorization: 'Bearer Alternate',
60
- },
61
- })
62
- ));
63
-
64
- [undefined, null, false].forEach((falsey) => {
65
- it(`does not add an auth header if ${falsey} has been provided`, () =>
66
- interceptor
67
- .onRequest({
68
- uri: `${config.services.discovery.hydra}/ping`,
69
- headers: {
70
- authorization: falsey,
71
- },
72
- })
73
- .then((result) =>
74
- assert.deepEqual(result, {
75
- uri: `${config.services.discovery.hydra}/ping`,
76
- headers: {},
77
- })
78
- ));
79
- });
80
-
81
- // There should never be a case in which the services plugin is not
82
- // loaded. But testing for legacy support.
83
- describe('when the services plugin has not been loaded', () => {
84
- it('does not add the auth header to hydra requests', () =>
85
- interceptor
86
- .onRequest({
87
- uri: `${config.services.discovery.hydra}/ping`,
88
- })
89
- .then((result) =>
90
- assert.deepEqual(result, {
91
- uri: `${config.services.discovery.hydra}/ping`,
92
- headers: {},
93
- })
94
- ));
95
-
96
- it('does not add the auth header to u2c requests', () =>
97
- interceptor
98
- .onRequest({
99
- uri: `${config.services.discovery.u2c}/ping`,
100
- })
101
- .then((result) =>
102
- assert.deepEqual(result, {
103
- uri: `${config.services.discovery.u2c}/ping`,
104
- headers: {},
105
- })
106
- ));
107
- });
108
-
109
- describe('when the services plugin has been loaded', () => {
110
- let services;
111
-
112
- beforeEach(() => {
113
- services = {
114
- hydra: 'https://hydra-a.wbx.com',
115
- example: 'https://service.example.com',
116
- };
117
-
118
- webex.internal.services = {
119
- hasService: (service) => Object.keys(services).includes(service),
120
- hasAllowedDomains: () => true,
121
- isAllowedDomainUrl: (uri) =>
122
- !!config.services.allowedDomains.find((host) => uri.includes(host)),
123
- getServiceFromUrl: (uri) => {
124
- let targetKey;
125
-
126
- Object.keys(services).forEach((key) => {
127
- if (uri.includes(services[key])) {
128
- targetKey = key;
129
- }
130
- });
131
-
132
- return targetKey ? {name: targetKey} : undefined;
133
- },
134
- };
135
-
136
- webex.internal.services.waitForService = (pto) =>
137
- Promise.resolve(services[pto.name] || pto.url);
138
- });
139
-
140
- it('adds the header to hydra requests', () =>
141
- Promise.all([
142
- interceptor.onRequest({uri: `${services.hydra}/ping`}).then((result) =>
143
- assert.deepEqual(result, {
144
- uri: `${services.hydra}/ping`,
145
- headers: {
146
- authorization: 'Bearer ST1',
147
- },
148
- })
149
- ),
150
- interceptor
151
- .onRequest({
152
- service: 'hydra',
153
- resource: 'ping',
154
- })
155
- .then((result) =>
156
- assert.deepEqual(result, {
157
- service: 'hydra',
158
- resource: 'ping',
159
- headers: {
160
- authorization: 'Bearer ST1',
161
- },
162
- })
163
- ),
164
- ]));
165
-
166
- it('adds an auth header to uris that are in the service catalog', () =>
167
- interceptor
168
- .onRequest({
169
- uri: `${services.example}/ping`,
170
- })
171
- .then((result) =>
172
- assert.deepEqual(result, {
173
- uri: `${services.example}/ping`,
174
- headers: {
175
- authorization: 'Bearer ST1',
176
- },
177
- })
178
- ));
179
-
180
- it('adds an auth header to services that are in the service catalog', () =>
181
- interceptor
182
- .onRequest({
183
- service: 'example',
184
- resource: 'some-resource',
185
- })
186
- .then((result) =>
187
- assert.deepEqual(result, {
188
- service: 'example',
189
- resource: 'some-resource',
190
- headers: {
191
- authorization: 'Bearer ST1',
192
- },
193
- })
194
- ));
195
-
196
- it('does not add an auth header to uris not in the service catalog', () =>
197
- interceptor
198
- .onRequest({
199
- uri: 'https://not-a-service.com/ping',
200
- })
201
- .then((result) =>
202
- assert.deepEqual(result, {
203
- headers: {},
204
- uri: 'https://not-a-service.com/ping',
205
- })
206
- ));
207
-
208
- it('does not add an auth header to non-existant services', () =>
209
- interceptor
210
- .onRequest({
211
- service: 'non-existant',
212
- resource: 'no-resource',
213
- })
214
- .then((result) =>
215
- assert.deepEqual(result, {
216
- headers: {},
217
- service: 'non-existant',
218
- resource: 'no-resource',
219
- })
220
- ));
221
- });
222
- });
223
-
224
- describe('#requiresCredentials()', () => {
225
- let services;
226
-
227
- beforeEach(() => {
228
- services = {
229
- hydra: 'https://hydra-a.wbx.com',
230
- u2c: 'https://u2c.wbx2.com/u2c/api/v1',
231
- example: 'https://service.example.com',
232
- };
233
-
234
- webex.internal.services = {
235
- getServiceFromUrl: (uri) => {
236
- let targetKey;
237
-
238
- Object.keys(services).forEach((key) => {
239
- if (uri.includes(services[key])) {
240
- targetKey = key;
241
- }
242
- });
243
-
244
- return targetKey ? {name: targetKey} : undefined;
245
- },
246
- hasService: (service) => Object.keys(services).includes(service),
247
- hasAllowedDomains: () => true,
248
- isAllowedDomainUrl: (uri) =>
249
- !!config.services.allowedDomains.find((host) => uri.includes(host)),
250
- validateDomains: true,
251
- };
252
-
253
- webex.internal.services.waitForService = (pto) =>
254
- Promise.resolve(services[pto.name] || pto.url);
255
- });
256
-
257
- afterEach(() => {
258
- if (webex.internal.services) {
259
- delete webex.internal.services;
260
- }
261
- });
262
-
263
- it('resolves to false when services plugin does not exist', () => {
264
- delete webex.internal.services;
265
-
266
- return interceptor
267
- .requiresCredentials({
268
- uri: `${services.hydra}/ping`,
269
- })
270
- .then((response) => assert.isFalse(response));
271
- });
272
-
273
- it('resolves to true when the u2c service is specified via service', () => {
274
- services = {};
275
-
276
- return interceptor
277
- .requiresCredentials({
278
- service: 'u2c',
279
- resource: 'something',
280
- })
281
- .then((response) => assert.isTrue(response));
282
- });
283
-
284
- it('resolves to false when the u2c limited service is used via uri', () =>
285
- interceptor
286
- .requiresCredentials({
287
- uri: `${services.u2c}/limited`,
288
- })
289
- .then((response) => assert.isFalse(response)));
290
-
291
- it('resolves to true if the service exists in catalog via service', () =>
292
- interceptor
293
- .requiresCredentials({service: 'hydra'})
294
- .then((response) => assert.isTrue(response)));
295
-
296
- it('resolves to true if the service exists in catalog via uri', () =>
297
- interceptor
298
- .requiresCredentials({uri: services.hydra})
299
- .then((response) => assert.isTrue(response)));
300
-
301
- it('resolves to false if that `addAuthHeader` is set to false', () =>
302
- interceptor
303
- .requiresCredentials({
304
- addAuthHeader: false,
305
- service: 'unknown',
306
- resource: 'ping',
307
- })
308
- .then((response) => assert.isFalse(response)));
309
-
310
- it('resolves to false if `validateDomains` is set to false', () => {
311
- webex.internal.services.validateDomains = false;
312
-
313
- return interceptor
314
- .requiresCredentials({
315
- uri: 'https://allowed-uri.com/resource',
316
- })
317
- .then((response) => assert.isFalse(response));
318
- });
319
-
320
- it('resolves to true with an allowed domain uri', () =>
321
- interceptor
322
- .requiresCredentials({
323
- uri: `https://${config.services.allowedDomains[0]}/resource`,
324
- })
325
- .then((response) => assert.isTrue(response)));
326
-
327
- it('resolves to false with a non-allowed uri', () =>
328
- interceptor
329
- .requiresCredentials({
330
- uri: 'https://not-allowed/resource',
331
- })
332
- .then((response) => assert.isFalse(response)));
333
-
334
- it('should return true if domain exists using isAllowedDomainUrl()', () => {
335
- webex.internal.services.waitForService = sinon.stub();
336
- const {isAllowedDomainUrl} = webex.internal.services;
337
-
338
- const result = isAllowedDomainUrl(
339
- `https://${config.services.allowedDomains[0]}/resource`
340
- );
341
-
342
- assert.equal(result, true);
343
- });
344
-
345
- it('should return true when called `requiresCredentials` with valid url', () => {
346
- webex.internal.services.waitForService = sinon.stub();
347
-
348
- return interceptor
349
- .requiresCredentials({
350
- uri: `https://${config.services.allowedDomains[0]}/resource`,
351
- })
352
- .then((res) => {
353
- assert.equal(res, true);
354
- });
355
- });
356
-
357
- it('should call waitForService()', () => {
358
- webex.internal.services.waitForService = sinon.stub();
359
- const {waitForService} = webex.internal.services;
360
-
361
- waitForService.resolves(`https://${config.services.allowedDomains[0]}/resource`);
362
-
363
- return interceptor
364
- .requiresCredentials({
365
- service: 'locus',
366
- })
367
- .then(() => assert.calledOnce(waitForService));
368
- });
369
- });
370
-
371
- describe('#onResponseError()', () => {
372
- describe('when the server responds with 401', () => {
373
- nodeOnly(it)('refreshes the access token and replays the request', () => {
374
- webex.request.onCall(0).returns(
375
- Promise.resolve({
376
- body: {
377
- access_token: 'ST2',
378
- },
379
- })
380
- );
381
- webex.credentials.supertoken = new Token(
382
- {
383
- access_token: 'ST1',
384
- refresh_token: 'RT1',
385
- },
386
- {parent: webex}
387
- );
388
-
389
- const err = new WebexHttpError.Unauthorized({
390
- statusCode: 401,
391
- options: {
392
- headers: {
393
- trackingid: 'blarg',
394
- },
395
- uri: `${config.services.discovery.hydra}/ping`,
396
- },
397
- body: {
398
- error: 'fake error',
399
- },
400
- });
401
-
402
- assert.notCalled(webex.request);
403
-
404
- return interceptor.onResponseError(err.options, err).then(() => {
405
- // once for refresh, once for replay
406
- assert.calledTwice(webex.request);
407
- assert.equal(webex.credentials.supertoken.access_token, 'ST2');
408
- assert.equal(webex.request.args[1][0].replayCount, 1);
409
- });
410
- });
411
-
412
- browserOnly(it)('refreshes the access token and replays the request', () => {
413
- webex.config.credentials.refreshCallback = sinon.stub().returns(
414
- Promise.resolve({
415
- access_token: 'ST2',
416
- })
417
- );
418
-
419
- webex.credentials.supertoken = new Token(
420
- {
421
- access_token: 'ST1',
422
- refresh_token: 'RT1',
423
- },
424
- {parent: webex}
425
- );
426
-
427
- const err = new WebexHttpError.Unauthorized({
428
- statusCode: 401,
429
- options: {
430
- headers: {
431
- trackingid: 'blarg',
432
- },
433
- uri: `${config.services.discovery.hydra}/ping`,
434
- },
435
- body: {
436
- error: 'fake error',
437
- },
438
- });
439
-
440
- assert.notCalled(webex.request);
441
-
442
- return interceptor.onResponseError(err.options, err).then(() => {
443
- // once for replay
444
- assert.calledOnce(webex.request);
445
- assert.equal(webex.credentials.supertoken.access_token, 'ST2');
446
- assert.equal(webex.request.args[0][0].replayCount, 1);
447
- });
448
- });
449
-
450
- describe('when the access token is not refreshable', () => {
451
- it('responds with the original error', () => {
452
- webex.credentials.supertoken = new Token(
453
- {
454
- access_token: 'ST1',
455
- },
456
- {parent: webex}
457
- );
458
-
459
- const err = new WebexHttpError.Unauthorized({
460
- statusCode: 401,
461
- options: {
462
- headers: {
463
- trackingid: 'blarg',
464
- },
465
- uri: `${config.services.discovery.hydra}/ping`,
466
- },
467
- body: {
468
- error: 'fake error',
469
- },
470
- });
471
-
472
- assert.notCalled(webex.request);
473
-
474
- return assert
475
- .isRejected(interceptor.onResponseError(err.options, err))
476
- .then((err2) => {
477
- assert.equal(err2, err);
478
- });
479
- });
480
- });
481
-
482
- it('does not refresh if shouldRefreshAccessToken was false', () => {
483
- webex.config.credentials.refreshCallback = sinon.stub().returns(
484
- Promise.resolve({
485
- access_token: 'ST2',
486
- })
487
- );
488
-
489
- webex.credentials.supertoken = new Token(
490
- {
491
- access_token: 'ST1',
492
- refresh_token: 'RT1',
493
- },
494
- {parent: webex}
495
- );
496
-
497
- const err = new WebexHttpError.Unauthorized({
498
- statusCode: 401,
499
- options: {
500
- headers: {
501
- trackingid: 'blarg',
502
- },
503
- uri: `${config.services.discovery.hydra}/ping`,
504
- shouldRefreshAccessToken: false,
505
- },
506
- body: {
507
- error: 'fake error',
508
- },
509
- });
510
-
511
- assert.notCalled(webex.request);
512
-
513
- return assert.isRejected(interceptor.onResponseError(err.options, err)).then((err2) => {
514
- assert.equal(err2, err);
515
- });
516
- });
517
- });
518
- });
519
- });
520
- });
521
- });
1
+ /*!
2
+ * Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.
3
+ */
4
+
5
+ /* eslint-disable camelcase */
6
+
7
+ import chai from 'chai';
8
+ import chaiAsPromised from 'chai-as-promised';
9
+ import sinon from 'sinon';
10
+ import {browserOnly, nodeOnly} from '@webex/test-helper-mocha';
11
+ import Logger from '@webex/plugin-logger';
12
+ import MockWebex from '@webex/test-helper-mock-webex';
13
+ import {AuthInterceptor, config, Credentials, WebexHttpError, Token} from '@webex/webex-core';
14
+ import {cloneDeep, merge} from 'lodash';
15
+
16
+ const {assert} = chai;
17
+
18
+ chai.use(chaiAsPromised);
19
+ sinon.assert.expose(chai.assert, {prefix: ''});
20
+
21
+ describe('webex-core', () => {
22
+ describe('Interceptors', () => {
23
+ describe('AuthInterceptor', () => {
24
+ let interceptor, webex;
25
+
26
+ beforeEach(() => {
27
+ webex = new MockWebex({
28
+ children: {
29
+ credentials: Credentials,
30
+ logger: Logger,
31
+ },
32
+ config: merge(cloneDeep(config), {credentials: {client_secret: 'fake'}}),
33
+ });
34
+
35
+ webex.credentials.supertoken = new Token(
36
+ {
37
+ access_token: 'ST1',
38
+ token_type: 'Bearer',
39
+ },
40
+ {parent: webex}
41
+ );
42
+
43
+ interceptor = Reflect.apply(AuthInterceptor.create, webex, []);
44
+ });
45
+
46
+ describe('#onRequest()', () => {
47
+ it('does not replace the auth header if one has been provided', () =>
48
+ interceptor
49
+ .onRequest({
50
+ uri: `${config.services.discovery.hydra}/ping`,
51
+ headers: {
52
+ authorization: 'Bearer Alternate',
53
+ },
54
+ })
55
+ .then((result) =>
56
+ assert.deepEqual(result, {
57
+ uri: `${config.services.discovery.hydra}/ping`,
58
+ headers: {
59
+ authorization: 'Bearer Alternate',
60
+ },
61
+ })
62
+ ));
63
+
64
+ [undefined, null, false].forEach((falsey) => {
65
+ it(`does not add an auth header if ${falsey} has been provided`, () =>
66
+ interceptor
67
+ .onRequest({
68
+ uri: `${config.services.discovery.hydra}/ping`,
69
+ headers: {
70
+ authorization: falsey,
71
+ },
72
+ })
73
+ .then((result) =>
74
+ assert.deepEqual(result, {
75
+ uri: `${config.services.discovery.hydra}/ping`,
76
+ headers: {},
77
+ })
78
+ ));
79
+ });
80
+
81
+ // There should never be a case in which the services plugin is not
82
+ // loaded. But testing for legacy support.
83
+ describe('when the services plugin has not been loaded', () => {
84
+ it('does not add the auth header to hydra requests', () =>
85
+ interceptor
86
+ .onRequest({
87
+ uri: `${config.services.discovery.hydra}/ping`,
88
+ })
89
+ .then((result) =>
90
+ assert.deepEqual(result, {
91
+ uri: `${config.services.discovery.hydra}/ping`,
92
+ headers: {},
93
+ })
94
+ ));
95
+
96
+ it('does not add the auth header to u2c requests', () =>
97
+ interceptor
98
+ .onRequest({
99
+ uri: `${config.services.discovery.u2c}/ping`,
100
+ })
101
+ .then((result) =>
102
+ assert.deepEqual(result, {
103
+ uri: `${config.services.discovery.u2c}/ping`,
104
+ headers: {},
105
+ })
106
+ ));
107
+ });
108
+
109
+ describe('when the services plugin has been loaded', () => {
110
+ let services;
111
+
112
+ beforeEach(() => {
113
+ services = {
114
+ hydra: 'https://hydra-a.wbx.com',
115
+ example: 'https://service.example.com',
116
+ };
117
+
118
+ webex.internal.services = {
119
+ hasService: (service) => Object.keys(services).includes(service),
120
+ hasAllowedDomains: () => true,
121
+ isAllowedDomainUrl: (uri) =>
122
+ !!config.services.allowedDomains.find((host) => uri.includes(host)),
123
+ getServiceFromUrl: (uri) => {
124
+ let targetKey;
125
+
126
+ Object.keys(services).forEach((key) => {
127
+ if (uri.includes(services[key])) {
128
+ targetKey = key;
129
+ }
130
+ });
131
+
132
+ return targetKey ? {name: targetKey} : undefined;
133
+ },
134
+ };
135
+
136
+ webex.internal.services.waitForService = (pto) =>
137
+ Promise.resolve(services[pto.name] || pto.url);
138
+ });
139
+
140
+ it('adds the header to hydra requests', () =>
141
+ Promise.all([
142
+ interceptor.onRequest({uri: `${services.hydra}/ping`}).then((result) =>
143
+ assert.deepEqual(result, {
144
+ uri: `${services.hydra}/ping`,
145
+ headers: {
146
+ authorization: 'Bearer ST1',
147
+ },
148
+ })
149
+ ),
150
+ interceptor
151
+ .onRequest({
152
+ service: 'hydra',
153
+ resource: 'ping',
154
+ })
155
+ .then((result) =>
156
+ assert.deepEqual(result, {
157
+ service: 'hydra',
158
+ resource: 'ping',
159
+ headers: {
160
+ authorization: 'Bearer ST1',
161
+ },
162
+ })
163
+ ),
164
+ ]));
165
+
166
+ it('adds an auth header to uris that are in the service catalog', () =>
167
+ interceptor
168
+ .onRequest({
169
+ uri: `${services.example}/ping`,
170
+ })
171
+ .then((result) =>
172
+ assert.deepEqual(result, {
173
+ uri: `${services.example}/ping`,
174
+ headers: {
175
+ authorization: 'Bearer ST1',
176
+ },
177
+ })
178
+ ));
179
+
180
+ it('adds an auth header to services that are in the service catalog', () =>
181
+ interceptor
182
+ .onRequest({
183
+ service: 'example',
184
+ resource: 'some-resource',
185
+ })
186
+ .then((result) =>
187
+ assert.deepEqual(result, {
188
+ service: 'example',
189
+ resource: 'some-resource',
190
+ headers: {
191
+ authorization: 'Bearer ST1',
192
+ },
193
+ })
194
+ ));
195
+
196
+ it('does not add an auth header to uris not in the service catalog', () =>
197
+ interceptor
198
+ .onRequest({
199
+ uri: 'https://not-a-service.com/ping',
200
+ })
201
+ .then((result) =>
202
+ assert.deepEqual(result, {
203
+ headers: {},
204
+ uri: 'https://not-a-service.com/ping',
205
+ })
206
+ ));
207
+
208
+ it('does not add an auth header to non-existant services', () =>
209
+ interceptor
210
+ .onRequest({
211
+ service: 'non-existant',
212
+ resource: 'no-resource',
213
+ })
214
+ .then((result) =>
215
+ assert.deepEqual(result, {
216
+ headers: {},
217
+ service: 'non-existant',
218
+ resource: 'no-resource',
219
+ })
220
+ ));
221
+ });
222
+ });
223
+
224
+ describe('#requiresCredentials()', () => {
225
+ let services;
226
+
227
+ beforeEach(() => {
228
+ services = {
229
+ hydra: 'https://hydra-a.wbx.com',
230
+ u2c: 'https://u2c.wbx2.com/u2c/api/v1',
231
+ example: 'https://service.example.com',
232
+ };
233
+
234
+ webex.internal.services = {
235
+ getServiceFromUrl: (uri) => {
236
+ let targetKey;
237
+
238
+ Object.keys(services).forEach((key) => {
239
+ if (uri.includes(services[key])) {
240
+ targetKey = key;
241
+ }
242
+ });
243
+
244
+ return targetKey ? {name: targetKey} : undefined;
245
+ },
246
+ hasService: (service) => Object.keys(services).includes(service),
247
+ hasAllowedDomains: () => true,
248
+ isAllowedDomainUrl: (uri) =>
249
+ !!config.services.allowedDomains.find((host) => uri.includes(host)),
250
+ validateDomains: true,
251
+ };
252
+
253
+ webex.internal.services.waitForService = (pto) =>
254
+ Promise.resolve(services[pto.name] || pto.url);
255
+ });
256
+
257
+ afterEach(() => {
258
+ if (webex.internal.services) {
259
+ delete webex.internal.services;
260
+ }
261
+ });
262
+
263
+ it('resolves to false when services plugin does not exist', () => {
264
+ delete webex.internal.services;
265
+
266
+ return interceptor
267
+ .requiresCredentials({
268
+ uri: `${services.hydra}/ping`,
269
+ })
270
+ .then((response) => assert.isFalse(response));
271
+ });
272
+
273
+ it('resolves to true when the u2c service is specified via service', () => {
274
+ services = {};
275
+
276
+ return interceptor
277
+ .requiresCredentials({
278
+ service: 'u2c',
279
+ resource: 'something',
280
+ })
281
+ .then((response) => assert.isTrue(response));
282
+ });
283
+
284
+ it('resolves to false when the u2c limited service is used via uri', () =>
285
+ interceptor
286
+ .requiresCredentials({
287
+ uri: `${services.u2c}/limited`,
288
+ })
289
+ .then((response) => assert.isFalse(response)));
290
+
291
+ it('resolves to true if the service exists in catalog via service', () =>
292
+ interceptor
293
+ .requiresCredentials({service: 'hydra'})
294
+ .then((response) => assert.isTrue(response)));
295
+
296
+ it('resolves to true if the service exists in catalog via uri', () =>
297
+ interceptor
298
+ .requiresCredentials({uri: services.hydra})
299
+ .then((response) => assert.isTrue(response)));
300
+
301
+ it('resolves to false if that `addAuthHeader` is set to false', () =>
302
+ interceptor
303
+ .requiresCredentials({
304
+ addAuthHeader: false,
305
+ service: 'unknown',
306
+ resource: 'ping',
307
+ })
308
+ .then((response) => assert.isFalse(response)));
309
+
310
+ it('resolves to false if `validateDomains` is set to false', () => {
311
+ webex.internal.services.validateDomains = false;
312
+
313
+ return interceptor
314
+ .requiresCredentials({
315
+ uri: 'https://allowed-uri.com/resource',
316
+ })
317
+ .then((response) => assert.isFalse(response));
318
+ });
319
+
320
+ it('resolves to true with an allowed domain uri', () =>
321
+ interceptor
322
+ .requiresCredentials({
323
+ uri: `https://${config.services.allowedDomains[0]}/resource`,
324
+ })
325
+ .then((response) => assert.isTrue(response)));
326
+
327
+ it('resolves to false with a non-allowed uri', () =>
328
+ interceptor
329
+ .requiresCredentials({
330
+ uri: 'https://not-allowed/resource',
331
+ })
332
+ .then((response) => assert.isFalse(response)));
333
+
334
+ it('should return true if domain exists using isAllowedDomainUrl()', () => {
335
+ webex.internal.services.waitForService = sinon.stub();
336
+ const {isAllowedDomainUrl} = webex.internal.services;
337
+
338
+ const result = isAllowedDomainUrl(
339
+ `https://${config.services.allowedDomains[0]}/resource`
340
+ );
341
+
342
+ assert.equal(result, true);
343
+ });
344
+
345
+ it('should return true when called `requiresCredentials` with valid url', () => {
346
+ webex.internal.services.waitForService = sinon.stub();
347
+
348
+ return interceptor
349
+ .requiresCredentials({
350
+ uri: `https://${config.services.allowedDomains[0]}/resource`,
351
+ })
352
+ .then((res) => {
353
+ assert.equal(res, true);
354
+ });
355
+ });
356
+
357
+ it('should call waitForService()', () => {
358
+ webex.internal.services.waitForService = sinon.stub();
359
+ const {waitForService} = webex.internal.services;
360
+
361
+ waitForService.resolves(`https://${config.services.allowedDomains[0]}/resource`);
362
+
363
+ return interceptor
364
+ .requiresCredentials({
365
+ service: 'locus',
366
+ })
367
+ .then(() => assert.calledOnce(waitForService));
368
+ });
369
+ });
370
+
371
+ describe('#onResponseError()', () => {
372
+ describe('when the server responds with 401', () => {
373
+ nodeOnly(it)('refreshes the access token and replays the request', () => {
374
+ webex.request.onCall(0).returns(
375
+ Promise.resolve({
376
+ body: {
377
+ access_token: 'ST2',
378
+ },
379
+ })
380
+ );
381
+ webex.credentials.supertoken = new Token(
382
+ {
383
+ access_token: 'ST1',
384
+ refresh_token: 'RT1',
385
+ },
386
+ {parent: webex}
387
+ );
388
+
389
+ const err = new WebexHttpError.Unauthorized({
390
+ statusCode: 401,
391
+ options: {
392
+ headers: {
393
+ trackingid: 'blarg',
394
+ },
395
+ uri: `${config.services.discovery.hydra}/ping`,
396
+ },
397
+ body: {
398
+ error: 'fake error',
399
+ },
400
+ });
401
+
402
+ assert.notCalled(webex.request);
403
+
404
+ return interceptor.onResponseError(err.options, err).then(() => {
405
+ // once for refresh, once for replay
406
+ assert.calledTwice(webex.request);
407
+ assert.equal(webex.credentials.supertoken.access_token, 'ST2');
408
+ assert.equal(webex.request.args[1][0].replayCount, 1);
409
+ });
410
+ });
411
+
412
+ browserOnly(it)('refreshes the access token and replays the request', () => {
413
+ webex.config.credentials.refreshCallback = sinon.stub().returns(
414
+ Promise.resolve({
415
+ access_token: 'ST2',
416
+ })
417
+ );
418
+
419
+ webex.credentials.supertoken = new Token(
420
+ {
421
+ access_token: 'ST1',
422
+ refresh_token: 'RT1',
423
+ },
424
+ {parent: webex}
425
+ );
426
+
427
+ const err = new WebexHttpError.Unauthorized({
428
+ statusCode: 401,
429
+ options: {
430
+ headers: {
431
+ trackingid: 'blarg',
432
+ },
433
+ uri: `${config.services.discovery.hydra}/ping`,
434
+ },
435
+ body: {
436
+ error: 'fake error',
437
+ },
438
+ });
439
+
440
+ assert.notCalled(webex.request);
441
+
442
+ return interceptor.onResponseError(err.options, err).then(() => {
443
+ // once for replay
444
+ assert.calledOnce(webex.request);
445
+ assert.equal(webex.credentials.supertoken.access_token, 'ST2');
446
+ assert.equal(webex.request.args[0][0].replayCount, 1);
447
+ });
448
+ });
449
+
450
+ describe('when the access token is not refreshable', () => {
451
+ it('responds with the original error', () => {
452
+ webex.credentials.supertoken = new Token(
453
+ {
454
+ access_token: 'ST1',
455
+ },
456
+ {parent: webex}
457
+ );
458
+
459
+ const err = new WebexHttpError.Unauthorized({
460
+ statusCode: 401,
461
+ options: {
462
+ headers: {
463
+ trackingid: 'blarg',
464
+ },
465
+ uri: `${config.services.discovery.hydra}/ping`,
466
+ },
467
+ body: {
468
+ error: 'fake error',
469
+ },
470
+ });
471
+
472
+ assert.notCalled(webex.request);
473
+
474
+ return assert
475
+ .isRejected(interceptor.onResponseError(err.options, err))
476
+ .then((err2) => {
477
+ assert.equal(err2, err);
478
+ });
479
+ });
480
+ });
481
+
482
+ it('does not refresh if shouldRefreshAccessToken was false', () => {
483
+ webex.config.credentials.refreshCallback = sinon.stub().returns(
484
+ Promise.resolve({
485
+ access_token: 'ST2',
486
+ })
487
+ );
488
+
489
+ webex.credentials.supertoken = new Token(
490
+ {
491
+ access_token: 'ST1',
492
+ refresh_token: 'RT1',
493
+ },
494
+ {parent: webex}
495
+ );
496
+
497
+ const err = new WebexHttpError.Unauthorized({
498
+ statusCode: 401,
499
+ options: {
500
+ headers: {
501
+ trackingid: 'blarg',
502
+ },
503
+ uri: `${config.services.discovery.hydra}/ping`,
504
+ shouldRefreshAccessToken: false,
505
+ },
506
+ body: {
507
+ error: 'fake error',
508
+ },
509
+ });
510
+
511
+ assert.notCalled(webex.request);
512
+
513
+ return assert.isRejected(interceptor.onResponseError(err.options, err)).then((err2) => {
514
+ assert.equal(err2, err);
515
+ });
516
+ });
517
+ });
518
+ });
519
+ });
520
+ });
521
+ });