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, | |
52 | 51 |
53 // Depending on the key type chosen, this will contain the plain text password | 52 // Depending on the key type chosen, this will contain the plain text password |
54 // or a credential derived from it along with the information required to | 53 // or a credential derived from it along with the information required to |
55 // repeat the derivation, such as a salt. The information will be encoded so | 54 // repeat the derivation, such as a salt. The information will be encoded so |
56 // that it contains printable ASCII characters only. The exact encoding is TBD | 55 // that it contains printable ASCII characters only. The exact encoding is TBD |
57 // when support for key types other than plain text password is added. | 56 // when support for key types other than plain text password is added. |
58 passwordBytes_: null, | 57 passwordBytes_: null, |
59 | 58 |
60 chooseWhatToSync_: false, | |
61 skipForNow_: false, | |
62 sessionIndex_: null, | |
63 attemptToken_: null, | 59 attemptToken_: null, |
64 | 60 |
65 // Input params from extension initialization URL. | 61 // Input params from extension initialization URL. |
66 inputLang_: undefined, | 62 inputLang_: undefined, |
67 intputEmail_: undefined, | 63 intputEmail_: undefined, |
68 | 64 |
69 isSAMLFlow_: false, | 65 isSAMLFlow_: false, |
70 gaiaLoaded_: false, | 66 gaiaLoaded_: false, |
71 supportChannel_: null, | 67 supportChannel_: null, |
72 | 68 |
(...skipping 28 matching lines...) Expand all Loading... |
101 | 97 |
102 document.addEventListener('DOMContentLoaded', this.onPageLoad_.bind(this)); | 98 document.addEventListener('DOMContentLoaded', this.onPageLoad_.bind(this)); |
103 }, | 99 }, |
104 | 100 |
105 isGaiaMessage_: function(msg) { | 101 isGaiaMessage_: function(msg) { |
106 // Not quite right, but good enough. | 102 // Not quite right, but good enough. |
107 return this.gaiaUrl_.indexOf(msg.origin) == 0 || | 103 return this.gaiaUrl_.indexOf(msg.origin) == 0 || |
108 this.GAIA_URL.indexOf(msg.origin) == 0; | 104 this.GAIA_URL.indexOf(msg.origin) == 0; |
109 }, | 105 }, |
110 | 106 |
| 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)); |
168 } | 170 } |
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=} msg Extra info to send. | 201 * @param {Object=} opt_extraMsg Optional 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 this.skipForNow_, | 224 'sessionIndex': opt_extraMsg && opt_extraMsg.sessionIndex |
225 'sessionIndex': (opt_extraMsg && opt_extraMsg.sessionIndex) || | |
226 this.sessionIndex_, | |
227 'gaiaId': (opt_extraMsg && opt_extraMsg.gaiaId) || this.gaiaId_ | |
228 }; | 225 }; |
229 window.parent.postMessage(msg, this.parentPage_); | 226 window.parent.postMessage(msg, this.parentPage_); |
230 this.supportChannel_.send({name: 'resetAuth'}); | 227 this.supportChannel_.send({name: 'resetAuth'}); |
231 }, | 228 }, |
232 | 229 |
233 /** | 230 /** |
234 * Invoked when support channel is connected. | 231 * Invoked when support channel is connected. |
235 */ | 232 */ |
236 initSAML_: function() { | 233 initSAML_: function() { |
237 this.isSAMLFlow_ = false; | 234 this.isSAMLFlow_ = false; |
(...skipping 26 matching lines...) Expand all Loading... |
264 */ | 261 */ |
265 onAuthPageLoaded_: function(msg) { | 262 onAuthPageLoaded_: function(msg) { |
266 var isSAMLPage = msg.url.indexOf(this.gaiaUrl_) != 0; | 263 var isSAMLPage = msg.url.indexOf(this.gaiaUrl_) != 0; |
267 | 264 |
268 if (isSAMLPage && !this.isSAMLFlow_) { | 265 if (isSAMLPage && !this.isSAMLFlow_) { |
269 // GAIA redirected to a SAML login page. The credentials provided to this | 266 // GAIA redirected to a SAML login page. The credentials provided to this |
270 // page will determine what user gets logged in. The credentials obtained | 267 // page will determine what user gets logged in. The credentials obtained |
271 // from the GAIA login form are no longer relevant and can be discarded. | 268 // from the GAIA login form are no longer relevant and can be discarded. |
272 this.isSAMLFlow_ = true; | 269 this.isSAMLFlow_ = true; |
273 this.email_ = null; | 270 this.email_ = null; |
274 this.gaiaId_ = null; | |
275 this.passwordBytes_ = null; | 271 this.passwordBytes_ = null; |
276 } | 272 } |
277 | 273 |
278 window.parent.postMessage({ | 274 window.parent.postMessage({ |
279 'method': 'authPageLoaded', | 275 'method': 'authPageLoaded', |
280 'isSAML': this.isSAMLFlow_, | 276 'isSAML': this.isSAMLFlow_, |
281 'domain': extractDomain(msg.url) | 277 'domain': extractDomain(msg.url) |
282 }, this.parentPage_); | 278 }, this.parentPage_); |
283 }, | 279 }, |
284 | 280 |
(...skipping 28 matching lines...) Expand all Loading... |
313 this.initialized_ = true; | 309 this.initialized_ = true; |
314 this.sendInitializationSuccess_(); | 310 this.sendInitializationSuccess_(); |
315 return; | 311 return; |
316 } | 312 } |
317 | 313 |
318 if (call.method == 'add') { | 314 if (call.method == 'add') { |
319 if (Authenticator.API_KEY_TYPES.indexOf(call.keyType) == -1) { | 315 if (Authenticator.API_KEY_TYPES.indexOf(call.keyType) == -1) { |
320 console.error('Authenticator.onAPICall_: unsupported key type'); | 316 console.error('Authenticator.onAPICall_: unsupported key type'); |
321 return; | 317 return; |
322 } | 318 } |
323 // Not setting |email_| and |gaiaId_| because this API call will | |
324 // eventually be followed by onCompleteLogin_() which does set it. | |
325 this.apiToken_ = call.token; | 319 this.apiToken_ = call.token; |
| 320 this.email_ = call.user; |
326 this.passwordBytes_ = call.passwordBytes; | 321 this.passwordBytes_ = call.passwordBytes; |
327 } else if (call.method == 'confirm') { | 322 } else if (call.method == 'confirm') { |
328 if (call.token != this.apiToken_) | 323 if (call.token != this.apiToken_) |
329 console.error('Authenticator.onAPICall_: token mismatch'); | 324 console.error('Authenticator.onAPICall_: token mismatch'); |
330 } else { | 325 } else { |
331 console.error('Authenticator.onAPICall_: unknown message'); | 326 console.error('Authenticator.onAPICall_: unknown message'); |
332 } | 327 } |
333 }, | 328 }, |
334 | 329 |
335 sendInitializationSuccess_: function() { | 330 sendInitializationSuccess_: function() { |
336 this.supportChannel_.send({name: 'apiResponse', response: { | 331 this.supportChannel_.send({name: 'apiResponse', response: { |
337 result: 'initialized', | 332 result: 'initialized', |
338 version: this.apiVersion_, | 333 version: this.apiVersion_, |
339 keyTypes: Authenticator.API_KEY_TYPES | 334 keyTypes: Authenticator.API_KEY_TYPES |
340 }}); | 335 }}); |
341 }, | 336 }, |
342 | 337 |
343 sendInitializationFailure_: function() { | 338 sendInitializationFailure_: function() { |
344 this.supportChannel_.send({ | 339 this.supportChannel_.send({ |
345 name: 'apiResponse', | 340 name: 'apiResponse', |
346 response: {result: 'initialization_failed'} | 341 response: {result: 'initialization_failed'} |
347 }); | 342 }); |
348 }, | 343 }, |
349 | 344 |
350 /** | 345 onConfirmLogin_: function() { |
351 * Callback invoked for 'completeLogin' message. | 346 if (!this.isSAMLFlow_) { |
352 * @param {Object=} msg Message sent from background page. | 347 this.completeLogin_(); |
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_); | |
358 return; | 348 return; |
359 } | 349 } |
360 | 350 |
361 // Skip SAML extra steps for desktop flow and non-SAML flow. | 351 var apiUsed = !!this.passwordBytes_; |
362 if (!this.isSAMLFlow_ || this.desktopMode_) { | |
363 this.completeLogin_(msg); | |
364 return; | |
365 } | |
366 | 352 |
367 this.email_ = msg.email; | 353 // Retrieve the e-mail address of the user who just authenticated from GAIA. |
368 this.gaiaId_ = msg.gaiaId; | 354 window.parent.postMessage({method: 'retrieveAuthenticatedUserEmail', |
369 // Password from |msg| is not used because ChromeOS SAML flow | 355 attemptToken: this.attemptToken_, |
370 // gets password by asking user to confirm. | 356 apiUsed: apiUsed}, |
371 this.skipForNow_ = msg.skipForNow; | 357 this.parentPage_); |
372 this.sessionIndex_ = msg.sessionIndex; | |
373 | 358 |
374 if (this.passwordBytes_) { | 359 if (!apiUsed) { |
375 window.parent.postMessage({method: 'samlApiUsed'}, this.parentPage_); | |
376 this.completeLogin_(msg); | |
377 } else { | |
378 this.supportChannel_.sendWithCallback( | 360 this.supportChannel_.sendWithCallback( |
379 {name: 'getScrapedPasswords'}, | 361 {name: 'getScrapedPasswords'}, |
380 function(passwords) { | 362 function(passwords) { |
381 if (passwords.length == 0) { | 363 if (passwords.length == 0) { |
382 window.parent.postMessage( | 364 window.parent.postMessage( |
383 {method: 'noPassword', email: this.email_}, | 365 {method: 'noPassword', email: this.email_}, |
384 this.parentPage_); | 366 this.parentPage_); |
385 } else { | 367 } else { |
386 window.parent.postMessage({method: 'confirmPassword', | 368 window.parent.postMessage({method: 'confirmPassword', |
387 email: this.email_, | 369 email: this.email_, |
388 passwordCount: passwords.length}, | 370 passwordCount: passwords.length}, |
389 this.parentPage_); | 371 this.parentPage_); |
390 } | 372 } |
391 }.bind(this)); | 373 }.bind(this)); |
392 } | 374 } |
393 }, | 375 }, |
394 | 376 |
| 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 |
395 onVerifyConfirmedPassword_: function(password) { | 384 onVerifyConfirmedPassword_: function(password) { |
396 this.supportChannel_.sendWithCallback( | 385 this.supportChannel_.sendWithCallback( |
397 {name: 'getScrapedPasswords'}, | 386 {name: 'getScrapedPasswords'}, |
398 function(passwords) { | 387 function(passwords) { |
399 for (var i = 0; i < passwords.length; ++i) { | 388 for (var i = 0; i < passwords.length; ++i) { |
400 if (passwords[i] == password) { | 389 if (passwords[i] == password) { |
401 this.passwordBytes_ = passwords[i]; | 390 this.passwordBytes_ = passwords[i]; |
402 // SAML login is complete when the user has successfully | 391 this.maybeCompleteSAMLLogin_(); |
403 // confirmed the password. | |
404 if (this.passwordBytes_ !== null) | |
405 this.completeLogin_(); | |
406 return; | 392 return; |
407 } | 393 } |
408 } | 394 } |
409 window.parent.postMessage( | 395 window.parent.postMessage( |
410 {method: 'confirmPassword', email: this.email_}, | 396 {method: 'confirmPassword', email: this.email_}, |
411 this.parentPage_); | 397 this.parentPage_); |
412 }.bind(this)); | 398 }.bind(this)); |
413 }, | 399 }, |
414 | 400 |
415 onMessage: function(e) { | 401 onMessage: function(e) { |
416 var msg = e.data; | 402 var msg = e.data; |
417 if (msg.method == 'attemptLogin' && this.isGaiaMessage_(e)) { | 403 if (msg.method == 'attemptLogin' && this.isGaiaMessage_(e)) { |
418 // At this point GAIA does not yet know the gaiaId, so its not set here. | |
419 this.email_ = msg.email; | 404 this.email_ = msg.email; |
420 this.passwordBytes_ = msg.password; | 405 this.passwordBytes_ = msg.password; |
421 this.attemptToken_ = msg.attemptToken; | 406 this.attemptToken_ = msg.attemptToken; |
422 this.chooseWhatToSync_ = msg.chooseWhatToSync; | 407 this.chooseWhatToSync_ = msg.chooseWhatToSync; |
423 this.isSAMLFlow_ = false; | 408 this.isSAMLFlow_ = false; |
424 if (this.supportChannel_) | 409 if (this.supportChannel_) |
425 this.supportChannel_.send({name: 'startAuth'}); | 410 this.supportChannel_.send({name: 'startAuth'}); |
426 else | 411 else |
427 console.error('Support channel is not initialized.'); | 412 console.error('Support channel is not initialized.'); |
428 } else if (msg.method == 'clearOldAttempts' && this.isGaiaMessage_(e)) { | 413 } else if (msg.method == 'clearOldAttempts' && this.isGaiaMessage_(e)) { |
429 if (!this.gaiaLoaded_) { | 414 if (!this.gaiaLoaded_) { |
430 this.gaiaLoaded_ = true; | 415 this.gaiaLoaded_ = true; |
431 this.maybeInitialized_(); | 416 this.maybeInitialized_(); |
432 } | 417 } |
433 this.email_ = null; | 418 this.email_ = null; |
434 this.gaiaId_ = null; | |
435 this.sessionIndex_ = false; | |
436 this.passwordBytes_ = null; | 419 this.passwordBytes_ = null; |
437 this.attemptToken_ = null; | 420 this.attemptToken_ = null; |
438 this.isSAMLFlow_ = false; | 421 this.isSAMLFlow_ = false; |
439 this.skipForNow_ = false; | |
440 this.chooseWhatToSync_ = false; | |
441 if (this.supportChannel_) | 422 if (this.supportChannel_) |
442 this.supportChannel_.send({name: 'resetAuth'}); | 423 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!?'); |
443 } else if (msg.method == 'verifyConfirmedPassword' && | 440 } else if (msg.method == 'verifyConfirmedPassword' && |
444 this.isParentMessage_(e)) { | 441 this.isParentMessage_(e)) { |
445 this.onVerifyConfirmedPassword_(msg.password); | 442 this.onVerifyConfirmedPassword_(msg.password); |
446 } else if (msg.method == 'redirectToSignin' && | 443 } else if (msg.method == 'redirectToSignin' && |
447 this.isParentMessage_(e)) { | 444 this.isParentMessage_(e)) { |
448 $('gaia-frame').src = this.constructInitialFrameUrl_(); | 445 $('gaia-frame').src = this.constructInitialFrameUrl_(); |
449 } else { | 446 } else { |
450 console.error('Authenticator.onMessage: unknown message + origin!?'); | 447 console.error('Authenticator.onMessage: unknown message + origin!?'); |
451 } | 448 } |
452 } | 449 } |
453 }; | 450 }; |
454 | 451 |
455 Authenticator.getInstance().initialize(); | 452 Authenticator.getInstance().initialize(); |
OLD | NEW |