Upstream version 9.38.198.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / resources / gaia_auth / main.js
index 14db335..1c61a24 100644 (file)
@@ -16,6 +16,26 @@ Authenticator.THIS_EXTENSION_ORIGIN =
     'chrome-extension://mfffpogegjflfpflabcdkioaeobkgjik';
 
 /**
+ * The lowest version of the credentials passing API supported.
+ * @type {number}
+ */
+Authenticator.MIN_API_VERSION_VERSION = 1;
+
+/**
+ * The highest version of the credentials passing API supported.
+ * @type {number}
+ */
+Authenticator.MAX_API_VERSION_VERSION = 1;
+
+/**
+ * The key types supported by the credentials passing API.
+ * @type {Array} Array of strings.
+ */
+Authenticator.API_KEY_TYPES = [
+  'KEY_TYPE_PASSWORD_PLAIN',
+];
+
+/**
  * Singleton getter of Authenticator.
  * @return {Object} The singleton instance of Authenticator.
  */
@@ -28,7 +48,14 @@ Authenticator.getInstance = function() {
 
 Authenticator.prototype = {
   email_: null,
-  password_: null,
+
+  // Depending on the key type chosen, this will contain the plain text password
+  // or a credential derived from it along with the information required to
+  // repeat the derivation, such as a salt. The information will be encoded so
+  // that it contains printable ASCII characters only. The exact encoding is TBD
+  // when support for key types other than plain text password is added.
+  passwordBytes_: null,
+
   attemptToken_: null,
 
   // Input params from extension initialization URL.
@@ -36,7 +63,7 @@ Authenticator.prototype = {
   intputEmail_: undefined,
 
   isSAMLFlow_: false,
-  isSAMLEnabled_: false,
+  gaiaLoaded_: false,
   supportChannel_: null,
 
   GAIA_URL: 'https://accounts.google.com/',
@@ -60,8 +87,15 @@ Authenticator.prototype = {
     this.initialFrameUrl_ = params.frameUrl || this.constructInitialFrameUrl_();
     this.initialFrameUrlWithoutParams_ = stripParams(this.initialFrameUrl_);
 
+    // For CrOS 'ServiceLogin' we assume that Gaia is loaded if we recieved
+    // 'clearOldAttempts' message. For other scenarios Gaia doesn't send this
+    // message so we have to rely on 'load' event.
+    // TODO(dzhioev): Do not rely on 'load' event after b/16313327 is fixed.
+    this.assumeLoadedOnLoadEvent_ =
+        this.gaiaPath_.indexOf('ServiceLogin') !== 0 ||
+        this.service_ !== 'chromeoslogin';
+
     document.addEventListener('DOMContentLoaded', this.onPageLoad_.bind(this));
-    document.addEventListener('enableSAML', this.onEnableSAML_.bind(this));
   },
 
   isGaiaMessage_: function(msg) {
@@ -94,54 +128,67 @@ Authenticator.prototype = {
 
   onPageLoad_: function() {
     window.addEventListener('message', this.onMessage.bind(this), false);
+    this.initSupportChannel_();
 
     var gaiaFrame = $('gaia-frame');
     gaiaFrame.src = this.initialFrameUrl_;
 
-    if (this.desktopMode_) {
+    if (this.assumeLoadedOnLoadEvent_) {
       var handler = function() {
-        this.onLoginUILoaded_();
         gaiaFrame.removeEventListener('load', handler);
-
-        this.initDesktopChannel_();
+        if (!this.gaiaLoaded_) {
+          this.gaiaLoaded_ = true;
+          this.maybeInitialized_();
+        }
       }.bind(this);
       gaiaFrame.addEventListener('load', handler);
     }
   },
 
-  initDesktopChannel_: function() {
-    this.supportChannel_ = new Channel();
-    this.supportChannel_.connect('authMain');
-
-    var channelConnected = false;
-    this.supportChannel_.registerMessage('channelConnected', function() {
-      channelConnected = true;
+  initSupportChannel_: function() {
+    var supportChannel = new Channel();
+    supportChannel.connect('authMain');
 
-      this.supportChannel_.send({
-        name: 'initDesktopFlow',
-        gaiaUrl: this.gaiaUrl_,
-        continueUrl: stripParams(this.continueUrl_),
-        isConstrainedWindow: this.isConstrainedWindow_
-      });
-      this.supportChannel_.registerMessage(
-        'switchToFullTab', this.switchToFullTab_.bind(this));
-      this.supportChannel_.registerMessage(
-        'completeLogin', this.completeLogin_.bind(this));
+    supportChannel.registerMessage('channelConnected', function() {
+      if (this.supportChannel_) {
+        console.error('Support channel is already initialized.');
+        return;
+      }
+      this.supportChannel_ = supportChannel;
+
+      if (this.desktopMode_) {
+        this.supportChannel_.send({
+          name: 'initDesktopFlow',
+          gaiaUrl: this.gaiaUrl_,
+          continueUrl: stripParams(this.continueUrl_),
+          isConstrainedWindow: this.isConstrainedWindow_
+        });
+        this.supportChannel_.registerMessage(
+            'switchToFullTab', this.switchToFullTab_.bind(this));
+        this.supportChannel_.registerMessage(
+            'completeLogin', this.completeLogin_.bind(this));
+      }
+      this.initSAML_();
+      this.maybeInitialized_();
     }.bind(this));
 
     window.setTimeout(function() {
-      if (!channelConnected) {
+      if (!this.supportChannel_) {
         // Re-initialize the channel if it is not connected properly, e.g.
         // connect may be called before background script started running.
-        this.initDesktopChannel_();
+        this.initSupportChannel_();
       }
     }.bind(this), 200);
   },
 
   /**
-   * Invoked when the login UI is initialized or reset.
+   * Called when one of the initialization stages has finished. If all the
+   * needed parts are initialized, notifies parent about successfull
+   * initialization.
    */
-  onLoginUILoaded_: function() {
+  maybeInitialized_: function() {
+    if (!this.gaiaLoaded_ || !this.supportChannel_)
+      return;
     var msg = {
       'method': 'loginUILoaded'
     };
@@ -169,37 +216,43 @@ Authenticator.prototype = {
     var msg = {
       'method': 'completeLogin',
       'email': (opt_extraMsg && opt_extraMsg.email) || this.email_,
-      'password': this.password_,
+      'password': (opt_extraMsg && opt_extraMsg.password) ||
+                  this.passwordBytes_,
       'usingSAML': this.isSAMLFlow_,
       'chooseWhatToSync': this.chooseWhatToSync_ || false,
       'skipForNow': opt_extraMsg && opt_extraMsg.skipForNow,
       'sessionIndex': opt_extraMsg && opt_extraMsg.sessionIndex
     };
     window.parent.postMessage(msg, this.parentPage_);
-    if (this.isSAMLEnabled_)
-      this.supportChannel_.send({name: 'resetAuth'});
+    this.supportChannel_.send({name: 'resetAuth'});
   },
 
   /**
-   * Invoked when 'enableSAML' event is received to initialize SAML support.
+   * Invoked when support channel is connected.
    */
-  onEnableSAML_: function() {
-    this.isSAMLEnabled_ = true;
+  initSAML_: function() {
     this.isSAMLFlow_ = false;
 
-    if (!this.supportChannel_) {
-      this.supportChannel_ = new Channel();
-      this.supportChannel_.connect('authMain');
-    }
-
     this.supportChannel_.registerMessage(
         'onAuthPageLoaded', this.onAuthPageLoaded_.bind(this));
     this.supportChannel_.registerMessage(
+        'onInsecureContentBlocked', this.onInsecureContentBlocked_.bind(this));
+    this.supportChannel_.registerMessage(
         'apiCall', this.onAPICall_.bind(this));
     this.supportChannel_.send({
       name: 'setGaiaUrl',
       gaiaUrl: this.gaiaUrl_
     });
+    if (!this.desktopMode_ && this.gaiaUrl_.indexOf('https://') == 0) {
+      // Abort the login flow when content served over an unencrypted connection
+      // is detected on Chrome OS. This does not apply to tests that explicitly
+      // set a non-https GAIA URL and want to perform all authentication over
+      // http.
+      this.supportChannel_.send({
+        name: 'setBlockInsecureContent',
+        blockInsecureContent: true
+      });
+    }
   },
 
   /**
@@ -212,10 +265,10 @@ Authenticator.prototype = {
     if (isSAMLPage && !this.isSAMLFlow_) {
       // GAIA redirected to a SAML login page. The credentials provided to this
       // page will determine what user gets logged in. The credentials obtained
-      // from the GAIA login from are no longer relevant and can be discarded.
+      // from the GAIA login form are no longer relevant and can be discarded.
       this.isSAMLFlow_ = true;
       this.email_ = null;
-      this.password_ = null;
+      this.passwordBytes_ = null;
     }
 
     window.parent.postMessage({
@@ -226,16 +279,46 @@ Authenticator.prototype = {
   },
 
   /**
+   * Invoked when the background page sends an 'onInsecureContentBlocked'
+   * message.
+   * @param {!Object} msg Details sent with the message.
+   */
+  onInsecureContentBlocked_: function(msg) {
+    window.parent.postMessage({
+      'method': 'insecureContentBlocked',
+      'url': stripParams(msg.url)
+    }, this.parentPage_);
+  },
+
+  /**
    * Invoked when one of the credential passing API methods is called by a SAML
    * provider.
    * @param {!Object} msg Details of the API call.
    */
   onAPICall_: function(msg) {
     var call = msg.call;
+    if (call.method == 'initialize') {
+      if (!Number.isInteger(call.requestedVersion) ||
+          call.requestedVersion < Authenticator.MIN_API_VERSION_VERSION) {
+        this.sendInitializationFailure_();
+        return;
+      }
+
+      this.apiVersion_ = Math.min(call.requestedVersion,
+                                  Authenticator.MAX_API_VERSION_VERSION);
+      this.initialized_ = true;
+      this.sendInitializationSuccess_();
+      return;
+    }
+
     if (call.method == 'add') {
+      if (Authenticator.API_KEY_TYPES.indexOf(call.keyType) == -1) {
+        console.error('Authenticator.onAPICall_: unsupported key type');
+        return;
+      }
       this.apiToken_ = call.token;
       this.email_ = call.user;
-      this.password_ = call.password;
+      this.passwordBytes_ = call.passwordBytes;
     } else if (call.method == 'confirm') {
       if (call.token != this.apiToken_)
         console.error('Authenticator.onAPICall_: token mismatch');
@@ -244,13 +327,28 @@ Authenticator.prototype = {
     }
   },
 
+  sendInitializationSuccess_: function() {
+    this.supportChannel_.send({name: 'apiResponse', response: {
+      result: 'initialized',
+      version: this.apiVersion_,
+      keyTypes: Authenticator.API_KEY_TYPES
+    }});
+  },
+
+  sendInitializationFailure_: function() {
+    this.supportChannel_.send({
+      name: 'apiResponse',
+      response: {result: 'initialization_failed'}
+    });
+  },
+
   onConfirmLogin_: function() {
     if (!this.isSAMLFlow_) {
       this.completeLogin_();
       return;
     }
 
-    var apiUsed = !!this.password_;
+    var apiUsed = !!this.passwordBytes_;
 
     // Retrieve the e-mail address of the user who just authenticated from GAIA.
     window.parent.postMessage({method: 'retrieveAuthenticatedUserEmail',
@@ -279,7 +377,7 @@ Authenticator.prototype = {
   maybeCompleteSAMLLogin_: function() {
     // SAML login is complete when the user's e-mail address has been retrieved
     // from GAIA and the user has successfully confirmed the password.
-    if (this.email_ !== null && this.password_ !== null)
+    if (this.email_ !== null && this.passwordBytes_ !== null)
       this.completeLogin_();
   },
 
@@ -289,7 +387,7 @@ Authenticator.prototype = {
         function(passwords) {
           for (var i = 0; i < passwords.length; ++i) {
             if (passwords[i] == password) {
-              this.password_ = passwords[i];
+              this.passwordBytes_ = passwords[i];
               this.maybeCompleteSAMLLogin_();
               return;
             }
@@ -304,19 +402,24 @@ Authenticator.prototype = {
     var msg = e.data;
     if (msg.method == 'attemptLogin' && this.isGaiaMessage_(e)) {
       this.email_ = msg.email;
-      this.password_ = msg.password;
+      this.passwordBytes_ = msg.password;
       this.attemptToken_ = msg.attemptToken;
       this.chooseWhatToSync_ = msg.chooseWhatToSync;
       this.isSAMLFlow_ = false;
-      if (this.isSAMLEnabled_)
+      if (this.supportChannel_)
         this.supportChannel_.send({name: 'startAuth'});
+      else
+        console.error('Support channel is not initialized.');
     } else if (msg.method == 'clearOldAttempts' && this.isGaiaMessage_(e)) {
+      if (!this.gaiaLoaded_) {
+        this.gaiaLoaded_ = true;
+        this.maybeInitialized_();
+      }
       this.email_ = null;
-      this.password_ = null;
+      this.passwordBytes_ = null;
       this.attemptToken_ = null;
       this.isSAMLFlow_ = false;
-      this.onLoginUILoaded_();
-      if (this.isSAMLEnabled_)
+      if (this.supportChannel_)
         this.supportChannel_.send({name: 'resetAuth'});
     } else if (msg.method == 'setAuthenticatedUserEmail' &&
                this.isParentMessage_(e)) {
@@ -332,9 +435,6 @@ Authenticator.prototype = {
     } else if (msg.method == 'verifyConfirmedPassword' &&
                this.isParentMessage_(e)) {
       this.onVerifyConfirmedPassword_(msg.password);
-    } else if (msg.method == 'navigate' &&
-               this.isParentMessage_(e)) {
-      $('gaia-frame').src = msg.src;
     } else if (msg.method == 'redirectToSignin' &&
                this.isParentMessage_(e)) {
       $('gaia-frame').src = this.constructInitialFrameUrl_();