@webex/webex-core 3.12.0-next.5 → 3.12.0-next.51
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.
- package/README.md +5 -2
- package/dist/config.js +38 -0
- package/dist/config.js.map +1 -1
- package/dist/credentials-config.js +12 -0
- package/dist/credentials-config.js.map +1 -1
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -1
- package/dist/interceptors/catalog-url.js +87 -0
- package/dist/interceptors/catalog-url.js.map +1 -0
- package/dist/interceptors/payload-transformer.js +25 -4
- package/dist/interceptors/payload-transformer.js.map +1 -1
- package/dist/lib/batcher.js +23 -7
- package/dist/lib/batcher.js.map +1 -1
- package/dist/lib/credentials/credentials.js +103 -4
- package/dist/lib/credentials/credentials.js.map +1 -1
- package/dist/lib/credentials/token.js +1 -1
- package/dist/lib/domains.js +90 -0
- package/dist/lib/domains.js.map +1 -0
- package/dist/lib/services/service-catalog.js +108 -18
- package/dist/lib/services/service-catalog.js.map +1 -1
- package/dist/lib/services/services.js +232 -34
- package/dist/lib/services/services.js.map +1 -1
- package/dist/lib/services-v2/service-catalog.js +14 -13
- package/dist/lib/services-v2/service-catalog.js.map +1 -1
- package/dist/lib/services-v2/services-v2.js +241 -37
- package/dist/lib/services-v2/services-v2.js.map +1 -1
- package/dist/plugins/logger.js +1 -1
- package/dist/webex-core.js +11 -3
- package/dist/webex-core.js.map +1 -1
- package/package.json +13 -13
- package/src/config.js +42 -0
- package/src/credentials-config.js +13 -0
- package/src/index.js +1 -0
- package/src/interceptors/catalog-url.js +66 -0
- package/src/interceptors/payload-transformer.js +23 -1
- package/src/lib/batcher.js +25 -10
- package/src/lib/credentials/credentials.js +93 -3
- package/src/lib/domains.ts +94 -0
- package/src/lib/services/service-catalog.js +112 -16
- package/src/lib/services/services.js +208 -20
- package/src/lib/services-v2/service-catalog.ts +16 -12
- package/src/lib/services-v2/services-v2.ts +218 -21
- package/src/webex-core.js +8 -0
- package/test/fixtures/activation-email.ts +22 -0
- package/test/integration/spec/services/services.js +183 -112
- package/test/integration/spec/services-v2/services-v2.js +169 -98
- package/test/unit/spec/credentials/credentials.js +247 -3
- package/test/unit/spec/interceptors/auth.js +56 -0
- package/test/unit/spec/interceptors/catalog-url.js +224 -0
- package/test/unit/spec/interceptors/payload-transformer.js +119 -1
- package/test/unit/spec/lib/batcher.js +56 -0
- package/test/unit/spec/services/service-catalog.js +368 -11
- package/test/unit/spec/services/services.js +440 -2
- package/test/unit/spec/services-v2/service-catalog.ts +134 -11
- package/test/unit/spec/services-v2/services-v2.ts +467 -0
- package/test/unit/spec/webex-core.js +77 -1
|
@@ -5,6 +5,178 @@
|
|
|
5
5
|
import {assert} from '@webex/test-helper-chai';
|
|
6
6
|
import MockWebex from '@webex/test-helper-mock-webex';
|
|
7
7
|
import {Services} from '@webex/webex-core';
|
|
8
|
+
import {
|
|
9
|
+
matchesCatalogUrl,
|
|
10
|
+
matchesParsedCatalogUrl,
|
|
11
|
+
parseCatalogUrl,
|
|
12
|
+
} from '../../../../src/lib/services/service-catalog';
|
|
13
|
+
|
|
14
|
+
describe('matchesCatalogUrl()', () => {
|
|
15
|
+
describe('origin validation', () => {
|
|
16
|
+
it('returns true when origins match exactly', () => {
|
|
17
|
+
assert.isTrue(
|
|
18
|
+
matchesCatalogUrl('https://example.com/api/v1/users', 'https://example.com/api/v1')
|
|
19
|
+
);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('returns false when hosts differ', () => {
|
|
23
|
+
assert.isFalse(
|
|
24
|
+
matchesCatalogUrl('https://other.com/api/v1/users', 'https://example.com/api/v1')
|
|
25
|
+
);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('returns false when catalog host is prefix of candidate host (SECURITY)', () => {
|
|
29
|
+
// Attack: trusted.example.attacker.com should NOT match trusted.example
|
|
30
|
+
assert.isFalse(
|
|
31
|
+
matchesCatalogUrl(
|
|
32
|
+
'https://trusted.example.attacker.com/activities/id',
|
|
33
|
+
'https://trusted.example'
|
|
34
|
+
)
|
|
35
|
+
);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('returns false when schemes differ', () => {
|
|
39
|
+
assert.isFalse(
|
|
40
|
+
matchesCatalogUrl('http://example.com/api/v1/users', 'https://example.com/api/v1')
|
|
41
|
+
);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('returns false when ports differ', () => {
|
|
45
|
+
assert.isFalse(
|
|
46
|
+
matchesCatalogUrl('https://example.com:8443/api/v1/users', 'https://example.com/api/v1')
|
|
47
|
+
);
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
describe('path validation', () => {
|
|
52
|
+
it('returns true for root path catalog entry', () => {
|
|
53
|
+
assert.isTrue(matchesCatalogUrl('https://example.com/any/path/here', 'https://example.com/'));
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('returns true when paths match exactly', () => {
|
|
57
|
+
assert.isTrue(matchesCatalogUrl('https://example.com/api/v1', 'https://example.com/api/v1'));
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('returns true when candidate path extends catalog path at boundary', () => {
|
|
61
|
+
assert.isTrue(
|
|
62
|
+
matchesCatalogUrl('https://example.com/api/v1/users/123', 'https://example.com/api/v1')
|
|
63
|
+
);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('returns true when catalog path has trailing slash', () => {
|
|
67
|
+
assert.isTrue(
|
|
68
|
+
matchesCatalogUrl('https://example.com/api/v1/users', 'https://example.com/api/v1/')
|
|
69
|
+
);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('returns false when catalog path is prefix but not at path boundary (SECURITY)', () => {
|
|
73
|
+
// /api/v1 should NOT match /api/v1extra
|
|
74
|
+
assert.isFalse(
|
|
75
|
+
matchesCatalogUrl('https://example.com/api/v1extra/something', 'https://example.com/api/v1')
|
|
76
|
+
);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('returns false when candidate path does not start with catalog path', () => {
|
|
80
|
+
assert.isFalse(
|
|
81
|
+
matchesCatalogUrl('https://example.com/other/path', 'https://example.com/api/v1')
|
|
82
|
+
);
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
describe('error handling', () => {
|
|
87
|
+
it('returns false for invalid candidate URL', () => {
|
|
88
|
+
assert.isFalse(matchesCatalogUrl('not-a-url', 'https://example.com/api'));
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('returns false for invalid catalog URL', () => {
|
|
92
|
+
assert.isFalse(matchesCatalogUrl('https://example.com/api', 'not-a-url'));
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('returns false when both URLs are invalid', () => {
|
|
96
|
+
assert.isFalse(matchesCatalogUrl('not-a-url', 'also-not-a-url'));
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
describe('parseCatalogUrl()', () => {
|
|
102
|
+
it('returns the origin and pathname for a valid url', () => {
|
|
103
|
+
assert.deepEqual(parseCatalogUrl('https://example.com/api/v1'), {
|
|
104
|
+
origin: 'https://example.com',
|
|
105
|
+
path: '/api/v1',
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('strips a trailing slash from the path', () => {
|
|
110
|
+
assert.deepEqual(parseCatalogUrl('https://example.com/api/v1/'), {
|
|
111
|
+
origin: 'https://example.com',
|
|
112
|
+
path: '/api/v1',
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('normalizes a root url to the "/" path', () => {
|
|
117
|
+
assert.deepEqual(parseCatalogUrl('https://example.com'), {
|
|
118
|
+
origin: 'https://example.com',
|
|
119
|
+
path: '/',
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('returns null for an unparsable url', () => {
|
|
124
|
+
assert.isNull(parseCatalogUrl('not-a-url'));
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('memoizes results, returning the same reference for repeated calls', () => {
|
|
128
|
+
const first = parseCatalogUrl('https://memoized.example.com/api/v1');
|
|
129
|
+
const second = parseCatalogUrl('https://memoized.example.com/api/v1');
|
|
130
|
+
|
|
131
|
+
assert.strictEqual(first, second);
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
describe('matchesParsedCatalogUrl()', () => {
|
|
136
|
+
it('returns true when the candidate is under the catalog origin and path', () => {
|
|
137
|
+
assert.isTrue(
|
|
138
|
+
matchesParsedCatalogUrl(
|
|
139
|
+
new URL('https://example.com/api/v1/users'),
|
|
140
|
+
parseCatalogUrl('https://example.com/api/v1')
|
|
141
|
+
)
|
|
142
|
+
);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('returns false when origins differ (SECURITY)', () => {
|
|
146
|
+
assert.isFalse(
|
|
147
|
+
matchesParsedCatalogUrl(
|
|
148
|
+
new URL('https://trusted.example.attacker.com/activities/id'),
|
|
149
|
+
parseCatalogUrl('https://trusted.example')
|
|
150
|
+
)
|
|
151
|
+
);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it('returns false when the catalog path is not matched at a boundary (SECURITY)', () => {
|
|
155
|
+
assert.isFalse(
|
|
156
|
+
matchesParsedCatalogUrl(
|
|
157
|
+
new URL('https://example.com/api/v1extra/something'),
|
|
158
|
+
parseCatalogUrl('https://example.com/api/v1')
|
|
159
|
+
)
|
|
160
|
+
);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it('returns true for a root catalog path', () => {
|
|
164
|
+
assert.isTrue(
|
|
165
|
+
matchesParsedCatalogUrl(
|
|
166
|
+
new URL('https://example.com/any/path'),
|
|
167
|
+
parseCatalogUrl('https://example.com/')
|
|
168
|
+
)
|
|
169
|
+
);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it('returns false when the candidate url is missing', () => {
|
|
173
|
+
assert.isFalse(matchesParsedCatalogUrl(undefined, parseCatalogUrl('https://example.com/api')));
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it('returns false when the parsed catalog url is null', () => {
|
|
177
|
+
assert.isFalse(matchesParsedCatalogUrl(new URL('https://example.com/api'), null));
|
|
178
|
+
});
|
|
179
|
+
});
|
|
8
180
|
|
|
9
181
|
/* eslint-disable no-underscore-dangle */
|
|
10
182
|
describe('webex-core', () => {
|
|
@@ -101,7 +273,24 @@ describe('webex-core', () => {
|
|
|
101
273
|
const domains = [];
|
|
102
274
|
|
|
103
275
|
beforeEach(() => {
|
|
104
|
-
|
|
276
|
+
// Shaped like a real catalog rather than a tidy list: the commercial
|
|
277
|
+
// domains the sdk ships with, a multi-part suffix, sites added at
|
|
278
|
+
// runtime from meeting preferences, an entry that overlaps another,
|
|
279
|
+
// and an unset entry, which callers can add and which must match
|
|
280
|
+
// nothing.
|
|
281
|
+
domains.push(
|
|
282
|
+
'wbx2.com',
|
|
283
|
+
'ciscospark.com',
|
|
284
|
+
'webex.com',
|
|
285
|
+
'webexapis.com',
|
|
286
|
+
'broadcloud.com.au',
|
|
287
|
+
'webexgov.us',
|
|
288
|
+
'example-a.com',
|
|
289
|
+
'example-b.com',
|
|
290
|
+
'example-c.com',
|
|
291
|
+
'go.example-a.com',
|
|
292
|
+
''
|
|
293
|
+
);
|
|
105
294
|
|
|
106
295
|
catalog.setAllowedDomains(domains);
|
|
107
296
|
});
|
|
@@ -110,10 +299,60 @@ describe('webex-core', () => {
|
|
|
110
299
|
domains.length = 0;
|
|
111
300
|
});
|
|
112
301
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
302
|
+
[
|
|
303
|
+
// real service hosts resolve to the entry that covers them
|
|
304
|
+
['https://u2c-a.wbx2.com/u2c/api/v1/limited/catalog', 'wbx2.com'],
|
|
305
|
+
['https://conv-a.wbx2.com/conversation/api/v1/conversations', 'wbx2.com'],
|
|
306
|
+
['https://foobar.ciscospark.com/resource/id', 'ciscospark.com'],
|
|
307
|
+
['https://idbroker.webex.com/idb/token', 'webex.com'],
|
|
308
|
+
['https://webexapis.com/v1/people/me', 'webexapis.com'],
|
|
309
|
+
// an entry is not required to be a bare registrable domain
|
|
310
|
+
['https://foo.broadcloud.com.au/resource/id', 'broadcloud.com.au'],
|
|
311
|
+
['https://a.b.webexgov.us/resource/id', 'webexgov.us'],
|
|
312
|
+
// the domain itself and its subdomains match
|
|
313
|
+
['https://example-a.com/resource/id', 'example-a.com'],
|
|
314
|
+
['https://sub.example-b.com/resource/id', 'example-b.com'],
|
|
315
|
+
['https://deep.sub.example-c.com/resource/id', 'example-c.com'],
|
|
316
|
+
// overlapping entries resolve to the first covering entry in the list
|
|
317
|
+
['https://go.example-a.com/resource/id', 'example-a.com'],
|
|
318
|
+
// an explicit port must not defeat the match
|
|
319
|
+
['https://example-a.com:8443/resource/id', 'example-a.com'],
|
|
320
|
+
['https://u2c-a.wbx2.com:8000/resource/id', 'wbx2.com'],
|
|
321
|
+
// hostname comparison is case insensitive
|
|
322
|
+
['https://U2C-A.WBX2.COM/resource/id', 'wbx2.com'],
|
|
323
|
+
// a trailing dot is the same name
|
|
324
|
+
['https://u2c-a.wbx2.com./resource/id', 'wbx2.com'],
|
|
325
|
+
// a sibling registrable domain must not match
|
|
326
|
+
['https://notwebex.com/resource/id', undefined],
|
|
327
|
+
['https://mywebexgov.us/resource/id', undefined],
|
|
328
|
+
['https://webex.company/resource/id', undefined],
|
|
329
|
+
['https://webex.com-unrelated.example/resource/id', undefined],
|
|
330
|
+
['https://example-a.community/resource/id', undefined],
|
|
331
|
+
// a partial label must not match, even against a multi-part entry
|
|
332
|
+
['https://broadcloud.com/resource/id', undefined],
|
|
333
|
+
// the domain matches only as a suffix, on a label boundary
|
|
334
|
+
['https://webex.com.unrelated.example/resource/id', undefined],
|
|
335
|
+
['https://unrelated.example/webex.com/resource/id', undefined],
|
|
336
|
+
['https://unrelated.example/?next=https://webex.com', undefined],
|
|
337
|
+
// userinfo is not the host
|
|
338
|
+
['https://webex.com@unrelated.example/resource/id', undefined],
|
|
339
|
+
// an encoded or unusual separator is not a label boundary, and the url
|
|
340
|
+
// parsers used by the transports do not agree on where these end the
|
|
341
|
+
// host, so they must not resolve to an allowed domain
|
|
342
|
+
['https://webex.com%2eunrelated.example/resource/id', undefined],
|
|
343
|
+
['https://webex.com%2Eunrelated.example/resource/id', undefined],
|
|
344
|
+
['https://unrelated.example%2ewebex.com/resource/id', undefined],
|
|
345
|
+
['https://unrelated.example%2Ewebex.com/resource/id', undefined],
|
|
346
|
+
['https://unrelated.example;.webex.com/resource/id', undefined],
|
|
347
|
+
// an empty allowed domain entry matches nothing
|
|
348
|
+
['https://unrelated.example/resource/id', undefined],
|
|
349
|
+
// unparseable urls
|
|
350
|
+
['', undefined],
|
|
351
|
+
['not a url', undefined],
|
|
352
|
+
].forEach(([url, expected]) => {
|
|
353
|
+
it(`returns ${expected} for ${url || '<empty>'}`, () => {
|
|
354
|
+
assert.equal(catalog.findAllowedDomain(url), expected);
|
|
355
|
+
});
|
|
117
356
|
});
|
|
118
357
|
});
|
|
119
358
|
|
|
@@ -121,7 +360,7 @@ describe('webex-core', () => {
|
|
|
121
360
|
const domains = [];
|
|
122
361
|
|
|
123
362
|
beforeEach(() => {
|
|
124
|
-
domains.push('example-a', 'example-b', 'example-c');
|
|
363
|
+
domains.push('example-a.com', 'example-b.com', 'example-c.com');
|
|
125
364
|
|
|
126
365
|
catalog.setAllowedDomains(domains);
|
|
127
366
|
});
|
|
@@ -160,7 +399,7 @@ describe('webex-core', () => {
|
|
|
160
399
|
const domains = [];
|
|
161
400
|
|
|
162
401
|
beforeEach(() => {
|
|
163
|
-
domains.push('example-a', 'example-b', 'example-c');
|
|
402
|
+
domains.push('example-a.com', 'example-b.com', 'example-c.com');
|
|
164
403
|
|
|
165
404
|
catalog.setAllowedDomains(domains);
|
|
166
405
|
});
|
|
@@ -170,19 +409,31 @@ describe('webex-core', () => {
|
|
|
170
409
|
});
|
|
171
410
|
|
|
172
411
|
it('sets the allowed domain entries to new values', () => {
|
|
173
|
-
const newValues = ['example-d', 'example-e', 'example-f'];
|
|
412
|
+
const newValues = ['example-d.com', 'example-e.com', 'example-f.com'];
|
|
174
413
|
|
|
175
414
|
catalog.setAllowedDomains(newValues);
|
|
176
415
|
|
|
177
416
|
assert.notDeepInclude(domains, newValues);
|
|
178
417
|
});
|
|
418
|
+
|
|
419
|
+
it('canonicalizes entries and discards those that are not usable', () => {
|
|
420
|
+
catalog.setAllowedDomains([
|
|
421
|
+
'Example-D.COM',
|
|
422
|
+
'example-d.com',
|
|
423
|
+
'example-e.com.',
|
|
424
|
+
'',
|
|
425
|
+
undefined,
|
|
426
|
+
]);
|
|
427
|
+
|
|
428
|
+
assert.deepEqual(catalog.getAllowedDomains(), ['example-d.com', 'example-e.com']);
|
|
429
|
+
});
|
|
179
430
|
});
|
|
180
431
|
|
|
181
432
|
describe('#addAllowedDomains()', () => {
|
|
182
433
|
const domains = [];
|
|
183
434
|
|
|
184
435
|
beforeEach(() => {
|
|
185
|
-
domains.push('example-a', 'example-b', 'example-c');
|
|
436
|
+
domains.push('example-a.com', 'example-b.com', 'example-c.com');
|
|
186
437
|
|
|
187
438
|
catalog.setAllowedDomains(domains);
|
|
188
439
|
});
|
|
@@ -192,13 +443,16 @@ describe('webex-core', () => {
|
|
|
192
443
|
});
|
|
193
444
|
|
|
194
445
|
it('merge the allowed domain entries with new values', () => {
|
|
195
|
-
const newValues = ['example-c', 'example-e', 'example-f'];
|
|
446
|
+
const newValues = ['example-c.com', 'example-e.com', 'example-f.com'];
|
|
196
447
|
|
|
197
448
|
catalog.addAllowedDomains(newValues);
|
|
198
449
|
|
|
199
450
|
const list = catalog.getAllowedDomains();
|
|
200
451
|
|
|
201
|
-
assert.match(
|
|
452
|
+
assert.match(
|
|
453
|
+
['example-a.com', 'example-b.com', 'example-c.com', 'example-e.com', 'example-f.com'],
|
|
454
|
+
list
|
|
455
|
+
);
|
|
202
456
|
});
|
|
203
457
|
});
|
|
204
458
|
|
|
@@ -250,6 +504,109 @@ describe('webex-core', () => {
|
|
|
250
504
|
|
|
251
505
|
assert.equal(service, exampleService);
|
|
252
506
|
});
|
|
507
|
+
|
|
508
|
+
describe('security: origin validation', () => {
|
|
509
|
+
it('rejects URLs where the catalog host is a prefix of the candidate host (SECURITY)', () => {
|
|
510
|
+
// Attack: https://trusted.example.attacker.com should NOT match https://trusted.example
|
|
511
|
+
const exampleService = {
|
|
512
|
+
defaultUrl: 'https://trusted.example',
|
|
513
|
+
hosts: [],
|
|
514
|
+
};
|
|
515
|
+
|
|
516
|
+
catalog.serviceGroups.postauth.push(exampleService);
|
|
517
|
+
|
|
518
|
+
// Attacker registers trusted.example.attacker.com
|
|
519
|
+
const attackerUrl = 'https://trusted.example.attacker.com/activities/id';
|
|
520
|
+
const service = catalog.findServiceUrlFromUrl(attackerUrl);
|
|
521
|
+
|
|
522
|
+
assert.isUndefined(service);
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
it('rejects URLs with different ports even if host matches', () => {
|
|
526
|
+
const exampleService = {
|
|
527
|
+
defaultUrl: 'https://example.com/resource',
|
|
528
|
+
hosts: [],
|
|
529
|
+
};
|
|
530
|
+
|
|
531
|
+
catalog.serviceGroups.postauth.push(exampleService);
|
|
532
|
+
|
|
533
|
+
const differentPortUrl = 'https://example.com:8443/resource/id';
|
|
534
|
+
const service = catalog.findServiceUrlFromUrl(differentPortUrl);
|
|
535
|
+
|
|
536
|
+
assert.isUndefined(service);
|
|
537
|
+
});
|
|
538
|
+
|
|
539
|
+
it('rejects URLs with different schemes', () => {
|
|
540
|
+
const exampleService = {
|
|
541
|
+
defaultUrl: 'https://example.com/resource',
|
|
542
|
+
hosts: [],
|
|
543
|
+
};
|
|
544
|
+
|
|
545
|
+
catalog.serviceGroups.postauth.push(exampleService);
|
|
546
|
+
|
|
547
|
+
const httpUrl = 'http://example.com/resource/id';
|
|
548
|
+
const service = catalog.findServiceUrlFromUrl(httpUrl);
|
|
549
|
+
|
|
550
|
+
assert.isUndefined(service);
|
|
551
|
+
});
|
|
552
|
+
|
|
553
|
+
it('rejects URLs where catalog path is a prefix but not at path boundary', () => {
|
|
554
|
+
// /api/v1 should NOT match /api/v1extra
|
|
555
|
+
const exampleService = {
|
|
556
|
+
defaultUrl: 'https://example.com/api/v1',
|
|
557
|
+
hosts: [],
|
|
558
|
+
};
|
|
559
|
+
|
|
560
|
+
catalog.serviceGroups.postauth.push(exampleService);
|
|
561
|
+
|
|
562
|
+
const nonBoundaryUrl = 'https://example.com/api/v1extra/something';
|
|
563
|
+
const service = catalog.findServiceUrlFromUrl(nonBoundaryUrl);
|
|
564
|
+
|
|
565
|
+
assert.isUndefined(service);
|
|
566
|
+
});
|
|
567
|
+
|
|
568
|
+
it('accepts URLs at exact path boundary', () => {
|
|
569
|
+
const exampleService = {
|
|
570
|
+
defaultUrl: 'https://example.com/api/v1',
|
|
571
|
+
hosts: [],
|
|
572
|
+
};
|
|
573
|
+
|
|
574
|
+
catalog.serviceGroups.postauth.push(exampleService);
|
|
575
|
+
|
|
576
|
+
const boundaryUrl = 'https://example.com/api/v1/users/123';
|
|
577
|
+
const service = catalog.findServiceUrlFromUrl(boundaryUrl);
|
|
578
|
+
|
|
579
|
+
assert.equal(service, exampleService);
|
|
580
|
+
});
|
|
581
|
+
|
|
582
|
+
it('returns undefined for invalid URLs', () => {
|
|
583
|
+
const exampleService = {
|
|
584
|
+
defaultUrl: 'https://example.com/resource',
|
|
585
|
+
hosts: [],
|
|
586
|
+
};
|
|
587
|
+
|
|
588
|
+
catalog.serviceGroups.postauth.push(exampleService);
|
|
589
|
+
|
|
590
|
+
const invalidUrl = 'not-a-valid-url';
|
|
591
|
+
const service = catalog.findServiceUrlFromUrl(invalidUrl);
|
|
592
|
+
|
|
593
|
+
assert.isUndefined(service);
|
|
594
|
+
});
|
|
595
|
+
|
|
596
|
+
it('matches root path catalog entries correctly', () => {
|
|
597
|
+
const exampleService = {
|
|
598
|
+
defaultUrl: 'https://example.com/',
|
|
599
|
+
hosts: [],
|
|
600
|
+
};
|
|
601
|
+
|
|
602
|
+
catalog.serviceGroups.postauth.push(exampleService);
|
|
603
|
+
|
|
604
|
+
const subpathUrl = 'https://example.com/any/path/here';
|
|
605
|
+
const service = catalog.findServiceUrlFromUrl(subpathUrl);
|
|
606
|
+
|
|
607
|
+
assert.equal(service, exampleService);
|
|
608
|
+
});
|
|
609
|
+
});
|
|
253
610
|
});
|
|
254
611
|
});
|
|
255
612
|
});
|