Bug 1267760 - Send push public key and auth secret when registering/updating a device. r=kitcambridge
☠☠ backed out by e3d409d10dea ☠ ☠
authorEdouard Oger <eoger@fastmail.com>
Tue, 24 May 2016 16:19:47 -0700
changeset 338886 7520b940afc85c299b6cb500477c8906eecde531
parent 338885 e88afebe9226340f67730f2c867a78a33e7fa4e0
child 338887 e3d409d10dea7c1c94630abf52e6e9245a77f448
push id6249
push userjlund@mozilla.com
push dateMon, 01 Aug 2016 13:59:36 +0000
treeherdermozilla-beta@bad9d4f5bf7e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskitcambridge
bugs1267760
milestone49.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 1267760 - Send push public key and auth secret when registering/updating a device. r=kitcambridge MozReview-Commit-ID: BVwaijvSsLL
services/fxaccounts/FxAccounts.jsm
services/fxaccounts/FxAccountsClient.jsm
services/fxaccounts/tests/xpcshell/test_accounts_device_registration.js
--- a/services/fxaccounts/FxAccounts.jsm
+++ b/services/fxaccounts/FxAccounts.jsm
@@ -291,16 +291,20 @@ function copyObjectProperties(from, to, 
     if (desc.set) {
       desc.set = desc.set.bind(thisArg);
     }
 
     Object.defineProperty(to, prop, desc);
   }
 }
 
+function urlsafeBase64Encode(key) {
+  return ChromeUtils.base64URLEncode(new Uint8Array(key), { pad: false });
+}
+
 /**
  * The public API's constructor.
  */
 this.FxAccounts = function (mockInternal) {
   let internal = new FxAccountsInternal();
   let external = {};
 
   // Copy all public properties to the 'external' object.
@@ -350,17 +354,17 @@ function FxAccountsInternal() {
  */
 FxAccountsInternal.prototype = {
   // The timeout (in ms) we use to poll for a verified mail for the first 2 mins.
   VERIFICATION_POLL_TIMEOUT_INITIAL: 15000, // 15 seconds
   // And how often we poll after the first 2 mins.
   VERIFICATION_POLL_TIMEOUT_SUBSEQUENT: 30000, // 30 seconds.
   // The current version of the device registration, we use this to re-register
   // devices after we update what we send on device registration.
-  DEVICE_REGISTRATION_VERSION: 1,
+  DEVICE_REGISTRATION_VERSION: 2,
 
   _fxAccountsClient: null,
 
   // All significant initialization should be done in this initialize() method,
   // as it's called after this object has been mocked for tests.
   initialize() {
     this.currentTimer = null;
     this.currentAccountState = this.newAccountState();
@@ -1508,16 +1512,22 @@ FxAccountsInternal.prototype = {
 
     return this.fxaPushService.registerPushEndpoint().then(subscription => {
       const deviceName = this._getDeviceName();
       let deviceOptions = {};
 
       // if we were able to obtain a subscription
       if (subscription && subscription.endpoint) {
         deviceOptions.pushCallback = subscription.endpoint;
+        let publicKey = subscription.getKey('p256dh');
+        let authKey = subscription.getKey('auth');
+        if (publicKey && authKey) {
+          deviceOptions.pushPublicKey = urlsafeBase64Encode(publicKey);
+          deviceOptions.pushAuthKey = urlsafeBase64Encode(authKey);
+        }
       }
 
       if (signedInUser.deviceId) {
         log.debug("updating existing device details");
         return this.fxAccountsClient.updateDevice(
           signedInUser.sessionToken, signedInUser.deviceId, deviceName, deviceOptions);
       }
 
--- a/services/fxaccounts/FxAccountsClient.jsm
+++ b/services/fxaccounts/FxAccountsClient.jsm
@@ -362,16 +362,20 @@ this.FxAccountsClient.prototype = {
    * @param  name
    *         Device name
    * @param  type
    *         Device type (mobile|desktop)
    * @param  [options]
    *         Extra device options
    * @param  [options.pushCallback]
    *         `pushCallback` push endpoint callback
+   * @param  [options.pushPublicKey]
+   *         `pushPublicKey` push public key (URLSafe Base64 string)
+   * @param  [options.pushAuthKey]
+   *         `pushAuthKey` push auth secret (URLSafe Base64 string)
    * @return Promise
    *         Resolves to an object:
    *         {
    *           id: Device identifier
    *           createdAt: Creation time (milliseconds since epoch)
    *           name: Name of device
    *           type: Type of device (mobile|desktop)
    *         }
@@ -380,16 +384,20 @@ this.FxAccountsClient.prototype = {
     let path = "/account/device";
 
     let creds = deriveHawkCredentials(sessionTokenHex, "sessionToken");
     let body = { name, type };
 
     if (options.pushCallback) {
       body.pushCallback = options.pushCallback;
     }
+    if (options.pushPublicKey && options.pushAuthKey) {
+      body.pushPublicKey = options.pushPublicKey;
+      body.pushAuthKey = options.pushAuthKey;
+    }
 
     return this._request(path, "POST", creds, body);
   },
 
   /**
    * Update the session or name for an existing device
    *
    * @method updateDevice
@@ -398,31 +406,39 @@ this.FxAccountsClient.prototype = {
    * @param  id
    *         Device identifier
    * @param  name
    *         Device name
    * @param  [options]
    *         Extra device options
    * @param  [options.pushCallback]
    *         `pushCallback` push endpoint callback
+   * @param  [options.pushPublicKey]
+   *         `pushPublicKey` push public key (URLSafe Base64 string)
+   * @param  [options.pushAuthKey]
+   *         `pushAuthKey` push auth secret (URLSafe Base64 string)
    * @return Promise
    *         Resolves to an object:
    *         {
    *           id: Device identifier
    *           name: Device name
    *         }
    */
   updateDevice(sessionTokenHex, id, name, options = {}) {
     let path = "/account/device";
 
     let creds = deriveHawkCredentials(sessionTokenHex, "sessionToken");
     let body = { id, name };
     if (options.pushCallback) {
       body.pushCallback = options.pushCallback;
     }
+    if (options.pushPublicKey && options.pushAuthKey) {
+      body.pushPublicKey = options.pushPublicKey;
+      body.pushAuthKey = options.pushAuthKey;
+    }
 
     return this._request(path, "POST", creds, body);
   },
 
   /**
    * Delete a device and its associated session token, signing the user
    * out of the server.
    *
--- a/services/fxaccounts/tests/xpcshell/test_accounts_device_registration.js
+++ b/services/fxaccounts/tests/xpcshell/test_accounts_device_registration.js
@@ -11,16 +11,19 @@ Cu.import("resource://gre/modules/FxAcco
 Cu.import("resource://gre/modules/Promise.jsm");
 Cu.import("resource://gre/modules/Log.jsm");
 
 initTestLogging("Trace");
 
 var log = Log.repository.getLogger("Services.FxAccounts.test");
 log.level = Log.Level.Debug;
 
+const BOGUS_PUBLICKEY = "BBXOKjUb84pzws1wionFpfCBjDuCh4-s_1b52WA46K5wYL2gCWEOmFKWn_NkS5nmJwTBuO8qxxdjAIDtNeklvQc";
+const BOGUS_AUTHKEY = "GSsIiaD2Mr83iPqwFNK4rw";
+
 Services.prefs.setCharPref("identity.fxaccounts.loglevel", "Trace");
 Log.repository.getLogger("FirefoxAccounts").level = Log.Level.Trace;
 
 Services.prefs.setCharPref("identity.fxaccounts.remote.oauth.uri", "https://example.com/v1");
 Services.prefs.setCharPref("identity.fxaccounts.oauth.client_id", "abc123");
 Services.prefs.setCharPref("identity.fxaccounts.remote.profile.uri", "http://example.com/v1");
 Services.prefs.setCharPref("identity.fxaccounts.settings.uri", "http://accounts.example.com/");
 
@@ -101,17 +104,21 @@ function MockFxAccounts(device = {}) {
     _getDeviceName() {
       return device.name || "mock device name";
     },
     fxAccountsClient: new MockFxAccountsClient(device),
     fxaPushService: {
       registerPushEndpoint() {
         return new Promise((resolve) => {
           resolve({
-            endpoint: "http://mochi.test:8888"
+            endpoint: "http://mochi.test:8888",
+            getKey: function(type) {
+              return ChromeUtils.base64URLDecode(
+                type === "auth" ? BOGUS_AUTHKEY : BOGUS_PUBLICKEY,
+                { padding: "ignore" });
           });
         });
       },
     },
     DEVICE_REGISTRATION_VERSION
   });
 }
 
@@ -157,16 +164,18 @@ add_task(function* test_updateDeviceRegi
   do_check_eq(spy.updateDevice.count, 0);
   do_check_eq(spy.getDeviceList.count, 0);
   do_check_eq(spy.registerDevice.count, 1);
   do_check_eq(spy.registerDevice.args[0].length, 4);
   do_check_eq(spy.registerDevice.args[0][0], credentials.sessionToken);
   do_check_eq(spy.registerDevice.args[0][1], deviceName);
   do_check_eq(spy.registerDevice.args[0][2], "desktop");
   do_check_eq(spy.registerDevice.args[0][3].pushCallback, "http://mochi.test:8888");
+  do_check_eq(spy.registerDevice.args[0][3].pushPublicKey, BOGUS_PUBLICKEY);
+  do_check_eq(spy.registerDevice.args[0][3].pushAuthKey, BOGUS_AUTHKEY);
 
   const state = fxa.internal.currentAccountState;
   const data = yield state.getUserAccountData();
 
   do_check_eq(data.deviceId, "newly-generated device id");
   do_check_eq(data.deviceRegistrationVersion, DEVICE_REGISTRATION_VERSION);
 });
 
@@ -208,16 +217,18 @@ add_task(function* test_updateDeviceRegi
   do_check_eq(spy.registerDevice.count, 0);
   do_check_eq(spy.getDeviceList.count, 0);
   do_check_eq(spy.updateDevice.count, 1);
   do_check_eq(spy.updateDevice.args[0].length, 4);
   do_check_eq(spy.updateDevice.args[0][0], credentials.sessionToken);
   do_check_eq(spy.updateDevice.args[0][1], credentials.deviceId);
   do_check_eq(spy.updateDevice.args[0][2], deviceName);
   do_check_eq(spy.updateDevice.args[0][3].pushCallback, "http://mochi.test:8888");
+  do_check_eq(spy.updateDevice.args[0][3].pushPublicKey, BOGUS_PUBLICKEY);
+  do_check_eq(spy.updateDevice.args[0][3].pushAuthKey, BOGUS_AUTHKEY);
 
   const state = fxa.internal.currentAccountState;
   const data = yield state.getUserAccountData();
 
   do_check_eq(data.deviceId, credentials.deviceId);
   do_check_eq(data.deviceRegistrationVersion, DEVICE_REGISTRATION_VERSION);
 });
 
@@ -265,16 +276,18 @@ add_task(function* test_updateDeviceRegi
   do_check_eq(spy.getDeviceList.count, 0);
   do_check_eq(spy.registerDevice.count, 0);
   do_check_eq(spy.updateDevice.count, 1);
   do_check_eq(spy.updateDevice.args[0].length, 4);
   do_check_eq(spy.updateDevice.args[0][0], credentials.sessionToken);
   do_check_eq(spy.updateDevice.args[0][1], credentials.deviceId);
   do_check_eq(spy.updateDevice.args[0][2], deviceName);
   do_check_eq(spy.updateDevice.args[0][3].pushCallback, "http://mochi.test:8888");
+  do_check_eq(spy.updateDevice.args[0][3].pushPublicKey, BOGUS_PUBLICKEY);
+  do_check_eq(spy.updateDevice.args[0][3].pushAuthKey, BOGUS_AUTHKEY);
 
 
   const state = fxa.internal.currentAccountState;
   const data = yield state.getUserAccountData();
 
   do_check_null(data.deviceId);
   do_check_eq(data.deviceRegistrationVersion, DEVICE_REGISTRATION_VERSION);
 });
@@ -328,16 +341,18 @@ add_task(function* test_updateDeviceRegi
   do_check_eq(result, credentials.deviceId);
   do_check_eq(spy.registerDevice.count, 0);
   do_check_eq(spy.updateDevice.count, 1);
   do_check_eq(spy.updateDevice.args[0].length, 4);
   do_check_eq(spy.updateDevice.args[0][0], credentials.sessionToken);
   do_check_eq(spy.updateDevice.args[0][1], credentials.deviceId);
   do_check_eq(spy.updateDevice.args[0][2], deviceName);
   do_check_eq(spy.updateDevice.args[0][3].pushCallback, "http://mochi.test:8888");
+  do_check_eq(spy.updateDevice.args[0][3].pushPublicKey, BOGUS_PUBLICKEY);
+  do_check_eq(spy.updateDevice.args[0][3].pushAuthKey, BOGUS_AUTHKEY);
   do_check_eq(spy.getDeviceList.count, 1);
   do_check_eq(spy.getDeviceList.args[0].length, 1);
   do_check_eq(spy.getDeviceList.args[0][0], credentials.sessionToken);
   do_check_true(spy.getDeviceList.time >= spy.updateDevice.time);
 
   const state = fxa.internal.currentAccountState;
   const data = yield state.getUserAccountData();