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">
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() {
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
21 var IDP_ORIGIN
= 'https://accounts.google.com/';
22 var IDP_PATH
= 'ServiceLogin?skipvpage=true&sarp=1&rm=hide';
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';
36 * The source URL parameter for the constrained signin flow.
38 var CONSTRAINED_FLOW_SOURCE
= 'chrome';
41 * Enum for the authorization mode, must match AuthMode defined in
42 * chrome/browser/ui/webui/inline_login_ui.cc.
52 * Enum for the authorization type.
61 * Supported Authenticator params.
62 * @type {!Array<string>}
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
76 'constrained', // Whether the extension is loaded in a constrained
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|.
84 'flow', // One of 'default', 'enterprise', or 'theftprotection'.
85 'enterpriseDomain', // Domain in which hosting device is (or should be)
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
96 * Initializes the authenticator component.
97 * @param {webview|string} webview The webview element or its ID to host IdP
101 function Authenticator(webview
) {
102 this.webview_
= typeof webview
== 'string' ? $(webview
) : webview
;
103 assert(this.webview_
);
106 this.password_
= 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(
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() {
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('?')) ||
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
) {
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
;
253 var url
= this.idpOrigin_
+ path
;
255 if (this.isNewGaiaFlowChromeOS
) {
257 url
= appendParam(url
, 'chrometype', data
.chromeType
);
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
);
271 url
= appendParam(url
, 'continue', this.continueUrl_
);
272 url
= appendParam(url
, 'service', data
.service
|| SERVICE_ID
);
275 url
= appendParam(url
, 'hl', data
.hl
);
277 url
= appendParam(url
, 'user_id', data
.gaiaId
);
279 url
= appendParam(url
, 'Email', data
.email
);
280 if (this.isConstrainedWindow_
)
281 url
= appendParam(url
, 'source', CONSTRAINED_FLOW_SOURCE
);
283 url
= appendParam(url
, 'flow', data
.flow
);
284 if (data
.emailDomain
)
285 url
= appendParam(url
, 'emaildomain', data
.emailDomain
);
290 * Invoked when a main frame request in the webview has completed.
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_();
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;
318 if (!isEmbeddedPage
) {
319 this.dispatchEvent(new CustomEvent('resize', {detail
: currentUrl
}));
324 this.updateHistoryState_(currentUrl
);
328 * Manually updates the history. Invoked upon completion of a webview
330 * @param {string} url Request URL.
333 Authenticator
.prototype.updateHistoryState_ = function(url
) {
334 if (history
.state
&& history
.state
.url
!= url
)
335 history
.pushState({url
: url
}, '');
337 history
.replaceState({url
: url
}, '');
341 * Invoked when the sign-in page takes focus.
342 * @param {object} e The focus event being triggered.
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.
355 Authenticator
.prototype.onPopState_ = function(e
) {
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.
368 Authenticator
.prototype.onHeadersReceived_ = function(details
) {
369 var currentUrl
= details
.url
;
370 if (currentUrl
.lastIndexOf(this.idpOrigin_
, 0) != 0)
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($|&)/);
394 this.isNewGaiaFlowChromeOS
&& headerName
== SET_COOKIE_HEADER
) {
395 var headerValue
= header
.value
;
396 if (headerValue
.indexOf(OAUTH_CODE_COOKIE
+ '=', 0) == 0) {
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.
416 Authenticator
.prototype.updateCookieValue_ = function(
417 header_value
, cookie_name
, cookie_value
) {
418 var cookies
= header_value
.split(/\s*;\s*/);
420 for (var i
= 0; i
< cookies
.length
; ++i
) {
421 if (cookies
[i
].indexOf(cookie_name
+ '=', 0) == 0) {
423 cookies
[i
] = cookie_name
+ '=' + cookie_value
;
428 cookies
.push(cookie_name
+ '=' + cookie_value
);
430 return cookies
.join('; ');
434 * Handler for webView.request.onBeforeSendHeaders .
435 * @return {!Object} Modified request headers.
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
;
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
);
456 details
.requestHeaders
.push(
457 {name
: COOKIE_HEADER
, value
: GAPS_COOKIE
+ '=' + gapsCookie
});
459 this.gapsCookieSent_
= true;
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
))
474 // The event origin does not have a trailing slash.
475 if (e
.origin
!= this.idpOrigin_
.substring(0, this.idpOrigin_
.length
- 1)) {
479 // EAFE passes back auth code via message.
481 typeof e
.data
== 'object' &&
482 e
.data
.hasOwnProperty('authorizationCode')) {
483 assert(!this.oauthCode_
);
484 this.oauthCode_
= e
.data
.authorizationCode
;
486 new CustomEvent('authCompleted',
490 authCode
: this.oauthCode_
496 // Gaia messages must be an object with 'method' property.
497 if (typeof e
.data
!= 'object' || !e
.data
.hasOwnProperty('method')) {
504 * Invoked when an HTML5 message is received from the webview element.
505 * @param {object} e Payload of the received HTML5 message.
508 Authenticator
.prototype.onMessageFromWebview_ = function(e
) {
509 if (!this.isGaiaMessage(e
))
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.
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(
533 {detail
: {accountIdentifier
: msg
.accountIdentifier
}}));
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
549 var invokeConfirmPassword
= (function() {
550 this.confirmPasswordCallback(this.email_
,
551 this.samlHandler_
.scrapedPasswordCount
);
553 window
.setTimeout(invokeConfirmPassword
, 0);
557 this.password_
= password
;
558 this.onAuthCompleted_();
562 * Check Saml flow and start password confirmation flow if needed. Otherwise,
563 * continue with auto completion.
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_
;
576 if (this.samlHandler_
.samlApiUsed
) {
577 if (this.samlApiUsedCallback
) {
578 this.samlApiUsedCallback();
580 this.password_
= this.samlHandler_
.apiPasswordBytes
;
581 this.onAuthCompleted_();
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_();
592 if (this.samlHandler_
.scrapedPasswordCount
== 0) {
593 if (this.noPasswordCallback
) {
594 this.noPasswordCallback(this.email_
);
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
);
612 this.onAuthCompleted_();
616 * Invoked to process authentication completion.
619 Authenticator
.prototype.onAuthCompleted_ = function() {
620 assert(this.skipForNow_
||
621 (this.email_
&& this.gaiaId_
&& this.sessionIndex_
));
622 this.dispatchEvent(new CustomEvent(
624 // TODO(rsorokin): get rid of the stub values.
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_
|| '',
639 this.clearCredentials_();
643 * Invoked when |samlHandler_| fires 'insecureContentBlocked' event.
646 Authenticator
.prototype.onInsecureContentBlocked_ = function(e
) {
647 if (this.insecureContentBlockedCallback
) {
648 this.insecureContentBlockedCallback(e
.detail
.url
);
650 console
.error('Authenticator: Insecure content blocked.');
655 * Invoked when |samlHandler_| fires 'authPageLoaded' event.
658 Authenticator
.prototype.onAuthPageLoaded_ = function(e
) {
659 if (!e
.detail
.isSAMLPage
)
662 this.authDomain
= this.samlHandler_
.authDomain
;
663 this.authFlow
= AuthFlow
.SAML
;
667 * Invoked when a link is dropped on the webview.
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.
678 Authenticator
.prototype.onNewWindow_ = function(e
) {
679 this.dispatchEvent(new CustomEvent('newWindow', {detail
: e
}));
683 * Invoked when a new document is loaded.
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) {
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.
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.
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.
730 // An arbitrary small timeout for delivering the initial message.
731 var EAFE_INITIAL_MESSAGE_DELAY_IN_MS
= 500;
732 window
.setTimeout((function() {
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.
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.
756 Authenticator
.prototype.isWebviewEvent_ = function(e
) {
757 // Note: <webview> prints error message to console if |contentWindow| is not
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.
768 cr
.defineProperty(Authenticator
, 'authFlow');
771 * The domain name of the current auth page.
774 cr
.defineProperty(Authenticator
, 'authDomain');
776 Authenticator
.AuthFlow
= AuthFlow
;
777 Authenticator
.AuthMode
= AuthMode
;
778 Authenticator
.SUPPORTED_PARAMS
= SUPPORTED_PARAMS
;
781 // TODO(guohui, xiyuan): Rename GaiaAuthHost to Authenticator once the old
782 // iframe-based flow is deprecated.
783 GaiaAuthHost
: Authenticator
,
784 Authenticator
: Authenticator