| OLD | NEW |
| (Empty) |
| 1 // Copyright 2013 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 /** | |
| 6 * @fileoverview An UI component to host gaia auth extension in an iframe. | |
| 7 * After the component binds with an iframe, call its {@code load} to start the | |
| 8 * authentication flow. There are two events would be raised after this point: | |
| 9 * a 'ready' event when the authentication UI is ready to use and a 'completed' | |
| 10 * event when the authentication is completed successfully. If caller is | |
| 11 * interested in the user credentials, they may supply a success callback with | |
| 12 * {@code load} call. The callback will be invoked when the authentication is | |
| 13 * completed successfully and with the available credential data. | |
| 14 */ | |
| 15 | |
| 16 cr.define('cr.login', function() { | |
| 17 'use strict'; | |
| 18 | |
| 19 /** | |
| 20 * Base URL of gaia auth extension. | |
| 21 * @const | |
| 22 */ | |
| 23 var AUTH_URL_BASE = 'chrome-extension://mfffpogegjflfpflabcdkioaeobkgjik'; | |
| 24 | |
| 25 /** | |
| 26 * Auth URL to use for online flow. | |
| 27 * @const | |
| 28 */ | |
| 29 var AUTH_URL = AUTH_URL_BASE + '/main.html'; | |
| 30 | |
| 31 /** | |
| 32 * Auth URL to use for offline flow. | |
| 33 * @const | |
| 34 */ | |
| 35 var OFFLINE_AUTH_URL = AUTH_URL_BASE + '/offline.html'; | |
| 36 | |
| 37 /** | |
| 38 * Origin of the gaia sign in page. | |
| 39 * @const | |
| 40 */ | |
| 41 var GAIA_ORIGIN = 'https://accounts.google.com'; | |
| 42 | |
| 43 /** | |
| 44 * Supported params of auth extension. For a complete list, check out the | |
| 45 * auth extension's main.js. | |
| 46 * @type {!Array<string>} | |
| 47 * @const | |
| 48 */ | |
| 49 var SUPPORTED_PARAMS = [ | |
| 50 'gaiaUrl', // Gaia url to use; | |
| 51 'gaiaPath', // Gaia path to use without a leading slash; | |
| 52 'hl', // Language code for the user interface; | |
| 53 'email', // Pre-fill the email field in Gaia UI; | |
| 54 'service', // Name of Gaia service; | |
| 55 'continueUrl', // Continue url to use; | |
| 56 'frameUrl', // Initial frame URL to use. If empty defaults to gaiaUrl. | |
| 57 'useEafe', // Whether to use EAFE. | |
| 58 'clientId', // Chrome's client id. | |
| 59 'constrained' // Whether the extension is loaded in a constrained window; | |
| 60 ]; | |
| 61 | |
| 62 /** | |
| 63 * Supported localized strings. For a complete list, check out the auth | |
| 64 * extension's offline.js | |
| 65 * @type {!Array<string>} | |
| 66 * @const | |
| 67 */ | |
| 68 var LOCALIZED_STRING_PARAMS = [ | |
| 69 'stringSignIn', | |
| 70 'stringEmail', | |
| 71 'stringPassword', | |
| 72 'stringEmptyEmail', | |
| 73 'stringEmptyPassword', | |
| 74 'stringError' | |
| 75 ]; | |
| 76 | |
| 77 /** | |
| 78 * Enum for the authorization mode, must match AuthMode defined in | |
| 79 * chrome/browser/ui/webui/inline_login_ui.cc. | |
| 80 * @enum {number} | |
| 81 */ | |
| 82 var AuthMode = { | |
| 83 DEFAULT: 0, | |
| 84 OFFLINE: 1, | |
| 85 DESKTOP: 2 | |
| 86 }; | |
| 87 | |
| 88 /** | |
| 89 * Enum for the auth flow. | |
| 90 * @enum {number} | |
| 91 */ | |
| 92 var AuthFlow = { | |
| 93 GAIA: 0, | |
| 94 SAML: 1 | |
| 95 }; | |
| 96 | |
| 97 /** | |
| 98 * Creates a new gaia auth extension host. | |
| 99 * @param {HTMLIFrameElement|string} container The iframe element or its id | |
| 100 * to host the auth extension. | |
| 101 * @constructor | |
| 102 * @extends {cr.EventTarget} | |
| 103 */ | |
| 104 function GaiaAuthHost(container) { | |
| 105 this.frame_ = typeof container == 'string' ? $(container) : container; | |
| 106 assert(this.frame_); | |
| 107 window.addEventListener('message', | |
| 108 this.onMessage_.bind(this), false); | |
| 109 } | |
| 110 | |
| 111 GaiaAuthHost.prototype = { | |
| 112 __proto__: cr.EventTarget.prototype, | |
| 113 | |
| 114 /** | |
| 115 * Auth extension params | |
| 116 * @type {Object} | |
| 117 */ | |
| 118 authParams_: {}, | |
| 119 | |
| 120 /** | |
| 121 * An url to use with {@code reload}. | |
| 122 * @type {?string} | |
| 123 * @private | |
| 124 */ | |
| 125 reloadUrl_: null, | |
| 126 | |
| 127 /** | |
| 128 * Invoked when authentication is completed successfully with credential | |
| 129 * data. A credential data object looks like this: | |
| 130 * <pre> | |
| 131 * {@code | |
| 132 * { | |
| 133 * email: 'xx@gmail.com', | |
| 134 * password: 'xxxx', // May not present | |
| 135 * authCode: 'x/xx', // May not present | |
| 136 * authMode: 'x', // Authorization mode, default/offline/desktop. | |
| 137 * } | |
| 138 * } | |
| 139 * </pre> | |
| 140 * @type {function(Object)} | |
| 141 * @private | |
| 142 */ | |
| 143 successCallback_: null, | |
| 144 | |
| 145 /** | |
| 146 * Invoked when the auth flow needs a user to confirm their passwords. This | |
| 147 * could happen when there are more than one passwords scraped during SAML | |
| 148 * flow. The embedder of GaiaAuthHost should show an UI to collect a | |
| 149 * password from user then call GaiaAuthHost.verifyConfirmedPassword to | |
| 150 * verify. If the password is good, the auth flow continues with success | |
| 151 * path. Otherwise, confirmPasswordCallback_ is invoked again. | |
| 152 * @type {function()} | |
| 153 */ | |
| 154 confirmPasswordCallback_: null, | |
| 155 | |
| 156 /** | |
| 157 * Similar to confirmPasswordCallback_ but is used when there is no | |
| 158 * password scraped after a success authentication. The authenticated user | |
| 159 * account is passed to the callback. The embedder should take over the | |
| 160 * flow and decide what to do next. | |
| 161 * @type {function(string)} | |
| 162 */ | |
| 163 noPasswordCallback_: null, | |
| 164 | |
| 165 /** | |
| 166 * Invoked when the authentication flow had to be aborted because content | |
| 167 * served over an unencrypted connection was detected. | |
| 168 */ | |
| 169 insecureContentBlockedCallback_: null, | |
| 170 | |
| 171 /** | |
| 172 * Invoked to display an error message to the user when a GAIA error occurs | |
| 173 * during authentication. | |
| 174 * @type {function()} | |
| 175 */ | |
| 176 missingGaiaInfoCallback_: null, | |
| 177 | |
| 178 /** | |
| 179 * Invoked to record that the credentials passing API was used. | |
| 180 * @type {function()} | |
| 181 */ | |
| 182 samlApiUsedCallback_: null, | |
| 183 | |
| 184 /** | |
| 185 * The iframe container. | |
| 186 * @type {HTMLIFrameElement} | |
| 187 */ | |
| 188 get frame() { | |
| 189 return this.frame_; | |
| 190 }, | |
| 191 | |
| 192 /** | |
| 193 * Sets confirmPasswordCallback_. | |
| 194 * @type {function()} | |
| 195 */ | |
| 196 set confirmPasswordCallback(callback) { | |
| 197 this.confirmPasswordCallback_ = callback; | |
| 198 }, | |
| 199 | |
| 200 /** | |
| 201 * Sets noPasswordCallback_. | |
| 202 * @type {function()} | |
| 203 */ | |
| 204 set noPasswordCallback(callback) { | |
| 205 this.noPasswordCallback_ = callback; | |
| 206 }, | |
| 207 | |
| 208 /** | |
| 209 * Sets insecureContentBlockedCallback_. | |
| 210 * @type {function(string)} | |
| 211 */ | |
| 212 set insecureContentBlockedCallback(callback) { | |
| 213 this.insecureContentBlockedCallback_ = callback; | |
| 214 }, | |
| 215 | |
| 216 /** | |
| 217 * Sets missingGaiaInfoCallback_. | |
| 218 * @type {function()} | |
| 219 */ | |
| 220 set missingGaiaInfoCallback(callback) { | |
| 221 this.missingGaiaInfoCallback_ = callback; | |
| 222 }, | |
| 223 | |
| 224 /** | |
| 225 * Sets samlApiUsedCallback_. | |
| 226 * @type {function()} | |
| 227 */ | |
| 228 set samlApiUsedCallback(callback) { | |
| 229 this.samlApiUsedCallback_ = callback; | |
| 230 }, | |
| 231 | |
| 232 /** | |
| 233 * Loads the auth extension. | |
| 234 * @param {AuthMode} authMode Authorization mode. | |
| 235 * @param {Object} data Parameters for the auth extension. See the auth | |
| 236 * extension's main.js for all supported params and their defaults. | |
| 237 * @param {function(Object)} successCallback A function to be called when | |
| 238 * the authentication is completed successfully. The callback is | |
| 239 * invoked with a credential object. | |
| 240 */ | |
| 241 load: function(authMode, data, successCallback) { | |
| 242 var params = {}; | |
| 243 | |
| 244 var populateParams = function(nameList, values) { | |
| 245 if (!values) | |
| 246 return; | |
| 247 | |
| 248 for (var i in nameList) { | |
| 249 var name = nameList[i]; | |
| 250 if (values[name]) | |
| 251 params[name] = values[name]; | |
| 252 } | |
| 253 }; | |
| 254 | |
| 255 populateParams(SUPPORTED_PARAMS, data); | |
| 256 populateParams(LOCALIZED_STRING_PARAMS, data.localizedStrings); | |
| 257 params['needPassword'] = true; | |
| 258 | |
| 259 var url; | |
| 260 switch (authMode) { | |
| 261 case AuthMode.OFFLINE: | |
| 262 url = OFFLINE_AUTH_URL; | |
| 263 break; | |
| 264 case AuthMode.DESKTOP: | |
| 265 url = AUTH_URL; | |
| 266 params['desktopMode'] = true; | |
| 267 break; | |
| 268 default: | |
| 269 url = AUTH_URL; | |
| 270 } | |
| 271 | |
| 272 this.authParams_ = params; | |
| 273 this.reloadUrl_ = url; | |
| 274 this.successCallback_ = successCallback; | |
| 275 | |
| 276 this.reload(); | |
| 277 }, | |
| 278 | |
| 279 /** | |
| 280 * Reloads the auth extension. | |
| 281 */ | |
| 282 reload: function() { | |
| 283 this.frame_.src = this.reloadUrl_; | |
| 284 this.authFlow = AuthFlow.GAIA; | |
| 285 }, | |
| 286 | |
| 287 /** | |
| 288 * Verifies the supplied password by sending it to the auth extension, | |
| 289 * which will then check if it matches the scraped passwords. | |
| 290 * @param {string} password The confirmed password that needs verification. | |
| 291 */ | |
| 292 verifyConfirmedPassword: function(password) { | |
| 293 var msg = { | |
| 294 method: 'verifyConfirmedPassword', | |
| 295 password: password | |
| 296 }; | |
| 297 this.frame_.contentWindow.postMessage(msg, AUTH_URL_BASE); | |
| 298 }, | |
| 299 | |
| 300 /** | |
| 301 * Invoked to process authentication success. | |
| 302 * @param {Object} credentials Credential object to pass to success | |
| 303 * callback. | |
| 304 * @private | |
| 305 */ | |
| 306 onAuthSuccess_: function(credentials) { | |
| 307 if (this.successCallback_) | |
| 308 this.successCallback_(credentials); | |
| 309 cr.dispatchSimpleEvent(this, 'completed'); | |
| 310 }, | |
| 311 | |
| 312 /** | |
| 313 * Checks if message comes from the loaded authentication extension. | |
| 314 * @param {Object} e Payload of the received HTML5 message. | |
| 315 * @type {boolean} | |
| 316 */ | |
| 317 isAuthExtMessage_: function(e) { | |
| 318 return this.frame_.src && | |
| 319 this.frame_.src.startsWith(e.origin) && | |
| 320 e.source == this.frame_.contentWindow; | |
| 321 }, | |
| 322 | |
| 323 /** | |
| 324 * Event handler that is invoked when HTML5 message is received. | |
| 325 * @param {object} e Payload of the received HTML5 message. | |
| 326 */ | |
| 327 onMessage_: function(e) { | |
| 328 var msg = e.data; | |
| 329 | |
| 330 if (!this.isAuthExtMessage_(e)) | |
| 331 return; | |
| 332 | |
| 333 if (msg.method == 'loginUIDOMContentLoaded') { | |
| 334 this.frame_.contentWindow.postMessage(this.authParams_, AUTH_URL_BASE); | |
| 335 return; | |
| 336 } | |
| 337 | |
| 338 if (msg.method == 'loginUILoaded') { | |
| 339 cr.dispatchSimpleEvent(this, 'ready'); | |
| 340 return; | |
| 341 } | |
| 342 | |
| 343 if (/^complete(Login|Authentication)$|^offlineLogin$/.test(msg.method)) { | |
| 344 if (!msg.email && !this.email_ && !msg.skipForNow) { | |
| 345 var msg = {method: 'redirectToSignin'}; | |
| 346 this.frame_.contentWindow.postMessage(msg, AUTH_URL_BASE); | |
| 347 return; | |
| 348 } | |
| 349 this.onAuthSuccess_({email: msg.email, | |
| 350 password: msg.password, | |
| 351 gaiaId: msg.gaiaId, | |
| 352 useOffline: msg.method == 'offlineLogin', | |
| 353 usingSAML: msg.usingSAML || false, | |
| 354 chooseWhatToSync: msg.chooseWhatToSync, | |
| 355 skipForNow: msg.skipForNow || false, | |
| 356 sessionIndex: msg.sessionIndex || ''}); | |
| 357 return; | |
| 358 } | |
| 359 | |
| 360 if (msg.method == 'completeAuthenticationAuthCodeOnly') { | |
| 361 if (!msg.authCode) { | |
| 362 console.error( | |
| 363 'GaiaAuthHost: completeAuthentication without auth code.'); | |
| 364 var msg = {method: 'redirectToSignin'}; | |
| 365 this.frame_.contentWindow.postMessage(msg, AUTH_URL_BASE); | |
| 366 return; | |
| 367 } | |
| 368 this.onAuthSuccess_({authCodeOnly: true, authCode: msg.authCode}); | |
| 369 return; | |
| 370 } | |
| 371 | |
| 372 if (msg.method == 'confirmPassword') { | |
| 373 if (this.confirmPasswordCallback_) | |
| 374 this.confirmPasswordCallback_(msg.email, msg.passwordCount); | |
| 375 else | |
| 376 console.error('GaiaAuthHost: Invalid confirmPasswordCallback_.'); | |
| 377 return; | |
| 378 } | |
| 379 | |
| 380 if (msg.method == 'noPassword') { | |
| 381 if (this.noPasswordCallback_) | |
| 382 this.noPasswordCallback_(msg.email); | |
| 383 else | |
| 384 console.error('GaiaAuthHost: Invalid noPasswordCallback_.'); | |
| 385 return; | |
| 386 } | |
| 387 | |
| 388 if (msg.method == 'authPageLoaded') { | |
| 389 this.authDomain = msg.domain; | |
| 390 this.authFlow = msg.isSAML ? AuthFlow.SAML : AuthFlow.GAIA; | |
| 391 return; | |
| 392 } | |
| 393 | |
| 394 if (msg.method == 'resetAuthFlow') { | |
| 395 this.authFlow = AuthFlow.GAIA; | |
| 396 return; | |
| 397 } | |
| 398 | |
| 399 if (msg.method == 'insecureContentBlocked') { | |
| 400 if (this.insecureContentBlockedCallback_) { | |
| 401 this.insecureContentBlockedCallback_(msg.url); | |
| 402 } else { | |
| 403 console.error( | |
| 404 'GaiaAuthHost: Invalid insecureContentBlockedCallback_.'); | |
| 405 } | |
| 406 return; | |
| 407 } | |
| 408 | |
| 409 if (msg.method == 'switchToFullTab') { | |
| 410 chrome.send('switchToFullTab', [msg.url]); | |
| 411 return; | |
| 412 } | |
| 413 | |
| 414 if (msg.method == 'missingGaiaInfo') { | |
| 415 if (this.missingGaiaInfoCallback_) { | |
| 416 this.missingGaiaInfoCallback_(); | |
| 417 } else { | |
| 418 console.error('GaiaAuthHost: Invalid missingGaiaInfoCallback_.'); | |
| 419 } | |
| 420 return; | |
| 421 } | |
| 422 | |
| 423 if (msg.method == 'samlApiUsed') { | |
| 424 if (this.samlApiUsedCallback_) { | |
| 425 this.samlApiUsedCallback_(); | |
| 426 } else { | |
| 427 console.error('GaiaAuthHost: Invalid samlApiUsedCallback_.'); | |
| 428 } | |
| 429 return; | |
| 430 } | |
| 431 | |
| 432 console.error('Unknown message method=' + msg.method); | |
| 433 } | |
| 434 }; | |
| 435 | |
| 436 /** | |
| 437 * The domain name of the current auth page. | |
| 438 * @type {string} | |
| 439 */ | |
| 440 cr.defineProperty(GaiaAuthHost, 'authDomain'); | |
| 441 | |
| 442 /** | |
| 443 * The current auth flow of the hosted gaia_auth extension. | |
| 444 * @type {AuthFlow} | |
| 445 */ | |
| 446 cr.defineProperty(GaiaAuthHost, 'authFlow'); | |
| 447 | |
| 448 GaiaAuthHost.SUPPORTED_PARAMS = SUPPORTED_PARAMS; | |
| 449 GaiaAuthHost.LOCALIZED_STRING_PARAMS = LOCALIZED_STRING_PARAMS; | |
| 450 GaiaAuthHost.AuthMode = AuthMode; | |
| 451 GaiaAuthHost.AuthFlow = AuthFlow; | |
| 452 | |
| 453 return { | |
| 454 GaiaAuthHost: GaiaAuthHost | |
| 455 }; | |
| 456 }); | |
| OLD | NEW |