Bug 1204937 - Part 3: Add Accounts:UpdateAccountFromJSON message. r=sebastian
authorNick Alexander <nalexander@mozilla.com>
Fri, 21 Aug 2015 11:27:54 -0700
changeset 295171 1938bacc4d61ade836cdffe21d70391fb7c9ab38
parent 295170 ec2b4ace61d3f2d85c555d62fe545fa6562244e8
child 295172 9ead51e40e481f3ae5b3f9ca8d49a4034fe22a17
push id5245
push userraliiev@mozilla.com
push dateThu, 29 Oct 2015 11:30:51 +0000
treeherdermozilla-beta@dac831dc1bd0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssebastian
bugs1204937
milestone43.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1204937 - Part 3: Add Accounts:UpdateAccountFromJSON message. r=sebastian This commit does a few things. First, it fixes a typo (s/ForResponse/ForResult/). It's not clear how this /ever/ worked, but it did. Second, it adds an UpdateAccountFromJSON sibling to CreateAccountFromJSON. It would have been reasonable to have the create message do double-duty and update an existing account (we have the latitude to change the meaning since this API is not yet public) but I generally prefer each consumer to perform the conditional state check and to act appropriately. Third, it generalizes the existing Accounts:Exist message to provide some details (including email and UID) of any existing Firefox Account. The Accounts.exist() API /is/ public, so I introduce a new (not yet public) API for this richer information.
mobile/android/base/AccountsHelper.java
mobile/android/modules/Accounts.jsm
--- a/mobile/android/base/AccountsHelper.java
+++ b/mobile/android/base/AccountsHelper.java
@@ -1,15 +1,16 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko;
 
+import android.accounts.Account;
 import android.content.Context;
 import android.content.Intent;
 import android.util.Log;
 import org.json.JSONException;
 import org.json.JSONObject;
 import org.mozilla.gecko.fxa.FirefoxAccounts;
 import org.mozilla.gecko.fxa.FxAccountConstants;
 import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
@@ -41,28 +42,30 @@ public class AccountsHelper implements N
 
         EventDispatcher dispatcher = EventDispatcher.getInstance();
         if (dispatcher == null) {
             Log.e(LOGTAG, "Gecko event dispatcher must not be null", new RuntimeException());
             return;
         }
         dispatcher.registerGeckoThreadListener(this,
                 "Accounts:CreateFirefoxAccountFromJSON",
+                "Accounts:UpdateFirefoxAccountFromJSON",
                 "Accounts:Create",
                 "Accounts:Exist");
     }
 
     public synchronized void uninit() {
         EventDispatcher dispatcher = EventDispatcher.getInstance();
         if (dispatcher == null) {
             Log.e(LOGTAG, "Gecko event dispatcher must not be null", new RuntimeException());
             return;
         }
         dispatcher.unregisterGeckoThreadListener(this,
                 "Accounts:CreateFirefoxAccountFromJSON",
+                "Accounts:UpdateFirefoxAccountFromJSON",
                 "Accounts:Create",
                 "Accounts:Exist");
     }
 
     @Override
     public void handleMessage(String event, NativeJSObject message, EventCallback callback) {
         if ("Accounts:CreateFirefoxAccountFromJSON".equals(event)) {
             AndroidFxAccount fxAccount = null;
@@ -96,16 +99,49 @@ public class AccountsHelper implements N
                     callback.sendError("Could not create Firefox Account from JSON: " + e.toString());
                     return;
                 }
             }
             if (callback != null) {
                 callback.sendSuccess(fxAccount != null);
             }
 
+        } else if ("Accounts:UpdateFirefoxAccountFromJSON".equals(event)) {
+            try {
+                final Account account = FirefoxAccounts.getFirefoxAccount(mContext);
+                if (account == null) {
+                    if (callback != null) {
+                        callback.sendError("Could not update Firefox Account since non exists");
+                    }
+                    return;
+                }
+
+                final NativeJSObject json = message.getObject("json");
+                final String email = json.getString("email");
+                final String uid = json.getString("uid");
+                final boolean verified = json.optBoolean("verified", false);
+                final byte[] unwrapkB = Utils.hex2Byte(json.getString("unwrapBKey"));
+                final byte[] sessionToken = Utils.hex2Byte(json.getString("sessionToken"));
+                final byte[] keyFetchToken = Utils.hex2Byte(json.getString("keyFetchToken"));
+                final State state = new Engaged(email, uid, verified, unwrapkB, sessionToken, keyFetchToken);
+
+                final AndroidFxAccount fxAccount = new AndroidFxAccount(mContext, account);
+                fxAccount.setState(state);
+
+                if (callback != null) {
+                    callback.sendSuccess(true);
+                }
+            } catch (Exception e) {
+                Log.w(LOGTAG, "Got exception updating Firefox Account from JSON; ignoring.", e);
+                if (callback != null) {
+                    callback.sendError("Could not update Firefox Account from JSON: " + e.toString());
+                    return;
+                }
+            }
+
         } else if ("Accounts:Create".equals(event)) {
             // Do exactly the same thing as if you tapped 'Sync' in Settings.
             final Intent intent = new Intent(FxAccountConstants.ACTION_FXA_GET_STARTED);
             intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
             final NativeJSObject extras = message.optObject("extras", null);
             if (extras != null) {
                 intent.putExtra("extras", extras.toString());
             }
@@ -121,17 +157,36 @@ public class AccountsHelper implements N
             final JSONObject response = new JSONObject();
 
             try {
                 if ("any".equals(kind)) {
                     response.put("exists", SyncAccounts.syncAccountsExist(mContext) ||
                             FirefoxAccounts.firefoxAccountsExist(mContext));
                     callback.sendSuccess(response);
                 } else if ("fxa".equals(kind)) {
-                    response.put("exists", FirefoxAccounts.firefoxAccountsExist(mContext));
+                    final Account account = FirefoxAccounts.getFirefoxAccount(mContext);
+                    response.put("exists", account != null);
+                    if (account != null) {
+                        response.put("email", account.name);
+                        // We should always be able to extract the server endpoints.
+                        final AndroidFxAccount fxAccount = new AndroidFxAccount(mContext, account);
+                        response.put("authServerEndpoint", fxAccount.getAccountServerURI());
+                        response.put("profileServerEndpoint", fxAccount.getProfileServerURI());
+                        response.put("tokenServerEndpoint", fxAccount.getTokenServerURI());
+                        try {
+                            // It is possible for the state fetch to fail and us to not be able to provide a UID.
+                            // Long term, the UID (and verification flag) will be attached to the Android account
+                            // user data and not the internal state representation.
+                            final State state = fxAccount.getState();
+                            response.put("uid", state.uid);
+                        } catch (Exception e) {
+                            Log.w(LOGTAG, "Got exception extracting account UID; ignoring.", e);
+                        }
+                    }
+
                     callback.sendSuccess(response);
                 } else if ("sync11".equals(kind)) {
                     response.put("exists", SyncAccounts.syncAccountsExist(mContext));
                     callback.sendSuccess(response);
                 } else {
                     callback.sendError("Could not query account existence: unknown kind.");
                 }
             } catch (JSONException e) {
--- a/mobile/android/modules/Accounts.jsm
+++ b/mobile/android/modules/Accounts.jsm
@@ -3,19 +3,19 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 this.EXPORTED_SYMBOLS = ["Accounts"];
 
 const { utils: Cu } = Components;
 
-Cu.import("resource://gre/modules/Messaging.jsm");
-Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/Promise.jsm");
+Cu.import("resource://gre/modules/Messaging.jsm"); /*global Messaging */
+Cu.import("resource://gre/modules/Promise.jsm"); /*global Promise */
+Cu.import("resource://gre/modules/Services.jsm"); /*global Services */
 
 /**
  * A promise-based API for querying the existence of Sync accounts,
  * and accessing the Sync setup wizard.
  *
  * Usage:
  *
  *   Cu.import("resource://gre/modules/Accounts.jsm");
@@ -63,24 +63,73 @@ let Accounts = Object.freeze({
    */
   launchSetup: function (extras) {
     Messaging.sendRequest({
       type: "Accounts:Create",
       extras: extras
     });
   },
 
+  _addDefaultEndpoints: function (json) {
+    let newData = Cu.cloneInto(json, {}, { cloneFunctions: false });
+    let associations = {
+      authServerEndpoint: 'identity.fxaccounts.auth.uri',
+      profileServerEndpoint: 'identity.fxaccounts.remote.profile.uri',
+      tokenServerEndpoint: 'identity.sync.tokenserver.uri'
+    };
+    for (let key in associations) {
+      newData[key] = newData[key] || Services.urlFormatter.formatURLPref(associations[key]);
+    }
+    return newData;
+  },
+
   /**
    * Create a new Android Account corresponding to the given
    * fxa-content-server "login" JSON datum.  The new account will be
    * in the "Engaged" state, and will start syncing immediately.
    *
    * It is an error if an Android Account already exists.
    *
    * Returns a Promise that resolves to a boolean indicating success.
    */
   createFirefoxAccountFromJSON: function (json) {
-    return Messaging.sendRequestForResponse({
+    return Messaging.sendRequestForResult({
       type: "Accounts:CreateFirefoxAccountFromJSON",
-      json: json
+      json: this._addDefaultEndpoints(json)
+    });
+  },
+
+  /**
+   * Move an existing Android Account to the "Engaged" state with the given
+   * fxa-content-server "login" JSON datum.  The account will (re)start
+   * syncing immediately, unless the user has manually configured the account
+   * to not Sync.
+   *
+   * It is an error if no Android Account exists.
+   *
+   * Returns a Promise that resolves to a boolean indicating success.
+   */
+  updateFirefoxAccountFromJSON: function (json) {
+    return Messaging.sendRequestForResult({
+      type: "Accounts:UpdateFirefoxAccountFromJSON",
+      json: this._addDefaultEndpoints(json)
+    });
+  },
+
+  /**
+   * Fetch information about an existing Android Firefox Account.
+   *
+   * Returns a Promise that resolves to null if no Android Firefox Account
+   * exists, or an object including at least a string-valued 'email' key.
+   */
+  getFirefoxAccount: function () {
+    return Messaging.sendRequestForResult({
+      type: "Accounts:Exist",
+      kind: "fxa",
+    }).then(data => {
+      if (!data || !data.exists) {
+        return null;
+      }
+      delete data.exists;
+      return data;
     });
   }
 });