@webex/plugin-authorization-browser 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.
@@ -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
+ });