Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(268)

Side by Side Diff: chrome/browser/resources/gaia_auth_host/authenticator.js

Issue 1229883003: ChromeOS: should send old user GAPS cookie to GAIA on user reauthentication. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Update after review. Created 5 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright 2014 The Chromium Authors. All rights reserved. 1 // Copyright 2014 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 <include src="saml_handler.js"> 5 <include src="saml_handler.js">
6 6
7 /** 7 /**
8 * @fileoverview An UI component to authenciate to Chrome. The component hosts 8 * @fileoverview An UI component to authenciate to Chrome. The component hosts
9 * IdP web pages in a webview. A client who is interested in monitoring 9 * IdP web pages in a webview. A client who is interested in monitoring
10 * authentication events should pass a listener object of type 10 * authentication events should pass a listener object of type
11 * cr.login.GaiaAuthHost.Listener as defined in this file. After initialization, 11 * cr.login.GaiaAuthHost.Listener as defined in this file. After initialization,
12 * call {@code load} to start the authentication flow. 12 * call {@code load} to start the authentication flow.
13 */ 13 */
14 14
15 cr.define('cr.login', function() { 15 cr.define('cr.login', function() {
16 'use strict'; 16 'use strict';
17 17
18 // TODO(rogerta): should use gaia URL from GaiaUrls::gaia_url() instead 18 // TODO(rogerta): should use gaia URL from GaiaUrls::gaia_url() instead
19 // of hardcoding the prod URL here. As is, this does not work with staging 19 // of hardcoding the prod URL here. As is, this does not work with staging
20 // environments. 20 // environments.
21 var IDP_ORIGIN = 'https://accounts.google.com/'; 21 var IDP_ORIGIN = 'https://accounts.google.com/';
22 var IDP_PATH = 'ServiceLogin?skipvpage=true&sarp=1&rm=hide'; 22 var IDP_PATH = 'ServiceLogin?skipvpage=true&sarp=1&rm=hide';
23 var CONTINUE_URL = 23 var CONTINUE_URL =
24 'chrome-extension://mfffpogegjflfpflabcdkioaeobkgjik/success.html'; 24 'chrome-extension://mfffpogegjflfpflabcdkioaeobkgjik/success.html';
25 var SIGN_IN_HEADER = 'google-accounts-signin'; 25 var SIGN_IN_HEADER = 'google-accounts-signin';
26 var EMBEDDED_FORM_HEADER = 'google-accounts-embedded'; 26 var EMBEDDED_FORM_HEADER = 'google-accounts-embedded';
27 var LOCATION_HEADER = 'location'; 27 var LOCATION_HEADER = 'location';
28 var COOKIE_HEADER = 'cookie';
28 var SET_COOKIE_HEADER = 'set-cookie'; 29 var SET_COOKIE_HEADER = 'set-cookie';
29 var OAUTH_CODE_COOKIE = 'oauth_code'; 30 var OAUTH_CODE_COOKIE = 'oauth_code';
31 var GAPS_COOKIE = 'GAPS';
30 var SERVICE_ID = 'chromeoslogin'; 32 var SERVICE_ID = 'chromeoslogin';
31 var EMBEDDED_SETUP_CHROMEOS_ENDPOINT = 'embedded/setup/chromeos'; 33 var EMBEDDED_SETUP_CHROMEOS_ENDPOINT = 'embedded/setup/chromeos';
32 34
33 /** 35 /**
34 * The source URL parameter for the constrained signin flow. 36 * The source URL parameter for the constrained signin flow.
35 */ 37 */
36 var CONSTRAINED_FLOW_SOURCE = 'chrome'; 38 var CONSTRAINED_FLOW_SOURCE = 'chrome';
37 39
38 /** 40 /**
39 * Enum for the authorization mode, must match AuthMode defined in 41 * Enum for the authorization mode, must match AuthMode defined in
(...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after
80 // not called before dispatching |authCopleted|. 82 // not called before dispatching |authCopleted|.
81 // Default is |true|. 83 // Default is |true|.
82 'flow', // One of 'default', 'enterprise', or 'theftprotection'. 84 'flow', // One of 'default', 'enterprise', or 'theftprotection'.
83 'enterpriseDomain', // Domain in which hosting device is (or should be) 85 'enterpriseDomain', // Domain in which hosting device is (or should be)
84 // enrolled. 86 // enrolled.
85 'emailDomain', // Value used to prefill domain for email. 87 'emailDomain', // Value used to prefill domain for email.
86 'clientVersion', // Version of the Chrome build. 88 'clientVersion', // Version of the Chrome build.
87 'platformVersion', // Version of the OS build. 89 'platformVersion', // Version of the OS build.
88 'releaseChannel', // Installation channel. 90 'releaseChannel', // Installation channel.
89 'endpointGen', // Current endpoint generation. 91 'endpointGen', // Current endpoint generation.
92 'gapsCookie', // GAPS cookie
90 ]; 93 ];
91 94
92 /** 95 /**
93 * Initializes the authenticator component. 96 * Initializes the authenticator component.
94 * @param {webview|string} webview The webview element or its ID to host IdP 97 * @param {webview|string} webview The webview element or its ID to host IdP
95 * web pages. 98 * web pages.
96 * @constructor 99 * @constructor
97 */ 100 */
98 function Authenticator(webview) { 101 function Authenticator(webview) {
99 this.webview_ = typeof webview == 'string' ? $(webview) : webview; 102 this.webview_ = typeof webview == 'string' ? $(webview) : webview;
100 assert(this.webview_); 103 assert(this.webview_);
101 104
102 this.email_ = null; 105 this.email_ = null;
103 this.password_ = null; 106 this.password_ = null;
104 this.gaiaId_ = null, 107 this.gaiaId_ = null,
105 this.sessionIndex_ = null; 108 this.sessionIndex_ = null;
106 this.chooseWhatToSync_ = false; 109 this.chooseWhatToSync_ = false;
107 this.skipForNow_ = false; 110 this.skipForNow_ = false;
108 this.authFlow = AuthFlow.DEFAULT; 111 this.authFlow = AuthFlow.DEFAULT;
109 this.authDomain = ''; 112 this.authDomain = '';
110 this.loaded_ = false; 113 this.loaded_ = false;
111 this.idpOrigin_ = null; 114 this.idpOrigin_ = null;
112 this.continueUrl_ = null; 115 this.continueUrl_ = null;
113 this.continueUrlWithoutParams_ = null; 116 this.continueUrlWithoutParams_ = null;
114 this.initialFrameUrl_ = null; 117 this.initialFrameUrl_ = null;
115 this.reloadUrl_ = null; 118 this.reloadUrl_ = null;
116 this.trusted_ = true; 119 this.trusted_ = true;
117 this.oauth_code_ = null; 120 this.oauthCode_ = null;
121 this.gapsCookie_ = null;
122 this.gapsCookieSent_ = false;
123 this.newGapsCookie_ = null;
118 124
119 this.useEafe_ = false; 125 this.useEafe_ = false;
120 this.clientId_ = null; 126 this.clientId_ = null;
121 127
122 this.samlHandler_ = new cr.login.SamlHandler(this.webview_); 128 this.samlHandler_ = new cr.login.SamlHandler(this.webview_);
123 this.confirmPasswordCallback = null; 129 this.confirmPasswordCallback = null;
124 this.noPasswordCallback = null; 130 this.noPasswordCallback = null;
125 this.insecureContentBlockedCallback = null; 131 this.insecureContentBlockedCallback = null;
126 this.samlApiUsedCallback = null; 132 this.samlApiUsedCallback = null;
127 this.missingGaiaInfoCallback = null; 133 this.missingGaiaInfoCallback = null;
(...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after
163 Authenticator.prototype = Object.create(cr.EventTarget.prototype); 169 Authenticator.prototype = Object.create(cr.EventTarget.prototype);
164 170
165 /** 171 /**
166 * Reinitializes authentication parameters so that a failed login attempt 172 * Reinitializes authentication parameters so that a failed login attempt
167 * would not result in an infinite loop. 173 * would not result in an infinite loop.
168 */ 174 */
169 Authenticator.prototype.clearCredentials_ = function() { 175 Authenticator.prototype.clearCredentials_ = function() {
170 this.email_ = null; 176 this.email_ = null;
171 this.gaiaId_ = null; 177 this.gaiaId_ = null;
172 this.password_ = null; 178 this.password_ = null;
173 this.oauth_code_ = null; 179 this.oauthCode_ = null;
180 this.gapsCookie_ = null;
181 this.gapsCookieSent_ = false;
182 this.newGapsCookie_ = null;
174 this.chooseWhatToSync_ = false; 183 this.chooseWhatToSync_ = false;
175 this.skipForNow_ = false; 184 this.skipForNow_ = false;
176 this.sessionIndex_ = null; 185 this.sessionIndex_ = null;
177 this.trusted_ = true; 186 this.trusted_ = true;
178 this.authFlow = AuthFlow.DEFAULT; 187 this.authFlow = AuthFlow.DEFAULT;
179 this.samlHandler_.reset(); 188 this.samlHandler_.reset();
180 }; 189 };
181 190
182 /** 191 /**
183 * Loads the authenticator component with the given parameters. 192 * Loads the authenticator component with the given parameters.
184 * @param {AuthMode} authMode Authorization mode. 193 * @param {AuthMode} authMode Authorization mode.
185 * @param {Object} data Parameters for the authorization flow. 194 * @param {Object} data Parameters for the authorization flow.
186 */ 195 */
187 Authenticator.prototype.load = function(authMode, data) { 196 Authenticator.prototype.load = function(authMode, data) {
188 this.clearCredentials_(); 197 this.clearCredentials_();
189 this.loaded_ = false; 198 this.loaded_ = false;
199 // gaiaUrl parameter is used for testing. Once defined, it is never changed.
190 this.idpOrigin_ = data.gaiaUrl || IDP_ORIGIN; 200 this.idpOrigin_ = data.gaiaUrl || IDP_ORIGIN;
191 this.continueUrl_ = data.continueUrl || CONTINUE_URL; 201 this.continueUrl_ = data.continueUrl || CONTINUE_URL;
192 this.continueUrlWithoutParams_ = 202 this.continueUrlWithoutParams_ =
193 this.continueUrl_.substring(0, this.continueUrl_.indexOf('?')) || 203 this.continueUrl_.substring(0, this.continueUrl_.indexOf('?')) ||
194 this.continueUrl_; 204 this.continueUrl_;
195 this.isConstrainedWindow_ = data.constrained == '1'; 205 this.isConstrainedWindow_ = data.constrained == '1';
196 this.isNewGaiaFlowChromeOS = data.isNewGaiaFlowChromeOS; 206 this.isNewGaiaFlowChromeOS = data.isNewGaiaFlowChromeOS;
197 this.useEafe_ = data.useEafe || false; 207 this.useEafe_ = data.useEafe || false;
198 this.clientId_ = data.clientId; 208 this.clientId_ = data.clientId;
209 this.gapsCookie_ = data.gapsCookie;
210 this.gapsCookieSent_ = false;
211 this.newGapsCookie_ = null;
199 212
200 this.initialFrameUrl_ = this.constructInitialFrameUrl_(data); 213 this.initialFrameUrl_ = this.constructInitialFrameUrl_(data);
201 this.reloadUrl_ = data.frameUrl || this.initialFrameUrl_; 214 this.reloadUrl_ = data.frameUrl || this.initialFrameUrl_;
202 // Don't block insecure content for desktop flow because it lands on 215 // Don't block insecure content for desktop flow because it lands on
203 // http. Otherwise, block insecure content as long as gaia is https. 216 // http. Otherwise, block insecure content as long as gaia is https.
204 this.samlHandler_.blockInsecureContent = authMode != AuthMode.DESKTOP && 217 this.samlHandler_.blockInsecureContent = authMode != AuthMode.DESKTOP &&
205 this.idpOrigin_.indexOf('https://') == 0; 218 this.idpOrigin_.indexOf('https://') == 0;
206 this.needPassword = !('needPassword' in data) || data.needPassword; 219 this.needPassword = !('needPassword' in data) || data.needPassword;
207 220
208 if (this.isNewGaiaFlowChromeOS) { 221 if (this.isNewGaiaFlowChromeOS) {
209 this.webview_.contextMenus.onShow.addListener(function(e) { 222 this.webview_.contextMenus.onShow.addListener(function(e) {
210 e.preventDefault(); 223 e.preventDefault();
211 }); 224 });
225
226 if (!this.onBeforeSetHeadersSet_) {
227 this.onBeforeSetHeadersSet_ = true;
228 var filterPrefix = this.idpOrigin_ + EMBEDDED_SETUP_CHROMEOS_ENDPOINT;
229 // This depends on gaiaUrl parameter, that is why it is here.
230 this.webview_.request.onBeforeSendHeaders.addListener(
231 this.onBeforeSendHeaders_.bind(this),
232 {urls: [filterPrefix + '?*', filterPrefix + '/*']},
233 ['requestHeaders', 'blocking']);
234 }
212 } 235 }
213 236
214 this.webview_.src = this.reloadUrl_; 237 this.webview_.src = this.reloadUrl_;
215 }; 238 };
216 239
217 /** 240 /**
218 * Reloads the authenticator component. 241 * Reloads the authenticator component.
219 */ 242 */
220 Authenticator.prototype.reload = function() { 243 Authenticator.prototype.reload = function() {
221 this.clearCredentials_(); 244 this.clearCredentials_();
(...skipping 143 matching lines...) Expand 10 before | Expand all | Expand 10 after
365 this.sessionIndex_ = signinDetails['sessionindex']; 388 this.sessionIndex_ = signinDetails['sessionindex'];
366 } else if (headerName == LOCATION_HEADER) { 389 } else if (headerName == LOCATION_HEADER) {
367 // If the "choose what to sync" checkbox was clicked, then the continue 390 // If the "choose what to sync" checkbox was clicked, then the continue
368 // URL will contain a source=3 field. 391 // URL will contain a source=3 field.
369 var location = decodeURIComponent(header.value); 392 var location = decodeURIComponent(header.value);
370 this.chooseWhatToSync_ = !!location.match(/(\?|&)source=3($|&)/); 393 this.chooseWhatToSync_ = !!location.match(/(\?|&)source=3($|&)/);
371 } else if ( 394 } else if (
372 this.isNewGaiaFlowChromeOS && headerName == SET_COOKIE_HEADER) { 395 this.isNewGaiaFlowChromeOS && headerName == SET_COOKIE_HEADER) {
373 var headerValue = header.value; 396 var headerValue = header.value;
374 if (headerValue.indexOf(OAUTH_CODE_COOKIE + '=', 0) == 0) { 397 if (headerValue.indexOf(OAUTH_CODE_COOKIE + '=', 0) == 0) {
375 this.oauth_code_ = 398 this.oauthCode_ =
376 headerValue.substring(OAUTH_CODE_COOKIE.length + 1).split(';')[0]; 399 headerValue.substring(OAUTH_CODE_COOKIE.length + 1).split(';')[0];
377 } 400 }
401 if (headerValue.indexOf(GAPS_COOKIE + '=', 0) == 0) {
402 this.newGapsCookie_ =
403 headerValue.substring(GAPS_COOKIE.length + 1).split(';')[0];
404 }
378 } 405 }
379 } 406 }
380 }; 407 };
381 408
382 /** 409 /**
410 * This method replaces cookie value in cookie header.
411 * @param@ {string} header_value Original string value of Cookie header.
412 * @param@ {string} cookie_name Name of cookie to be replaced.
413 * @param@ {string} cookie_value New cookie value.
414 * @return {string} New Cookie header value.
415 * @private
416 */
417 Authenticator.prototype.updateCookieValue_ = function(
418 header_value, cookie_name, cookie_value) {
419 var cookies = header_value.split(/\s*;\s*/);
420 var found = false;
421 for (var i = 0; i < cookies.length; ++i) {
422 if (cookies[i].indexOf(cookie_name + '=', 0) == 0) {
423 found = true;
424 cookies[i] = cookie_name + '=' + cookie_value;
425 break;
426 }
427 }
428 if (!found) {
429 cookies.push(cookie_name + '=' + cookie_value);
430 }
431 return cookies.join('; ');
432 };
433
434 /**
435 * Handler for webView.request.onBeforeSendHeaders .
436 * @return {!Object} Modified request headers.
437 * @private
438 */
439 Authenticator.prototype.onBeforeSendHeaders_ = function(details) {
440 // We should re-send cookie if first request was unsuccessful (i.e. no new
441 // GAPS cookie was received).
442 if (this.isNewGaiaFlowChromeOS && this.gapsCookie_ &&
443 (!this.gapsCookieSent_ || !this.newGapsCookie_)) {
444 var headers = details.requestHeaders;
445 var found = false;
446 var gapsCookie = this.gapsCookie_;
447
448 for (var i = 0, l = headers.length; i < l; ++i) {
449 if (headers[i].name == COOKIE_HEADER) {
450 headers[i].value = this.updateCookieValue_(headers[i].value,
451 GAPS_COOKIE, gapsCookie);
452 found = true;
453 break;
454 }
455 }
456 if (!found) {
457 details.requestHeaders.push(
458 {name: COOKIE_HEADER, value: GAPS_COOKIE + '=' + gapsCookie});
459 }
460 this.gapsCookieSent_ = true;
461 }
462 return {
463 requestHeaders: details.requestHeaders
464 };
465 };
466
467 /**
383 * Returns true if given HTML5 message is received from the webview element. 468 * Returns true if given HTML5 message is received from the webview element.
384 * @param {object} e Payload of the received HTML5 message. 469 * @param {object} e Payload of the received HTML5 message.
385 */ 470 */
386 Authenticator.prototype.isGaiaMessage = function(e) { 471 Authenticator.prototype.isGaiaMessage = function(e) {
387 if (!this.isWebviewEvent_(e)) 472 if (!this.isWebviewEvent_(e))
388 return false; 473 return false;
389 474
390 // The event origin does not have a trailing slash. 475 // The event origin does not have a trailing slash.
391 if (e.origin != this.idpOrigin_.substring(0, this.idpOrigin_.length - 1)) { 476 if (e.origin != this.idpOrigin_.substring(0, this.idpOrigin_.length - 1)) {
392 return false; 477 return false;
393 } 478 }
394 479
395 // EAFE passes back auth code via message. 480 // EAFE passes back auth code via message.
396 if (this.useEafe_ && 481 if (this.useEafe_ &&
397 typeof e.data == 'object' && 482 typeof e.data == 'object' &&
398 e.data.hasOwnProperty('authorizationCode')) { 483 e.data.hasOwnProperty('authorizationCode')) {
399 assert(!this.oauth_code_); 484 assert(!this.oauthCode_);
400 this.oauth_code_ = e.data.authorizationCode; 485 this.oauthCode_ = e.data.authorizationCode;
401 this.dispatchEvent( 486 this.dispatchEvent(
402 new CustomEvent('authCompleted', 487 new CustomEvent('authCompleted',
403 { 488 {
404 detail: { 489 detail: {
405 authCodeOnly: true, 490 authCodeOnly: true,
406 authCode: this.oauth_code_ 491 authCode: this.oauthCode_
407 } 492 }
408 })); 493 }));
409 return; 494 return;
410 } 495 }
411 496
412 // Gaia messages must be an object with 'method' property. 497 // Gaia messages must be an object with 'method' property.
413 if (typeof e.data != 'object' || !e.data.hasOwnProperty('method')) { 498 if (typeof e.data != 'object' || !e.data.hasOwnProperty('method')) {
414 return false; 499 return false;
415 } 500 }
416 return true; 501 return true;
(...skipping 98 matching lines...) Expand 10 before | Expand all | Expand 10 after
515 this.onAuthCompleted_(); 600 this.onAuthCompleted_();
516 }; 601 };
517 602
518 /** 603 /**
519 * Invoked to process authentication completion. 604 * Invoked to process authentication completion.
520 * @private 605 * @private
521 */ 606 */
522 Authenticator.prototype.onAuthCompleted_ = function() { 607 Authenticator.prototype.onAuthCompleted_ = function() {
523 assert(this.skipForNow_ || 608 assert(this.skipForNow_ ||
524 (this.email_ && this.gaiaId_ && this.sessionIndex_)); 609 (this.email_ && this.gaiaId_ && this.sessionIndex_));
525 this.dispatchEvent( 610 this.dispatchEvent(new CustomEvent(
526 new CustomEvent('authCompleted', 611 'authCompleted',
527 // TODO(rsorokin): get rid of the stub values. 612 // TODO(rsorokin): get rid of the stub values.
528 { 613 {
529 detail: { 614 detail: {
530 email: this.email_ || '', 615 email: this.email_ || '',
531 gaiaId: this.gaiaId_ || '', 616 gaiaId: this.gaiaId_ || '',
532 password: this.password_ || '', 617 password: this.password_ || '',
533 authCode: this.oauth_code_, 618 authCode: this.oauthCode_,
534 usingSAML: this.authFlow == AuthFlow.SAML, 619 usingSAML: this.authFlow == AuthFlow.SAML,
535 chooseWhatToSync: this.chooseWhatToSync_, 620 chooseWhatToSync: this.chooseWhatToSync_,
536 skipForNow: this.skipForNow_, 621 skipForNow: this.skipForNow_,
537 sessionIndex: this.sessionIndex_ || '', 622 sessionIndex: this.sessionIndex_ || '',
538 trusted: this.trusted_ 623 trusted: this.trusted_,
539 } 624 gapsCookie: this.newGapsCookie_ || this.gapsCookie_ || '',
540 })); 625 }
626 }));
541 this.clearCredentials_(); 627 this.clearCredentials_();
542 }; 628 };
543 629
544 /** 630 /**
545 * Invoked when |samlHandler_| fires 'insecureContentBlocked' event. 631 * Invoked when |samlHandler_| fires 'insecureContentBlocked' event.
546 * @private 632 * @private
547 */ 633 */
548 Authenticator.prototype.onInsecureContentBlocked_ = function(e) { 634 Authenticator.prototype.onInsecureContentBlocked_ = function(e) {
549 if (this.insecureContentBlockedCallback) { 635 if (this.insecureContentBlockedCallback) {
550 this.insecureContentBlockedCallback(e.detail.url); 636 this.insecureContentBlockedCallback(e.detail.url);
(...skipping 90 matching lines...) Expand 10 before | Expand all | Expand 10 after
641 this.webview_.contentWindow.postMessage(msg, this.idpOrigin_); 727 this.webview_.contentWindow.postMessage(msg, this.idpOrigin_);
642 }).bind(this), EAFE_INITIAL_MESSAGE_DELAY_IN_MS); 728 }).bind(this), EAFE_INITIAL_MESSAGE_DELAY_IN_MS);
643 } 729 }
644 }; 730 };
645 731
646 /** 732 /**
647 * Invoked when the webview navigates withing the current document. 733 * Invoked when the webview navigates withing the current document.
648 * @private 734 * @private
649 */ 735 */
650 Authenticator.prototype.onLoadCommit_ = function(e) { 736 Authenticator.prototype.onLoadCommit_ = function(e) {
651 if (this.oauth_code_) { 737 if (this.oauthCode_) {
652 this.skipForNow_ = true; 738 this.skipForNow_ = true;
653 this.maybeCompleteAuth_(); 739 this.maybeCompleteAuth_();
654 } 740 }
655 }; 741 };
656 742
657 /** 743 /**
658 * Returns |true| if event |e| was sent from the hosted webview. 744 * Returns |true| if event |e| was sent from the hosted webview.
659 * @private 745 * @private
660 */ 746 */
661 Authenticator.prototype.isWebviewEvent_ = function(e) { 747 Authenticator.prototype.isWebviewEvent_ = function(e) {
(...skipping 20 matching lines...) Expand all
682 Authenticator.AuthMode = AuthMode; 768 Authenticator.AuthMode = AuthMode;
683 Authenticator.SUPPORTED_PARAMS = SUPPORTED_PARAMS; 769 Authenticator.SUPPORTED_PARAMS = SUPPORTED_PARAMS;
684 770
685 return { 771 return {
686 // TODO(guohui, xiyuan): Rename GaiaAuthHost to Authenticator once the old 772 // TODO(guohui, xiyuan): Rename GaiaAuthHost to Authenticator once the old
687 // iframe-based flow is deprecated. 773 // iframe-based flow is deprecated.
688 GaiaAuthHost: Authenticator, 774 GaiaAuthHost: Authenticator,
689 Authenticator: Authenticator 775 Authenticator: Authenticator
690 }; 776 };
691 }); 777 });
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698