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 |
11 /** | 11 /** |
12 * Gaia auth extension url origin. | 12 * Gaia auth extension url origin. |
13 * @type {string} | 13 * @type {string} |
14 */ | 14 */ |
15 Authenticator.THIS_EXTENSION_ORIGIN = | 15 Authenticator.THIS_EXTENSION_ORIGIN = |
16 'chrome-extension://mfffpogegjflfpflabcdkioaeobkgjik'; | 16 'chrome-extension://mfffpogegjflfpflabcdkioaeobkgjik'; |
17 | 17 |
18 /** | 18 /** |
| 19 * The lowest version of the credentials passing API supported. |
| 20 * @type {number} |
| 21 */ |
| 22 Authenticator.MIN_API_VERSION_VERSION = 1; |
| 23 |
| 24 /** |
| 25 * The highest version of the credentials passing API supported. |
| 26 * @type {number} |
| 27 */ |
| 28 Authenticator.MAX_API_VERSION_VERSION = 2; |
| 29 |
| 30 /** |
| 31 * The key types supported for credentials passing API 2 and higher. |
| 32 * @type {Array} Array of strings. |
| 33 */ |
| 34 Authenticator.API_KEY_TYPES = [ |
| 35 'KEY_TYPE_PASSWORD_PLAIN', |
| 36 ]; |
| 37 |
| 38 /** |
19 * Singleton getter of Authenticator. | 39 * Singleton getter of Authenticator. |
20 * @return {Object} The singleton instance of Authenticator. | 40 * @return {Object} The singleton instance of Authenticator. |
21 */ | 41 */ |
22 Authenticator.getInstance = function() { | 42 Authenticator.getInstance = function() { |
23 if (!Authenticator.instance_) { | 43 if (!Authenticator.instance_) { |
24 Authenticator.instance_ = new Authenticator(); | 44 Authenticator.instance_ = new Authenticator(); |
25 } | 45 } |
26 return Authenticator.instance_; | 46 return Authenticator.instance_; |
27 }; | 47 }; |
28 | 48 |
29 Authenticator.prototype = { | 49 Authenticator.prototype = { |
30 email_: null, | 50 email_: null, |
31 password_: null, | 51 |
| 52 // 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 // 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 // when support for key types other than plain text password is added. |
| 57 passwordBytes_: null, |
| 58 |
32 attemptToken_: null, | 59 attemptToken_: null, |
33 | 60 |
34 // Input params from extension initialization URL. | 61 // Input params from extension initialization URL. |
35 inputLang_: undefined, | 62 inputLang_: undefined, |
36 intputEmail_: undefined, | 63 intputEmail_: undefined, |
37 | 64 |
38 isSAMLFlow_: false, | 65 isSAMLFlow_: false, |
39 isSAMLEnabled_: false, | 66 isSAMLEnabled_: false, |
40 supportChannel_: null, | 67 supportChannel_: null, |
41 | 68 |
(...skipping 126 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
168 }, | 195 }, |
169 | 196 |
170 /** | 197 /** |
171 * Invoked when the signin flow is complete. | 198 * Invoked when the signin flow is complete. |
172 * @param {Object=} opt_extraMsg Optional extra info to send. | 199 * @param {Object=} opt_extraMsg Optional extra info to send. |
173 */ | 200 */ |
174 completeLogin_: function(opt_extraMsg) { | 201 completeLogin_: function(opt_extraMsg) { |
175 var msg = { | 202 var msg = { |
176 'method': 'completeLogin', | 203 'method': 'completeLogin', |
177 'email': (opt_extraMsg && opt_extraMsg.email) || this.email_, | 204 'email': (opt_extraMsg && opt_extraMsg.email) || this.email_, |
178 'password': (opt_extraMsg && opt_extraMsg.password) || this.password_, | 205 'password': (opt_extraMsg && opt_extraMsg.password) || |
| 206 this.passwordBytes_, |
179 'usingSAML': this.isSAMLFlow_, | 207 'usingSAML': this.isSAMLFlow_, |
180 'chooseWhatToSync': this.chooseWhatToSync_ || false, | 208 'chooseWhatToSync': this.chooseWhatToSync_ || false, |
181 'skipForNow': opt_extraMsg && opt_extraMsg.skipForNow, | 209 'skipForNow': opt_extraMsg && opt_extraMsg.skipForNow, |
182 'sessionIndex': opt_extraMsg && opt_extraMsg.sessionIndex | 210 'sessionIndex': opt_extraMsg && opt_extraMsg.sessionIndex |
183 }; | 211 }; |
184 window.parent.postMessage(msg, this.parentPage_); | 212 window.parent.postMessage(msg, this.parentPage_); |
185 if (this.isSAMLEnabled_) | 213 if (this.isSAMLEnabled_) |
186 this.supportChannel_.send({name: 'resetAuth'}); | 214 this.supportChannel_.send({name: 'resetAuth'}); |
187 }, | 215 }, |
188 | 216 |
(...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
224 /** | 252 /** |
225 * Invoked when the background page sends 'onHostedPageLoaded' message. | 253 * Invoked when the background page sends 'onHostedPageLoaded' message. |
226 * @param {!Object} msg Details sent with the message. | 254 * @param {!Object} msg Details sent with the message. |
227 */ | 255 */ |
228 onAuthPageLoaded_: function(msg) { | 256 onAuthPageLoaded_: function(msg) { |
229 var isSAMLPage = msg.url.indexOf(this.gaiaUrl_) != 0; | 257 var isSAMLPage = msg.url.indexOf(this.gaiaUrl_) != 0; |
230 | 258 |
231 if (isSAMLPage && !this.isSAMLFlow_) { | 259 if (isSAMLPage && !this.isSAMLFlow_) { |
232 // GAIA redirected to a SAML login page. The credentials provided to this | 260 // GAIA redirected to a SAML login page. The credentials provided to this |
233 // page will determine what user gets logged in. The credentials obtained | 261 // page will determine what user gets logged in. The credentials obtained |
234 // from the GAIA login from are no longer relevant and can be discarded. | 262 // from the GAIA login form are no longer relevant and can be discarded. |
235 this.isSAMLFlow_ = true; | 263 this.isSAMLFlow_ = true; |
236 this.email_ = null; | 264 this.email_ = null; |
237 this.password_ = null; | 265 this.passwordBytes_ = null; |
238 } | 266 } |
239 | 267 |
240 window.parent.postMessage({ | 268 window.parent.postMessage({ |
241 'method': 'authPageLoaded', | 269 'method': 'authPageLoaded', |
242 'isSAML': this.isSAMLFlow_, | 270 'isSAML': this.isSAMLFlow_, |
243 'domain': extractDomain(msg.url) | 271 'domain': extractDomain(msg.url) |
244 }, this.parentPage_); | 272 }, this.parentPage_); |
245 }, | 273 }, |
246 | 274 |
247 /** | 275 /** |
248 * Invoked when the background page sends an 'onInsecureContentBlocked' | 276 * Invoked when the background page sends an 'onInsecureContentBlocked' |
249 * message. | 277 * message. |
250 */ | 278 */ |
251 onInsecureContentBlocked_: function() { | 279 onInsecureContentBlocked_: function() { |
252 window.parent.postMessage({'method': 'insecureContentBlocked'}, | 280 window.parent.postMessage({'method': 'insecureContentBlocked'}, |
253 this.parentPage_); | 281 this.parentPage_); |
254 }, | 282 }, |
255 | 283 |
256 /** | 284 /** |
257 * Invoked when one of the credential passing API methods is called by a SAML | 285 * Invoked when one of the credential passing API methods is called by a SAML |
258 * provider. | 286 * provider. |
259 * @param {!Object} msg Details of the API call. | 287 * @param {!Object} msg Details of the API call. |
260 */ | 288 */ |
261 onAPICall_: function(msg) { | 289 onAPICall_: function(msg) { |
262 var call = msg.call; | 290 var call = msg.call; |
| 291 if (call.method == 'initialize') { |
| 292 // TODO(bartfab): There was no |requestedVersion| parameter in version 1 |
| 293 // of the API. Remove this code once all consumers have switched to |
| 294 // version 2 or higher. |
| 295 if (!call.hasOwnProperty('requestedVersion')) { |
| 296 if (Authenticator.MIN_API_VERSION_VERSION == 1) { |
| 297 this.apiVersion_ = 1; |
| 298 this.initialized_ = true; |
| 299 this.sendInitializationSuccess_(); |
| 300 } |
| 301 // The glue code for API version 1 interprets all responses as success. |
| 302 // Instead of reporting failure, do not send any response at all. |
| 303 return; |
| 304 } |
| 305 |
| 306 if (!Number.isInteger(call.requestedVersion) || |
| 307 call.requestedVersion < Authenticator.MIN_API_VERSION_VERSION) { |
| 308 this.sendInitializationFailure_(); |
| 309 return; |
| 310 } |
| 311 |
| 312 this.apiVersion_ = Math.min(call.requestedVersion, |
| 313 Authenticator.MAX_API_VERSION_VERSION); |
| 314 this.initialized_ = true; |
| 315 this.sendInitializationSuccess_(); |
| 316 return; |
| 317 } |
| 318 |
263 if (call.method == 'add') { | 319 if (call.method == 'add') { |
| 320 if (this.apiVersion_ > 1 && |
| 321 Authenticator.API_KEY_TYPES.indexOf(call.keyType) == -1) { |
| 322 console.error('Authenticator.onAPICall_: unsupported key type'); |
| 323 return; |
| 324 } |
264 this.apiToken_ = call.token; | 325 this.apiToken_ = call.token; |
265 this.email_ = call.user; | 326 this.email_ = call.user; |
266 this.password_ = call.password; | 327 if (this.apiVersion_ == 1) |
| 328 this.passwordBytes_ = call.password; |
| 329 else |
| 330 this.passwordBytes_ = call.passwordBytes; |
267 } else if (call.method == 'confirm') { | 331 } else if (call.method == 'confirm') { |
268 if (call.token != this.apiToken_) | 332 if (call.token != this.apiToken_) |
269 console.error('Authenticator.onAPICall_: token mismatch'); | 333 console.error('Authenticator.onAPICall_: token mismatch'); |
270 } else { | 334 } else { |
271 console.error('Authenticator.onAPICall_: unknown message'); | 335 console.error('Authenticator.onAPICall_: unknown message'); |
272 } | 336 } |
273 }, | 337 }, |
274 | 338 |
| 339 sendInitializationSuccess_: function() { |
| 340 var response = { |
| 341 result: 'initialized', |
| 342 version: this.apiVersion_ |
| 343 }; |
| 344 if (this.apiVersion_ >= 2) |
| 345 response['keyTypes'] = Authenticator.API_KEY_TYPES; |
| 346 |
| 347 this.supportChannel_.send({name: 'apiResponse', response: response}); |
| 348 }, |
| 349 |
| 350 sendInitializationFailure_: function() { |
| 351 this.supportChannel_.send({ |
| 352 name: 'apiResponse', |
| 353 response: {result: 'initialization_failed'} |
| 354 }); |
| 355 }, |
| 356 |
275 onConfirmLogin_: function() { | 357 onConfirmLogin_: function() { |
276 if (!this.isSAMLFlow_) { | 358 if (!this.isSAMLFlow_) { |
277 this.completeLogin_(); | 359 this.completeLogin_(); |
278 return; | 360 return; |
279 } | 361 } |
280 | 362 |
281 var apiUsed = !!this.password_; | 363 var apiUsed = !!this.passwordBytes_; |
282 | 364 |
283 // Retrieve the e-mail address of the user who just authenticated from GAIA. | 365 // Retrieve the e-mail address of the user who just authenticated from GAIA. |
284 window.parent.postMessage({method: 'retrieveAuthenticatedUserEmail', | 366 window.parent.postMessage({method: 'retrieveAuthenticatedUserEmail', |
285 attemptToken: this.attemptToken_, | 367 attemptToken: this.attemptToken_, |
286 apiUsed: apiUsed}, | 368 apiUsed: apiUsed}, |
287 this.parentPage_); | 369 this.parentPage_); |
288 | 370 |
289 if (!apiUsed) { | 371 if (!apiUsed) { |
290 this.supportChannel_.sendWithCallback( | 372 this.supportChannel_.sendWithCallback( |
291 {name: 'getScrapedPasswords'}, | 373 {name: 'getScrapedPasswords'}, |
292 function(passwords) { | 374 function(passwords) { |
293 if (passwords.length == 0) { | 375 if (passwords.length == 0) { |
294 window.parent.postMessage( | 376 window.parent.postMessage( |
295 {method: 'noPassword', email: this.email_}, | 377 {method: 'noPassword', email: this.email_}, |
296 this.parentPage_); | 378 this.parentPage_); |
297 } else { | 379 } else { |
298 window.parent.postMessage({method: 'confirmPassword', | 380 window.parent.postMessage({method: 'confirmPassword', |
299 email: this.email_, | 381 email: this.email_, |
300 passwordCount: passwords.length}, | 382 passwordCount: passwords.length}, |
301 this.parentPage_); | 383 this.parentPage_); |
302 } | 384 } |
303 }.bind(this)); | 385 }.bind(this)); |
304 } | 386 } |
305 }, | 387 }, |
306 | 388 |
307 maybeCompleteSAMLLogin_: function() { | 389 maybeCompleteSAMLLogin_: function() { |
308 // SAML login is complete when the user's e-mail address has been retrieved | 390 // SAML login is complete when the user's e-mail address has been retrieved |
309 // from GAIA and the user has successfully confirmed the password. | 391 // from GAIA and the user has successfully confirmed the password. |
310 if (this.email_ !== null && this.password_ !== null) | 392 if (this.email_ !== null && this.passwordBytes_ !== null) |
311 this.completeLogin_(); | 393 this.completeLogin_(); |
312 }, | 394 }, |
313 | 395 |
314 onVerifyConfirmedPassword_: function(password) { | 396 onVerifyConfirmedPassword_: function(password) { |
315 this.supportChannel_.sendWithCallback( | 397 this.supportChannel_.sendWithCallback( |
316 {name: 'getScrapedPasswords'}, | 398 {name: 'getScrapedPasswords'}, |
317 function(passwords) { | 399 function(passwords) { |
318 for (var i = 0; i < passwords.length; ++i) { | 400 for (var i = 0; i < passwords.length; ++i) { |
319 if (passwords[i] == password) { | 401 if (passwords[i] == password) { |
320 this.password_ = passwords[i]; | 402 this.passwordBytes_ = passwords[i]; |
321 this.maybeCompleteSAMLLogin_(); | 403 this.maybeCompleteSAMLLogin_(); |
322 return; | 404 return; |
323 } | 405 } |
324 } | 406 } |
325 window.parent.postMessage( | 407 window.parent.postMessage( |
326 {method: 'confirmPassword', email: this.email_}, | 408 {method: 'confirmPassword', email: this.email_}, |
327 this.parentPage_); | 409 this.parentPage_); |
328 }.bind(this)); | 410 }.bind(this)); |
329 }, | 411 }, |
330 | 412 |
331 onMessage: function(e) { | 413 onMessage: function(e) { |
332 var msg = e.data; | 414 var msg = e.data; |
333 if (msg.method == 'attemptLogin' && this.isGaiaMessage_(e)) { | 415 if (msg.method == 'attemptLogin' && this.isGaiaMessage_(e)) { |
334 this.email_ = msg.email; | 416 this.email_ = msg.email; |
335 this.password_ = msg.password; | 417 this.passwordBytes_ = msg.password; |
336 this.attemptToken_ = msg.attemptToken; | 418 this.attemptToken_ = msg.attemptToken; |
337 this.chooseWhatToSync_ = msg.chooseWhatToSync; | 419 this.chooseWhatToSync_ = msg.chooseWhatToSync; |
338 this.isSAMLFlow_ = false; | 420 this.isSAMLFlow_ = false; |
339 if (this.isSAMLEnabled_) | 421 if (this.isSAMLEnabled_) |
340 this.supportChannel_.send({name: 'startAuth'}); | 422 this.supportChannel_.send({name: 'startAuth'}); |
341 } else if (msg.method == 'clearOldAttempts' && this.isGaiaMessage_(e)) { | 423 } else if (msg.method == 'clearOldAttempts' && this.isGaiaMessage_(e)) { |
342 this.email_ = null; | 424 this.email_ = null; |
343 this.password_ = null; | 425 this.passwordBytes_ = null; |
344 this.attemptToken_ = null; | 426 this.attemptToken_ = null; |
345 this.isSAMLFlow_ = false; | 427 this.isSAMLFlow_ = false; |
346 this.onLoginUILoaded_(); | 428 this.onLoginUILoaded_(); |
347 if (this.isSAMLEnabled_) | 429 if (this.isSAMLEnabled_) |
348 this.supportChannel_.send({name: 'resetAuth'}); | 430 this.supportChannel_.send({name: 'resetAuth'}); |
349 } else if (msg.method == 'setAuthenticatedUserEmail' && | 431 } else if (msg.method == 'setAuthenticatedUserEmail' && |
350 this.isParentMessage_(e)) { | 432 this.isParentMessage_(e)) { |
351 if (this.attemptToken_ == msg.attemptToken) { | 433 if (this.attemptToken_ == msg.attemptToken) { |
352 this.email_ = msg.email; | 434 this.email_ = msg.email; |
353 this.maybeCompleteSAMLLogin_(); | 435 this.maybeCompleteSAMLLogin_(); |
354 } | 436 } |
355 } else if (msg.method == 'confirmLogin' && this.isInternalMessage_(e)) { | 437 } else if (msg.method == 'confirmLogin' && this.isInternalMessage_(e)) { |
356 if (this.attemptToken_ == msg.attemptToken) | 438 if (this.attemptToken_ == msg.attemptToken) |
357 this.onConfirmLogin_(); | 439 this.onConfirmLogin_(); |
358 else | 440 else |
359 console.error('Authenticator.onMessage: unexpected attemptToken!?'); | 441 console.error('Authenticator.onMessage: unexpected attemptToken!?'); |
360 } else if (msg.method == 'verifyConfirmedPassword' && | 442 } else if (msg.method == 'verifyConfirmedPassword' && |
361 this.isParentMessage_(e)) { | 443 this.isParentMessage_(e)) { |
362 this.onVerifyConfirmedPassword_(msg.password); | 444 this.onVerifyConfirmedPassword_(msg.password); |
363 } else if (msg.method == 'redirectToSignin' && | 445 } else if (msg.method == 'redirectToSignin' && |
364 this.isParentMessage_(e)) { | 446 this.isParentMessage_(e)) { |
365 $('gaia-frame').src = this.constructInitialFrameUrl_(); | 447 $('gaia-frame').src = this.constructInitialFrameUrl_(); |
366 } else { | 448 } else { |
367 console.error('Authenticator.onMessage: unknown message + origin!?'); | 449 console.error('Authenticator.onMessage: unknown message + origin!?'); |
368 } | 450 } |
369 } | 451 } |
370 }; | 452 }; |
371 | 453 |
372 Authenticator.getInstance().initialize(); | 454 Authenticator.getInstance().initialize(); |
OLD | NEW |