| OLD | NEW |
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 /** | 5 /** |
| 6 * Authenticator class wraps the communications between Gaia and its host. | 6 * Authenticator class wraps the communications between Gaia and its host. |
| 7 */ | 7 */ |
| 8 function Authenticator() { | 8 function Authenticator() { |
| 9 } | 9 } |
| 10 | 10 |
| (...skipping 30 matching lines...) Expand all Loading... |
| 41 */ | 41 */ |
| 42 Authenticator.getInstance = function() { | 42 Authenticator.getInstance = function() { |
| 43 if (!Authenticator.instance_) { | 43 if (!Authenticator.instance_) { |
| 44 Authenticator.instance_ = new Authenticator(); | 44 Authenticator.instance_ = new Authenticator(); |
| 45 } | 45 } |
| 46 return Authenticator.instance_; | 46 return Authenticator.instance_; |
| 47 }; | 47 }; |
| 48 | 48 |
| 49 Authenticator.prototype = { | 49 Authenticator.prototype = { |
| 50 email_: null, | 50 email_: null, |
| 51 gaiaId_: null, |
| 51 | 52 |
| 52 // Depending on the key type chosen, this will contain the plain text password | 53 // Depending on the key type chosen, this will contain the plain text password |
| 53 // or a credential derived from it along with the information required to | 54 // or a credential derived from it along with the information required to |
| 54 // repeat the derivation, such as a salt. The information will be encoded so | 55 // repeat the derivation, such as a salt. The information will be encoded so |
| 55 // that it contains printable ASCII characters only. The exact encoding is TBD | 56 // that it contains printable ASCII characters only. The exact encoding is TBD |
| 56 // when support for key types other than plain text password is added. | 57 // when support for key types other than plain text password is added. |
| 57 passwordBytes_: null, | 58 passwordBytes_: null, |
| 58 | 59 |
| 60 chooseWhatToSync_: false, |
| 61 skipForNow_: false, |
| 62 sessionIndex_: null, |
| 59 attemptToken_: null, | 63 attemptToken_: null, |
| 60 | 64 |
| 61 // Input params from extension initialization URL. | 65 // Input params from extension initialization URL. |
| 62 inputLang_: undefined, | 66 inputLang_: undefined, |
| 63 intputEmail_: undefined, | 67 intputEmail_: undefined, |
| 64 | 68 |
| 65 isSAMLFlow_: false, | 69 isSAMLFlow_: false, |
| 66 gaiaLoaded_: false, | 70 gaiaLoaded_: false, |
| 67 supportChannel_: null, | 71 supportChannel_: null, |
| 68 | 72 |
| (...skipping 28 matching lines...) Expand all Loading... |
| 97 | 101 |
| 98 document.addEventListener('DOMContentLoaded', this.onPageLoad_.bind(this)); | 102 document.addEventListener('DOMContentLoaded', this.onPageLoad_.bind(this)); |
| 99 }, | 103 }, |
| 100 | 104 |
| 101 isGaiaMessage_: function(msg) { | 105 isGaiaMessage_: function(msg) { |
| 102 // Not quite right, but good enough. | 106 // Not quite right, but good enough. |
| 103 return this.gaiaUrl_.indexOf(msg.origin) == 0 || | 107 return this.gaiaUrl_.indexOf(msg.origin) == 0 || |
| 104 this.GAIA_URL.indexOf(msg.origin) == 0; | 108 this.GAIA_URL.indexOf(msg.origin) == 0; |
| 105 }, | 109 }, |
| 106 | 110 |
| 107 isInternalMessage_: function(msg) { | |
| 108 return msg.origin == Authenticator.THIS_EXTENSION_ORIGIN; | |
| 109 }, | |
| 110 | |
| 111 isParentMessage_: function(msg) { | 111 isParentMessage_: function(msg) { |
| 112 return msg.origin == this.parentPage_; | 112 return msg.origin == this.parentPage_; |
| 113 }, | 113 }, |
| 114 | 114 |
| 115 constructInitialFrameUrl_: function() { | 115 constructInitialFrameUrl_: function() { |
| 116 var url = this.gaiaUrl_ + this.gaiaPath_; | 116 var url = this.gaiaUrl_ + this.gaiaPath_; |
| 117 | 117 |
| 118 url = appendParam(url, 'service', this.service_); | 118 url = appendParam(url, 'service', this.service_); |
| 119 url = appendParam(url, 'continue', this.continueUrl_); | 119 url = appendParam(url, 'continue', this.continueUrl_); |
| 120 if (this.inputLang_) | 120 if (this.inputLang_) |
| (...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 158 | 158 |
| 159 if (this.desktopMode_) { | 159 if (this.desktopMode_) { |
| 160 this.supportChannel_.send({ | 160 this.supportChannel_.send({ |
| 161 name: 'initDesktopFlow', | 161 name: 'initDesktopFlow', |
| 162 gaiaUrl: this.gaiaUrl_, | 162 gaiaUrl: this.gaiaUrl_, |
| 163 continueUrl: stripParams(this.continueUrl_), | 163 continueUrl: stripParams(this.continueUrl_), |
| 164 isConstrainedWindow: this.isConstrainedWindow_ | 164 isConstrainedWindow: this.isConstrainedWindow_ |
| 165 }); | 165 }); |
| 166 this.supportChannel_.registerMessage( | 166 this.supportChannel_.registerMessage( |
| 167 'switchToFullTab', this.switchToFullTab_.bind(this)); | 167 'switchToFullTab', this.switchToFullTab_.bind(this)); |
| 168 this.supportChannel_.registerMessage( | |
| 169 'completeLogin', this.completeLogin_.bind(this)); | |
| 170 } | 168 } |
| 169 this.supportChannel_.registerMessage( |
| 170 'completeLogin', this.onCompleteLogin_.bind(this)); |
| 171 this.initSAML_(); | 171 this.initSAML_(); |
| 172 this.maybeInitialized_(); | 172 this.maybeInitialized_(); |
| 173 }.bind(this)); | 173 }.bind(this)); |
| 174 | 174 |
| 175 window.setTimeout(function() { | 175 window.setTimeout(function() { |
| 176 if (!this.supportChannel_) { | 176 if (!this.supportChannel_) { |
| 177 // Re-initialize the channel if it is not connected properly, e.g. | 177 // Re-initialize the channel if it is not connected properly, e.g. |
| 178 // connect may be called before background script started running. | 178 // connect may be called before background script started running. |
| 179 this.initSupportChannel_(); | 179 this.initSupportChannel_(); |
| 180 } | 180 } |
| (...skipping 10 matching lines...) Expand all Loading... |
| 191 return; | 191 return; |
| 192 var msg = { | 192 var msg = { |
| 193 'method': 'loginUILoaded' | 193 'method': 'loginUILoaded' |
| 194 }; | 194 }; |
| 195 window.parent.postMessage(msg, this.parentPage_); | 195 window.parent.postMessage(msg, this.parentPage_); |
| 196 }, | 196 }, |
| 197 | 197 |
| 198 /** | 198 /** |
| 199 * Invoked when the background script sends a message to indicate that the | 199 * Invoked when the background script sends a message to indicate that the |
| 200 * current content does not fit in a constrained window. | 200 * current content does not fit in a constrained window. |
| 201 * @param {Object=} opt_extraMsg Optional extra info to send. | 201 * @param {Object=} msg Extra info to send. |
| 202 */ | 202 */ |
| 203 switchToFullTab_: function(msg) { | 203 switchToFullTab_: function(msg) { |
| 204 var parentMsg = { | 204 var parentMsg = { |
| 205 'method': 'switchToFullTab', | 205 'method': 'switchToFullTab', |
| 206 'url': msg.url | 206 'url': msg.url |
| 207 }; | 207 }; |
| 208 window.parent.postMessage(parentMsg, this.parentPage_); | 208 window.parent.postMessage(parentMsg, this.parentPage_); |
| 209 }, | 209 }, |
| 210 | 210 |
| 211 /** | 211 /** |
| 212 * Invoked when the signin flow is complete. | 212 * Invoked when the signin flow is complete. |
| 213 * @param {Object=} opt_extraMsg Optional extra info to send. | 213 * @param {Object=} opt_extraMsg Optional extra info to send. |
| 214 */ | 214 */ |
| 215 completeLogin_: function(opt_extraMsg) { | 215 completeLogin_: function(opt_extraMsg) { |
| 216 var msg = { | 216 var msg = { |
| 217 'method': 'completeLogin', | 217 'method': 'completeLogin', |
| 218 'email': (opt_extraMsg && opt_extraMsg.email) || this.email_, | 218 'email': (opt_extraMsg && opt_extraMsg.email) || this.email_, |
| 219 'password': (opt_extraMsg && opt_extraMsg.password) || | 219 'password': (opt_extraMsg && opt_extraMsg.password) || |
| 220 this.passwordBytes_, | 220 this.passwordBytes_, |
| 221 'usingSAML': this.isSAMLFlow_, | 221 'usingSAML': this.isSAMLFlow_, |
| 222 'chooseWhatToSync': this.chooseWhatToSync_ || false, | 222 'chooseWhatToSync': this.chooseWhatToSync_ || false, |
| 223 'skipForNow': opt_extraMsg && opt_extraMsg.skipForNow, | 223 'skipForNow': (opt_extraMsg && opt_extraMsg.skipForNow) || |
| 224 'sessionIndex': opt_extraMsg && opt_extraMsg.sessionIndex | 224 this.skipForNow_, |
| 225 'sessionIndex': (opt_extraMsg && opt_extraMsg.sessionIndex) || |
| 226 this.sessionIndex_, |
| 227 'gaiaId': (opt_extraMsg && opt_extraMsg.gaiaId) || this.gaiaId_ |
| 225 }; | 228 }; |
| 226 window.parent.postMessage(msg, this.parentPage_); | 229 window.parent.postMessage(msg, this.parentPage_); |
| 227 this.supportChannel_.send({name: 'resetAuth'}); | 230 this.supportChannel_.send({name: 'resetAuth'}); |
| 228 }, | 231 }, |
| 229 | 232 |
| 230 /** | 233 /** |
| 231 * Invoked when support channel is connected. | 234 * Invoked when support channel is connected. |
| 232 */ | 235 */ |
| 233 initSAML_: function() { | 236 initSAML_: function() { |
| 234 this.isSAMLFlow_ = false; | 237 this.isSAMLFlow_ = false; |
| (...skipping 26 matching lines...) Expand all Loading... |
| 261 */ | 264 */ |
| 262 onAuthPageLoaded_: function(msg) { | 265 onAuthPageLoaded_: function(msg) { |
| 263 var isSAMLPage = msg.url.indexOf(this.gaiaUrl_) != 0; | 266 var isSAMLPage = msg.url.indexOf(this.gaiaUrl_) != 0; |
| 264 | 267 |
| 265 if (isSAMLPage && !this.isSAMLFlow_) { | 268 if (isSAMLPage && !this.isSAMLFlow_) { |
| 266 // GAIA redirected to a SAML login page. The credentials provided to this | 269 // GAIA redirected to a SAML login page. The credentials provided to this |
| 267 // page will determine what user gets logged in. The credentials obtained | 270 // page will determine what user gets logged in. The credentials obtained |
| 268 // from the GAIA login form are no longer relevant and can be discarded. | 271 // from the GAIA login form are no longer relevant and can be discarded. |
| 269 this.isSAMLFlow_ = true; | 272 this.isSAMLFlow_ = true; |
| 270 this.email_ = null; | 273 this.email_ = null; |
| 274 this.gaiaId_ = null; |
| 271 this.passwordBytes_ = null; | 275 this.passwordBytes_ = null; |
| 272 } | 276 } |
| 273 | 277 |
| 274 window.parent.postMessage({ | 278 window.parent.postMessage({ |
| 275 'method': 'authPageLoaded', | 279 'method': 'authPageLoaded', |
| 276 'isSAML': this.isSAMLFlow_, | 280 'isSAML': this.isSAMLFlow_, |
| 277 'domain': extractDomain(msg.url) | 281 'domain': extractDomain(msg.url) |
| 278 }, this.parentPage_); | 282 }, this.parentPage_); |
| 279 }, | 283 }, |
| 280 | 284 |
| (...skipping 28 matching lines...) Expand all Loading... |
| 309 this.initialized_ = true; | 313 this.initialized_ = true; |
| 310 this.sendInitializationSuccess_(); | 314 this.sendInitializationSuccess_(); |
| 311 return; | 315 return; |
| 312 } | 316 } |
| 313 | 317 |
| 314 if (call.method == 'add') { | 318 if (call.method == 'add') { |
| 315 if (Authenticator.API_KEY_TYPES.indexOf(call.keyType) == -1) { | 319 if (Authenticator.API_KEY_TYPES.indexOf(call.keyType) == -1) { |
| 316 console.error('Authenticator.onAPICall_: unsupported key type'); | 320 console.error('Authenticator.onAPICall_: unsupported key type'); |
| 317 return; | 321 return; |
| 318 } | 322 } |
| 323 // Not setting |email_| and |gaiaId_| because this API call will |
| 324 // eventually be followed by onCompleteLogin_() which does set it. |
| 319 this.apiToken_ = call.token; | 325 this.apiToken_ = call.token; |
| 320 this.email_ = call.user; | |
| 321 this.passwordBytes_ = call.passwordBytes; | 326 this.passwordBytes_ = call.passwordBytes; |
| 322 } else if (call.method == 'confirm') { | 327 } else if (call.method == 'confirm') { |
| 323 if (call.token != this.apiToken_) | 328 if (call.token != this.apiToken_) |
| 324 console.error('Authenticator.onAPICall_: token mismatch'); | 329 console.error('Authenticator.onAPICall_: token mismatch'); |
| 325 } else { | 330 } else { |
| 326 console.error('Authenticator.onAPICall_: unknown message'); | 331 console.error('Authenticator.onAPICall_: unknown message'); |
| 327 } | 332 } |
| 328 }, | 333 }, |
| 329 | 334 |
| 330 sendInitializationSuccess_: function() { | 335 sendInitializationSuccess_: function() { |
| 331 this.supportChannel_.send({name: 'apiResponse', response: { | 336 this.supportChannel_.send({name: 'apiResponse', response: { |
| 332 result: 'initialized', | 337 result: 'initialized', |
| 333 version: this.apiVersion_, | 338 version: this.apiVersion_, |
| 334 keyTypes: Authenticator.API_KEY_TYPES | 339 keyTypes: Authenticator.API_KEY_TYPES |
| 335 }}); | 340 }}); |
| 336 }, | 341 }, |
| 337 | 342 |
| 338 sendInitializationFailure_: function() { | 343 sendInitializationFailure_: function() { |
| 339 this.supportChannel_.send({ | 344 this.supportChannel_.send({ |
| 340 name: 'apiResponse', | 345 name: 'apiResponse', |
| 341 response: {result: 'initialization_failed'} | 346 response: {result: 'initialization_failed'} |
| 342 }); | 347 }); |
| 343 }, | 348 }, |
| 344 | 349 |
| 345 onConfirmLogin_: function() { | 350 /** |
| 346 if (!this.isSAMLFlow_) { | 351 * Callback invoked for 'completeLogin' message. |
| 347 this.completeLogin_(); | 352 * @param {Object=} msg Message sent from background page. |
| 353 */ |
| 354 onCompleteLogin_: function(msg) { |
| 355 if (!msg.email || !msg.gaiaId || !msg.sessionIndex) { |
| 356 console.error('Missing fields to complete login.'); |
| 357 window.parent.postMessage({method: 'missingGaiaInfo'}, this.parentPage_); |
| 348 return; | 358 return; |
| 349 } | 359 } |
| 350 | 360 |
| 351 var apiUsed = !!this.passwordBytes_; | 361 // Skip SAML extra steps for desktop flow and non-SAML flow. |
| 362 if (!this.isSAMLFlow_ || this.desktopMode_) { |
| 363 this.completeLogin_(msg); |
| 364 return; |
| 365 } |
| 352 | 366 |
| 353 // Retrieve the e-mail address of the user who just authenticated from GAIA. | 367 this.email_ = msg.email; |
| 354 window.parent.postMessage({method: 'retrieveAuthenticatedUserEmail', | 368 this.gaiaId_ = msg.gaiaId; |
| 355 attemptToken: this.attemptToken_, | 369 // Password from |msg| is not used because ChromeOS SAML flow |
| 356 apiUsed: apiUsed}, | 370 // gets password by asking user to confirm. |
| 357 this.parentPage_); | 371 this.skipForNow_ = msg.skipForNow; |
| 372 this.sessionIndex_ = msg.sessionIndex; |
| 358 | 373 |
| 359 if (!apiUsed) { | 374 if (this.passwordBytes_) { |
| 375 window.parent.postMessage({method: 'samlApiUsed'}, this.parentPage_); |
| 376 this.completeLogin_(msg); |
| 377 } else { |
| 360 this.supportChannel_.sendWithCallback( | 378 this.supportChannel_.sendWithCallback( |
| 361 {name: 'getScrapedPasswords'}, | 379 {name: 'getScrapedPasswords'}, |
| 362 function(passwords) { | 380 function(passwords) { |
| 363 if (passwords.length == 0) { | 381 if (passwords.length == 0) { |
| 364 window.parent.postMessage( | 382 window.parent.postMessage( |
| 365 {method: 'noPassword', email: this.email_}, | 383 {method: 'noPassword', email: this.email_}, |
| 366 this.parentPage_); | 384 this.parentPage_); |
| 367 } else { | 385 } else { |
| 368 window.parent.postMessage({method: 'confirmPassword', | 386 window.parent.postMessage({method: 'confirmPassword', |
| 369 email: this.email_, | 387 email: this.email_, |
| 370 passwordCount: passwords.length}, | 388 passwordCount: passwords.length}, |
| 371 this.parentPage_); | 389 this.parentPage_); |
| 372 } | 390 } |
| 373 }.bind(this)); | 391 }.bind(this)); |
| 374 } | 392 } |
| 375 }, | 393 }, |
| 376 | 394 |
| 377 maybeCompleteSAMLLogin_: function() { | |
| 378 // SAML login is complete when the user's e-mail address has been retrieved | |
| 379 // from GAIA and the user has successfully confirmed the password. | |
| 380 if (this.email_ !== null && this.passwordBytes_ !== null) | |
| 381 this.completeLogin_(); | |
| 382 }, | |
| 383 | |
| 384 onVerifyConfirmedPassword_: function(password) { | 395 onVerifyConfirmedPassword_: function(password) { |
| 385 this.supportChannel_.sendWithCallback( | 396 this.supportChannel_.sendWithCallback( |
| 386 {name: 'getScrapedPasswords'}, | 397 {name: 'getScrapedPasswords'}, |
| 387 function(passwords) { | 398 function(passwords) { |
| 388 for (var i = 0; i < passwords.length; ++i) { | 399 for (var i = 0; i < passwords.length; ++i) { |
| 389 if (passwords[i] == password) { | 400 if (passwords[i] == password) { |
| 390 this.passwordBytes_ = passwords[i]; | 401 this.passwordBytes_ = passwords[i]; |
| 391 this.maybeCompleteSAMLLogin_(); | 402 // SAML login is complete when the user has successfully |
| 403 // confirmed the password. |
| 404 if (this.passwordBytes_ !== null) |
| 405 this.completeLogin_(); |
| 392 return; | 406 return; |
| 393 } | 407 } |
| 394 } | 408 } |
| 395 window.parent.postMessage( | 409 window.parent.postMessage( |
| 396 {method: 'confirmPassword', email: this.email_}, | 410 {method: 'confirmPassword', email: this.email_}, |
| 397 this.parentPage_); | 411 this.parentPage_); |
| 398 }.bind(this)); | 412 }.bind(this)); |
| 399 }, | 413 }, |
| 400 | 414 |
| 401 onMessage: function(e) { | 415 onMessage: function(e) { |
| 402 var msg = e.data; | 416 var msg = e.data; |
| 403 if (msg.method == 'attemptLogin' && this.isGaiaMessage_(e)) { | 417 if (msg.method == 'attemptLogin' && this.isGaiaMessage_(e)) { |
| 418 // At this point GAIA does not yet know the gaiaId, so its not set here. |
| 404 this.email_ = msg.email; | 419 this.email_ = msg.email; |
| 405 this.passwordBytes_ = msg.password; | 420 this.passwordBytes_ = msg.password; |
| 406 this.attemptToken_ = msg.attemptToken; | 421 this.attemptToken_ = msg.attemptToken; |
| 407 this.chooseWhatToSync_ = msg.chooseWhatToSync; | 422 this.chooseWhatToSync_ = msg.chooseWhatToSync; |
| 408 this.isSAMLFlow_ = false; | 423 this.isSAMLFlow_ = false; |
| 409 if (this.supportChannel_) | 424 if (this.supportChannel_) |
| 410 this.supportChannel_.send({name: 'startAuth'}); | 425 this.supportChannel_.send({name: 'startAuth'}); |
| 411 else | 426 else |
| 412 console.error('Support channel is not initialized.'); | 427 console.error('Support channel is not initialized.'); |
| 413 } else if (msg.method == 'clearOldAttempts' && this.isGaiaMessage_(e)) { | 428 } else if (msg.method == 'clearOldAttempts' && this.isGaiaMessage_(e)) { |
| 414 if (!this.gaiaLoaded_) { | 429 if (!this.gaiaLoaded_) { |
| 415 this.gaiaLoaded_ = true; | 430 this.gaiaLoaded_ = true; |
| 416 this.maybeInitialized_(); | 431 this.maybeInitialized_(); |
| 417 } | 432 } |
| 418 this.email_ = null; | 433 this.email_ = null; |
| 434 this.gaiaId_ = null; |
| 435 this.sessionIndex_ = false; |
| 419 this.passwordBytes_ = null; | 436 this.passwordBytes_ = null; |
| 420 this.attemptToken_ = null; | 437 this.attemptToken_ = null; |
| 421 this.isSAMLFlow_ = false; | 438 this.isSAMLFlow_ = false; |
| 439 this.skipForNow_ = false; |
| 440 this.chooseWhatToSync_ = false; |
| 422 if (this.supportChannel_) | 441 if (this.supportChannel_) |
| 423 this.supportChannel_.send({name: 'resetAuth'}); | 442 this.supportChannel_.send({name: 'resetAuth'}); |
| 424 } else if (msg.method == 'setAuthenticatedUserEmail' && | |
| 425 this.isParentMessage_(e)) { | |
| 426 if (this.attemptToken_ == msg.attemptToken) { | |
| 427 this.email_ = msg.email; | |
| 428 this.maybeCompleteSAMLLogin_(); | |
| 429 } | |
| 430 } else if (msg.method == 'confirmLogin' && this.isInternalMessage_(e)) { | |
| 431 // In the desktop mode, Chrome needs to wait for extra info such as | |
| 432 // session index from the background JS. | |
| 433 if (this.desktopMode_) | |
| 434 return; | |
| 435 | |
| 436 if (this.attemptToken_ == msg.attemptToken) | |
| 437 this.onConfirmLogin_(); | |
| 438 else | |
| 439 console.error('Authenticator.onMessage: unexpected attemptToken!?'); | |
| 440 } else if (msg.method == 'verifyConfirmedPassword' && | 443 } else if (msg.method == 'verifyConfirmedPassword' && |
| 441 this.isParentMessage_(e)) { | 444 this.isParentMessage_(e)) { |
| 442 this.onVerifyConfirmedPassword_(msg.password); | 445 this.onVerifyConfirmedPassword_(msg.password); |
| 443 } else if (msg.method == 'redirectToSignin' && | 446 } else if (msg.method == 'redirectToSignin' && |
| 444 this.isParentMessage_(e)) { | 447 this.isParentMessage_(e)) { |
| 445 $('gaia-frame').src = this.constructInitialFrameUrl_(); | 448 $('gaia-frame').src = this.constructInitialFrameUrl_(); |
| 446 } else { | 449 } else { |
| 447 console.error('Authenticator.onMessage: unknown message + origin!?'); | 450 console.error('Authenticator.onMessage: unknown message + origin!?'); |
| 448 } | 451 } |
| 449 } | 452 } |
| 450 }; | 453 }; |
| 451 | 454 |
| 452 Authenticator.getInstance().initialize(); | 455 Authenticator.getInstance().initialize(); |
| OLD | NEW |