| Index: chrome/browser/resources/gaia_auth_host/authenticator.js
|
| diff --git a/chrome/browser/resources/gaia_auth_host/authenticator.js b/chrome/browser/resources/gaia_auth_host/authenticator.js
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..4a1bf5aa7ff4d61688064587da4c1d796283d052
|
| --- /dev/null
|
| +++ b/chrome/browser/resources/gaia_auth_host/authenticator.js
|
| @@ -0,0 +1,315 @@
|
| +// Copyright 2014 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +/**
|
| + * @fileoverview An UI component to authenciate to Chrome. The component hosts
|
| + * IdP web pages in a webview. A client who is interested in monitoring
|
| + * authentication events should pass a listener object of type
|
| + * cr.login.GaiaAuthHost.Listener as defined in this file. After initialization,
|
| + * call {@code load} to start the authentication flow.
|
| + */
|
| +cr.define('cr.login', function() {
|
| + 'use strict';
|
| +
|
| + var IDP_ORIGIN = 'https://accounts.google.com/';
|
| + var IDP_PATH = 'ServiceLogin?skipvpage=true&sarp=1&rm=hide';
|
| + var CONTINUE_URL =
|
| + 'chrome-extension://mfffpogegjflfpflabcdkioaeobkgjik/success.html';
|
| + var SIGN_IN_HEADER = 'google-accounts-signin';
|
| + var EMBEDDED_FORM_HEADER = 'google-accounts-embedded';
|
| + var SAML_HEADER = 'google-accounts-saml';
|
| +
|
| + /**
|
| + * The source URL parameter for the constrained signin flow.
|
| + */
|
| + var CONSTRAINED_FLOW_SOURCE = 'chrome';
|
| +
|
| + /**
|
| + * Enum for the authorization mode, must match AuthMode defined in
|
| + * chrome/browser/ui/webui/inline_login_ui.cc.
|
| + * @enum {number}
|
| + */
|
| + var AuthMode = {
|
| + DEFAULT: 0,
|
| + OFFLINE: 1,
|
| + DESKTOP: 2
|
| + };
|
| +
|
| + /**
|
| + * Enum for the authorization type.
|
| + * @enum {number}
|
| + */
|
| + var AuthFlow = {
|
| + DEFAULT: 0,
|
| + SAML: 1
|
| + };
|
| +
|
| + /**
|
| + * Initializes the authenticator component.
|
| + * @param {webview|string} webview The webview element or its ID to host IdP
|
| + * web pages.
|
| + * @param {Authenticator.Listener=} opt_listener An optional listener for
|
| + * authentication events.
|
| + * @constructor
|
| + * @extends {cr.EventTarget}
|
| + */
|
| + function Authenticator(webview, opt_listener) {
|
| + this.webview_ = typeof webview == 'string' ? $(webview) : webview;
|
| + assert(this.webview_);
|
| +
|
| + this.listener_ = opt_listener || null;
|
| +
|
| + this.email_ = null;
|
| + this.password_ = null;
|
| + this.sessionIndex_ = null;
|
| + this.chooseWhatToSync_ = false;
|
| + this.skipForNow_ = false;
|
| + this.authFlow_ = AuthFlow.DEFAULT;
|
| + this.loaded_ = false;
|
| + this.idpOrigin_ = null;
|
| + this.continueUrl_ = null;
|
| + this.continueUrlWithoutParams_ = null;
|
| + this.initialFrameUrl_ = null;
|
| + this.reloadUrl_ = null;
|
| + }
|
| +
|
| + // TODO(guohui,xiyuan): no need to inherit EventTarget once we deprecate the
|
| + // old event-based signin flow.
|
| + Authenticator.prototype = Object.create(cr.EventTarget.prototype);
|
| +
|
| + /**
|
| + * An interface for receiving notifications upon authentication events.
|
| + * @interface
|
| + */
|
| + Authenticator.Listener = function() {};
|
| +
|
| + /**
|
| + * Invoked when authentication UI is ready.
|
| + */
|
| + Authenticator.Listener.prototype.onReady = function(e) {};
|
| +
|
| + /**
|
| + * Invoked when authentication is completed successfully with credential data.
|
| + * A credential data object looks like this:
|
| + * <pre>
|
| + * {@code
|
| + * {
|
| + * email: 'xx@gmail.com',
|
| + * password: 'xxxx', // May be null or empty.
|
| + * usingSAML: false,
|
| + * chooseWhatToSync: false,
|
| + * skipForNow: false,
|
| + * sessionIndex: '0'
|
| + * }
|
| + * }
|
| + * </pre>
|
| + * @param {Object} credentials A credential data object.
|
| + */
|
| + Authenticator.Listener.prototype.onSuccess = function(credentials) {};
|
| +
|
| + /**
|
| + * Invoked when the requested URL does not fit the container.
|
| + * @param {string} url Request URL.
|
| + */
|
| + Authenticator.Listener.prototype.onResize = function(url) {};
|
| +
|
| + /**
|
| + * Invoked when a new window event is fired.
|
| + * @param {Event} e Event object.
|
| + */
|
| + Authenticator.Listener.prototype.onNewWindow = function(e) {};
|
| +
|
| + /**
|
| + * Loads the authenticator component with the given parameters.
|
| + * @param {AuthMode} authMode Authorization mode.
|
| + * @param {Object} data Parameters for the authorization flow.
|
| + */
|
| + Authenticator.prototype.load = function(authMode, data) {
|
| + this.idpOrigin_ = data.gaiaUrl || IDP_ORIGIN;
|
| + this.continueUrl_ = data.continueUrl || CONTINUE_URL;
|
| + this.continueUrlWithoutParams_ =
|
| + this.continueUrl_.substring(0, this.continueUrl_.indexOf('?')) ||
|
| + this.continueUrl_;
|
| + this.isConstrainedWindow_ = data.constrained == '1';
|
| +
|
| + this.initialFrameUrl_ = this.constructInitialFrameUrl_(data);
|
| + this.reloadUrl_ = data.frameUrl || this.initialFrameUrl_;
|
| + this.authFlow_ = AuthFlow.DEFAULT;
|
| +
|
| + this.webview_.src = this.reloadUrl_;
|
| + this.webview_.addEventListener(
|
| + 'newwindow', this.onNewWindow_.bind(this));
|
| + this.webview_.request.onCompleted.addListener(
|
| + this.onRequestCompleted_.bind(this),
|
| + {urls: ['*://*/*', this.continueUrlWithoutParams_ + '*'],
|
| + types: ['main_frame']},
|
| + ['responseHeaders']);
|
| + this.webview_.request.onHeadersReceived.addListener(
|
| + this.onHeadersReceived_.bind(this),
|
| + {urls: [this.idpOrigin_ + '*'], types: ['main_frame']},
|
| + ['responseHeaders']);
|
| + window.addEventListener(
|
| + 'message', this.onMessage_.bind(this), false);
|
| + };
|
| +
|
| + /**
|
| + * Reloads the authenticator component.
|
| + */
|
| + Authenticator.prototype.reload = function() {
|
| + this.webview_.src = this.reloadUrl_;
|
| + this.authFlow_ = AuthFlow.DEFAULT;
|
| + };
|
| +
|
| + Authenticator.prototype.constructInitialFrameUrl_ = function(data) {
|
| + var url = this.idpOrigin_ + (data.gaiaPath || IDP_PATH);
|
| +
|
| + url = appendParam(url, 'continue', this.continueUrl_);
|
| + url = appendParam(url, 'service', data.service);
|
| + if (data.hl)
|
| + url = appendParam(url, 'hl', data.hl);
|
| + if (data.email)
|
| + url = appendParam(url, 'Email', data.email);
|
| + if (this.isConstrainedWindow_)
|
| + url = appendParam(url, 'source', CONSTRAINED_FLOW_SOURCE);
|
| + return url;
|
| + };
|
| +
|
| + /**
|
| + * Invoked when a main frame request in the webview has completed.
|
| + * @private
|
| + */
|
| + Authenticator.prototype.onRequestCompleted_ = function(details) {
|
| + var currentUrl = details.url;
|
| + if (currentUrl.lastIndexOf(this.continueUrlWithoutParams_, 0) == 0) {
|
| + if (currentUrl.indexOf('ntp=1') >= 0) {
|
| + this.skipForNow_ = true;
|
| + }
|
| + this.onAuthCompleted_();
|
| + return;
|
| + }
|
| +
|
| + if (this.isConstrainedWindow_) {
|
| + var isEmbeddedPage = false;
|
| + if (this.idpOrigin_ && currentUrl.lastIndexOf(this.idpOrigin_) == 0) {
|
| + var headers = details.responseHeaders;
|
| + for (var i = 0; headers && i < headers.length; ++i) {
|
| + if (headers[i].name.toLowerCase() == EMBEDDED_FORM_HEADER) {
|
| + isEmbeddedPage = true;
|
| + break;
|
| + }
|
| + }
|
| + }
|
| + if (!isEmbeddedPage && this.listener_) {
|
| + this.listener_.onResize(currentUrl);
|
| + return;
|
| + }
|
| + }
|
| +
|
| + if (currentUrl.lastIndexOf(this.idpOrigin_) == 0) {
|
| + this.webview_.contentWindow.postMessage({}, currentUrl);
|
| + }
|
| +
|
| + if (!this.loaded_) {
|
| + this.loaded_ = true;
|
| + if (this.listener_) {
|
| + this.listener_.onReady();
|
| + }
|
| + }
|
| + };
|
| +
|
| + /**
|
| + * Invoked when headers are received in the main frame of the webview. It
|
| + * 1) reads the authenticated user info from a signin header,
|
| + * 2) signals the start of a saml flow upon receiving a saml header.
|
| + * @return {!Object} Modified request headers.
|
| + * @private
|
| + */
|
| + Authenticator.prototype.onHeadersReceived_ = function(details) {
|
| + var headers = details.responseHeaders;
|
| + for (var i = 0; headers && i < headers.length; ++i) {
|
| + var header = headers[i];
|
| + var headerName = header.name.toLowerCase();
|
| + if (headerName == SIGN_IN_HEADER) {
|
| + var headerValues = header.value.toLowerCase().split(',');
|
| + var signinDetails = {};
|
| + headerValues.forEach(function(e) {
|
| + var pair = e.split('=');
|
| + signinDetails[pair[0].trim()] = pair[1].trim();
|
| + });
|
| + // Removes "" around.
|
| + var email = signinDetails['email'].slice(1, -1);
|
| + if (this.email_ != email) {
|
| + this.email_ = email;
|
| + // Clears the scraped password if the email has changed.
|
| + this.password_ = null;
|
| + }
|
| + this.sessionIndex_ = signinDetails['sessionindex'];
|
| + } else if (headerName == SAML_HEADER) {
|
| + this.authFlow_ = AuthFlow.SAML;
|
| + }
|
| + }
|
| + };
|
| +
|
| + /**
|
| + * Invoked when an HTML5 message is received.
|
| + * @param {object} e Payload of the received HTML5 message.
|
| + * @private
|
| + */
|
| + Authenticator.prototype.onMessage_ = function(e) {
|
| + if (e.origin != this.idpOrigin_) {
|
| + return;
|
| + }
|
| +
|
| + var msg = e.data;
|
| +
|
| + if (msg.method == 'attemptLogin') {
|
| + this.email_ = msg.email;
|
| + this.password_ = msg.password;
|
| + this.chooseWhatToSync_ = msg.chooseWhatToSync;
|
| + }
|
| + };
|
| +
|
| + /**
|
| + * Invoked to process authentication completion.
|
| + * @private
|
| + */
|
| + Authenticator.prototype.onAuthCompleted_ = function() {
|
| + if (!this.listener_) {
|
| + return;
|
| + }
|
| +
|
| + if (!this.email_ && !this.skipForNow_) {
|
| + this.webview_.src = this.initialFrameUrl_;
|
| + return;
|
| + }
|
| +
|
| + this.listener_.onSuccess({email: this.email_,
|
| + password: this.password_,
|
| + usingSAML: this.authFlow_ == AuthFlow.SAML,
|
| + chooseWhatToSync: this.chooseWhatToSync_,
|
| + skipForNow: this.skipForNow_,
|
| + sessionIndex: this.sessionIndex_ || ''});
|
| + };
|
| +
|
| + /**
|
| + * Invoked when the webview attempts to open a new window.
|
| + * @private
|
| + */
|
| + Authenticator.prototype.onNewWindow_ = function(e) {
|
| + if (!this.listener_) {
|
| + return;
|
| + }
|
| +
|
| + this.listener_.onNewWindow(e);
|
| + };
|
| +
|
| + Authenticator.AuthFlow = AuthFlow;
|
| + Authenticator.AuthMode = AuthMode;
|
| +
|
| + return {
|
| + // TODO(guohui, xiyuan): Rename GaiaAuthHost to Authenticator once the old
|
| + // iframe-based flow is deprecated.
|
| + GaiaAuthHost: Authenticator
|
| + };
|
| +});
|
|
|