Bug 1339163 - Make TPS tests attempt to automatically verify fxa emails when using a restmail account r=markh
authorThom Chiovoloni <tchiovoloni@mozilla.com>
Fri, 17 Mar 2017 16:41:58 -0400
changeset 399190 81e0dfa05b1966e28d7c817368f46a7047142ae7
parent 399189 fc61bbc304d73ccefdb9462fd071bd4320fd805a
child 399191 afcb04dafd033d90eac3e896e097d21de96ca221
push id1490
push usermtabara@mozilla.com
push dateMon, 31 Jul 2017 14:08:16 +0000
treeherdermozilla-release@70e32e6bf15e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmarkh
bugs1339163
milestone55.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 1339163 - Make TPS tests attempt to automatically verify fxa emails when using a restmail account r=markh MozReview-Commit-ID: LwBrVSXFqyc
services/sync/tps/extensions/tps/resource/auth/fxaccounts.jsm
services/sync/tps/extensions/tps/resource/tps.jsm
--- a/services/sync/tps/extensions/tps/resource/auth/fxaccounts.jsm
+++ b/services/sync/tps/extensions/tps/resource/auth/fxaccounts.jsm
@@ -5,36 +5,142 @@
 "use strict";
 
 this.EXPORTED_SYMBOLS = [
   "Authentication",
 ];
 
 const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
 
+Cu.import("resource://gre/modules/Log.jsm");
+Cu.import("resource://gre/modules/Timer.jsm");
 Cu.import("resource://gre/modules/FxAccounts.jsm");
 Cu.import("resource://gre/modules/FxAccountsClient.jsm");
 Cu.import("resource://gre/modules/FxAccountsConfig.jsm");
 Cu.import("resource://services-common/async.js");
 Cu.import("resource://services-sync/main.js");
 Cu.import("resource://tps/logger.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Services", "resource://gre/modules/Services.jsm");
 
+Cu.importGlobalProperties(["fetch"]);
 
 /**
  * Helper object for Firefox Accounts authentication
  */
 var Authentication = {
 
   /**
    * Check if an user has been logged in
    */
   get isLoggedIn() {
     return !!this.getSignedInUser();
   },
 
+  _getRestmailUsername(user) {
+    const restmailSuffix = "@restmail.net";
+    if (user.toLowerCase().endsWith(restmailSuffix)) {
+      return user.slice(0, -restmailSuffix.length);
+    }
+    return null;
+  },
+
+  async shortWaitForVerification(ms) {
+    let userData = this.getSignedInUser();
+    await Promise.race([
+      fxAccounts.whenVerified(userData),
+      new Promise(resolve => {
+        setTimeout(() => {
+          Logger.logInfo(`Warning: no verification after ${ms}ms.`);
+          resolve();
+        }, ms);
+      })
+    ]);
+    userData = this.getSignedInUser();
+    return userData && userData.verified;
+  },
+
+  async _openVerificationPage(uri) {
+    let mainWindow = Services.wm.getMostRecentWindow("navigator:browser");
+    let newtab = mainWindow.getBrowser().addTab(uri);
+    let win = mainWindow.getBrowser().getBrowserForTab(newtab);
+    await new Promise(resolve => {
+      win.addEventListener("loadend", resolve, { once: true });
+    });
+    let didVerify = await this.shortWaitForVerification(10000);
+    mainWindow.getBrowser().removeTab(newtab);
+    return didVerify;
+  },
+
+  async _completeVerification(user) {
+    let username = this._getRestmailUsername(user);
+    if (!username) {
+      Logger.logInfo(`Username "${user}" isn't a restmail username so can't complete verification`);
+      return false;
+    }
+    Logger.logInfo("Fetching mail (from restmail) for user " + username);
+    let restmailURI = `https://www.restmail.net/mail/${encodeURIComponent(username)}`;
+    let triedAlready = new Set();
+    const tries = 10;
+    const normalWait = 2000;
+    for (let i = 0; i < tries; ++i) {
+      if (await this.shortWaitForVerification(normalWait)) {
+        return true;
+      }
+      let resp = await fetch(restmailURI);
+      let messages = await resp.json();
+      // Sort so that the most recent emails are first.
+      messages.sort((a, b) => new Date(b.receivedAt) - new Date(a.receivedAt));
+      for (let m of messages) {
+        // We look for a link that has a x-link that we haven't yet tried.
+        if (!m.headers["x-link"] || triedAlready.has(m.headers["x-link"])) {
+          continue;
+        }
+        let confirmLink = m.headers["x-link"];
+        triedAlready.add(confirmLink);
+        Logger.logInfo("Trying confirmation link " + confirmLink);
+        try {
+          if (await this._openVerificationPage(confirmLink)) {
+            return true;
+          }
+        } catch (e) {
+          Logger.logInfo("Warning: Failed to follow confirmation link: " + Log.exceptionStr(e));
+        }
+      }
+      if (i === 0) {
+        // first time through after failing we'll do this.
+        await fxAccounts.resendVerificationEmail();
+      }
+    }
+    // One last try.
+    return this.shortWaitForVerification(normalWait);
+  },
+
+  async deleteEmail(user) {
+    let username = this._getRestmailUsername(user);
+    if (!username) {
+      Logger.logInfo("Not a restmail username, can't delete");
+      return false;
+    }
+    Logger.logInfo("Deleting mail (from restmail) for user " + username);
+    let restmailURI = `https://www.restmail.net/mail/${encodeURIComponent(username)}`;
+    try {
+      // Clean up after ourselves.
+      let deleteResult = await fetch(restmailURI, { method: "DELETE" });
+      if (!deleteResult.ok) {
+        Logger.logInfo(`Warning: Got non-success status ${deleteResult.status} when deleting emails`);
+        return false;
+      }
+    } catch (e) {
+      Logger.logInfo("Warning: Failed to delete old emails: " + Log.exceptionStr(e));
+      return false;
+    }
+    return true;
+  },
+
   /**
    * Wrapper to retrieve the currently signed in user
    *
    * @returns Information about the currently signed in user
    */
   getSignedInUser: function getSignedInUser() {
     let cb = Async.makeSpinningCallback();
 
@@ -72,16 +178,18 @@ var Authentication = {
 
     // Required here since we don't go through the real login page
     Async.promiseSpinningly(FxAccountsConfig.ensureConfigured());
 
     let client = new FxAccountsClient();
     client.signIn(account["username"], account["password"], true).then(credentials => {
       return fxAccounts.setSignedInUser(credentials);
     }).then(() => {
+      return this._completeVerification(account["username"])
+    }).then(() => {
       cb(null, true);
     }, error => {
       cb(error, false);
     });
 
     try {
       cb.wait();
 
@@ -107,15 +215,16 @@ var Authentication = {
       if (!user) {
         throw new Error("Failed to get signed in user!");
       }
       let fxc = new FxAccountsClient();
       let { sessionToken, deviceId } = user;
       if (deviceId) {
         Logger.logInfo("Destroying device " + deviceId);
         Async.promiseSpinningly(fxAccounts.deleteDeviceRegistration(sessionToken, deviceId));
+        Async.promiseSpinningly(fxAccounts.signOut(true));
       } else {
         Logger.logError("No device found.");
         Async.promiseSpinningly(fxc.signOut(sessionToken, { service: "sync" }));
       }
     }
   }
 };
--- a/services/sync/tps/extensions/tps/resource/tps.jsm
+++ b/services/sync/tps/extensions/tps/resource/tps.jsm
@@ -582,16 +582,17 @@ var TPS = {
       if (Authentication.isLoggedIn) {
         // signout and wait for Sync to completely reset itself.
         Logger.logInfo("signing out");
         let waiter = this.createEventWaiter("weave:service:start-over:finish");
         Authentication.signOut();
         waiter();
         Logger.logInfo("signout complete");
       }
+      Authentication.deleteEmail(this.config.fx_account.username);
     } catch (e) {
       Logger.logError("Failed to sign out: " + Log.exceptionStr(e));
     }
   },
 
   /**
    * Use Sync's bookmark validation code to see if we've corrupted the tree.
    */