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

Unified Diff: chrome/browser/resources/gaia_auth_host/authenticator.js

Issue 646983008: Implement signin using webview (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: split extension framework changes into separate cl and fixed nits Created 6 years, 2 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 side-by-side diff with in-line comments
Download patch
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..aaff9986839a1dcca3c5acf827b1dbfc5253ef64
--- /dev/null
+++ b/chrome/browser/resources/gaia_auth_host/authenticator.js
@@ -0,0 +1,285 @@
+// 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. After initialization, call {@code load} to start
+ * the authentication flow. Two events may be raised after this point:
+ * 1) a 'ready' event when the authentication UI is ready to use.
+ * 2) a 'completed' event when the authentication is completed successfully. If
+ * the caller is interested in the user credentials, it may supply a success
+ * callback when calling {@code load}. The callback will be invoked with the
+ * available credential data before the 'completed' event is dispatched.
+ */
+
+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} container The webview element or its ID to host IdP
+ * web pages.
+ * @constructor
+ * @extends {cr.EventTarget}
+ */
+ function Authenticator(container) {
+ this.frame_ = typeof container == 'string' ? $(container) : container;
+ assert(this.frame_);
Roger Tawa OOO till Jul 10th 2014/10/22 19:30:00 Nit: rename frame_ to webView_ ? Rename container
guohui 2014/10/23 19:12:01 Done.
+
+ this.email_ = null;
+ this.password_ = null;
+ this.sessionIndex_ = null;
+ this.chooseWhatToSync_ = false;
+ this.authFlow_ = AuthFlow.DEFAULT;
+ this.loaded_ = false;
+ this.idpOrigin_ = null;
+ this.continueUrl_ = null;
+ this.continueUrlWithoutParams_ = null;
+ this.initialFrameUrl_ = null;
+ this.reloadUrl_ = null;
+
+ /**
+ * 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 not present
+ * authMode: 'x', // Authorization mode, default/offline/desktop.
+ * }
+ * }
+ * </pre>
+ */
+ this.successCallback_ = null;
+ }
+
+ Authenticator.prototype = Object.create(cr.EventTarget.prototype);
+
+ /**
+ * Loads the authenticator component with the given parameters.
+ * @param {AuthMode} authMode Authorization mode.
+ * @param {Object} data Parameters for the authorization flow.
+ * @param {function(Object)} successCallback A function to be called when
+ * the authentication is completed successfully. The callback is
+ * invoked with a credential object.
+ */
+ Authenticator.prototype.load = function(authMode, data, successCallback) {
+ 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.successCallback_ = successCallback;
+ this.authFlow_ = AuthFlow.DEFAULT;
+
+ this.frame_.src = this.reloadUrl_;
+ this.frame_.addEventListener(
+ 'newwindow', this.onNewWindow_.bind(this));
+ this.frame_.request.onCompleted.addListener(
+ this.onRequestCompleted_.bind(this),
+ {urls: ['*://*/*', this.continueUrlWithoutParams_ + '*'],
+ types: ['main_frame']},
+ ['responseHeaders']);
+ this.frame_.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.frame_.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) {
+ 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) {
+ chrome.send('switchToFullTab', [currentUrl]);
xiyuan 2014/10/22 17:20:53 This assumes webui page. Maybe we should have a ca
guohui 2014/10/23 19:12:01 Done.
+ return;
+ }
+ }
+
+ if (currentUrl.lastIndexOf(this.idpOrigin_) == 0) {
+ this.frame_.contentWindow.postMessage('', this.frame_.src);
xiyuan 2014/10/22 17:20:53 nit: Maybe using an empty object {} instead of ''?
guohui 2014/10/23 19:12:01 Done.
+ }
+
+ if (!this.loaded_) {
+ this.loaded_ = true;
+ cr.dispatchSimpleEvent(this, 'ready');
+ }
+ };
+
+ /**
+ * 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() {
+ var skipForNow = false;
+ if (this.frame_.src.indexOf('ntp=1') >= 0) {
+ skipForNow = true;
+ }
+
+ if (!this.email_ && !skipForNow) {
+ this.frame_.src = this.initialFrameUrl_;
+ return;
+ }
+
+ if (this.successCallback_) {
+ this.successCallback_({email: this.email_,
+ password: this.password_,
+ usingSAML: this.authFlow_ == AuthFlow.SAML,
+ chooseWhatToSync: this.chooseWhatToSync_,
+ skipForNow: skipForNow,
+ sessionIndex: this.sessionIndex_ || ''});
+ }
+ cr.dispatchSimpleEvent(this, 'completed');
+ };
+
+ /**
+ * Invoked when the webview attempts to open a new window.
+ * @private
+ */
+ Authenticator.prototype.onNewWindow_ = function(e) {
+ window.open(e.targetUrl, '_blank');
xiyuan 2014/10/22 17:20:53 I wonder if we should let the embedder to decide w
guohui 2014/10/23 19:12:01 Done.
+ e.window.discard();
+ };
+
+ Authenticator.AuthFlow = AuthFlow;
+ Authenticator.AuthMode = AuthMode;
+
+ return {
+ // TODO(guohui, xiyuan): Rename GaiaAuthHost to Authenticator once the old
+ // iframe-based flow is deprecated.
+ GaiaAuthHost: Authenticator
+ };
+});

Powered by Google App Engine
This is Rietveld 408576698