Bug 1339163 - Make TPS tests attempt to automatically verify fxa emails when using a restmail account r?markh draft
authorThom Chiovoloni <tchiovoloni@mozilla.com>
Fri, 17 Mar 2017 16:41:58 -0400
changeset 502479 a9065e0014df7ac3420a930227698e9b2c90f3e5
parent 500859 62f80de05fa2039bc9be0e9cfed70d420e49ffbb
child 550169 97cad84b918d93d5f4ae838cf37250453158bfeb
push id50292
push userbmo:tchiovoloni@mozilla.com
push dateTue, 21 Mar 2017 20:53:03 +0000
reviewersmarkh
bugs1339163
milestone55.0a1
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.
    */