QUIC - cleanup changes to sync chromium tree with internal source.
[chromium-blink-merge.git] / chrome / browser / resources / gaia_auth_host / authenticator.js
blob99a072849c07d38078349c600498342c4a771a69
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
3 // found in the LICENSE file.
5 <include src="saml_handler.js">
7 /**
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
10 * authentication events should pass a listener object of type
11 * cr.login.GaiaAuthHost.Listener as defined in this file. After initialization,
12 * call {@code load} to start the authentication flow.
15 cr.define('cr.login', function() {
16 'use strict';
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
20 // environments.
21 var IDP_ORIGIN = 'https://accounts.google.com/';
22 var IDP_PATH = 'ServiceLogin?skipvpage=true&sarp=1&rm=hide';
23 var CONTINUE_URL =
24 'chrome-extension://mfffpogegjflfpflabcdkioaeobkgjik/success.html';
25 var SIGN_IN_HEADER = 'google-accounts-signin';
26 var EMBEDDED_FORM_HEADER = 'google-accounts-embedded';
27 var LOCATION_HEADER = 'location';
28 var COOKIE_HEADER = 'cookie';
29 var SET_COOKIE_HEADER = 'set-cookie';
30 var OAUTH_CODE_COOKIE = 'oauth_code';
31 var GAPS_COOKIE = 'GAPS';
32 var SERVICE_ID = 'chromeoslogin';
33 var EMBEDDED_SETUP_CHROMEOS_ENDPOINT = 'embedded/setup/chromeos';
35 /**
36 * The source URL parameter for the constrained signin flow.
38 var CONSTRAINED_FLOW_SOURCE = 'chrome';
40 /**
41 * Enum for the authorization mode, must match AuthMode defined in
42 * chrome/browser/ui/webui/inline_login_ui.cc.
43 * @enum {number}
45 var AuthMode = {
46 DEFAULT: 0,
47 OFFLINE: 1,
48 DESKTOP: 2
51 /**
52 * Enum for the authorization type.
53 * @enum {number}
55 var AuthFlow = {
56 DEFAULT: 0,
57 SAML: 1
60 /**
61 * Supported Authenticator params.
62 * @type {!Array<string>}
63 * @const
65 var SUPPORTED_PARAMS = [
66 'gaiaId', // Obfuscated GAIA ID to skip the email prompt page
67 // during the re-auth flow.
68 'gaiaUrl', // Gaia url to use.
69 'gaiaPath', // Gaia path to use without a leading slash.
70 'hl', // Language code for the user interface.
71 'email', // Pre-fill the email field in Gaia UI.
72 'service', // Name of Gaia service.
73 'continueUrl', // Continue url to use.
74 'frameUrl', // Initial frame URL to use. If empty defaults to
75 // gaiaUrl.
76 'constrained', // Whether the extension is loaded in a constrained
77 // window.
78 'clientId', // Chrome client id.
79 'useEafe', // Whether to use EAFE.
80 'needPassword', // Whether the host is interested in getting a password.
81 // If this set to |false|, |confirmPasswordCallback| is
82 // not called before dispatching |authCopleted|.
83 // Default is |true|.
84 'flow', // One of 'default', 'enterprise', or 'theftprotection'.
85 'enterpriseDomain', // Domain in which hosting device is (or should be)
86 // enrolled.
87 'emailDomain', // Value used to prefill domain for email.
88 'clientVersion', // Version of the Chrome build.
89 'platformVersion', // Version of the OS build.
90 'releaseChannel', // Installation channel.
91 'endpointGen', // Current endpoint generation.
92 'gapsCookie', // GAPS cookie
95 /**
96 * Initializes the authenticator component.
97 * @param {webview|string} webview The webview element or its ID to host IdP
98 * web pages.
99 * @constructor
101 function Authenticator(webview) {
102 this.webview_ = typeof webview == 'string' ? $(webview) : webview;
103 assert(this.webview_);
105 this.email_ = null;
106 this.password_ = null;
107 this.gaiaId_ = null,
108 this.sessionIndex_ = null;
109 this.chooseWhatToSync_ = false;
110 this.skipForNow_ = false;
111 this.authFlow = AuthFlow.DEFAULT;
112 this.authDomain = '';
113 this.idpOrigin_ = null;
114 this.continueUrl_ = null;
115 this.continueUrlWithoutParams_ = null;
116 this.initialFrameUrl_ = null;
117 this.reloadUrl_ = null;
118 this.trusted_ = true;
119 this.oauthCode_ = null;
120 this.gapsCookie_ = null;
121 this.gapsCookieSent_ = false;
122 this.newGapsCookie_ = null;
124 this.useEafe_ = false;
125 this.clientId_ = null;
127 this.samlHandler_ = new cr.login.SamlHandler(this.webview_);
128 this.confirmPasswordCallback = null;
129 this.noPasswordCallback = null;
130 this.insecureContentBlockedCallback = null;
131 this.samlApiUsedCallback = null;
132 this.missingGaiaInfoCallback = null;
133 this.needPassword = true;
134 this.samlHandler_.addEventListener(
135 'insecureContentBlocked',
136 this.onInsecureContentBlocked_.bind(this));
137 this.samlHandler_.addEventListener(
138 'authPageLoaded',
139 this.onAuthPageLoaded_.bind(this));
141 this.webview_.addEventListener('droplink', this.onDropLink_.bind(this));
142 this.webview_.addEventListener(
143 'newwindow', this.onNewWindow_.bind(this));
144 this.webview_.addEventListener(
145 'contentload', this.onContentLoad_.bind(this));
146 this.webview_.addEventListener(
147 'loadabort', this.onLoadAbort_.bind(this));
148 this.webview_.addEventListener(
149 'loadstop', this.onLoadStop_.bind(this));
150 this.webview_.addEventListener(
151 'loadcommit', this.onLoadCommit_.bind(this));
152 this.webview_.request.onCompleted.addListener(
153 this.onRequestCompleted_.bind(this),
154 {urls: ['<all_urls>'], types: ['main_frame']},
155 ['responseHeaders']);
156 this.webview_.request.onHeadersReceived.addListener(
157 this.onHeadersReceived_.bind(this),
158 {urls: ['<all_urls>'], types: ['main_frame', 'xmlhttprequest']},
159 ['responseHeaders']);
160 window.addEventListener(
161 'message', this.onMessageFromWebview_.bind(this), false);
162 window.addEventListener(
163 'focus', this.onFocus_.bind(this), false);
164 window.addEventListener(
165 'popstate', this.onPopState_.bind(this), false);
168 Authenticator.prototype = Object.create(cr.EventTarget.prototype);
171 * Reinitializes authentication parameters so that a failed login attempt
172 * would not result in an infinite loop.
174 Authenticator.prototype.clearCredentials_ = function() {
175 this.email_ = null;
176 this.gaiaId_ = null;
177 this.password_ = null;
178 this.oauthCode_ = null;
179 this.gapsCookie_ = null;
180 this.gapsCookieSent_ = false;
181 this.newGapsCookie_ = null;
182 this.chooseWhatToSync_ = false;
183 this.skipForNow_ = false;
184 this.sessionIndex_ = null;
185 this.trusted_ = true;
186 this.authFlow = AuthFlow.DEFAULT;
187 this.samlHandler_.reset();
191 * Loads the authenticator component with the given parameters.
192 * @param {AuthMode} authMode Authorization mode.
193 * @param {Object} data Parameters for the authorization flow.
195 Authenticator.prototype.load = function(authMode, data) {
196 this.authMode = authMode;
197 this.clearCredentials_();
198 // gaiaUrl parameter is used for testing. Once defined, it is never changed.
199 this.idpOrigin_ = data.gaiaUrl || IDP_ORIGIN;
200 this.continueUrl_ = data.continueUrl || CONTINUE_URL;
201 this.continueUrlWithoutParams_ =
202 this.continueUrl_.substring(0, this.continueUrl_.indexOf('?')) ||
203 this.continueUrl_;
204 this.isConstrainedWindow_ = data.constrained == '1';
205 this.isNewGaiaFlowChromeOS = data.isNewGaiaFlowChromeOS;
206 this.useEafe_ = data.useEafe || false;
207 this.clientId_ = data.clientId;
208 this.gapsCookie_ = data.gapsCookie;
209 this.gapsCookieSent_ = false;
210 this.newGapsCookie_ = null;
212 this.initialFrameUrl_ = this.constructInitialFrameUrl_(data);
213 this.reloadUrl_ = data.frameUrl || this.initialFrameUrl_;
214 // Don't block insecure content for desktop flow because it lands on
215 // http. Otherwise, block insecure content as long as gaia is https.
216 this.samlHandler_.blockInsecureContent = authMode != AuthMode.DESKTOP &&
217 this.idpOrigin_.indexOf('https://') == 0;
218 this.needPassword = !('needPassword' in data) || data.needPassword;
220 if (this.isNewGaiaFlowChromeOS) {
221 this.webview_.contextMenus.onShow.addListener(function(e) {
222 e.preventDefault();
225 if (!this.onBeforeSetHeadersSet_) {
226 this.onBeforeSetHeadersSet_ = true;
227 var filterPrefix = this.idpOrigin_ + EMBEDDED_SETUP_CHROMEOS_ENDPOINT;
228 // This depends on gaiaUrl parameter, that is why it is here.
229 this.webview_.request.onBeforeSendHeaders.addListener(
230 this.onBeforeSendHeaders_.bind(this),
231 {urls: [filterPrefix + '?*', filterPrefix + '/*']},
232 ['requestHeaders', 'blocking']);
236 this.webview_.src = this.reloadUrl_;
240 * Reloads the authenticator component.
242 Authenticator.prototype.reload = function() {
243 this.clearCredentials_();
244 this.webview_.src = this.reloadUrl_;
247 Authenticator.prototype.constructInitialFrameUrl_ = function(data) {
248 var path = data.gaiaPath;
249 if (!path && this.isNewGaiaFlowChromeOS)
250 path = EMBEDDED_SETUP_CHROMEOS_ENDPOINT;
251 if (!path)
252 path = IDP_PATH;
253 var url = this.idpOrigin_ + path;
255 if (this.isNewGaiaFlowChromeOS) {
256 if (data.chromeType)
257 url = appendParam(url, 'chrometype', data.chromeType);
258 if (data.clientId)
259 url = appendParam(url, 'client_id', data.clientId);
260 if (data.enterpriseDomain)
261 url = appendParam(url, 'manageddomain', data.enterpriseDomain);
262 if (data.clientVersion)
263 url = appendParam(url, 'client_version', data.clientVersion);
264 if (data.platformVersion)
265 url = appendParam(url, 'platform_version', data.platformVersion);
266 if (data.releaseChannel)
267 url = appendParam(url, 'release_channel', data.releaseChannel);
268 if (data.endpointGen)
269 url = appendParam(url, 'endpoint_gen', data.endpointGen);
270 } else {
271 url = appendParam(url, 'continue', this.continueUrl_);
272 url = appendParam(url, 'service', data.service || SERVICE_ID);
274 if (data.hl)
275 url = appendParam(url, 'hl', data.hl);
276 if (data.gaiaId)
277 url = appendParam(url, 'user_id', data.gaiaId);
278 if (data.email)
279 url = appendParam(url, 'Email', data.email);
280 if (this.isConstrainedWindow_)
281 url = appendParam(url, 'source', CONSTRAINED_FLOW_SOURCE);
282 if (data.flow)
283 url = appendParam(url, 'flow', data.flow);
284 if (data.emailDomain)
285 url = appendParam(url, 'emaildomain', data.emailDomain);
286 return url;
290 * Invoked when a main frame request in the webview has completed.
291 * @private
293 Authenticator.prototype.onRequestCompleted_ = function(details) {
294 var currentUrl = details.url;
296 if (currentUrl.lastIndexOf(this.continueUrlWithoutParams_, 0) == 0) {
297 if (currentUrl.indexOf('ntp=1') >= 0)
298 this.skipForNow_ = true;
300 this.maybeCompleteAuth_();
301 return;
304 if (currentUrl.indexOf('https') != 0)
305 this.trusted_ = false;
307 if (this.isConstrainedWindow_) {
308 var isEmbeddedPage = false;
309 if (this.idpOrigin_ && currentUrl.lastIndexOf(this.idpOrigin_) == 0) {
310 var headers = details.responseHeaders;
311 for (var i = 0; headers && i < headers.length; ++i) {
312 if (headers[i].name.toLowerCase() == EMBEDDED_FORM_HEADER) {
313 isEmbeddedPage = true;
314 break;
318 if (!isEmbeddedPage) {
319 this.dispatchEvent(new CustomEvent('resize', {detail: currentUrl}));
320 return;
324 this.updateHistoryState_(currentUrl);
328 * Manually updates the history. Invoked upon completion of a webview
329 * navigation.
330 * @param {string} url Request URL.
331 * @private
333 Authenticator.prototype.updateHistoryState_ = function(url) {
334 if (history.state && history.state.url != url)
335 history.pushState({url: url}, '');
336 else
337 history.replaceState({url: url}, '');
341 * Invoked when the sign-in page takes focus.
342 * @param {object} e The focus event being triggered.
343 * @private
345 Authenticator.prototype.onFocus_ = function(e) {
346 if (this.authMode == AuthMode.DESKTOP)
347 this.webview_.focus();
351 * Invoked when the history state is changed.
352 * @param {object} e The popstate event being triggered.
353 * @private
355 Authenticator.prototype.onPopState_ = function(e) {
356 var state = e.state;
357 if (state && state.url)
358 this.webview_.src = state.url;
362 * Invoked when headers are received in the main frame of the webview. It
363 * 1) reads the authenticated user info from a signin header,
364 * 2) signals the start of a saml flow upon receiving a saml header.
365 * @return {!Object} Modified request headers.
366 * @private
368 Authenticator.prototype.onHeadersReceived_ = function(details) {
369 var currentUrl = details.url;
370 if (currentUrl.lastIndexOf(this.idpOrigin_, 0) != 0)
371 return;
373 var headers = details.responseHeaders;
374 for (var i = 0; headers && i < headers.length; ++i) {
375 var header = headers[i];
376 var headerName = header.name.toLowerCase();
377 if (headerName == SIGN_IN_HEADER) {
378 var headerValues = header.value.toLowerCase().split(',');
379 var signinDetails = {};
380 headerValues.forEach(function(e) {
381 var pair = e.split('=');
382 signinDetails[pair[0].trim()] = pair[1].trim();
384 // Removes "" around.
385 this.email_ = signinDetails['email'].slice(1, -1);
386 this.gaiaId_ = signinDetails['obfuscatedid'].slice(1, -1);
387 this.sessionIndex_ = signinDetails['sessionindex'];
388 } else if (headerName == LOCATION_HEADER) {
389 // If the "choose what to sync" checkbox was clicked, then the continue
390 // URL will contain a source=3 field.
391 var location = decodeURIComponent(header.value);
392 this.chooseWhatToSync_ = !!location.match(/(\?|&)source=3($|&)/);
393 } else if (
394 this.isNewGaiaFlowChromeOS && headerName == SET_COOKIE_HEADER) {
395 var headerValue = header.value;
396 if (headerValue.indexOf(OAUTH_CODE_COOKIE + '=', 0) == 0) {
397 this.oauthCode_ =
398 headerValue.substring(OAUTH_CODE_COOKIE.length + 1).split(';')[0];
400 if (headerValue.indexOf(GAPS_COOKIE + '=', 0) == 0) {
401 this.newGapsCookie_ =
402 headerValue.substring(GAPS_COOKIE.length + 1).split(';')[0];
409 * This method replaces cookie value in cookie header.
410 * @param@ {string} header_value Original string value of Cookie header.
411 * @param@ {string} cookie_name Name of cookie to be replaced.
412 * @param@ {string} cookie_value New cookie value.
413 * @return {string} New Cookie header value.
414 * @private
416 Authenticator.prototype.updateCookieValue_ = function(
417 header_value, cookie_name, cookie_value) {
418 var cookies = header_value.split(/\s*;\s*/);
419 var found = false;
420 for (var i = 0; i < cookies.length; ++i) {
421 if (cookies[i].indexOf(cookie_name + '=', 0) == 0) {
422 found = true;
423 cookies[i] = cookie_name + '=' + cookie_value;
424 break;
427 if (!found) {
428 cookies.push(cookie_name + '=' + cookie_value);
430 return cookies.join('; ');
434 * Handler for webView.request.onBeforeSendHeaders .
435 * @return {!Object} Modified request headers.
436 * @private
438 Authenticator.prototype.onBeforeSendHeaders_ = function(details) {
439 // We should re-send cookie if first request was unsuccessful (i.e. no new
440 // GAPS cookie was received).
441 if (this.isNewGaiaFlowChromeOS && this.gapsCookie_ &&
442 (!this.gapsCookieSent_ || !this.newGapsCookie_)) {
443 var headers = details.requestHeaders;
444 var found = false;
445 var gapsCookie = this.gapsCookie_;
447 for (var i = 0, l = headers.length; i < l; ++i) {
448 if (headers[i].name == COOKIE_HEADER) {
449 headers[i].value = this.updateCookieValue_(headers[i].value,
450 GAPS_COOKIE, gapsCookie);
451 found = true;
452 break;
455 if (!found) {
456 details.requestHeaders.push(
457 {name: COOKIE_HEADER, value: GAPS_COOKIE + '=' + gapsCookie});
459 this.gapsCookieSent_ = true;
461 return {
462 requestHeaders: details.requestHeaders
467 * Returns true if given HTML5 message is received from the webview element.
468 * @param {object} e Payload of the received HTML5 message.
470 Authenticator.prototype.isGaiaMessage = function(e) {
471 if (!this.isWebviewEvent_(e))
472 return false;
474 // The event origin does not have a trailing slash.
475 if (e.origin != this.idpOrigin_.substring(0, this.idpOrigin_.length - 1)) {
476 return false;
479 // EAFE passes back auth code via message.
480 if (this.useEafe_ &&
481 typeof e.data == 'object' &&
482 e.data.hasOwnProperty('authorizationCode')) {
483 assert(!this.oauthCode_);
484 this.oauthCode_ = e.data.authorizationCode;
485 this.dispatchEvent(
486 new CustomEvent('authCompleted',
488 detail: {
489 authCodeOnly: true,
490 authCode: this.oauthCode_
492 }));
493 return;
496 // Gaia messages must be an object with 'method' property.
497 if (typeof e.data != 'object' || !e.data.hasOwnProperty('method')) {
498 return false;
500 return true;
504 * Invoked when an HTML5 message is received from the webview element.
505 * @param {object} e Payload of the received HTML5 message.
506 * @private
508 Authenticator.prototype.onMessageFromWebview_ = function(e) {
509 if (!this.isGaiaMessage(e))
510 return;
512 var msg = e.data;
513 if (msg.method == 'attemptLogin') {
514 this.email_ = msg.email;
515 this.password_ = msg.password;
516 this.chooseWhatToSync_ = msg.chooseWhatToSync;
517 // We need to dispatch only first event, before user enters password.
518 if (!msg.password) {
519 this.dispatchEvent(
520 new CustomEvent('attemptLogin', {detail: msg.email}));
522 } else if (msg.method == 'dialogShown') {
523 this.dispatchEvent(new Event('dialogShown'));
524 } else if (msg.method == 'dialogHidden') {
525 this.dispatchEvent(new Event('dialogHidden'));
526 } else if (msg.method == 'backButton') {
527 this.dispatchEvent(new CustomEvent('backButton', {detail: msg.show}));
528 } else if (msg.method == 'showView') {
529 this.dispatchEvent(new Event('showView'));
530 } else if (msg.method == 'identifierEntered') {
531 this.dispatchEvent(new CustomEvent(
532 'identifierEntered',
533 {detail: {accountIdentifier: msg.accountIdentifier}}));
534 } else {
535 console.warn('Unrecognized message from GAIA: ' + msg.method);
540 * Invoked by the hosting page to verify the Saml password.
542 Authenticator.prototype.verifyConfirmedPassword = function(password) {
543 if (!this.samlHandler_.verifyConfirmedPassword(password)) {
544 // Invoke confirm password callback asynchronously because the
545 // verification was based on messages and caller (GaiaSigninScreen)
546 // does not expect it to be called immediately.
547 // TODO(xiyuan): Change to synchronous call when iframe based code
548 // is removed.
549 var invokeConfirmPassword = (function() {
550 this.confirmPasswordCallback(this.email_,
551 this.samlHandler_.scrapedPasswordCount);
552 }).bind(this);
553 window.setTimeout(invokeConfirmPassword, 0);
554 return;
557 this.password_ = password;
558 this.onAuthCompleted_();
562 * Check Saml flow and start password confirmation flow if needed. Otherwise,
563 * continue with auto completion.
564 * @private
566 Authenticator.prototype.maybeCompleteAuth_ = function() {
567 var missingGaiaInfo = !this.email_ || !this.gaiaId_ || !this.sessionIndex_;
568 if (missingGaiaInfo && !this.skipForNow_) {
569 if (this.missingGaiaInfoCallback)
570 this.missingGaiaInfoCallback();
572 this.webview_.src = this.initialFrameUrl_;
573 return;
576 if (this.samlHandler_.samlApiUsed) {
577 if (this.samlApiUsedCallback) {
578 this.samlApiUsedCallback();
580 this.password_ = this.samlHandler_.apiPasswordBytes;
581 this.onAuthCompleted_();
582 return;
585 // TODO(achuith): Eliminate this branch when credential passing api is
586 // stable on prod. crbug.com/467778.
587 if (this.authFlow != AuthFlow.SAML) {
588 this.onAuthCompleted_();
589 return;
592 if (this.samlHandler_.scrapedPasswordCount == 0) {
593 if (this.noPasswordCallback) {
594 this.noPasswordCallback(this.email_);
595 return;
598 // Fall through to finish the auth flow even if this.needPassword
599 // is true. This is because the flag is used as an intention to get
600 // password when it is available but not a mandatory requirement.
601 console.warn('Authenticator: No password scraped for SAML.');
602 } else if (this.needPassword) {
603 if (this.confirmPasswordCallback) {
604 // Confirm scraped password. The flow follows in
605 // verifyConfirmedPassword.
606 this.confirmPasswordCallback(this.email_,
607 this.samlHandler_.scrapedPasswordCount);
608 return;
612 this.onAuthCompleted_();
616 * Invoked to process authentication completion.
617 * @private
619 Authenticator.prototype.onAuthCompleted_ = function() {
620 assert(this.skipForNow_ ||
621 (this.email_ && this.gaiaId_ && this.sessionIndex_));
622 this.dispatchEvent(new CustomEvent(
623 'authCompleted',
624 // TODO(rsorokin): get rid of the stub values.
626 detail: {
627 email: this.email_ || '',
628 gaiaId: this.gaiaId_ || '',
629 password: this.password_ || '',
630 authCode: this.oauthCode_,
631 usingSAML: this.authFlow == AuthFlow.SAML,
632 chooseWhatToSync: this.chooseWhatToSync_,
633 skipForNow: this.skipForNow_,
634 sessionIndex: this.sessionIndex_ || '',
635 trusted: this.trusted_,
636 gapsCookie: this.newGapsCookie_ || this.gapsCookie_ || '',
638 }));
639 this.clearCredentials_();
643 * Invoked when |samlHandler_| fires 'insecureContentBlocked' event.
644 * @private
646 Authenticator.prototype.onInsecureContentBlocked_ = function(e) {
647 if (this.insecureContentBlockedCallback) {
648 this.insecureContentBlockedCallback(e.detail.url);
649 } else {
650 console.error('Authenticator: Insecure content blocked.');
655 * Invoked when |samlHandler_| fires 'authPageLoaded' event.
656 * @private
658 Authenticator.prototype.onAuthPageLoaded_ = function(e) {
659 if (!e.detail.isSAMLPage)
660 return;
662 this.authDomain = this.samlHandler_.authDomain;
663 this.authFlow = AuthFlow.SAML;
667 * Invoked when a link is dropped on the webview.
668 * @private
670 Authenticator.prototype.onDropLink_ = function(e) {
671 this.dispatchEvent(new CustomEvent('dropLink', {detail: e.url}));
675 * Invoked when the webview attempts to open a new window.
676 * @private
678 Authenticator.prototype.onNewWindow_ = function(e) {
679 this.dispatchEvent(new CustomEvent('newWindow', {detail: e}));
683 * Invoked when a new document is loaded.
684 * @private
686 Authenticator.prototype.onContentLoad_ = function(e) {
687 if (this.isConstrainedWindow_) {
688 // Signin content in constrained windows should not zoom. Isolate the
689 // webview from the zooming of other webviews using the 'per-view' zoom
690 // mode, and then set it to 100% zoom.
691 this.webview_.setZoomMode('per-view');
692 this.webview_.setZoom(1);
695 // Posts a message to IdP pages to initiate communication.
696 var currentUrl = this.webview_.src;
697 if (currentUrl.lastIndexOf(this.idpOrigin_) == 0) {
698 var msg = {
699 'method': 'handshake',
702 this.webview_.contentWindow.postMessage(msg, currentUrl);
704 this.dispatchEvent(new Event('ready'));
705 // Focus webview after dispatching event when webview is already visible.
706 this.webview_.focus();
711 * Invoked when the webview fails loading a page.
712 * @private
714 Authenticator.prototype.onLoadAbort_ = function(e) {
715 this.dispatchEvent(new CustomEvent('loadAbort',
716 {detail: {error: e.reason,
717 src: this.webview_.src}}));
721 * Invoked when the webview finishes loading a page.
722 * @private
724 Authenticator.prototype.onLoadStop_ = function(e) {
725 // Sends client id to EAFE on every loadstop after a small timeout. This is
726 // needed because EAFE sits behind SSO and initialize asynchrounouly
727 // and we don't know for sure when it is loaded and ready to listen
728 // for message. The postMessage is guarded by EAFE's origin.
729 if (this.useEafe_) {
730 // An arbitrary small timeout for delivering the initial message.
731 var EAFE_INITIAL_MESSAGE_DELAY_IN_MS = 500;
732 window.setTimeout((function() {
733 var msg = {
734 'clientId': this.clientId_
736 this.webview_.contentWindow.postMessage(msg, this.idpOrigin_);
737 }).bind(this), EAFE_INITIAL_MESSAGE_DELAY_IN_MS);
742 * Invoked when the webview navigates withing the current document.
743 * @private
745 Authenticator.prototype.onLoadCommit_ = function(e) {
746 if (this.oauthCode_) {
747 this.skipForNow_ = true;
748 this.maybeCompleteAuth_();
753 * Returns |true| if event |e| was sent from the hosted webview.
754 * @private
756 Authenticator.prototype.isWebviewEvent_ = function(e) {
757 // Note: <webview> prints error message to console if |contentWindow| is not
758 // defined.
759 // TODO(dzhioev): remove the message. http://crbug.com/469522
760 var webviewWindow = this.webview_.contentWindow;
761 return !!webviewWindow && webviewWindow === e.source;
765 * The current auth flow of the hosted auth page.
766 * @type {AuthFlow}
768 cr.defineProperty(Authenticator, 'authFlow');
771 * The domain name of the current auth page.
772 * @type {string}
774 cr.defineProperty(Authenticator, 'authDomain');
776 Authenticator.AuthFlow = AuthFlow;
777 Authenticator.AuthMode = AuthMode;
778 Authenticator.SUPPORTED_PARAMS = SUPPORTED_PARAMS;
780 return {
781 // TODO(guohui, xiyuan): Rename GaiaAuthHost to Authenticator once the old
782 // iframe-based flow is deprecated.
783 GaiaAuthHost: Authenticator,
784 Authenticator: Authenticator