Inline sign in flow with gaia auth extension
Design doc at https://docs.google.com/a/google.com/document/d/1L0gcqZEZ0lWL0PgqO6uPyecuxJdTvzZpps2zRly_wTg/edit#heading=h.5cwdwtkutcuo
BUG=302037
Review URL: https://codereview.chromium.org/25347002
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@226569 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/chrome/browser/browser_resources.grd b/chrome/browser/browser_resources.grd
index 28252791..0435551 100644
--- a/chrome/browser/browser_resources.grd
+++ b/chrome/browser/browser_resources.grd
@@ -279,6 +279,7 @@
<include name="IDR_GAIA_AUTH_MANIFEST" file="resources\gaia_auth\manifest.json" type="BINDATA" />
<include name="IDR_GAIA_AUTH_KEYBOARD_MANIFEST" file="resources\gaia_auth\manifest_keyboard.json" type="BINDATA" />
<include name="IDR_GAIA_AUTH_SAML_MANIFEST" file="resources\gaia_auth\manifest_saml.json" type="BINDATA" />
+ <include name="IDR_GAIA_AUTH_INLINE_MANIFEST" file="resources\gaia_auth\manifest_inline.json" type="BINDATA" />
<if expr="pp_ifdef('chromeos')">
<include name="IDR_ABOUT_SYS_HTML" file="resources\chromeos\about_sys.html" flattenhtml="true" type="BINDATA" />
<include name="IDR_BACKLOADER_MANIFEST" file="resources\backloader\manifest.json" type="BINDATA" />
diff --git a/chrome/browser/extensions/scoped_gaia_auth_extension.cc b/chrome/browser/extensions/scoped_gaia_auth_extension.cc
index 8897c95..7889dc2e 100644
--- a/chrome/browser/extensions/scoped_gaia_auth_extension.cc
+++ b/chrome/browser/extensions/scoped_gaia_auth_extension.cc
@@ -45,7 +45,10 @@
manifest_resource_id = IDR_GAIA_AUTH_KEYBOARD_MANIFEST;
else if (command_line->HasSwitch(chromeos::switches::kEnableSamlSignin))
manifest_resource_id = IDR_GAIA_AUTH_SAML_MANIFEST;
-#endif // OS_CHROMEOS
+#elif !defined(OS_ANDROID)
+ if (command_line->HasSwitch(switches::kEnableInlineSignin))
+ manifest_resource_id = IDR_GAIA_AUTH_INLINE_MANIFEST;
+#endif
component_loader->Add(manifest_resource_id,
base::FilePath(FILE_PATH_LITERAL("gaia_auth")));
diff --git a/chrome/browser/resources/chromeos/login/screen_gaia_signin.js b/chrome/browser/resources/chromeos/login/screen_gaia_signin.js
index 48adcd93..edfe4660 100644
--- a/chrome/browser/resources/chromeos/login/screen_gaia_signin.js
+++ b/chrome/browser/resources/chromeos/login/screen_gaia_signin.js
@@ -241,7 +241,9 @@
if (data.forceReload ||
JSON.stringify(this.gaiaAuthParams_) != JSON.stringify(params)) {
this.error_ = 0;
- this.gaiaAuthHost_.load(data.useOffline,
+ this.gaiaAuthHost_.load(data.useOffline ?
+ cr.login.GaiaAuthHost.AuthMode.OFFLINE :
+ cr.login.GaiaAuthHost.AuthMode.DEFAULT,
params,
this.onAuthCompleted_.bind(this));
this.gaiaAuthParams_ = params;
diff --git a/chrome/browser/resources/component_extension_resources.grd b/chrome/browser/resources/component_extension_resources.grd
index 1f409b1f..2cb63c7 100644
--- a/chrome/browser/resources/component_extension_resources.grd
+++ b/chrome/browser/resources/component_extension_resources.grd
@@ -53,6 +53,8 @@
<include name="IDR_GAIA_AUTH_BACKGROUND_JS" file="gaia_auth/background.js" type="BINDATA" />
<include name="IDR_GAIA_AUTH_SAML_INJECTED_JS" file="gaia_auth/saml_injected.js" type="BINDATA" />
<include name="IDR_GAIA_AUTH_CHANNEL_JS" file="gaia_auth/channel.js" type="BINDATA" />
+ <include name="IDR_GAIA_AUTH_INLINE_INJECTED_JS" file="gaia_auth/inline_injected.js" type="BINDATA" />
+ <include name="IDR_GAIA_AUTH_INLINE_MAIN" file="gaia_auth/inline_main.html" allowexternalscript="true" type="BINDATA" />
<if expr="pp_ifdef('chromeos')">
<!-- Background page loader -->
<include name="IDR_BACKLOADER_BACKGROUND_HTML" file="backloader/background.html" type="BINDATA" />
diff --git a/chrome/browser/resources/gaia_auth/inline_injected.js b/chrome/browser/resources/gaia_auth/inline_injected.js
new file mode 100644
index 0000000..a56336d5
--- /dev/null
+++ b/chrome/browser/resources/gaia_auth/inline_injected.js
@@ -0,0 +1,43 @@
+// Copyright 2013 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 Code injected into Gaia sign in page for inline sign in flow.
+ * On load stop, it receives a message from the embedder extension with a
+ * JavaScript reference to the embedder window. Then upon submit of the sign
+ * in form, it posts the username and password to the embedder window.
+ * Prototype Only.
+ */
+
+(function() {
+ var extWindow;
+
+ var $ = function(id) { return document.getElementById(id); };
+ var gaiaLoginForm = $('gaia_loginform');
+
+ var onMessage = function(e) {
+ extWindow = e.source;
+ };
+ window.addEventListener('message', onMessage);
+
+ var onLoginSubmit = function(e) {
+ if (!extWindow) {
+ console.log('ERROR: no initial message received from the gaia ext');
+ e.preventDefault();
+ return;
+ }
+
+ var msg = {method: 'attemptLogin',
+ email: gaiaLoginForm['Email'].value,
+ password: gaiaLoginForm['Passwd'].value,
+ attemptToken: new Date().getTime()};
+
+ extWindow.postMessage(msg, 'chrome://inline-login');
+ console.log('Credentials sent');
+
+ return;
+ };
+ // Overrides the submit handler for the gaia login form.
+ gaiaLoginForm.onsubmit = onLoginSubmit;
+})();
diff --git a/chrome/browser/resources/gaia_auth/inline_main.html b/chrome/browser/resources/gaia_auth/inline_main.html
new file mode 100644
index 0000000..00ed78b
--- /dev/null
+++ b/chrome/browser/resources/gaia_auth/inline_main.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <link rel="stylesheet" href="main.css">
+ <meta charset="utf-8">
+ <script src="channel.js"></script>
+ <script src="util.js"></script>
+ <script src="main.js"></script>
+</head>
+<body>
+ <webview id="gaia-frame"></webview>
+</body>
+</html>
diff --git a/chrome/browser/resources/gaia_auth/main.css b/chrome/browser/resources/gaia_auth/main.css
index 980e609..dda571f 100644
--- a/chrome/browser/resources/gaia_auth/main.css
+++ b/chrome/browser/resources/gaia_auth/main.css
@@ -15,3 +15,9 @@
iframe {
overflow: hidden;
}
+
+webview {
+ display: inline-block;
+ height: 300px;
+ width: 300px;
+}
diff --git a/chrome/browser/resources/gaia_auth/main.js b/chrome/browser/resources/gaia_auth/main.js
index a191d46..667ed67 100644
--- a/chrome/browser/resources/gaia_auth/main.js
+++ b/chrome/browser/resources/gaia_auth/main.js
@@ -33,18 +33,19 @@
GAIA_URL: 'https://accounts.google.com/',
GAIA_PAGE_PATH: 'ServiceLogin?service=chromeoslogin' +
- '&skipvpage=true&sarp=1&rm=hide' +
- '&continue=chrome-extension://mfffpogegjflfpflabcdkioaeobkgjik/' +
- 'success.html',
+ '&skipvpage=true&sarp=1&rm=hide',
THIS_EXTENSION_ORIGIN: 'chrome-extension://mfffpogegjflfpflabcdkioaeobkgjik',
PARENT_PAGE: 'chrome://oobe/',
+ CONTINUE_URL: Authenticator.THIS_EXTENSION_ORIGIN + '/success.html',
initialize: function() {
var params = getUrlSearchParams(location.search);
- this.parentPage_ = params['parentPage'] || this.PARENT_PAGE;
- this.gaiaUrl_ = params['gaiaUrl'] || this.GAIA_URL;
- this.inputLang_ = params['hl'];
- this.inputEmail_ = params['email'];
+ this.parentPage_ = params.parentPage || this.PARENT_PAGE;
+ this.gaiaUrl_ = params.gaiaUrl || this.GAIA_URL;
+ this.inputLang_ = params.hl;
+ this.inputEmail_ = params.email;
+ this.continueUrl_ = params.continueUrl || this.CONTINUE_URL;
+ this.inlineMode_ = params.inlineMode;
document.addEventListener('DOMContentLoaded', this.onPageLoad.bind(this));
document.addEventListener('enableSAML', this.onEnableSAML_.bind(this));
@@ -67,7 +68,8 @@
getFrameUrl_: function() {
var url = this.gaiaUrl_;
- url += this.GAIA_PAGE_PATH;
+ url += this.GAIA_PAGE_PATH + '&continue=' +
+ encodeURIComponent(this.continueUrl_);
if (this.inputLang_)
url += '&hl=' + encodeURIComponent(this.inputLang_);
@@ -76,8 +78,33 @@
return url;
},
+ /** Callback when all loads in the gaia webview is complete. */
+ onWebviewLoadstop_: function(gaiaFrame) {
+ if (gaiaFrame.src.lastIndexOf(
+ this.gaiaUrl_ + this.GAIA_PAGE_PATH, 0) == 0) {
+ gaiaFrame.executeScript({file: 'inline_injected.js'}, function() {
+ // Send an initial message to gaia so that it has an JavaScript
+ // reference to the embedder.
+ gaiaFrame.contentWindow.postMessage('', gaiaFrame.src);
+ });
+ this.onLoginUILoaded();
+ } else if (gaiaFrame.src.lastIndexOf(this.continueUrl_, 0) == 0) {
+ // Detect when login is finished by the load stop event of the continue
+ // URL. Cannot reuse the login complete flow in success.html, because
+ // webview does not support extension pages yet.
+ gaiaFrame.hidden = true;
+ var msg = {'method': 'completeLogin'};
+ window.parent.postMessage(msg, this.parentPage_);
+ }
+ },
+
loadFrame_: function() {
- $('gaia-frame').src = this.getFrameUrl_();
+ var gaiaFrame = $('gaia-frame');
+ gaiaFrame.src = this.getFrameUrl_();
+ if (this.inlineMode_) {
+ gaiaFrame.addEventListener(
+ 'loadstop', this.onWebviewLoadstop_.bind(this, gaiaFrame));
+ }
},
completeLogin: function(username, password) {
diff --git a/chrome/browser/resources/gaia_auth/manifest_inline.json b/chrome/browser/resources/gaia_auth/manifest_inline.json
new file mode 100644
index 0000000..b593cc99
--- /dev/null
+++ b/chrome/browser/resources/gaia_auth/manifest_inline.json
@@ -0,0 +1,29 @@
+{
+ // chrome-extension://mfffpogegjflfpflabcdkioaeobkgjik/
+ "key": "MIGdMA0GCSqGSIb3DQEBAQUAA4GLADCBhwKBgQC4L17nAfeTd6Xhtx96WhQ6DSr8KdHeQmfzgCkieKLCgUkWdwB9G1DCuh0EPMDn1MdtSwUAT7xE36APEzi0X/UpKjOVyX8tCC3aQcLoRAE0aJAvCcGwK7qIaQaczHmHKvPC2lrRdzSoMMTC5esvHX+ZqIBMi123FOL0dGW6OPKzIwIBIw==",
+ "name": "GaiaAuthExtension",
+ "version": "0.0.1",
+ "manifest_version": 2,
+ "content_security_policy": "default-src 'self'; script-src 'self'; frame-src *; style-src 'self' 'unsafe-inline'",
+ "description": "GAIA Component Extension",
+ "web_accessible_resources": [
+ "main.css",
+ "inline_main.html",
+ "main.js",
+ "offline.css",
+ "offline.html",
+ "offline.js",
+ "success.html",
+ "success.js",
+ "util.js"
+ ],
+ // cookies for getting hash passed back from GAIA on login success
+ // tabs for calling current webui's login. This might not be needed once
+ // we have extension API
+ "permissions": [
+ "cookies",
+ "tabs",
+ "chrome://oobe/",
+ "webview"
+ ]
+}
diff --git a/chrome/browser/resources/gaia_auth_host/gaia_auth_host.js b/chrome/browser/resources/gaia_auth_host/gaia_auth_host.js
index 9fd0db0..edcb413 100644
--- a/chrome/browser/resources/gaia_auth_host/gaia_auth_host.js
+++ b/chrome/browser/resources/gaia_auth_host/gaia_auth_host.js
@@ -35,6 +35,18 @@
var OFFLINE_AUTH_URL = AUTH_URL_BASE + '/offline.html';
/**
+ * Auth URL to use for inline flow.
+ * @const
+ */
+ var INLINE_AUTH_URL = AUTH_URL_BASE + '/inline_main.html';
+
+ /**
+ * Origin of the gaia sign in page.
+ * @const
+ */
+ var GAIA_ORIGIN = 'https://accounts.google.com';
+
+ /**
* Supported params of auth extension. For a complete list, check out the
* auth extension's main.js.
* @type {!Array.<string>}
@@ -43,7 +55,8 @@
var SUPPORTED_PARAMS = [
'gaiaUrl', // Gaia url to use;
'hl', // Language code for the user interface;
- 'email' // Pre-fill the email field in Gaia UI;
+ 'email', // Pre-fill the email field in Gaia UI;
+ 'continueUrl' // Continue url to use;
];
/**
@@ -62,6 +75,17 @@
];
/**
+ * 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,
+ INLINE: 2
+ };
+
+ /**
* Creates a new gaia auth extension host.
* @param {HTMLIFrameElement|string} container The iframe element or its id
* to host the auth extension.
@@ -94,7 +118,7 @@
* email: '[email protected]',
* password: 'xxxx', // May not present
* authCode: 'x/xx', // May not present
- * useOffline: false // Whether the authentication uses the offline flow.
+ * authMode: 'x', // Authorization mode, default/inline/offline.
* }
* }
* </pre>
@@ -165,14 +189,14 @@
/**
* Loads the auth extension.
- * @param {boolean} useOffline Whether to use offline url or not.
+ * @param {AuthMode} authMode Authorization mode.
* @param {Object} data Parameters for the auth extension. See the auth
* extension's main.js for all supported params and their defaults.
* @param {function(Object)} successCallback A function to be called when
* the authentication is completed successfully. The callback is
* invoked with a credential object.
*/
- load: function(useOffline, data, successCallback) {
+ load: function(authMode, data, successCallback) {
var params = [];
var populateParams = function(nameList, values) {
@@ -190,7 +214,18 @@
populateParams(LOCALIZED_STRING_PARAMS, data.localizedStrings);
params.push('parentPage=' + encodeURIComponent(window.location.origin));
- var url = useOffline ? OFFLINE_AUTH_URL : AUTH_URL;
+ var url;
+ switch (authMode) {
+ case AuthMode.OFFLINE:
+ url = OFFLINE_AUTH_URL;
+ break;
+ case AuthMode.INLINE:
+ url = INLINE_AUTH_URL;
+ params.push('inlineMode=true');
+ break;
+ default:
+ url = AUTH_URL;
+ }
url += '?' + params.join('&');
this.frame_.src = url;
@@ -246,19 +281,29 @@
* @param {object} e Payload of the received HTML5 message.
*/
onMessage_: function(e) {
+ var msg = e.data;
+
+ // In the inline sign in flow, the embedded gaia webview posts credential
+ // directly to the inline sign in page, because its parent JavaScript
+ // reference points to the top frame of the embedder instead of the sub
+ // frame of the gaia auth extension.
+ if (e.origin == GAIA_ORIGIN && msg.method == 'attemptLogin') {
+ this.email_ = msg.email;
+ this.password_ = msg.password;
+ return;
+ }
+
if (!this.isAuthExtMessage_(e))
return;
- var msg = e.data;
-
if (msg.method == 'loginUILoaded') {
cr.dispatchSimpleEvent(this, 'ready');
return;
}
if (/^complete(Login|Authentication)$|^offlineLogin$/.test(msg.method)) {
- this.onAuthSuccess_({email: msg.email,
- password: msg.password,
+ this.onAuthSuccess_({email: msg.email || this.email_,
+ password: msg.password || this.password_,
authCode: msg.authCode,
useOffline: msg.method == 'offlineLogin'});
return;
@@ -292,6 +337,7 @@
GaiaAuthHost.SUPPORTED_PARAMS = SUPPORTED_PARAMS;
GaiaAuthHost.LOCALIZED_STRING_PARAMS = LOCALIZED_STRING_PARAMS;
+ GaiaAuthHost.AuthMode = AuthMode;
return {
GaiaAuthHost: GaiaAuthHost
diff --git a/chrome/browser/resources/inline_login/inline_login.js b/chrome/browser/resources/inline_login/inline_login.js
index 9d17a80..6e09bb6 100644
--- a/chrome/browser/resources/inline_login/inline_login.js
+++ b/chrome/browser/resources/inline_login/inline_login.js
@@ -48,8 +48,7 @@
* @param {Object} data Parameters for auth extension.
*/
function loadAuthExtension(data) {
- authExtHost.load(
- false /* useOffline */, data, onAuthCompleted);
+ authExtHost.load(data.authMode, data, onAuthCompleted);
$('contents').classList.toggle('loading', true);
}
diff --git a/chrome/browser/ui/webui/inline_login_ui.cc b/chrome/browser/ui/webui/inline_login_ui.cc
index da5e138..80c0b64 100644
--- a/chrome/browser/ui/webui/inline_login_ui.cc
+++ b/chrome/browser/ui/webui/inline_login_ui.cc
@@ -8,6 +8,7 @@
#include "base/command_line.h"
#include "base/memory/scoped_ptr.h"
#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
#include "base/values.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/profiles/profile.h"
@@ -15,6 +16,7 @@
#include "chrome/browser/signin/token_service.h"
#include "chrome/browser/signin/token_service_factory.h"
#include "chrome/browser/ui/sync/one_click_signin_sync_starter.h"
+#include "chrome/common/chrome_switches.h"
#include "chrome/common/url_constants.h"
#include "content/public/browser/web_ui.h"
#include "content/public/browser/web_ui_data_source.h"
@@ -22,6 +24,7 @@
#include "google_apis/gaia/gaia_switches.h"
#include "google_apis/gaia/gaia_urls.h"
#include "grit/browser_resources.h"
+#include "net/base/escape.h"
#if defined(OS_CHROMEOS)
#include "chrome/browser/chromeos/login/oauth2_token_fetcher.h"
@@ -90,12 +93,41 @@
}
private:
+ // Enum for gaia auth mode, must match AuthMode defined in
+ // chrome/browser/resources/gaia_auth_host/gaia_auth_host.js.
+ enum AuthMode {
+ kDefaultAuthMode = 0,
+ kOfflineAuthMode = 1,
+ kInlineAuthMode = 2
+ };
+
void LoadAuthExtension() {
base::DictionaryValue params;
const std::string& app_locale = g_browser_process->GetApplicationLocale();
params.SetString("hl", app_locale);
- params.SetString("gaiaUrl", GaiaUrls::GetInstance()->gaia_url().spec());
+
+ GaiaUrls* gaiaUrls = GaiaUrls::GetInstance();
+ params.SetString("gaiaUrl", gaiaUrls->gaia_url().spec());
+
+ bool enable_inline = CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kEnableInlineSignin);
+ params.SetInteger("authMode",
+ enable_inline ? kInlineAuthMode : kDefaultAuthMode);
+ // Set continueUrl param for the inline sign in flow. It should point to
+ // the oauth2 auth code URL so that later we can grab the auth code from
+ // the cookie jar of the embedded webview.
+ if (enable_inline) {
+ std::string scope = net::EscapeUrlEncodedData(
+ gaiaUrls->oauth1_login_scope(), true);
+ std::string client_id = net::EscapeUrlEncodedData(
+ gaiaUrls->oauth2_chrome_client_id(), true);
+ std::string encoded_continue_params = base::StringPrintf(
+ "?scope=%s&client_id=%s", scope.c_str(), client_id.c_str());
+ params.SetString("continueUrl",
+ gaiaUrls->client_login_to_oauth2_url().Resolve(
+ encoded_continue_params).spec());
+ }
web_ui()->CallJavascriptFunction("inline.login.loadAuthExtension", params);
}