@webex/plugin-authorization-browser 2.59.1 → 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.
- package/.eslintrc.js +6 -6
- package/README.md +53 -53
- package/babel.config.js +3 -3
- package/dist/authorization.js +109 -109
- package/dist/authorization.js.map +1 -1
- package/dist/config.js +8 -8
- package/dist/config.js.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/jest.config.js +3 -3
- package/package.json +20 -19
- package/process +1 -1
- package/src/authorization.js +394 -394
- package/src/config.js +16 -16
- package/src/index.js +19 -19
- package/test/automation/fixtures/app.js +76 -76
- package/test/automation/fixtures/index.html +27 -27
- package/test/automation/spec/authorization-code-grant.js +123 -123
- package/test/automation/spec/implicit-grant.js +90 -90
- package/test/integration/spec/authorization.js +76 -76
- package/test/unit/spec/authorization.js +430 -430
|
@@ -1,430 +1,430 @@
|
|
|
1
|
-
/*!
|
|
2
|
-
* Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
/* eslint camelcase: [0] */
|
|
6
|
-
|
|
7
|
-
import url from 'url';
|
|
8
|
-
|
|
9
|
-
import {assert} from '@webex/test-helper-chai';
|
|
10
|
-
import sinon from 'sinon';
|
|
11
|
-
import MockWebex from '@webex/test-helper-mock-webex';
|
|
12
|
-
import {Credentials} from '@webex/webex-core';
|
|
13
|
-
import Authorization from '@webex/plugin-authorization-browser';
|
|
14
|
-
import {base64, patterns} from '@webex/common';
|
|
15
|
-
import {merge} from 'lodash';
|
|
16
|
-
import {expect} from '@jest/globals';
|
|
17
|
-
|
|
18
|
-
describe('plugin-authorization-browser', () => {
|
|
19
|
-
describe('Authorization', () => {
|
|
20
|
-
function makeWebexCore(href = 'https://example.com', csrfToken = undefined, config = {}) {
|
|
21
|
-
const mockWindow = {
|
|
22
|
-
history: {
|
|
23
|
-
replaceState(a, b, location) {
|
|
24
|
-
mockWindow.location.href = location;
|
|
25
|
-
},
|
|
26
|
-
},
|
|
27
|
-
location: {
|
|
28
|
-
href,
|
|
29
|
-
},
|
|
30
|
-
sessionStorage: {
|
|
31
|
-
getItem: sinon.stub().returns(csrfToken),
|
|
32
|
-
removeItem: sinon.spy(),
|
|
33
|
-
setItem: sinon.spy(),
|
|
34
|
-
},
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
const webex = new MockWebex({
|
|
38
|
-
children: {
|
|
39
|
-
authorization: Authorization,
|
|
40
|
-
credentials: Credentials,
|
|
41
|
-
},
|
|
42
|
-
config: merge(
|
|
43
|
-
{
|
|
44
|
-
credentials: {
|
|
45
|
-
authorizeUrl: `${
|
|
46
|
-
process.env.IDBROKER_BASE_URL || 'https://idbroker.webex.com'
|
|
47
|
-
}/idb/oauth2/v1/authorize`,
|
|
48
|
-
logoutUrl: `${
|
|
49
|
-
process.env.IDBROKER_BASE_URL || 'https://idbroker.webex.com'
|
|
50
|
-
}/idb/oauth2/v1/logout`,
|
|
51
|
-
// eslint-disable-next-line camelcase
|
|
52
|
-
client_id: 'fake',
|
|
53
|
-
// eslint-disable-next-line camelcase
|
|
54
|
-
client_secret: 'fake',
|
|
55
|
-
// eslint-disable-next-line camelcase
|
|
56
|
-
redirect_uri: 'http://example.com',
|
|
57
|
-
// eslint-disable-next-line camelcase
|
|
58
|
-
scope: 'scope:one',
|
|
59
|
-
refreshCallback: () => Promise.resolve(),
|
|
60
|
-
},
|
|
61
|
-
},
|
|
62
|
-
config
|
|
63
|
-
),
|
|
64
|
-
getWindow() {
|
|
65
|
-
return mockWindow;
|
|
66
|
-
},
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
return webex;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
describe('#initialize()', () => {
|
|
73
|
-
describe('when there is a token in the url', () => {
|
|
74
|
-
it('sets the token and sets ready', () => {
|
|
75
|
-
const webex = makeWebexCore(
|
|
76
|
-
'http://example.com/#access_token=AT&expires_in=3600&token_type=Bearer'
|
|
77
|
-
);
|
|
78
|
-
|
|
79
|
-
assert.isFalse(webex.authorization.ready);
|
|
80
|
-
assert.isFalse(webex.credentials.canAuthorize);
|
|
81
|
-
|
|
82
|
-
return webex.authorization.when('change:ready').then(() => {
|
|
83
|
-
assert.isTrue(webex.authorization.ready);
|
|
84
|
-
assert.isTrue(webex.credentials.canAuthorize);
|
|
85
|
-
});
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
describe('when url parsing is disabled', () => {
|
|
89
|
-
it('sets ready', () => {
|
|
90
|
-
const webex = new MockWebex({
|
|
91
|
-
children: {
|
|
92
|
-
credentials: Credentials,
|
|
93
|
-
},
|
|
94
|
-
getWindow() {
|
|
95
|
-
return {
|
|
96
|
-
location: {
|
|
97
|
-
href: 'http://example.com/#access_token=AT&expires_in=3600&token_type=Bearer',
|
|
98
|
-
},
|
|
99
|
-
};
|
|
100
|
-
},
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
webex.authorization = new Authorization({parse: false}, {parent: webex});
|
|
104
|
-
|
|
105
|
-
assert.isTrue(webex.authorization.ready);
|
|
106
|
-
assert.isFalse(webex.credentials.canAuthorize);
|
|
107
|
-
});
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
it('sets the token, refresh token and sets ready', () => {
|
|
111
|
-
const webex = makeWebexCore(
|
|
112
|
-
'http://example.com/#access_token=AT&expires_in=3600&token_type=Bearer&refresh_token=RT&refresh_token_expires_in=36000'
|
|
113
|
-
);
|
|
114
|
-
|
|
115
|
-
assert.isFalse(webex.authorization.ready);
|
|
116
|
-
assert.isFalse(webex.credentials.canAuthorize);
|
|
117
|
-
assert.isFalse(webex.credentials.canRefresh);
|
|
118
|
-
|
|
119
|
-
return webex.authorization.when('change:ready').then(() => {
|
|
120
|
-
assert.isTrue(webex.authorization.ready);
|
|
121
|
-
assert.isTrue(webex.credentials.canAuthorize);
|
|
122
|
-
assert.isTrue(webex.credentials.canRefresh);
|
|
123
|
-
});
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
it('validates the csrf token', () => {
|
|
127
|
-
const csrfToken = 'abcd';
|
|
128
|
-
|
|
129
|
-
assert.throws(() => {
|
|
130
|
-
// eslint-disable-next-line no-unused-vars
|
|
131
|
-
const webex = makeWebexCore(
|
|
132
|
-
`http://example.com/#access_token=AT&expires_in=3600&token_type=Bearer&refresh_token=RT&refresh_token_expires_in=36000&state=${base64.encode(
|
|
133
|
-
JSON.stringify({csrf_token: 'someothertoken'})
|
|
134
|
-
)}`,
|
|
135
|
-
csrfToken
|
|
136
|
-
);
|
|
137
|
-
}, /CSRF token someothertoken does not match stored token abcd/);
|
|
138
|
-
|
|
139
|
-
assert.throws(() => {
|
|
140
|
-
// eslint-disable-next-line no-unused-vars
|
|
141
|
-
const webex = makeWebexCore(
|
|
142
|
-
`http://example.com/#access_token=AT&expires_in=3600&token_type=Bearer&refresh_token=RT&refresh_token_expires_in=36000&state=${base64.encode(
|
|
143
|
-
JSON.stringify({})
|
|
144
|
-
)}`,
|
|
145
|
-
csrfToken
|
|
146
|
-
);
|
|
147
|
-
}, /Expected CSRF token abcd, but not found in redirect hash/);
|
|
148
|
-
|
|
149
|
-
assert.throws(() => {
|
|
150
|
-
// eslint-disable-next-line no-unused-vars
|
|
151
|
-
const webex = makeWebexCore(
|
|
152
|
-
'http://example.com/#access_token=AT&expires_in=3600&token_type=Bearer&refresh_token=RT&refresh_token_expires_in=36000',
|
|
153
|
-
csrfToken
|
|
154
|
-
);
|
|
155
|
-
}, /Expected CSRF token abcd, but not found in redirect hash/);
|
|
156
|
-
|
|
157
|
-
const webex = makeWebexCore(
|
|
158
|
-
`http://example.com/#access_token=AT&expires_in=3600&token_type=Bearer&refresh_token=RT&refresh_token_expires_in=36000&state=${base64.encode(
|
|
159
|
-
JSON.stringify({csrf_token: csrfToken})
|
|
160
|
-
)}`,
|
|
161
|
-
csrfToken
|
|
162
|
-
);
|
|
163
|
-
|
|
164
|
-
return webex.authorization.when('change:ready').then(() => {
|
|
165
|
-
assert.isTrue(webex.credentials.canAuthorize);
|
|
166
|
-
assert.called(webex.getWindow().sessionStorage.removeItem);
|
|
167
|
-
});
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
it('removes the oauth parameters from the url', () => {
|
|
171
|
-
const csrfToken = 'abcd';
|
|
172
|
-
|
|
173
|
-
const webex = makeWebexCore(
|
|
174
|
-
`http://example.com/#access_token=AT&expires_in=3600&token_type=Bearer&refresh_token=RT&refresh_token_expires_in=36000&state=${base64.encode(
|
|
175
|
-
JSON.stringify({csrf_token: csrfToken, something: true})
|
|
176
|
-
)}`,
|
|
177
|
-
csrfToken
|
|
178
|
-
);
|
|
179
|
-
|
|
180
|
-
return webex.authorization.when('change:ready').then(() => {
|
|
181
|
-
assert.isTrue(webex.credentials.canAuthorize);
|
|
182
|
-
assert.called(webex.getWindow().sessionStorage.removeItem);
|
|
183
|
-
assert.equal(
|
|
184
|
-
webex.getWindow().location.href,
|
|
185
|
-
`http://example.com/#state=${base64.encode(JSON.stringify({something: true}))}`
|
|
186
|
-
);
|
|
187
|
-
});
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
it('throws a grant error when the url contains one', () => {
|
|
191
|
-
let err = null;
|
|
192
|
-
try {
|
|
193
|
-
makeWebexCore('http://127.0.0.1:8000/?error=invalid_scope&error_description=The%20requested%20scope%20is%20invalid.');
|
|
194
|
-
}
|
|
195
|
-
catch (e) {
|
|
196
|
-
err = e;
|
|
197
|
-
}
|
|
198
|
-
expect(err?.message).toBe('Cannot convert object to primitive value')
|
|
199
|
-
});
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
describe('when there is nothing in the url', () => {
|
|
203
|
-
it('sets ready', () => {
|
|
204
|
-
const webex = makeWebexCore('http://example.com');
|
|
205
|
-
|
|
206
|
-
assert.isTrue(webex.authorization.ready);
|
|
207
|
-
assert.isFalse(webex.credentials.canAuthorize);
|
|
208
|
-
});
|
|
209
|
-
});
|
|
210
|
-
});
|
|
211
|
-
|
|
212
|
-
describe('#initiateLogin()', () => {
|
|
213
|
-
describe('when clientType is "public"', () => {
|
|
214
|
-
it('calls #initiateImplicitGrant()', () => {
|
|
215
|
-
const webex = makeWebexCore(undefined, undefined, {
|
|
216
|
-
credentials: {
|
|
217
|
-
clientType: 'public',
|
|
218
|
-
},
|
|
219
|
-
});
|
|
220
|
-
|
|
221
|
-
sinon.spy(webex.authorization, 'initiateImplicitGrant');
|
|
222
|
-
|
|
223
|
-
return webex.authorization.initiateLogin().then(() => {
|
|
224
|
-
assert.called(webex.authorization.initiateImplicitGrant);
|
|
225
|
-
assert.include(webex.getWindow().location, 'response_type=token');
|
|
226
|
-
});
|
|
227
|
-
});
|
|
228
|
-
|
|
229
|
-
it('adds a csrf_token to the login url and sessionStorage', () => {
|
|
230
|
-
const webex = makeWebexCore(undefined, undefined, {
|
|
231
|
-
credentials: {
|
|
232
|
-
clientType: 'public',
|
|
233
|
-
},
|
|
234
|
-
});
|
|
235
|
-
|
|
236
|
-
sinon.spy(webex.authorization, 'initiateImplicitGrant');
|
|
237
|
-
|
|
238
|
-
return webex.authorization.initiateLogin().then(() => {
|
|
239
|
-
assert.called(webex.authorization.initiateImplicitGrant);
|
|
240
|
-
assert.include(webex.getWindow().location, 'response_type=token');
|
|
241
|
-
const {query} = url.parse(webex.getWindow().location, true);
|
|
242
|
-
let {state} = query;
|
|
243
|
-
|
|
244
|
-
state = JSON.parse(base64.decode(state));
|
|
245
|
-
assert.property(state, 'csrf_token');
|
|
246
|
-
assert.isDefined(state.csrf_token);
|
|
247
|
-
assert.match(state.csrf_token, patterns.uuid);
|
|
248
|
-
assert.called(webex.getWindow().sessionStorage.setItem);
|
|
249
|
-
assert.calledWith(
|
|
250
|
-
webex.getWindow().sessionStorage.setItem,
|
|
251
|
-
'oauth2-csrf-token',
|
|
252
|
-
state.csrf_token
|
|
253
|
-
);
|
|
254
|
-
});
|
|
255
|
-
});
|
|
256
|
-
});
|
|
257
|
-
|
|
258
|
-
describe('when clientType is "private"', () => {
|
|
259
|
-
it('calls #initiateAuthorizationCodeGrant()', () => {
|
|
260
|
-
const webex = makeWebexCore(undefined, undefined, {
|
|
261
|
-
credentials: {
|
|
262
|
-
clientType: 'confidential',
|
|
263
|
-
},
|
|
264
|
-
});
|
|
265
|
-
|
|
266
|
-
sinon.spy(webex.authorization, 'initiateAuthorizationCodeGrant');
|
|
267
|
-
|
|
268
|
-
return webex.authorization.initiateLogin().then(() => {
|
|
269
|
-
assert.called(webex.authorization.initiateAuthorizationCodeGrant);
|
|
270
|
-
assert.include(webex.getWindow().location, 'response_type=code');
|
|
271
|
-
});
|
|
272
|
-
});
|
|
273
|
-
|
|
274
|
-
it('adds a csrf_token to the login url and sessionStorage', () => {
|
|
275
|
-
const webex = makeWebexCore(undefined, undefined, {
|
|
276
|
-
credentials: {
|
|
277
|
-
clientType: 'confidential',
|
|
278
|
-
},
|
|
279
|
-
});
|
|
280
|
-
|
|
281
|
-
sinon.spy(webex.authorization, 'initiateAuthorizationCodeGrant');
|
|
282
|
-
|
|
283
|
-
return webex.authorization.initiateLogin().then(() => {
|
|
284
|
-
assert.called(webex.authorization.initiateAuthorizationCodeGrant);
|
|
285
|
-
assert.include(webex.getWindow().location, 'response_type=code');
|
|
286
|
-
const {query} = url.parse(webex.getWindow().location, true);
|
|
287
|
-
let {state} = query;
|
|
288
|
-
|
|
289
|
-
state = JSON.parse(base64.decode(state));
|
|
290
|
-
assert.property(state, 'csrf_token');
|
|
291
|
-
assert.isDefined(state.csrf_token);
|
|
292
|
-
assert.match(state.csrf_token, patterns.uuid);
|
|
293
|
-
assert.called(webex.getWindow().sessionStorage.setItem);
|
|
294
|
-
assert.calledWith(
|
|
295
|
-
webex.getWindow().sessionStorage.setItem,
|
|
296
|
-
'oauth2-csrf-token',
|
|
297
|
-
state.csrf_token
|
|
298
|
-
);
|
|
299
|
-
});
|
|
300
|
-
});
|
|
301
|
-
});
|
|
302
|
-
|
|
303
|
-
it('sets #isAuthorizing', () => {
|
|
304
|
-
const webex = makeWebexCore(undefined, undefined, {
|
|
305
|
-
credentials: {
|
|
306
|
-
clientType: 'confidential',
|
|
307
|
-
},
|
|
308
|
-
});
|
|
309
|
-
|
|
310
|
-
assert.isFalse(webex.authorization.isAuthorizing);
|
|
311
|
-
const p = webex.authorization.initiateLogin();
|
|
312
|
-
|
|
313
|
-
assert.isTrue(webex.authorization.isAuthorizing);
|
|
314
|
-
|
|
315
|
-
return p.then(() => assert.isFalse(webex.authorization.isAuthorizing));
|
|
316
|
-
});
|
|
317
|
-
|
|
318
|
-
it('sets #isAuthenticating', () => {
|
|
319
|
-
const webex = makeWebexCore(undefined, undefined, {
|
|
320
|
-
credentials: {
|
|
321
|
-
clientType: 'confidential',
|
|
322
|
-
},
|
|
323
|
-
});
|
|
324
|
-
|
|
325
|
-
assert.isFalse(webex.authorization.isAuthenticating);
|
|
326
|
-
const p = webex.authorization.initiateLogin();
|
|
327
|
-
|
|
328
|
-
assert.isTrue(webex.authorization.isAuthenticating);
|
|
329
|
-
|
|
330
|
-
return p.then(() => assert.isFalse(webex.authorization.isAuthenticating));
|
|
331
|
-
});
|
|
332
|
-
});
|
|
333
|
-
|
|
334
|
-
describe('#_cleanUrl()', () => {
|
|
335
|
-
it('removes the state parameter when it is empty', () => {
|
|
336
|
-
const webex = makeWebexCore(undefined, undefined, {
|
|
337
|
-
credentials: {
|
|
338
|
-
clientType: 'confidential',
|
|
339
|
-
},
|
|
340
|
-
});
|
|
341
|
-
|
|
342
|
-
sinon.spy(webex.authorization, '_cleanUrl');
|
|
343
|
-
[{}, {state: {}}].forEach((hash) => {
|
|
344
|
-
const location = {hash};
|
|
345
|
-
|
|
346
|
-
webex.authorization._cleanUrl(location);
|
|
347
|
-
assert.equal(webex.getWindow().location.href, '');
|
|
348
|
-
});
|
|
349
|
-
});
|
|
350
|
-
|
|
351
|
-
it('removes the state parameter when only token is present', () => {
|
|
352
|
-
const webex = makeWebexCore(undefined, undefined, {
|
|
353
|
-
credentials: {
|
|
354
|
-
clientType: 'confidential',
|
|
355
|
-
},
|
|
356
|
-
});
|
|
357
|
-
|
|
358
|
-
sinon.spy(webex.authorization, '_cleanUrl');
|
|
359
|
-
const location = {
|
|
360
|
-
hash: {
|
|
361
|
-
state: {
|
|
362
|
-
csrf_token: 'token',
|
|
363
|
-
},
|
|
364
|
-
},
|
|
365
|
-
};
|
|
366
|
-
|
|
367
|
-
webex.authorization._cleanUrl(location);
|
|
368
|
-
assert.equal(webex.getWindow().location.href, '');
|
|
369
|
-
});
|
|
370
|
-
|
|
371
|
-
it('keeps the state parameter when it has keys', () => {
|
|
372
|
-
const webex = makeWebexCore(undefined, undefined, {
|
|
373
|
-
credentials: {
|
|
374
|
-
clientType: 'confidential',
|
|
375
|
-
},
|
|
376
|
-
});
|
|
377
|
-
const location = {
|
|
378
|
-
hash: {
|
|
379
|
-
state: {
|
|
380
|
-
csrf_token: 'token',
|
|
381
|
-
key: 'value',
|
|
382
|
-
},
|
|
383
|
-
},
|
|
384
|
-
};
|
|
385
|
-
|
|
386
|
-
sinon.spy(webex.authorization, '_cleanUrl');
|
|
387
|
-
webex.authorization._cleanUrl(location);
|
|
388
|
-
const {href} = webex.getWindow().location;
|
|
389
|
-
|
|
390
|
-
assert.isDefined(href);
|
|
391
|
-
assert.equal(href, `#state=${base64.encode(JSON.stringify({key: 'value'}))}`);
|
|
392
|
-
assert.notInclude(href, 'csrf_token');
|
|
393
|
-
});
|
|
394
|
-
});
|
|
395
|
-
|
|
396
|
-
describe('#initiateImplicitGrant()', () => {
|
|
397
|
-
it('redirects to the login page with response_type=token', () => {
|
|
398
|
-
const webex = makeWebexCore(undefined, undefined, {
|
|
399
|
-
credentials: {
|
|
400
|
-
clientType: 'public',
|
|
401
|
-
},
|
|
402
|
-
});
|
|
403
|
-
|
|
404
|
-
sinon.spy(webex.authorization, 'initiateImplicitGrant');
|
|
405
|
-
|
|
406
|
-
return webex.authorization.initiateLogin().then(() => {
|
|
407
|
-
assert.called(webex.authorization.initiateImplicitGrant);
|
|
408
|
-
assert.include(webex.getWindow().location, 'response_type=token');
|
|
409
|
-
});
|
|
410
|
-
});
|
|
411
|
-
});
|
|
412
|
-
|
|
413
|
-
describe('#initiateAuthorizationCodeGrant()', () => {
|
|
414
|
-
it('redirects to the login page with response_type=code', () => {
|
|
415
|
-
const webex = makeWebexCore(undefined, undefined, {
|
|
416
|
-
credentials: {
|
|
417
|
-
clientType: 'confidential',
|
|
418
|
-
},
|
|
419
|
-
});
|
|
420
|
-
|
|
421
|
-
sinon.spy(webex.authorization, 'initiateAuthorizationCodeGrant');
|
|
422
|
-
|
|
423
|
-
return webex.authorization.initiateLogin().then(() => {
|
|
424
|
-
assert.called(webex.authorization.initiateAuthorizationCodeGrant);
|
|
425
|
-
assert.include(webex.getWindow().location, 'response_type=code');
|
|
426
|
-
});
|
|
427
|
-
});
|
|
428
|
-
});
|
|
429
|
-
});
|
|
430
|
-
});
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/* eslint camelcase: [0] */
|
|
6
|
+
|
|
7
|
+
import url from 'url';
|
|
8
|
+
|
|
9
|
+
import {assert} from '@webex/test-helper-chai';
|
|
10
|
+
import sinon from 'sinon';
|
|
11
|
+
import MockWebex from '@webex/test-helper-mock-webex';
|
|
12
|
+
import {Credentials} from '@webex/webex-core';
|
|
13
|
+
import Authorization from '@webex/plugin-authorization-browser';
|
|
14
|
+
import {base64, patterns} from '@webex/common';
|
|
15
|
+
import {merge} from 'lodash';
|
|
16
|
+
import {expect} from '@jest/globals';
|
|
17
|
+
|
|
18
|
+
describe('plugin-authorization-browser', () => {
|
|
19
|
+
describe('Authorization', () => {
|
|
20
|
+
function makeWebexCore(href = 'https://example.com', csrfToken = undefined, config = {}) {
|
|
21
|
+
const mockWindow = {
|
|
22
|
+
history: {
|
|
23
|
+
replaceState(a, b, location) {
|
|
24
|
+
mockWindow.location.href = location;
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
location: {
|
|
28
|
+
href,
|
|
29
|
+
},
|
|
30
|
+
sessionStorage: {
|
|
31
|
+
getItem: sinon.stub().returns(csrfToken),
|
|
32
|
+
removeItem: sinon.spy(),
|
|
33
|
+
setItem: sinon.spy(),
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const webex = new MockWebex({
|
|
38
|
+
children: {
|
|
39
|
+
authorization: Authorization,
|
|
40
|
+
credentials: Credentials,
|
|
41
|
+
},
|
|
42
|
+
config: merge(
|
|
43
|
+
{
|
|
44
|
+
credentials: {
|
|
45
|
+
authorizeUrl: `${
|
|
46
|
+
process.env.IDBROKER_BASE_URL || 'https://idbroker.webex.com'
|
|
47
|
+
}/idb/oauth2/v1/authorize`,
|
|
48
|
+
logoutUrl: `${
|
|
49
|
+
process.env.IDBROKER_BASE_URL || 'https://idbroker.webex.com'
|
|
50
|
+
}/idb/oauth2/v1/logout`,
|
|
51
|
+
// eslint-disable-next-line camelcase
|
|
52
|
+
client_id: 'fake',
|
|
53
|
+
// eslint-disable-next-line camelcase
|
|
54
|
+
client_secret: 'fake',
|
|
55
|
+
// eslint-disable-next-line camelcase
|
|
56
|
+
redirect_uri: 'http://example.com',
|
|
57
|
+
// eslint-disable-next-line camelcase
|
|
58
|
+
scope: 'scope:one',
|
|
59
|
+
refreshCallback: () => Promise.resolve(),
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
config
|
|
63
|
+
),
|
|
64
|
+
getWindow() {
|
|
65
|
+
return mockWindow;
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
return webex;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
describe('#initialize()', () => {
|
|
73
|
+
describe('when there is a token in the url', () => {
|
|
74
|
+
it('sets the token and sets ready', () => {
|
|
75
|
+
const webex = makeWebexCore(
|
|
76
|
+
'http://example.com/#access_token=AT&expires_in=3600&token_type=Bearer'
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
assert.isFalse(webex.authorization.ready);
|
|
80
|
+
assert.isFalse(webex.credentials.canAuthorize);
|
|
81
|
+
|
|
82
|
+
return webex.authorization.when('change:ready').then(() => {
|
|
83
|
+
assert.isTrue(webex.authorization.ready);
|
|
84
|
+
assert.isTrue(webex.credentials.canAuthorize);
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
describe('when url parsing is disabled', () => {
|
|
89
|
+
it('sets ready', () => {
|
|
90
|
+
const webex = new MockWebex({
|
|
91
|
+
children: {
|
|
92
|
+
credentials: Credentials,
|
|
93
|
+
},
|
|
94
|
+
getWindow() {
|
|
95
|
+
return {
|
|
96
|
+
location: {
|
|
97
|
+
href: 'http://example.com/#access_token=AT&expires_in=3600&token_type=Bearer',
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
webex.authorization = new Authorization({parse: false}, {parent: webex});
|
|
104
|
+
|
|
105
|
+
assert.isTrue(webex.authorization.ready);
|
|
106
|
+
assert.isFalse(webex.credentials.canAuthorize);
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('sets the token, refresh token and sets ready', () => {
|
|
111
|
+
const webex = makeWebexCore(
|
|
112
|
+
'http://example.com/#access_token=AT&expires_in=3600&token_type=Bearer&refresh_token=RT&refresh_token_expires_in=36000'
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
assert.isFalse(webex.authorization.ready);
|
|
116
|
+
assert.isFalse(webex.credentials.canAuthorize);
|
|
117
|
+
assert.isFalse(webex.credentials.canRefresh);
|
|
118
|
+
|
|
119
|
+
return webex.authorization.when('change:ready').then(() => {
|
|
120
|
+
assert.isTrue(webex.authorization.ready);
|
|
121
|
+
assert.isTrue(webex.credentials.canAuthorize);
|
|
122
|
+
assert.isTrue(webex.credentials.canRefresh);
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('validates the csrf token', () => {
|
|
127
|
+
const csrfToken = 'abcd';
|
|
128
|
+
|
|
129
|
+
assert.throws(() => {
|
|
130
|
+
// eslint-disable-next-line no-unused-vars
|
|
131
|
+
const webex = makeWebexCore(
|
|
132
|
+
`http://example.com/#access_token=AT&expires_in=3600&token_type=Bearer&refresh_token=RT&refresh_token_expires_in=36000&state=${base64.encode(
|
|
133
|
+
JSON.stringify({csrf_token: 'someothertoken'})
|
|
134
|
+
)}`,
|
|
135
|
+
csrfToken
|
|
136
|
+
);
|
|
137
|
+
}, /CSRF token someothertoken does not match stored token abcd/);
|
|
138
|
+
|
|
139
|
+
assert.throws(() => {
|
|
140
|
+
// eslint-disable-next-line no-unused-vars
|
|
141
|
+
const webex = makeWebexCore(
|
|
142
|
+
`http://example.com/#access_token=AT&expires_in=3600&token_type=Bearer&refresh_token=RT&refresh_token_expires_in=36000&state=${base64.encode(
|
|
143
|
+
JSON.stringify({})
|
|
144
|
+
)}`,
|
|
145
|
+
csrfToken
|
|
146
|
+
);
|
|
147
|
+
}, /Expected CSRF token abcd, but not found in redirect hash/);
|
|
148
|
+
|
|
149
|
+
assert.throws(() => {
|
|
150
|
+
// eslint-disable-next-line no-unused-vars
|
|
151
|
+
const webex = makeWebexCore(
|
|
152
|
+
'http://example.com/#access_token=AT&expires_in=3600&token_type=Bearer&refresh_token=RT&refresh_token_expires_in=36000',
|
|
153
|
+
csrfToken
|
|
154
|
+
);
|
|
155
|
+
}, /Expected CSRF token abcd, but not found in redirect hash/);
|
|
156
|
+
|
|
157
|
+
const webex = makeWebexCore(
|
|
158
|
+
`http://example.com/#access_token=AT&expires_in=3600&token_type=Bearer&refresh_token=RT&refresh_token_expires_in=36000&state=${base64.encode(
|
|
159
|
+
JSON.stringify({csrf_token: csrfToken})
|
|
160
|
+
)}`,
|
|
161
|
+
csrfToken
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
return webex.authorization.when('change:ready').then(() => {
|
|
165
|
+
assert.isTrue(webex.credentials.canAuthorize);
|
|
166
|
+
assert.called(webex.getWindow().sessionStorage.removeItem);
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it('removes the oauth parameters from the url', () => {
|
|
171
|
+
const csrfToken = 'abcd';
|
|
172
|
+
|
|
173
|
+
const webex = makeWebexCore(
|
|
174
|
+
`http://example.com/#access_token=AT&expires_in=3600&token_type=Bearer&refresh_token=RT&refresh_token_expires_in=36000&state=${base64.encode(
|
|
175
|
+
JSON.stringify({csrf_token: csrfToken, something: true})
|
|
176
|
+
)}`,
|
|
177
|
+
csrfToken
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
return webex.authorization.when('change:ready').then(() => {
|
|
181
|
+
assert.isTrue(webex.credentials.canAuthorize);
|
|
182
|
+
assert.called(webex.getWindow().sessionStorage.removeItem);
|
|
183
|
+
assert.equal(
|
|
184
|
+
webex.getWindow().location.href,
|
|
185
|
+
`http://example.com/#state=${base64.encode(JSON.stringify({something: true}))}`
|
|
186
|
+
);
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it('throws a grant error when the url contains one', () => {
|
|
191
|
+
let err = null;
|
|
192
|
+
try {
|
|
193
|
+
makeWebexCore('http://127.0.0.1:8000/?error=invalid_scope&error_description=The%20requested%20scope%20is%20invalid.');
|
|
194
|
+
}
|
|
195
|
+
catch (e) {
|
|
196
|
+
err = e;
|
|
197
|
+
}
|
|
198
|
+
expect(err?.message).toBe('Cannot convert object to primitive value')
|
|
199
|
+
});
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
describe('when there is nothing in the url', () => {
|
|
203
|
+
it('sets ready', () => {
|
|
204
|
+
const webex = makeWebexCore('http://example.com');
|
|
205
|
+
|
|
206
|
+
assert.isTrue(webex.authorization.ready);
|
|
207
|
+
assert.isFalse(webex.credentials.canAuthorize);
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
describe('#initiateLogin()', () => {
|
|
213
|
+
describe('when clientType is "public"', () => {
|
|
214
|
+
it('calls #initiateImplicitGrant()', () => {
|
|
215
|
+
const webex = makeWebexCore(undefined, undefined, {
|
|
216
|
+
credentials: {
|
|
217
|
+
clientType: 'public',
|
|
218
|
+
},
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
sinon.spy(webex.authorization, 'initiateImplicitGrant');
|
|
222
|
+
|
|
223
|
+
return webex.authorization.initiateLogin().then(() => {
|
|
224
|
+
assert.called(webex.authorization.initiateImplicitGrant);
|
|
225
|
+
assert.include(webex.getWindow().location, 'response_type=token');
|
|
226
|
+
});
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
it('adds a csrf_token to the login url and sessionStorage', () => {
|
|
230
|
+
const webex = makeWebexCore(undefined, undefined, {
|
|
231
|
+
credentials: {
|
|
232
|
+
clientType: 'public',
|
|
233
|
+
},
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
sinon.spy(webex.authorization, 'initiateImplicitGrant');
|
|
237
|
+
|
|
238
|
+
return webex.authorization.initiateLogin().then(() => {
|
|
239
|
+
assert.called(webex.authorization.initiateImplicitGrant);
|
|
240
|
+
assert.include(webex.getWindow().location, 'response_type=token');
|
|
241
|
+
const {query} = url.parse(webex.getWindow().location, true);
|
|
242
|
+
let {state} = query;
|
|
243
|
+
|
|
244
|
+
state = JSON.parse(base64.decode(state));
|
|
245
|
+
assert.property(state, 'csrf_token');
|
|
246
|
+
assert.isDefined(state.csrf_token);
|
|
247
|
+
assert.match(state.csrf_token, patterns.uuid);
|
|
248
|
+
assert.called(webex.getWindow().sessionStorage.setItem);
|
|
249
|
+
assert.calledWith(
|
|
250
|
+
webex.getWindow().sessionStorage.setItem,
|
|
251
|
+
'oauth2-csrf-token',
|
|
252
|
+
state.csrf_token
|
|
253
|
+
);
|
|
254
|
+
});
|
|
255
|
+
});
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
describe('when clientType is "private"', () => {
|
|
259
|
+
it('calls #initiateAuthorizationCodeGrant()', () => {
|
|
260
|
+
const webex = makeWebexCore(undefined, undefined, {
|
|
261
|
+
credentials: {
|
|
262
|
+
clientType: 'confidential',
|
|
263
|
+
},
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
sinon.spy(webex.authorization, 'initiateAuthorizationCodeGrant');
|
|
267
|
+
|
|
268
|
+
return webex.authorization.initiateLogin().then(() => {
|
|
269
|
+
assert.called(webex.authorization.initiateAuthorizationCodeGrant);
|
|
270
|
+
assert.include(webex.getWindow().location, 'response_type=code');
|
|
271
|
+
});
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
it('adds a csrf_token to the login url and sessionStorage', () => {
|
|
275
|
+
const webex = makeWebexCore(undefined, undefined, {
|
|
276
|
+
credentials: {
|
|
277
|
+
clientType: 'confidential',
|
|
278
|
+
},
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
sinon.spy(webex.authorization, 'initiateAuthorizationCodeGrant');
|
|
282
|
+
|
|
283
|
+
return webex.authorization.initiateLogin().then(() => {
|
|
284
|
+
assert.called(webex.authorization.initiateAuthorizationCodeGrant);
|
|
285
|
+
assert.include(webex.getWindow().location, 'response_type=code');
|
|
286
|
+
const {query} = url.parse(webex.getWindow().location, true);
|
|
287
|
+
let {state} = query;
|
|
288
|
+
|
|
289
|
+
state = JSON.parse(base64.decode(state));
|
|
290
|
+
assert.property(state, 'csrf_token');
|
|
291
|
+
assert.isDefined(state.csrf_token);
|
|
292
|
+
assert.match(state.csrf_token, patterns.uuid);
|
|
293
|
+
assert.called(webex.getWindow().sessionStorage.setItem);
|
|
294
|
+
assert.calledWith(
|
|
295
|
+
webex.getWindow().sessionStorage.setItem,
|
|
296
|
+
'oauth2-csrf-token',
|
|
297
|
+
state.csrf_token
|
|
298
|
+
);
|
|
299
|
+
});
|
|
300
|
+
});
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
it('sets #isAuthorizing', () => {
|
|
304
|
+
const webex = makeWebexCore(undefined, undefined, {
|
|
305
|
+
credentials: {
|
|
306
|
+
clientType: 'confidential',
|
|
307
|
+
},
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
assert.isFalse(webex.authorization.isAuthorizing);
|
|
311
|
+
const p = webex.authorization.initiateLogin();
|
|
312
|
+
|
|
313
|
+
assert.isTrue(webex.authorization.isAuthorizing);
|
|
314
|
+
|
|
315
|
+
return p.then(() => assert.isFalse(webex.authorization.isAuthorizing));
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
it('sets #isAuthenticating', () => {
|
|
319
|
+
const webex = makeWebexCore(undefined, undefined, {
|
|
320
|
+
credentials: {
|
|
321
|
+
clientType: 'confidential',
|
|
322
|
+
},
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
assert.isFalse(webex.authorization.isAuthenticating);
|
|
326
|
+
const p = webex.authorization.initiateLogin();
|
|
327
|
+
|
|
328
|
+
assert.isTrue(webex.authorization.isAuthenticating);
|
|
329
|
+
|
|
330
|
+
return p.then(() => assert.isFalse(webex.authorization.isAuthenticating));
|
|
331
|
+
});
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
describe('#_cleanUrl()', () => {
|
|
335
|
+
it('removes the state parameter when it is empty', () => {
|
|
336
|
+
const webex = makeWebexCore(undefined, undefined, {
|
|
337
|
+
credentials: {
|
|
338
|
+
clientType: 'confidential',
|
|
339
|
+
},
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
sinon.spy(webex.authorization, '_cleanUrl');
|
|
343
|
+
[{}, {state: {}}].forEach((hash) => {
|
|
344
|
+
const location = {hash};
|
|
345
|
+
|
|
346
|
+
webex.authorization._cleanUrl(location);
|
|
347
|
+
assert.equal(webex.getWindow().location.href, '');
|
|
348
|
+
});
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
it('removes the state parameter when only token is present', () => {
|
|
352
|
+
const webex = makeWebexCore(undefined, undefined, {
|
|
353
|
+
credentials: {
|
|
354
|
+
clientType: 'confidential',
|
|
355
|
+
},
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
sinon.spy(webex.authorization, '_cleanUrl');
|
|
359
|
+
const location = {
|
|
360
|
+
hash: {
|
|
361
|
+
state: {
|
|
362
|
+
csrf_token: 'token',
|
|
363
|
+
},
|
|
364
|
+
},
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
webex.authorization._cleanUrl(location);
|
|
368
|
+
assert.equal(webex.getWindow().location.href, '');
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
it('keeps the state parameter when it has keys', () => {
|
|
372
|
+
const webex = makeWebexCore(undefined, undefined, {
|
|
373
|
+
credentials: {
|
|
374
|
+
clientType: 'confidential',
|
|
375
|
+
},
|
|
376
|
+
});
|
|
377
|
+
const location = {
|
|
378
|
+
hash: {
|
|
379
|
+
state: {
|
|
380
|
+
csrf_token: 'token',
|
|
381
|
+
key: 'value',
|
|
382
|
+
},
|
|
383
|
+
},
|
|
384
|
+
};
|
|
385
|
+
|
|
386
|
+
sinon.spy(webex.authorization, '_cleanUrl');
|
|
387
|
+
webex.authorization._cleanUrl(location);
|
|
388
|
+
const {href} = webex.getWindow().location;
|
|
389
|
+
|
|
390
|
+
assert.isDefined(href);
|
|
391
|
+
assert.equal(href, `#state=${base64.encode(JSON.stringify({key: 'value'}))}`);
|
|
392
|
+
assert.notInclude(href, 'csrf_token');
|
|
393
|
+
});
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
describe('#initiateImplicitGrant()', () => {
|
|
397
|
+
it('redirects to the login page with response_type=token', () => {
|
|
398
|
+
const webex = makeWebexCore(undefined, undefined, {
|
|
399
|
+
credentials: {
|
|
400
|
+
clientType: 'public',
|
|
401
|
+
},
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
sinon.spy(webex.authorization, 'initiateImplicitGrant');
|
|
405
|
+
|
|
406
|
+
return webex.authorization.initiateLogin().then(() => {
|
|
407
|
+
assert.called(webex.authorization.initiateImplicitGrant);
|
|
408
|
+
assert.include(webex.getWindow().location, 'response_type=token');
|
|
409
|
+
});
|
|
410
|
+
});
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
describe('#initiateAuthorizationCodeGrant()', () => {
|
|
414
|
+
it('redirects to the login page with response_type=code', () => {
|
|
415
|
+
const webex = makeWebexCore(undefined, undefined, {
|
|
416
|
+
credentials: {
|
|
417
|
+
clientType: 'confidential',
|
|
418
|
+
},
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
sinon.spy(webex.authorization, 'initiateAuthorizationCodeGrant');
|
|
422
|
+
|
|
423
|
+
return webex.authorization.initiateLogin().then(() => {
|
|
424
|
+
assert.called(webex.authorization.initiateAuthorizationCodeGrant);
|
|
425
|
+
assert.include(webex.getWindow().location, 'response_type=code');
|
|
426
|
+
});
|
|
427
|
+
});
|
|
428
|
+
});
|
|
429
|
+
});
|
|
430
|
+
});
|