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);
   }