@webex/plugin-authorization-browser-first-party 3.0.0-beta.13 → 3.0.0-beta.15
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/dist/authorization.js +1 -1
- package/dist/authorization.js.map +1 -1
- package/dist/config.js.map +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +10 -10
- package/src/authorization.js +33 -36
- package/src/config.js +18 -17
- package/src/index.js +2 -5
- package/test/automation/fixtures/app.js +15 -15
- package/test/automation/fixtures/index.html +18 -15
- package/test/automation/spec/authorization-code-grant.js +86 -68
- package/test/unit/spec/authorization.js +177 -155
package/dist/authorization.js
CHANGED
|
@@ -379,7 +379,7 @@ var Authorization = _webexCore.WebexPlugin.extend((_dec = (0, _common.whileInFli
|
|
|
379
379
|
throw new Error("CSRF token ".concat(token, " does not match stored token ").concat(sessionToken));
|
|
380
380
|
}
|
|
381
381
|
},
|
|
382
|
-
version: "3.0.0-beta.
|
|
382
|
+
version: "3.0.0-beta.15"
|
|
383
383
|
}, ((0, _applyDecoratedDescriptor2.default)(_obj, "initiateAuthorizationCodeGrant", [_dec], (0, _getOwnPropertyDescriptor.default)(_obj, "initiateAuthorizationCodeGrant"), _obj), (0, _applyDecoratedDescriptor2.default)(_obj, "requestAuthorizationCodeGrant", [_dec2, _common.oneFlight], (0, _getOwnPropertyDescriptor.default)(_obj, "requestAuthorizationCodeGrant"), _obj)), _obj)));
|
|
384
384
|
|
|
385
385
|
var _default = Authorization;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["lodash","require","OAUTH2_CSRF_TOKEN","OAUTH2_CODE_VERIFIER","Authorization","WebexPlugin","extend","whileInFlight","derived","isAuthenticating","deps","fn","isAuthorizing","session","default","type","ready","namespace","initialize","attrs","ret","prototype","location","url","parse","webex","getWindow","href","_checkForErrors","code","query","state","JSON","base64","decode","codeVerifier","sessionStorage","getItem","removeItem","emailhash","_verifySecurityToken","_cleanUrl","process","nextTick","internal","services","collectPreauthCatalog","catch","resolve","then","requestAuthorizationCodeGrant","initiateLogin","options","email","emailHash","CryptoJS","SHA256","toString","csrf_token","_generateSecurityToken","code_challenge","_generateCodeChallenge","code_challenge_method","initiateAuthorizationCodeGrant","logger","info","credentials","buildLoginUrl","response_type","logout","noRedirect","buildLogoutUrl","reject","Error","form","grant_type","redirect_uri","config","self_contained_token","code_verifier","request","method","uri","tokenUrl","auth","user","client_id","pass","client_secret","sendImmediately","shouldRefreshAccessToken","res","set","supertoken","body","statusCode","ErrorConstructor","grantErrors","select","error","_res","history","replaceState","encode","search","querystring","stringify","format","safeCharacterMap","base64url","_safe_map","times","random","length","join","codeChallenge","setItem","token","uuid","v4","sessionToken","oneFlight"],"sources":["authorization.js"],"sourcesContent":["/*!\n * Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.\n */\n\n/* eslint camelcase: [0] */\n\nimport querystring from 'querystring';\nimport url from 'url';\n\nimport {base64, oneFlight, whileInFlight} from '@webex/common';\nimport {grantErrors, WebexPlugin} from '@webex/webex-core';\nimport {cloneDeep, isEmpty, omit} from 'lodash';\nimport uuid from 'uuid';\nimport base64url from 'crypto-js/enc-base64url';\nimport CryptoJS from 'crypto-js';\n\n// Necessary to require lodash this way in order to stub\n// methods in the unit test\nconst lodash = require('lodash');\n\nconst OAUTH2_CSRF_TOKEN = 'oauth2-csrf-token';\nconst OAUTH2_CODE_VERIFIER = 'oauth2-code-verifier';\n\n/**\n * Browser support for OAuth2. Automatically parses the URL query for an\n * authorization code\n *\n * Use of this plugin for anything other than the Webex Web Client is strongly\n * discouraged and may be broken at any time\n * @class\n * @name AuthorizationBrowserFirstParty\n * @private\n */\nconst Authorization = WebexPlugin.extend({\n derived: {\n /**\n * Alias of {@link AuthorizationBrowserFirstParty#isAuthorizing}\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @type {boolean}\n */\n isAuthenticating: {\n deps: ['isAuthorizing'],\n fn() {\n return this.isAuthorizing;\n }\n }\n },\n\n session: {\n /**\n * Indicates if an Authorization Code exchange is inflight\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @type {boolean}\n */\n isAuthorizing: {\n default: false,\n type: 'boolean'\n },\n ready: {\n default: false,\n type: 'boolean'\n }\n },\n\n namespace: 'Credentials',\n\n /**\n * Initializer\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @private\n * @returns {Authorization}\n */\n // eslint-disable-next-line complexity\n initialize(...attrs) {\n const ret = Reflect.apply(WebexPlugin.prototype.initialize, this, attrs);\n const location = url.parse(this.webex.getWindow().location.href, true);\n\n this._checkForErrors(location);\n\n const {code} = location.query;\n\n if (!code) {\n this.ready = true;\n\n return ret;\n }\n\n if (location.query.state) {\n location.query.state = JSON.parse(base64.decode(location.query.state));\n }\n else {\n location.query.state = {};\n }\n\n const codeVerifier = this.webex.getWindow().sessionStorage.getItem(OAUTH2_CODE_VERIFIER);\n\n this.webex.getWindow().sessionStorage.removeItem(OAUTH2_CODE_VERIFIER);\n\n\n const {emailhash} = location.query.state;\n\n this._verifySecurityToken(location.query);\n this._cleanUrl(location);\n\n // Wait until nextTick in case `credentials` hasn't initialized yet\n process.nextTick(() => {\n this.webex.internal.services.collectPreauthCatalog({emailhash})\n .catch(() => Promise.resolve())\n .then(() => this.requestAuthorizationCodeGrant({code, codeVerifier}))\n .then(() => {\n this.ready = true;\n });\n });\n\n\n return ret;\n },\n\n /**\n * Kicks off an oauth flow\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @param {Object} options\n * @returns {Promise}\n */\n initiateLogin(options = {}) {\n options = cloneDeep(options);\n if (options.email) {\n options.emailHash = CryptoJS.SHA256(options.email).toString();\n }\n delete options.email;\n options.state = options.state || {};\n options.state.csrf_token = this._generateSecurityToken();\n // catalog uses emailhash and redirectCI uses emailHash\n options.state.emailhash = options.emailHash;\n\n options.code_challenge = this._generateCodeChallenge();\n options.code_challenge_method = 'S256';\n\n\n return this.initiateAuthorizationCodeGrant(options);\n },\n\n @whileInFlight('isAuthorizing')\n /**\n * Kicks off the Implicit Code grant flow. Typically called via\n * {@link AuthorizationBrowserFirstParty#initiateLogin}\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @param {Object} options\n * @returns {Promise}\n */\n initiateAuthorizationCodeGrant(options) {\n this.logger.info('authorization: initiating authorization code grant flow');\n this.webex.getWindow().location = this.webex.credentials.buildLoginUrl(Object.assign({response_type: 'code'}, options));\n\n return Promise.resolve();\n },\n\n /**\n * Called by {@link WebexCore#logout()}. Redirects to the logout page\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @param {Object} options\n * @param {boolean} options.noRedirect if true, does not redirect\n * @returns {Promise}\n */\n logout(options = {}) {\n if (!options.noRedirect) {\n this.webex.getWindow().location = this.webex.credentials.buildLogoutUrl(options);\n }\n },\n\n\n @whileInFlight('isAuthorizing')\n @oneFlight\n /**\n * Exchanges an authorization code for an access token\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @param {Object} options\n * @param {Object} options.code\n * @returns {Promise}\n */\n requestAuthorizationCodeGrant(options = {}) {\n this.logger.info('credentials: requesting authorization code grant');\n\n if (!options.code) {\n return Promise.reject(new Error('`options.code` is required'));\n }\n\n const form = {\n grant_type: 'authorization_code',\n redirect_uri: this.config.redirect_uri,\n code: options.code,\n self_contained_token: true\n };\n\n if (options.codeVerifier) {\n form.code_verifier = options.codeVerifier;\n }\n\n return this.webex.request({\n method: 'POST',\n uri: this.config.tokenUrl,\n form,\n auth: {\n user: this.config.client_id,\n pass: this.config.client_secret,\n sendImmediately: true\n },\n shouldRefreshAccessToken: false\n })\n .then((res) => {\n this.webex.credentials.set({supertoken: res.body});\n })\n .catch((res) => {\n if (res.statusCode !== 400) {\n return Promise.reject(res);\n }\n\n const ErrorConstructor = grantErrors.select(res.body.error);\n\n return Promise.reject(new ErrorConstructor(res._res || res));\n });\n },\n\n /**\n * Checks if the result of the login redirect contains an error string\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @param {Object} location\n * @private\n * @returns {Promise}\n */\n _checkForErrors(location) {\n const {query} = location;\n\n if (query && query.error) {\n const ErrorConstructor = grantErrors.select(query.error);\n\n throw new ErrorConstructor(query);\n }\n },\n\n /**\n * Removes no-longer needed values from the url (access token, csrf token, etc)\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @param {Object} location\n * @private\n * @returns {Promise}\n */\n _cleanUrl(location) {\n location = cloneDeep(location);\n if (this.webex.getWindow().history && this.webex.getWindow().history.replaceState) {\n Reflect.deleteProperty(location.query, 'code');\n if (isEmpty(omit(location.query.state, 'csrf_token'))) {\n Reflect.deleteProperty(location.query, 'state');\n }\n else {\n location.query.state = base64.encode(JSON.stringify(omit(location.query.state, 'csrf_token')));\n }\n location.search = querystring.stringify(location.query);\n Reflect.deleteProperty(location, 'query');\n this.webex.getWindow().history.replaceState({}, null, url.format(location));\n }\n },\n\n /**\n * Generates PKCE code verifier and code challenge and sets the the code verifier in sessionStorage\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @private\n * @returns {string}\n */\n _generateCodeChallenge() {\n this.logger.info('authorization: generating PKCE code challenge');\n\n // eslint-disable-next-line no-underscore-dangle\n const safeCharacterMap = base64url._safe_map;\n\n const codeVerifier = lodash.times(\n 128,\n () => safeCharacterMap[lodash.random(0, safeCharacterMap.length - 1)]\n ).join('');\n\n const codeChallenge = CryptoJS.SHA256(codeVerifier).toString(base64url);\n\n this.webex.getWindow().sessionStorage.setItem(\n OAUTH2_CODE_VERIFIER, codeVerifier\n );\n\n return codeChallenge;\n },\n\n /**\n * Generates a CSRF token and sticks in in sessionStorage\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @private\n * @returns {Promise}\n */\n _generateSecurityToken() {\n this.logger.info('authorization: generating csrf token');\n\n const token = uuid.v4();\n\n this.webex.getWindow().sessionStorage.setItem('oauth2-csrf-token', token);\n\n return token;\n },\n\n /**\n * Checks if the CSRF token in sessionStorage is the same as the one returned\n * in the url.\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @param {Object} query\n * @private\n * @returns {Promise}\n */\n _verifySecurityToken(query) {\n const sessionToken = this.webex.getWindow().sessionStorage.getItem(OAUTH2_CSRF_TOKEN);\n\n this.webex.getWindow().sessionStorage.removeItem(OAUTH2_CSRF_TOKEN);\n if (!sessionToken) {\n return;\n }\n\n if (!query.state) {\n throw new Error(`Expected CSRF token ${sessionToken}, but not found in redirect query`);\n }\n\n if (!query.state.csrf_token) {\n throw new Error(`Expected CSRF token ${sessionToken}, but not found in redirect query`);\n }\n\n const token = query.state.csrf_token;\n\n if (token !== sessionToken) {\n throw new Error(`CSRF token ${token} does not match stored token ${sessionToken}`);\n }\n }\n});\n\nexport default Authorization;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"names":["lodash","require","OAUTH2_CSRF_TOKEN","OAUTH2_CODE_VERIFIER","Authorization","WebexPlugin","extend","whileInFlight","derived","isAuthenticating","deps","fn","isAuthorizing","session","default","type","ready","namespace","initialize","attrs","ret","prototype","location","url","parse","webex","getWindow","href","_checkForErrors","code","query","state","JSON","base64","decode","codeVerifier","sessionStorage","getItem","removeItem","emailhash","_verifySecurityToken","_cleanUrl","process","nextTick","internal","services","collectPreauthCatalog","catch","resolve","then","requestAuthorizationCodeGrant","initiateLogin","options","email","emailHash","CryptoJS","SHA256","toString","csrf_token","_generateSecurityToken","code_challenge","_generateCodeChallenge","code_challenge_method","initiateAuthorizationCodeGrant","logger","info","credentials","buildLoginUrl","response_type","logout","noRedirect","buildLogoutUrl","reject","Error","form","grant_type","redirect_uri","config","self_contained_token","code_verifier","request","method","uri","tokenUrl","auth","user","client_id","pass","client_secret","sendImmediately","shouldRefreshAccessToken","res","set","supertoken","body","statusCode","ErrorConstructor","grantErrors","select","error","_res","history","replaceState","encode","search","querystring","stringify","format","safeCharacterMap","base64url","_safe_map","times","random","length","join","codeChallenge","setItem","token","uuid","v4","sessionToken","oneFlight"],"sources":["authorization.js"],"sourcesContent":["/*!\n * Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.\n */\n\n/* eslint camelcase: [0] */\n\nimport querystring from 'querystring';\nimport url from 'url';\n\nimport {base64, oneFlight, whileInFlight} from '@webex/common';\nimport {grantErrors, WebexPlugin} from '@webex/webex-core';\nimport {cloneDeep, isEmpty, omit} from 'lodash';\nimport uuid from 'uuid';\nimport base64url from 'crypto-js/enc-base64url';\nimport CryptoJS from 'crypto-js';\n\n// Necessary to require lodash this way in order to stub\n// methods in the unit test\nconst lodash = require('lodash');\n\nconst OAUTH2_CSRF_TOKEN = 'oauth2-csrf-token';\nconst OAUTH2_CODE_VERIFIER = 'oauth2-code-verifier';\n\n/**\n * Browser support for OAuth2. Automatically parses the URL query for an\n * authorization code\n *\n * Use of this plugin for anything other than the Webex Web Client is strongly\n * discouraged and may be broken at any time\n * @class\n * @name AuthorizationBrowserFirstParty\n * @private\n */\nconst Authorization = WebexPlugin.extend({\n derived: {\n /**\n * Alias of {@link AuthorizationBrowserFirstParty#isAuthorizing}\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @type {boolean}\n */\n isAuthenticating: {\n deps: ['isAuthorizing'],\n fn() {\n return this.isAuthorizing;\n },\n },\n },\n\n session: {\n /**\n * Indicates if an Authorization Code exchange is inflight\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @type {boolean}\n */\n isAuthorizing: {\n default: false,\n type: 'boolean',\n },\n ready: {\n default: false,\n type: 'boolean',\n },\n },\n\n namespace: 'Credentials',\n\n /**\n * Initializer\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @private\n * @returns {Authorization}\n */\n // eslint-disable-next-line complexity\n initialize(...attrs) {\n const ret = Reflect.apply(WebexPlugin.prototype.initialize, this, attrs);\n const location = url.parse(this.webex.getWindow().location.href, true);\n\n this._checkForErrors(location);\n\n const {code} = location.query;\n\n if (!code) {\n this.ready = true;\n\n return ret;\n }\n\n if (location.query.state) {\n location.query.state = JSON.parse(base64.decode(location.query.state));\n } else {\n location.query.state = {};\n }\n\n const codeVerifier = this.webex.getWindow().sessionStorage.getItem(OAUTH2_CODE_VERIFIER);\n\n this.webex.getWindow().sessionStorage.removeItem(OAUTH2_CODE_VERIFIER);\n\n const {emailhash} = location.query.state;\n\n this._verifySecurityToken(location.query);\n this._cleanUrl(location);\n\n // Wait until nextTick in case `credentials` hasn't initialized yet\n process.nextTick(() => {\n this.webex.internal.services\n .collectPreauthCatalog({emailhash})\n .catch(() => Promise.resolve())\n .then(() => this.requestAuthorizationCodeGrant({code, codeVerifier}))\n .then(() => {\n this.ready = true;\n });\n });\n\n return ret;\n },\n\n /**\n * Kicks off an oauth flow\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @param {Object} options\n * @returns {Promise}\n */\n initiateLogin(options = {}) {\n options = cloneDeep(options);\n if (options.email) {\n options.emailHash = CryptoJS.SHA256(options.email).toString();\n }\n delete options.email;\n options.state = options.state || {};\n options.state.csrf_token = this._generateSecurityToken();\n // catalog uses emailhash and redirectCI uses emailHash\n options.state.emailhash = options.emailHash;\n\n options.code_challenge = this._generateCodeChallenge();\n options.code_challenge_method = 'S256';\n\n return this.initiateAuthorizationCodeGrant(options);\n },\n\n @whileInFlight('isAuthorizing')\n /**\n * Kicks off the Implicit Code grant flow. Typically called via\n * {@link AuthorizationBrowserFirstParty#initiateLogin}\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @param {Object} options\n * @returns {Promise}\n */\n initiateAuthorizationCodeGrant(options) {\n this.logger.info('authorization: initiating authorization code grant flow');\n this.webex.getWindow().location = this.webex.credentials.buildLoginUrl(\n Object.assign({response_type: 'code'}, options)\n );\n\n return Promise.resolve();\n },\n\n /**\n * Called by {@link WebexCore#logout()}. Redirects to the logout page\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @param {Object} options\n * @param {boolean} options.noRedirect if true, does not redirect\n * @returns {Promise}\n */\n logout(options = {}) {\n if (!options.noRedirect) {\n this.webex.getWindow().location = this.webex.credentials.buildLogoutUrl(options);\n }\n },\n\n @whileInFlight('isAuthorizing')\n @oneFlight\n /**\n * Exchanges an authorization code for an access token\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @param {Object} options\n * @param {Object} options.code\n * @returns {Promise}\n */\n requestAuthorizationCodeGrant(options = {}) {\n this.logger.info('credentials: requesting authorization code grant');\n\n if (!options.code) {\n return Promise.reject(new Error('`options.code` is required'));\n }\n\n const form = {\n grant_type: 'authorization_code',\n redirect_uri: this.config.redirect_uri,\n code: options.code,\n self_contained_token: true,\n };\n\n if (options.codeVerifier) {\n form.code_verifier = options.codeVerifier;\n }\n\n return this.webex\n .request({\n method: 'POST',\n uri: this.config.tokenUrl,\n form,\n auth: {\n user: this.config.client_id,\n pass: this.config.client_secret,\n sendImmediately: true,\n },\n shouldRefreshAccessToken: false,\n })\n .then((res) => {\n this.webex.credentials.set({supertoken: res.body});\n })\n .catch((res) => {\n if (res.statusCode !== 400) {\n return Promise.reject(res);\n }\n\n const ErrorConstructor = grantErrors.select(res.body.error);\n\n return Promise.reject(new ErrorConstructor(res._res || res));\n });\n },\n\n /**\n * Checks if the result of the login redirect contains an error string\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @param {Object} location\n * @private\n * @returns {Promise}\n */\n _checkForErrors(location) {\n const {query} = location;\n\n if (query && query.error) {\n const ErrorConstructor = grantErrors.select(query.error);\n\n throw new ErrorConstructor(query);\n }\n },\n\n /**\n * Removes no-longer needed values from the url (access token, csrf token, etc)\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @param {Object} location\n * @private\n * @returns {Promise}\n */\n _cleanUrl(location) {\n location = cloneDeep(location);\n if (this.webex.getWindow().history && this.webex.getWindow().history.replaceState) {\n Reflect.deleteProperty(location.query, 'code');\n if (isEmpty(omit(location.query.state, 'csrf_token'))) {\n Reflect.deleteProperty(location.query, 'state');\n } else {\n location.query.state = base64.encode(\n JSON.stringify(omit(location.query.state, 'csrf_token'))\n );\n }\n location.search = querystring.stringify(location.query);\n Reflect.deleteProperty(location, 'query');\n this.webex.getWindow().history.replaceState({}, null, url.format(location));\n }\n },\n\n /**\n * Generates PKCE code verifier and code challenge and sets the the code verifier in sessionStorage\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @private\n * @returns {string}\n */\n _generateCodeChallenge() {\n this.logger.info('authorization: generating PKCE code challenge');\n\n // eslint-disable-next-line no-underscore-dangle\n const safeCharacterMap = base64url._safe_map;\n\n const codeVerifier = lodash\n .times(128, () => safeCharacterMap[lodash.random(0, safeCharacterMap.length - 1)])\n .join('');\n\n const codeChallenge = CryptoJS.SHA256(codeVerifier).toString(base64url);\n\n this.webex.getWindow().sessionStorage.setItem(OAUTH2_CODE_VERIFIER, codeVerifier);\n\n return codeChallenge;\n },\n\n /**\n * Generates a CSRF token and sticks in in sessionStorage\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @private\n * @returns {Promise}\n */\n _generateSecurityToken() {\n this.logger.info('authorization: generating csrf token');\n\n const token = uuid.v4();\n\n this.webex.getWindow().sessionStorage.setItem('oauth2-csrf-token', token);\n\n return token;\n },\n\n /**\n * Checks if the CSRF token in sessionStorage is the same as the one returned\n * in the url.\n * @instance\n * @memberof AuthorizationBrowserFirstParty\n * @param {Object} query\n * @private\n * @returns {Promise}\n */\n _verifySecurityToken(query) {\n const sessionToken = this.webex.getWindow().sessionStorage.getItem(OAUTH2_CSRF_TOKEN);\n\n this.webex.getWindow().sessionStorage.removeItem(OAUTH2_CSRF_TOKEN);\n if (!sessionToken) {\n return;\n }\n\n if (!query.state) {\n throw new Error(`Expected CSRF token ${sessionToken}, but not found in redirect query`);\n }\n\n if (!query.state.csrf_token) {\n throw new Error(`Expected CSRF token ${sessionToken}, but not found in redirect query`);\n }\n\n const token = query.state.csrf_token;\n\n if (token !== sessionToken) {\n throw new Error(`CSRF token ${token} does not match stored token ${sessionToken}`);\n }\n },\n});\n\nexport default Authorization;\n"],"mappings":"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"}
|
package/dist/config.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["credentials","clientType","refreshCallback","webex","token","request","method","uri","config","tokenUrl","form","grant_type","redirect_uri","refresh_token","auth","user","client_id","pass","client_secret","sendImmediately","shouldRefreshAccessToken","then","res","body"],"sources":["config.js"],"sourcesContent":["/*!\n * Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.\n */\n\nexport default {\n credentials: {\n /**\n * Controls whether {@link Authorization#initiateLogin()} requests a token\n * or an auth code. Anything other than 'confidential' will be treated as\n * 'public'\n * @private\n * @type {string}\n */\n clientType: 'public',\n\n refreshCallback(webex, token) {\n /* eslint-disable camelcase */\n return webex.request({\n
|
|
1
|
+
{"version":3,"names":["credentials","clientType","refreshCallback","webex","token","request","method","uri","config","tokenUrl","form","grant_type","redirect_uri","refresh_token","auth","user","client_id","pass","client_secret","sendImmediately","shouldRefreshAccessToken","then","res","body"],"sources":["config.js"],"sourcesContent":["/*!\n * Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.\n */\n\nexport default {\n credentials: {\n /**\n * Controls whether {@link Authorization#initiateLogin()} requests a token\n * or an auth code. Anything other than 'confidential' will be treated as\n * 'public'\n * @private\n * @type {string}\n */\n clientType: 'public',\n\n refreshCallback(webex, token) {\n /* eslint-disable camelcase */\n return webex\n .request({\n method: 'POST',\n uri: token.config.tokenUrl,\n form: {\n grant_type: 'refresh_token',\n redirect_uri: token.config.redirect_uri,\n refresh_token: token.refresh_token,\n },\n auth: {\n user: token.config.client_id,\n pass: token.config.client_secret,\n sendImmediately: true,\n },\n shouldRefreshAccessToken: false,\n })\n .then((res) => res.body);\n /* eslint-enable camelcase */\n },\n },\n};\n"],"mappings":";;;;;;;;;;AAAA;AACA;AACA;eAEe;EACbA,WAAW,EAAE;IACX;AACJ;AACA;AACA;AACA;AACA;AACA;IACIC,UAAU,EAAE,QARD;IAUXC,eAVW,2BAUKC,KAVL,EAUYC,KAVZ,EAUmB;MAC5B;MACA,OAAOD,KAAK,CACTE,OADI,CACI;QACPC,MAAM,EAAE,MADD;QAEPC,GAAG,EAAEH,KAAK,CAACI,MAAN,CAAaC,QAFX;QAGPC,IAAI,EAAE;UACJC,UAAU,EAAE,eADR;UAEJC,YAAY,EAAER,KAAK,CAACI,MAAN,CAAaI,YAFvB;UAGJC,aAAa,EAAET,KAAK,CAACS;QAHjB,CAHC;QAQPC,IAAI,EAAE;UACJC,IAAI,EAAEX,KAAK,CAACI,MAAN,CAAaQ,SADf;UAEJC,IAAI,EAAEb,KAAK,CAACI,MAAN,CAAaU,aAFf;UAGJC,eAAe,EAAE;QAHb,CARC;QAaPC,wBAAwB,EAAE;MAbnB,CADJ,EAgBJC,IAhBI,CAgBC,UAACC,GAAD;QAAA,OAASA,GAAG,CAACC,IAAb;MAAA,CAhBD,CAAP;MAiBA;IACD;EA9BU;AADA,C"}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["proxies","registerPlugin","Authorization","config"],"sources":["index.js"],"sourcesContent":["/*!\n * Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.\n */\n\nimport {registerPlugin} from '@webex/webex-core';\n\nimport Authorization from './authorization';\nimport config from './config';\n\nconst proxies = [
|
|
1
|
+
{"version":3,"names":["proxies","registerPlugin","Authorization","config"],"sources":["index.js"],"sourcesContent":["/*!\n * Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.\n */\n\nimport {registerPlugin} from '@webex/webex-core';\n\nimport Authorization from './authorization';\nimport config from './config';\n\nconst proxies = ['isAuthorizing', 'isAuthenticating'];\n\nregisterPlugin('authorization', Authorization, {\n config,\n proxies,\n});\n\nexport {default} from './authorization';\nexport {default as config} from './config';\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAIA;;AAEA;;AACA;;AAPA;AACA;AACA;AAOA,IAAMA,OAAO,GAAG,CAAC,eAAD,EAAkB,kBAAlB,CAAhB;AAEA,IAAAC,yBAAA,EAAe,eAAf,EAAgCC,sBAAhC,EAA+C;EAC7CC,MAAM,EAANA,eAD6C;EAE7CH,OAAO,EAAPA;AAF6C,CAA/C"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webex/plugin-authorization-browser-first-party",
|
|
3
|
-
"version": "3.0.0-beta.
|
|
3
|
+
"version": "3.0.0-beta.15",
|
|
4
4
|
"description": "",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Ian W. Remmel <iremmel@cisco.com>",
|
|
@@ -25,15 +25,15 @@
|
|
|
25
25
|
"sinon": "^9.2.4"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@webex/common": "3.0.0-beta.
|
|
29
|
-
"@webex/plugin-authorization-browser-first-party": "3.0.0-beta.
|
|
30
|
-
"@webex/storage-adapter-local-storage": "3.0.0-beta.
|
|
31
|
-
"@webex/test-helper-automation": "3.0.0-beta.
|
|
32
|
-
"@webex/test-helper-chai": "3.0.0-beta.
|
|
33
|
-
"@webex/test-helper-mocha": "3.0.0-beta.
|
|
34
|
-
"@webex/test-helper-mock-webex": "3.0.0-beta.
|
|
35
|
-
"@webex/test-helper-test-users": "3.0.0-beta.
|
|
36
|
-
"@webex/webex-core": "3.0.0-beta.
|
|
28
|
+
"@webex/common": "3.0.0-beta.15",
|
|
29
|
+
"@webex/plugin-authorization-browser-first-party": "3.0.0-beta.15",
|
|
30
|
+
"@webex/storage-adapter-local-storage": "3.0.0-beta.15",
|
|
31
|
+
"@webex/test-helper-automation": "3.0.0-beta.15",
|
|
32
|
+
"@webex/test-helper-chai": "3.0.0-beta.15",
|
|
33
|
+
"@webex/test-helper-mocha": "3.0.0-beta.15",
|
|
34
|
+
"@webex/test-helper-mock-webex": "3.0.0-beta.15",
|
|
35
|
+
"@webex/test-helper-test-users": "3.0.0-beta.15",
|
|
36
|
+
"@webex/webex-core": "3.0.0-beta.15",
|
|
37
37
|
"crypto-js": "^4.1.1",
|
|
38
38
|
"lodash": "^4.17.21",
|
|
39
39
|
"uuid": "^3.3.2"
|
package/src/authorization.js
CHANGED
|
@@ -43,8 +43,8 @@ const Authorization = WebexPlugin.extend({
|
|
|
43
43
|
deps: ['isAuthorizing'],
|
|
44
44
|
fn() {
|
|
45
45
|
return this.isAuthorizing;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
48
|
},
|
|
49
49
|
|
|
50
50
|
session: {
|
|
@@ -56,12 +56,12 @@ const Authorization = WebexPlugin.extend({
|
|
|
56
56
|
*/
|
|
57
57
|
isAuthorizing: {
|
|
58
58
|
default: false,
|
|
59
|
-
type: 'boolean'
|
|
59
|
+
type: 'boolean',
|
|
60
60
|
},
|
|
61
61
|
ready: {
|
|
62
62
|
default: false,
|
|
63
|
-
type: 'boolean'
|
|
64
|
-
}
|
|
63
|
+
type: 'boolean',
|
|
64
|
+
},
|
|
65
65
|
},
|
|
66
66
|
|
|
67
67
|
namespace: 'Credentials',
|
|
@@ -90,8 +90,7 @@ const Authorization = WebexPlugin.extend({
|
|
|
90
90
|
|
|
91
91
|
if (location.query.state) {
|
|
92
92
|
location.query.state = JSON.parse(base64.decode(location.query.state));
|
|
93
|
-
}
|
|
94
|
-
else {
|
|
93
|
+
} else {
|
|
95
94
|
location.query.state = {};
|
|
96
95
|
}
|
|
97
96
|
|
|
@@ -99,7 +98,6 @@ const Authorization = WebexPlugin.extend({
|
|
|
99
98
|
|
|
100
99
|
this.webex.getWindow().sessionStorage.removeItem(OAUTH2_CODE_VERIFIER);
|
|
101
100
|
|
|
102
|
-
|
|
103
101
|
const {emailhash} = location.query.state;
|
|
104
102
|
|
|
105
103
|
this._verifySecurityToken(location.query);
|
|
@@ -107,7 +105,8 @@ const Authorization = WebexPlugin.extend({
|
|
|
107
105
|
|
|
108
106
|
// Wait until nextTick in case `credentials` hasn't initialized yet
|
|
109
107
|
process.nextTick(() => {
|
|
110
|
-
this.webex.internal.services
|
|
108
|
+
this.webex.internal.services
|
|
109
|
+
.collectPreauthCatalog({emailhash})
|
|
111
110
|
.catch(() => Promise.resolve())
|
|
112
111
|
.then(() => this.requestAuthorizationCodeGrant({code, codeVerifier}))
|
|
113
112
|
.then(() => {
|
|
@@ -115,7 +114,6 @@ const Authorization = WebexPlugin.extend({
|
|
|
115
114
|
});
|
|
116
115
|
});
|
|
117
116
|
|
|
118
|
-
|
|
119
117
|
return ret;
|
|
120
118
|
},
|
|
121
119
|
|
|
@@ -140,7 +138,6 @@ const Authorization = WebexPlugin.extend({
|
|
|
140
138
|
options.code_challenge = this._generateCodeChallenge();
|
|
141
139
|
options.code_challenge_method = 'S256';
|
|
142
140
|
|
|
143
|
-
|
|
144
141
|
return this.initiateAuthorizationCodeGrant(options);
|
|
145
142
|
},
|
|
146
143
|
|
|
@@ -155,7 +152,9 @@ const Authorization = WebexPlugin.extend({
|
|
|
155
152
|
*/
|
|
156
153
|
initiateAuthorizationCodeGrant(options) {
|
|
157
154
|
this.logger.info('authorization: initiating authorization code grant flow');
|
|
158
|
-
this.webex.getWindow().location = this.webex.credentials.buildLoginUrl(
|
|
155
|
+
this.webex.getWindow().location = this.webex.credentials.buildLoginUrl(
|
|
156
|
+
Object.assign({response_type: 'code'}, options)
|
|
157
|
+
);
|
|
159
158
|
|
|
160
159
|
return Promise.resolve();
|
|
161
160
|
},
|
|
@@ -174,7 +173,6 @@ const Authorization = WebexPlugin.extend({
|
|
|
174
173
|
}
|
|
175
174
|
},
|
|
176
175
|
|
|
177
|
-
|
|
178
176
|
@whileInFlight('isAuthorizing')
|
|
179
177
|
@oneFlight
|
|
180
178
|
/**
|
|
@@ -196,24 +194,25 @@ const Authorization = WebexPlugin.extend({
|
|
|
196
194
|
grant_type: 'authorization_code',
|
|
197
195
|
redirect_uri: this.config.redirect_uri,
|
|
198
196
|
code: options.code,
|
|
199
|
-
self_contained_token: true
|
|
197
|
+
self_contained_token: true,
|
|
200
198
|
};
|
|
201
199
|
|
|
202
200
|
if (options.codeVerifier) {
|
|
203
201
|
form.code_verifier = options.codeVerifier;
|
|
204
202
|
}
|
|
205
203
|
|
|
206
|
-
return this.webex
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
204
|
+
return this.webex
|
|
205
|
+
.request({
|
|
206
|
+
method: 'POST',
|
|
207
|
+
uri: this.config.tokenUrl,
|
|
208
|
+
form,
|
|
209
|
+
auth: {
|
|
210
|
+
user: this.config.client_id,
|
|
211
|
+
pass: this.config.client_secret,
|
|
212
|
+
sendImmediately: true,
|
|
213
|
+
},
|
|
214
|
+
shouldRefreshAccessToken: false,
|
|
215
|
+
})
|
|
217
216
|
.then((res) => {
|
|
218
217
|
this.webex.credentials.set({supertoken: res.body});
|
|
219
218
|
})
|
|
@@ -260,9 +259,10 @@ const Authorization = WebexPlugin.extend({
|
|
|
260
259
|
Reflect.deleteProperty(location.query, 'code');
|
|
261
260
|
if (isEmpty(omit(location.query.state, 'csrf_token'))) {
|
|
262
261
|
Reflect.deleteProperty(location.query, 'state');
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
|
|
262
|
+
} else {
|
|
263
|
+
location.query.state = base64.encode(
|
|
264
|
+
JSON.stringify(omit(location.query.state, 'csrf_token'))
|
|
265
|
+
);
|
|
266
266
|
}
|
|
267
267
|
location.search = querystring.stringify(location.query);
|
|
268
268
|
Reflect.deleteProperty(location, 'query');
|
|
@@ -283,16 +283,13 @@ const Authorization = WebexPlugin.extend({
|
|
|
283
283
|
// eslint-disable-next-line no-underscore-dangle
|
|
284
284
|
const safeCharacterMap = base64url._safe_map;
|
|
285
285
|
|
|
286
|
-
const codeVerifier = lodash
|
|
287
|
-
128,
|
|
288
|
-
|
|
289
|
-
).join('');
|
|
286
|
+
const codeVerifier = lodash
|
|
287
|
+
.times(128, () => safeCharacterMap[lodash.random(0, safeCharacterMap.length - 1)])
|
|
288
|
+
.join('');
|
|
290
289
|
|
|
291
290
|
const codeChallenge = CryptoJS.SHA256(codeVerifier).toString(base64url);
|
|
292
291
|
|
|
293
|
-
this.webex.getWindow().sessionStorage.setItem(
|
|
294
|
-
OAUTH2_CODE_VERIFIER, codeVerifier
|
|
295
|
-
);
|
|
292
|
+
this.webex.getWindow().sessionStorage.setItem(OAUTH2_CODE_VERIFIER, codeVerifier);
|
|
296
293
|
|
|
297
294
|
return codeChallenge;
|
|
298
295
|
},
|
|
@@ -344,7 +341,7 @@ const Authorization = WebexPlugin.extend({
|
|
|
344
341
|
if (token !== sessionToken) {
|
|
345
342
|
throw new Error(`CSRF token ${token} does not match stored token ${sessionToken}`);
|
|
346
343
|
}
|
|
347
|
-
}
|
|
344
|
+
},
|
|
348
345
|
});
|
|
349
346
|
|
|
350
347
|
export default Authorization;
|
package/src/config.js
CHANGED
|
@@ -15,23 +15,24 @@ export default {
|
|
|
15
15
|
|
|
16
16
|
refreshCallback(webex, token) {
|
|
17
17
|
/* eslint-disable camelcase */
|
|
18
|
-
return webex
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
18
|
+
return webex
|
|
19
|
+
.request({
|
|
20
|
+
method: 'POST',
|
|
21
|
+
uri: token.config.tokenUrl,
|
|
22
|
+
form: {
|
|
23
|
+
grant_type: 'refresh_token',
|
|
24
|
+
redirect_uri: token.config.redirect_uri,
|
|
25
|
+
refresh_token: token.refresh_token,
|
|
26
|
+
},
|
|
27
|
+
auth: {
|
|
28
|
+
user: token.config.client_id,
|
|
29
|
+
pass: token.config.client_secret,
|
|
30
|
+
sendImmediately: true,
|
|
31
|
+
},
|
|
32
|
+
shouldRefreshAccessToken: false,
|
|
33
|
+
})
|
|
33
34
|
.then((res) => res.body);
|
|
34
35
|
/* eslint-enable camelcase */
|
|
35
|
-
}
|
|
36
|
-
}
|
|
36
|
+
},
|
|
37
|
+
},
|
|
37
38
|
};
|
package/src/index.js
CHANGED
|
@@ -7,14 +7,11 @@ import {registerPlugin} from '@webex/webex-core';
|
|
|
7
7
|
import Authorization from './authorization';
|
|
8
8
|
import config from './config';
|
|
9
9
|
|
|
10
|
-
const proxies = [
|
|
11
|
-
'isAuthorizing',
|
|
12
|
-
'isAuthenticating'
|
|
13
|
-
];
|
|
10
|
+
const proxies = ['isAuthorizing', 'isAuthenticating'];
|
|
14
11
|
|
|
15
12
|
registerPlugin('authorization', Authorization, {
|
|
16
13
|
config,
|
|
17
|
-
proxies
|
|
14
|
+
proxies,
|
|
18
15
|
});
|
|
19
16
|
|
|
20
17
|
export {default} from './authorization';
|
|
@@ -11,22 +11,23 @@ import WebexCore from '@webex/webex-core';
|
|
|
11
11
|
|
|
12
12
|
import pkg from '../../../package';
|
|
13
13
|
|
|
14
|
-
const webex = window.webex = new WebexCore({
|
|
14
|
+
const webex = (window.webex = new WebexCore({
|
|
15
15
|
config: {
|
|
16
16
|
storage: {
|
|
17
|
-
boundedAdapter: new StorageAdapterLocalStorage('webex')
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
});
|
|
17
|
+
boundedAdapter: new StorageAdapterLocalStorage('webex'),
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
}));
|
|
21
21
|
|
|
22
22
|
webex.once('ready', () => {
|
|
23
23
|
if (webex.canAuthorize) {
|
|
24
24
|
document.getElementById('access-token').innerHTML = webex.credentials.supertoken.access_token;
|
|
25
25
|
document.getElementById('refresh-token').innerHTML = webex.credentials.supertoken.refresh_token;
|
|
26
26
|
|
|
27
|
-
webex
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
webex
|
|
28
|
+
.request({
|
|
29
|
+
uri: 'https://locus-a.wbx2.com/locus/api/v1/ping',
|
|
30
|
+
})
|
|
30
31
|
.then(() => {
|
|
31
32
|
document.getElementById('ping-complete').innerHTML = 'success';
|
|
32
33
|
});
|
|
@@ -41,18 +42,17 @@ document.getElementById('initiate-authorization-code-grant').addEventListener('c
|
|
|
41
42
|
webex.authorization.initiateLogin({
|
|
42
43
|
state: {
|
|
43
44
|
exchange: false,
|
|
44
|
-
name: pkg.name
|
|
45
|
-
}
|
|
45
|
+
name: pkg.name,
|
|
46
|
+
},
|
|
46
47
|
});
|
|
47
48
|
});
|
|
48
49
|
|
|
49
50
|
document.getElementById('token-refresh').addEventListener('click', () => {
|
|
50
51
|
document.getElementById('access-token').innerHTML = '';
|
|
51
|
-
webex.refresh({force: true})
|
|
52
|
-
.
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
});
|
|
52
|
+
webex.refresh({force: true}).then(() => {
|
|
53
|
+
document.getElementById('access-token').innerHTML = webex.credentials.supertoken.access_token;
|
|
54
|
+
document.getElementById('refresh-token').innerHTML = webex.credentials.supertoken.refresh_token;
|
|
55
|
+
});
|
|
56
56
|
});
|
|
57
57
|
|
|
58
58
|
document.getElementById('logout').addEventListener('click', () => {
|
|
@@ -1,21 +1,24 @@
|
|
|
1
1
|
<!DOCTYPE html>
|
|
2
2
|
<html>
|
|
3
|
-
<head>
|
|
4
|
-
|
|
5
|
-
</head>
|
|
6
|
-
<body class="authorization-automation-test">
|
|
7
|
-
|
|
8
|
-
|
|
3
|
+
<head>
|
|
4
|
+
<title>Authorization Automation Test</title>
|
|
5
|
+
</head>
|
|
6
|
+
<body class="authorization-automation-test">
|
|
7
|
+
<button title="Login with Authorization Code Grant" id="initiate-authorization-code-grant">
|
|
8
|
+
Login with Authorization Code Grant
|
|
9
|
+
</button>
|
|
10
|
+
<button title="Refresh Access Token" id="token-refresh">Refresh Access Token</button>
|
|
9
11
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
<h1>Access Token</h1>
|
|
13
|
+
<div id="access-token"></div>
|
|
14
|
+
<h1>Refresh Token</h1>
|
|
15
|
+
<div id="refresh-token"></div>
|
|
14
16
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
+
<h1>Pinging WDM</h1>
|
|
18
|
+
<div id="ping-complete"></div>
|
|
17
19
|
|
|
18
|
-
|
|
20
|
+
<button title="Logout" id="logout">Logout</button>
|
|
19
21
|
|
|
20
|
-
|
|
21
|
-
</body>
|
|
22
|
+
<script src="app.js"></script>
|
|
23
|
+
</body>
|
|
24
|
+
</html>
|
|
@@ -16,9 +16,17 @@ const redirectUri = process.env.WEBEX_REDIRECT_URI || process.env.REDIRECT_URI;
|
|
|
16
16
|
// for test users in EU (Federation) and US
|
|
17
17
|
// Also try US user with Federation enabled
|
|
18
18
|
const runs = [
|
|
19
|
-
{
|
|
19
|
+
{
|
|
20
|
+
it: 'with EU user with Federation enabled',
|
|
21
|
+
EUUser: true,
|
|
22
|
+
attrs: {config: {credentials: {federation: true}}},
|
|
23
|
+
},
|
|
20
24
|
{it: 'with US user without Federation enabled', EUUser: false, attrs: {}},
|
|
21
|
-
{
|
|
25
|
+
{
|
|
26
|
+
it: 'with US user with Federation enabled',
|
|
27
|
+
EUUser: false,
|
|
28
|
+
attrs: {config: {credentials: {federation: true}}},
|
|
29
|
+
},
|
|
22
30
|
];
|
|
23
31
|
|
|
24
32
|
runs.forEach((run) => {
|
|
@@ -35,98 +43,108 @@ runs.forEach((run) => {
|
|
|
35
43
|
testUserParm.config = {orgId: process.env.EU_PRIMARY_ORG_ID};
|
|
36
44
|
}
|
|
37
45
|
|
|
38
|
-
before(() =>
|
|
39
|
-
.then((users) => {
|
|
46
|
+
before(() =>
|
|
47
|
+
testUsers.create(testUserParm).then((users) => {
|
|
40
48
|
user = users[0];
|
|
41
|
-
})
|
|
49
|
+
})
|
|
50
|
+
);
|
|
42
51
|
|
|
43
|
-
before(() =>
|
|
44
|
-
.then((b) => {
|
|
52
|
+
before(() =>
|
|
53
|
+
createBrowser(pkg).then((b) => {
|
|
45
54
|
browser = b;
|
|
46
|
-
})
|
|
55
|
+
})
|
|
56
|
+
);
|
|
47
57
|
|
|
48
58
|
after(() => browser && browser.printLogs());
|
|
49
59
|
|
|
50
|
-
after(
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
60
|
+
after(
|
|
61
|
+
() =>
|
|
62
|
+
browser &&
|
|
63
|
+
browser.quit().catch((reason) => {
|
|
64
|
+
console.warn(reason);
|
|
65
|
+
})
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
it('authorizes a user', () =>
|
|
69
|
+
browser
|
|
70
|
+
.get(`${redirectUri}/${pkg.name}`)
|
|
71
|
+
.waitForElementByClassName('ready')
|
|
72
|
+
.title()
|
|
59
73
|
.should.eventually.become('Authorization Automation Test')
|
|
60
|
-
|
|
74
|
+
.waitForElementByCssSelector('[title="Login with Authorization Code Grant"]')
|
|
61
75
|
.click()
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
76
|
+
.login(user)
|
|
77
|
+
.waitForElementByClassName('authorization-automation-test')
|
|
78
|
+
.waitForElementById('refresh-token')
|
|
65
79
|
.text()
|
|
66
|
-
|
|
67
|
-
|
|
80
|
+
.should.eventually.not.be.empty.waitForElementByCssSelector(
|
|
81
|
+
'#ping-complete:not(:empty)'
|
|
82
|
+
)
|
|
68
83
|
.text()
|
|
69
|
-
|
|
84
|
+
.should.eventually.become('success'));
|
|
70
85
|
|
|
71
|
-
it('is still logged in after reloading the page', () =>
|
|
72
|
-
|
|
73
|
-
.
|
|
74
|
-
.should.eventually.not.be.empty
|
|
75
|
-
.get(`${redirectUri}/${pkg.name}`)
|
|
76
|
-
.sleep(500)
|
|
77
|
-
.waitForElementById('access-token')
|
|
86
|
+
it('is still logged in after reloading the page', () =>
|
|
87
|
+
browser
|
|
88
|
+
.waitForElementById('access-token')
|
|
78
89
|
.text()
|
|
79
|
-
|
|
90
|
+
.should.eventually.not.be.empty.get(`${redirectUri}/${pkg.name}`)
|
|
91
|
+
.sleep(500)
|
|
92
|
+
.waitForElementById('access-token')
|
|
93
|
+
.text().should.eventually.not.be.empty);
|
|
80
94
|
|
|
81
|
-
it(
|
|
95
|
+
it("refreshes the user's access token", () => {
|
|
82
96
|
let accessToken = '';
|
|
83
97
|
|
|
84
|
-
return
|
|
85
|
-
|
|
98
|
+
return (
|
|
99
|
+
browser
|
|
100
|
+
.waitForElementByCssSelector('#access-token:not(:empty)')
|
|
86
101
|
.text()
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
102
|
+
.then((text) => {
|
|
103
|
+
accessToken = text;
|
|
104
|
+
assert.isString(accessToken);
|
|
105
|
+
assert.isAbove(accessToken.length, 0);
|
|
106
|
+
|
|
107
|
+
return browser;
|
|
108
|
+
})
|
|
109
|
+
.waitForElementByCssSelector('[title="Refresh Access Token"]')
|
|
95
110
|
.click()
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
111
|
+
// Not thrilled by a sleep, but we just need to give the button click
|
|
112
|
+
// enough time to clear the #access-token box
|
|
113
|
+
.sleep(500)
|
|
114
|
+
.waitForElementByCssSelector('#access-token:not(:empty)')
|
|
100
115
|
.text()
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
116
|
+
.then((text) => {
|
|
117
|
+
assert.isString(text);
|
|
118
|
+
assert.isAbove(text.length, 0);
|
|
119
|
+
assert.notEqual(text, accessToken);
|
|
120
|
+
|
|
121
|
+
return browser;
|
|
122
|
+
})
|
|
123
|
+
);
|
|
108
124
|
});
|
|
109
125
|
|
|
110
|
-
it('logs out a user', () =>
|
|
111
|
-
|
|
126
|
+
it('logs out a user', () =>
|
|
127
|
+
browser
|
|
128
|
+
.title()
|
|
112
129
|
.should.eventually.become('Authorization Automation Test')
|
|
113
|
-
|
|
130
|
+
.waitForElementByCssSelector('[title="Logout"]')
|
|
114
131
|
.click()
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
132
|
+
// We need to revoke three tokens before the window.location assignment.
|
|
133
|
+
// So far, I haven't found any ques to wait for, so sleep seems to be
|
|
134
|
+
// the only option.
|
|
135
|
+
.sleep(3000)
|
|
136
|
+
.title()
|
|
120
137
|
.should.eventually.become('Redirect Dispatcher')
|
|
121
|
-
|
|
122
|
-
|
|
138
|
+
.get(`${redirectUri}/${pkg.name}`)
|
|
139
|
+
.title()
|
|
123
140
|
.should.eventually.become('Authorization Automation Test')
|
|
124
|
-
|
|
141
|
+
.waitForElementById('access-token')
|
|
125
142
|
.text()
|
|
126
|
-
|
|
127
|
-
|
|
143
|
+
.should.eventually.be.empty.waitForElementByCssSelector(
|
|
144
|
+
'[title="Login with Authorization Code Grant"]'
|
|
145
|
+
)
|
|
128
146
|
.click()
|
|
129
|
-
|
|
147
|
+
.waitForElementById('IDToken1'));
|
|
130
148
|
});
|
|
131
149
|
});
|
|
132
150
|
});
|
|
@@ -19,28 +19,28 @@ import Authorization from '@webex/plugin-authorization-browser-first-party';
|
|
|
19
19
|
// Necessary to require lodash this way in order to stub the method
|
|
20
20
|
const lodash = require('lodash');
|
|
21
21
|
|
|
22
|
-
|
|
23
22
|
browserOnly(describe)('plugin-authorization-browser-first-party', () => {
|
|
24
23
|
describe('Authorization', () => {
|
|
25
|
-
function makeWebex(
|
|
24
|
+
function makeWebex(
|
|
25
|
+
href = 'https://example.com',
|
|
26
|
+
csrfToken = undefined,
|
|
27
|
+
pkceVerifier = undefined,
|
|
28
|
+
config = {}
|
|
29
|
+
) {
|
|
26
30
|
const mockWindow = {
|
|
27
31
|
history: {
|
|
28
32
|
replaceState(a, b, location) {
|
|
29
33
|
mockWindow.location.href = location;
|
|
30
|
-
}
|
|
34
|
+
},
|
|
31
35
|
},
|
|
32
36
|
location: {
|
|
33
|
-
href
|
|
37
|
+
href,
|
|
34
38
|
},
|
|
35
39
|
sessionStorage: {
|
|
36
|
-
getItem: sinon.stub()
|
|
37
|
-
.onCall(0)
|
|
38
|
-
.returns(pkceVerifier)
|
|
39
|
-
.onCall(1)
|
|
40
|
-
.returns(csrfToken),
|
|
40
|
+
getItem: sinon.stub().onCall(0).returns(pkceVerifier).onCall(1).returns(csrfToken),
|
|
41
41
|
removeItem: sinon.spy(),
|
|
42
|
-
setItem: sinon.spy()
|
|
43
|
-
}
|
|
42
|
+
setItem: sinon.spy(),
|
|
43
|
+
},
|
|
44
44
|
};
|
|
45
45
|
|
|
46
46
|
sinon.spy(mockWindow.history, 'replaceState');
|
|
@@ -49,37 +49,52 @@ browserOnly(describe)('plugin-authorization-browser-first-party', () => {
|
|
|
49
49
|
children: {
|
|
50
50
|
authorization: Authorization,
|
|
51
51
|
credentials: Credentials,
|
|
52
|
-
services: Services
|
|
52
|
+
services: Services,
|
|
53
53
|
},
|
|
54
|
-
request: sinon
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
54
|
+
request: sinon
|
|
55
|
+
.stub()
|
|
56
|
+
.returns(
|
|
57
|
+
Promise.resolve({body: {access_token: 'AT', token_type: 'Fake', refresh_token: 'RT'}})
|
|
58
|
+
),
|
|
59
|
+
config: merge(
|
|
60
|
+
{
|
|
61
|
+
credentials: {
|
|
62
|
+
idbroker: {
|
|
63
|
+
url: process.env.IDBROKER_BASE_URL,
|
|
64
|
+
defaultUrl: process.env.IDBROKER_BASE_URL,
|
|
65
|
+
},
|
|
66
|
+
identity: {
|
|
67
|
+
url: process.env.IDENTITY_BASE_URL,
|
|
68
|
+
defaultUrl: process.env.IDENTITY_BASE_URL,
|
|
69
|
+
},
|
|
70
|
+
activationUrl: `${
|
|
71
|
+
process.env.IDBROKER_BASE_URL || 'https://idbroker.webex.com'
|
|
72
|
+
}/idb/token/v1/actions/UserActivation/invoke`,
|
|
73
|
+
authorizeUrl: `${
|
|
74
|
+
process.env.IDBROKER_BASE_URL || 'https://idbroker.webex.com'
|
|
75
|
+
}/idb/oauth2/v1/authorize`,
|
|
76
|
+
setPasswordUrl: `${
|
|
77
|
+
process.env.IDBROKER_BASE_URL || 'https://identity.webex.com'
|
|
78
|
+
}/identity/scim/v1/Users`,
|
|
79
|
+
logoutUrl: `${
|
|
80
|
+
process.env.IDBROKER_BASE_URL || 'https://idbroker.webex.com'
|
|
81
|
+
}/idb/oauth2/v1/logout`,
|
|
82
|
+
// eslint-disable-next-line camelcase
|
|
83
|
+
client_id: 'fake',
|
|
84
|
+
// eslint-disable-next-line camelcase
|
|
85
|
+
client_secret: 'fake',
|
|
86
|
+
// eslint-disable-next-line camelcase
|
|
87
|
+
redirect_uri: 'http://example.com',
|
|
88
|
+
// eslint-disable-next-line camelcase
|
|
89
|
+
scope: 'scope:one',
|
|
90
|
+
refreshCallback: () => Promise.resolve(),
|
|
64
91
|
},
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
logoutUrl: `${process.env.IDBROKER_BASE_URL || 'https://idbroker.webex.com'}/idb/oauth2/v1/logout`,
|
|
69
|
-
// eslint-disable-next-line camelcase
|
|
70
|
-
client_id: 'fake',
|
|
71
|
-
// eslint-disable-next-line camelcase
|
|
72
|
-
client_secret: 'fake',
|
|
73
|
-
// eslint-disable-next-line camelcase
|
|
74
|
-
redirect_uri: 'http://example.com',
|
|
75
|
-
// eslint-disable-next-line camelcase
|
|
76
|
-
scope: 'scope:one',
|
|
77
|
-
refreshCallback: () => Promise.resolve()
|
|
78
|
-
}
|
|
79
|
-
}, config),
|
|
92
|
+
},
|
|
93
|
+
config
|
|
94
|
+
),
|
|
80
95
|
getWindow() {
|
|
81
96
|
return mockWindow;
|
|
82
|
-
}
|
|
97
|
+
},
|
|
83
98
|
});
|
|
84
99
|
|
|
85
100
|
return webex;
|
|
@@ -93,15 +108,14 @@ browserOnly(describe)('plugin-authorization-browser-first-party', () => {
|
|
|
93
108
|
assert.isFalse(webex.authorization.ready);
|
|
94
109
|
assert.isFalse(webex.credentials.canAuthorize);
|
|
95
110
|
|
|
96
|
-
return webex.authorization.when('change:ready')
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
});
|
|
111
|
+
return webex.authorization.when('change:ready').then(() => {
|
|
112
|
+
// Webex request gets called twice:
|
|
113
|
+
// once for the pre-auth catalog
|
|
114
|
+
// once for auth token exchange
|
|
115
|
+
assert.calledTwice(webex.request);
|
|
116
|
+
assert.isTrue(webex.authorization.ready);
|
|
117
|
+
assert.isTrue(webex.credentials.canAuthorize);
|
|
118
|
+
});
|
|
105
119
|
});
|
|
106
120
|
|
|
107
121
|
it('validates the csrf token', () => {
|
|
@@ -109,12 +123,20 @@ browserOnly(describe)('plugin-authorization-browser-first-party', () => {
|
|
|
109
123
|
|
|
110
124
|
assert.throws(() => {
|
|
111
125
|
// eslint-disable-next-line no-unused-vars
|
|
112
|
-
const webex = makeWebex(
|
|
126
|
+
const webex = makeWebex(
|
|
127
|
+
`http://example.com/?code=5&state=${base64.encode(
|
|
128
|
+
JSON.stringify({csrf_token: 'someothertoken'})
|
|
129
|
+
)}`,
|
|
130
|
+
csrfToken
|
|
131
|
+
);
|
|
113
132
|
}, /CSRF token someothertoken does not match stored token abcd/);
|
|
114
133
|
|
|
115
134
|
assert.throws(() => {
|
|
116
135
|
// eslint-disable-next-line no-unused-vars
|
|
117
|
-
const webex = makeWebex(
|
|
136
|
+
const webex = makeWebex(
|
|
137
|
+
`http://example.com/?code=5&state=${base64.encode(JSON.stringify({}))}`,
|
|
138
|
+
csrfToken
|
|
139
|
+
);
|
|
118
140
|
}, /Expected CSRF token abcd, but not found in redirect query/);
|
|
119
141
|
|
|
120
142
|
assert.throws(() => {
|
|
@@ -122,33 +144,46 @@ browserOnly(describe)('plugin-authorization-browser-first-party', () => {
|
|
|
122
144
|
const webex = makeWebex('http://example.com/?code=5', csrfToken);
|
|
123
145
|
}, /Expected CSRF token abcd, but not found in redirect query/);
|
|
124
146
|
|
|
125
|
-
const webex = makeWebex(
|
|
147
|
+
const webex = makeWebex(
|
|
148
|
+
`http://example.com/?code=5&state=${base64.encode(
|
|
149
|
+
JSON.stringify({csrf_token: csrfToken})
|
|
150
|
+
)}`,
|
|
151
|
+
csrfToken
|
|
152
|
+
);
|
|
126
153
|
|
|
127
|
-
return webex.authorization.when('change:ready')
|
|
128
|
-
.
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
});
|
|
154
|
+
return webex.authorization.when('change:ready').then(() => {
|
|
155
|
+
assert.isTrue(webex.credentials.canAuthorize);
|
|
156
|
+
assert.called(webex.getWindow().sessionStorage.removeItem);
|
|
157
|
+
});
|
|
132
158
|
});
|
|
133
159
|
|
|
134
160
|
it('removes the oauth parameters from the url', () => {
|
|
135
161
|
const csrfToken = 'abcd';
|
|
136
162
|
|
|
137
|
-
const webex = makeWebex(
|
|
163
|
+
const webex = makeWebex(
|
|
164
|
+
`http://example.com/?code=5&state=${base64.encode(
|
|
165
|
+
JSON.stringify({csrf_token: csrfToken, something: true})
|
|
166
|
+
)}`,
|
|
167
|
+
csrfToken
|
|
168
|
+
);
|
|
138
169
|
|
|
139
|
-
return webex.authorization.when('change:ready')
|
|
140
|
-
.
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
170
|
+
return webex.authorization.when('change:ready').then(() => {
|
|
171
|
+
assert.isTrue(webex.credentials.canAuthorize);
|
|
172
|
+
assert.called(webex.getWindow().sessionStorage.removeItem);
|
|
173
|
+
assert.called(webex.getWindow().history.replaceState);
|
|
174
|
+
assert.equal(
|
|
175
|
+
webex.getWindow().location.href,
|
|
176
|
+
`http://example.com/?state=${base64.encode(JSON.stringify({something: true}))}`
|
|
177
|
+
);
|
|
178
|
+
});
|
|
146
179
|
});
|
|
147
180
|
});
|
|
148
181
|
describe('when the url contains an error', () => {
|
|
149
182
|
it('throws a grant error', () => {
|
|
150
183
|
assert.throws(() => {
|
|
151
|
-
makeWebex(
|
|
184
|
+
makeWebex(
|
|
185
|
+
'http://127.0.0.1:8000/?error=invalid_scope&error_description=The%20requested%20scope%20is%20invalid.'
|
|
186
|
+
);
|
|
152
187
|
}, /The requested scope is invalid./);
|
|
153
188
|
});
|
|
154
189
|
});
|
|
@@ -166,28 +201,14 @@ browserOnly(describe)('plugin-authorization-browser-first-party', () => {
|
|
|
166
201
|
it('passes codeVerifier to requestAuthorizationCodeGrant', () => {
|
|
167
202
|
const expectedVerifier = 'test verifier';
|
|
168
203
|
|
|
169
|
-
const webex = makeWebex(
|
|
170
|
-
'http://example.com?code=5',
|
|
171
|
-
undefined,
|
|
172
|
-
expectedVerifier
|
|
173
|
-
);
|
|
204
|
+
const webex = makeWebex('http://example.com?code=5', undefined, expectedVerifier);
|
|
174
205
|
|
|
175
|
-
return webex.authorization.when('change:ready')
|
|
176
|
-
.
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
);
|
|
182
|
-
assert.calledWith(
|
|
183
|
-
webex.getWindow().sessionStorage.removeItem,
|
|
184
|
-
'oauth2-code-verifier'
|
|
185
|
-
);
|
|
186
|
-
assert.equal(
|
|
187
|
-
webex.request.getCall(1).args[0].form.code_verifier,
|
|
188
|
-
expectedVerifier
|
|
189
|
-
);
|
|
190
|
-
});
|
|
206
|
+
return webex.authorization.when('change:ready').then(() => {
|
|
207
|
+
assert.calledTwice(webex.request);
|
|
208
|
+
assert.calledWith(webex.getWindow().sessionStorage.getItem, 'oauth2-code-verifier');
|
|
209
|
+
assert.calledWith(webex.getWindow().sessionStorage.removeItem, 'oauth2-code-verifier');
|
|
210
|
+
assert.equal(webex.request.getCall(1).args[0].form.code_verifier, expectedVerifier);
|
|
211
|
+
});
|
|
191
212
|
});
|
|
192
213
|
});
|
|
193
214
|
});
|
|
@@ -196,95 +217,97 @@ browserOnly(describe)('plugin-authorization-browser-first-party', () => {
|
|
|
196
217
|
it('calls #initiateAuthorizationCodeGrant()', () => {
|
|
197
218
|
const webex = makeWebex(undefined, undefined, {
|
|
198
219
|
credentials: {
|
|
199
|
-
clientType: 'confidential'
|
|
200
|
-
}
|
|
220
|
+
clientType: 'confidential',
|
|
221
|
+
},
|
|
201
222
|
});
|
|
202
223
|
|
|
203
224
|
sinon.spy(webex.authorization, 'initiateAuthorizationCodeGrant');
|
|
204
225
|
|
|
205
|
-
return webex.authorization.initiateLogin()
|
|
206
|
-
.
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
});
|
|
226
|
+
return webex.authorization.initiateLogin().then(() => {
|
|
227
|
+
assert.called(webex.authorization.initiateAuthorizationCodeGrant);
|
|
228
|
+
assert.include(webex.getWindow().location, 'response_type=code');
|
|
229
|
+
});
|
|
210
230
|
});
|
|
211
231
|
|
|
212
232
|
it('adds a csrf_token to the login url and sessionStorage', () => {
|
|
213
233
|
const webex = makeWebex(undefined, undefined, {
|
|
214
234
|
credentials: {
|
|
215
|
-
clientType: 'confidential'
|
|
216
|
-
}
|
|
235
|
+
clientType: 'confidential',
|
|
236
|
+
},
|
|
217
237
|
});
|
|
218
238
|
|
|
219
239
|
sinon.spy(webex.authorization, 'initiateAuthorizationCodeGrant');
|
|
220
240
|
|
|
221
|
-
return webex.authorization.initiateLogin()
|
|
222
|
-
.
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
241
|
+
return webex.authorization.initiateLogin().then(() => {
|
|
242
|
+
assert.called(webex.authorization.initiateAuthorizationCodeGrant);
|
|
243
|
+
assert.include(webex.getWindow().location, 'response_type=code');
|
|
244
|
+
const {query} = url.parse(webex.getWindow().location, true);
|
|
245
|
+
let {state} = query;
|
|
246
|
+
|
|
247
|
+
state = JSON.parse(base64.decode(state));
|
|
248
|
+
assert.property(state, 'csrf_token');
|
|
249
|
+
assert.isDefined(state.csrf_token);
|
|
250
|
+
assert.match(state.csrf_token, patterns.uuid);
|
|
251
|
+
assert.called(webex.getWindow().sessionStorage.setItem);
|
|
252
|
+
assert.calledWith(
|
|
253
|
+
webex.getWindow().sessionStorage.setItem,
|
|
254
|
+
'oauth2-csrf-token',
|
|
255
|
+
state.csrf_token
|
|
256
|
+
);
|
|
257
|
+
});
|
|
235
258
|
});
|
|
236
259
|
|
|
237
260
|
it('adds a pkce code challenge', () => {
|
|
238
261
|
const webex = makeWebex(undefined, undefined, {
|
|
239
262
|
credentials: {
|
|
240
|
-
clientType: 'confidential'
|
|
241
|
-
}
|
|
263
|
+
clientType: 'confidential',
|
|
264
|
+
},
|
|
242
265
|
});
|
|
243
266
|
|
|
244
267
|
const expectedCodeChallenge = 'test challenge';
|
|
245
268
|
|
|
246
269
|
sinon.spy(webex.authorization, 'initiateAuthorizationCodeGrant');
|
|
247
|
-
sinon.stub(webex.authorization, '_generateCodeChallenge')
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
});
|
|
270
|
+
sinon.stub(webex.authorization, '_generateCodeChallenge').returns(expectedCodeChallenge);
|
|
271
|
+
|
|
272
|
+
return webex.authorization.initiateLogin().then(() => {
|
|
273
|
+
assert.called(webex.authorization.initiateAuthorizationCodeGrant);
|
|
274
|
+
const grantOptions =
|
|
275
|
+
webex.authorization.initiateAuthorizationCodeGrant.getCall(0).args[0];
|
|
276
|
+
|
|
277
|
+
assert.equal(grantOptions.code_challenge, expectedCodeChallenge);
|
|
278
|
+
assert.equal(grantOptions.code_challenge_method, 'S256');
|
|
279
|
+
// eslint-disable-next-line no-underscore-dangle
|
|
280
|
+
assert.calledWith(webex.authorization._generateCodeChallenge);
|
|
281
|
+
});
|
|
260
282
|
});
|
|
261
283
|
|
|
262
284
|
it('adds emailHash', () => {
|
|
263
285
|
const webex = makeWebex(undefined, undefined, {
|
|
264
286
|
credentials: {
|
|
265
|
-
clientType: 'confidential'
|
|
266
|
-
}
|
|
287
|
+
clientType: 'confidential',
|
|
288
|
+
},
|
|
267
289
|
});
|
|
268
290
|
|
|
269
|
-
const expectedEmailHash =
|
|
291
|
+
const expectedEmailHash =
|
|
292
|
+
'73062d872926c2a556f17b36f50e328ddf9bff9d403939bd14b6c3b7f5a33fc2';
|
|
270
293
|
|
|
271
294
|
sinon.spy(webex.authorization, 'initiateAuthorizationCodeGrant');
|
|
272
295
|
|
|
273
|
-
return webex.authorization.initiateLogin({email: 'test@email.com'})
|
|
274
|
-
.
|
|
275
|
-
|
|
276
|
-
|
|
296
|
+
return webex.authorization.initiateLogin({email: 'test@email.com'}).then(() => {
|
|
297
|
+
assert.called(webex.authorization.initiateAuthorizationCodeGrant);
|
|
298
|
+
const grantOptions =
|
|
299
|
+
webex.authorization.initiateAuthorizationCodeGrant.getCall(0).args[0];
|
|
277
300
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
301
|
+
assert.equal(grantOptions.emailHash, expectedEmailHash);
|
|
302
|
+
assert.isUndefined(grantOptions.email);
|
|
303
|
+
});
|
|
281
304
|
});
|
|
282
305
|
|
|
283
306
|
it('sets #isAuthorizing', () => {
|
|
284
307
|
const webex = makeWebex(undefined, undefined, {
|
|
285
308
|
credentials: {
|
|
286
|
-
clientType: 'confidential'
|
|
287
|
-
}
|
|
309
|
+
clientType: 'confidential',
|
|
310
|
+
},
|
|
288
311
|
});
|
|
289
312
|
|
|
290
313
|
assert.isFalse(webex.authorization.isAuthorizing);
|
|
@@ -298,8 +321,8 @@ browserOnly(describe)('plugin-authorization-browser-first-party', () => {
|
|
|
298
321
|
it('sets #isAuthenticating', () => {
|
|
299
322
|
const webex = makeWebex(undefined, undefined, {
|
|
300
323
|
credentials: {
|
|
301
|
-
clientType: 'confidential'
|
|
302
|
-
}
|
|
324
|
+
clientType: 'confidential',
|
|
325
|
+
},
|
|
303
326
|
});
|
|
304
327
|
|
|
305
328
|
assert.isFalse(webex.authorization.isAuthenticating);
|
|
@@ -315,17 +338,16 @@ browserOnly(describe)('plugin-authorization-browser-first-party', () => {
|
|
|
315
338
|
it('redirects to the login page with response_type=code', () => {
|
|
316
339
|
const webex = makeWebex(undefined, undefined, {
|
|
317
340
|
credentials: {
|
|
318
|
-
clientType: 'confidential'
|
|
319
|
-
}
|
|
341
|
+
clientType: 'confidential',
|
|
342
|
+
},
|
|
320
343
|
});
|
|
321
344
|
|
|
322
345
|
sinon.spy(webex.authorization, 'initiateAuthorizationCodeGrant');
|
|
323
346
|
|
|
324
|
-
return webex.authorization.initiateLogin()
|
|
325
|
-
.
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
});
|
|
347
|
+
return webex.authorization.initiateLogin().then(() => {
|
|
348
|
+
assert.called(webex.authorization.initiateAuthorizationCodeGrant);
|
|
349
|
+
assert.include(webex.getWindow().location, 'response_type=code');
|
|
350
|
+
});
|
|
329
351
|
});
|
|
330
352
|
});
|
|
331
353
|
|
|
@@ -342,7 +364,7 @@ browserOnly(describe)('plugin-authorization-browser-first-party', () => {
|
|
|
342
364
|
const toStringStub = sinon.stub().returns(expectedCodeChallenge);
|
|
343
365
|
const randomStub = sinon.stub(lodash, 'random').returns(0);
|
|
344
366
|
const sha256Stub = sinon.stub(CryptoJS, 'SHA256').returns({
|
|
345
|
-
toString: toStringStub
|
|
367
|
+
toString: toStringStub,
|
|
346
368
|
});
|
|
347
369
|
|
|
348
370
|
// eslint-disable-next-line no-underscore-dangle
|
|
@@ -365,16 +387,16 @@ browserOnly(describe)('plugin-authorization-browser-first-party', () => {
|
|
|
365
387
|
it('removes the state parameter when it has no keys', () => {
|
|
366
388
|
const webex = makeWebex(undefined, undefined, {
|
|
367
389
|
credentials: {
|
|
368
|
-
clientType: 'confidential'
|
|
369
|
-
}
|
|
390
|
+
clientType: 'confidential',
|
|
391
|
+
},
|
|
370
392
|
});
|
|
371
393
|
const location = {
|
|
372
394
|
query: {
|
|
373
395
|
code: 'code',
|
|
374
396
|
state: {
|
|
375
|
-
csrf_token: 'token'
|
|
376
|
-
}
|
|
377
|
-
}
|
|
397
|
+
csrf_token: 'token',
|
|
398
|
+
},
|
|
399
|
+
},
|
|
378
400
|
};
|
|
379
401
|
|
|
380
402
|
sinon.spy(webex.authorization, '_cleanUrl');
|
|
@@ -386,17 +408,17 @@ browserOnly(describe)('plugin-authorization-browser-first-party', () => {
|
|
|
386
408
|
it('keeps the parameter when it has keys', () => {
|
|
387
409
|
const webex = makeWebex(undefined, undefined, {
|
|
388
410
|
credentials: {
|
|
389
|
-
clientType: 'confidential'
|
|
390
|
-
}
|
|
411
|
+
clientType: 'confidential',
|
|
412
|
+
},
|
|
391
413
|
});
|
|
392
414
|
const location = {
|
|
393
415
|
query: {
|
|
394
416
|
code: 'code',
|
|
395
417
|
state: {
|
|
396
418
|
csrf_token: 'token',
|
|
397
|
-
key: 'value'
|
|
398
|
-
}
|
|
399
|
-
}
|
|
419
|
+
key: 'value',
|
|
420
|
+
},
|
|
421
|
+
},
|
|
400
422
|
};
|
|
401
423
|
|
|
402
424
|
sinon.spy(webex.authorization, '_cleanUrl');
|