services/mobileid/MobileIdentityVerificationFlow.jsm
author Mike Hommey <mh+mozilla@glandium.org>
Sat, 04 Oct 2014 10:33:00 +0900
changeset 208801 d959a6081ceacde13d41b8a4ee192c912c85ef02
parent 187426 e3dc24f425734635f3f6d663d2339c4234027d86
child 217887 dce264e3c9f10e77e24c6450682ff417dd85a58a
permissions -rw-r--r--
Bug 1077151 - Always use expandlibs descriptors when they exist. r=mshal Currently, when there is both an expandlibs descriptor and an actual static library, expandlibs picks the static library. This has the side effect that if there are object files in the static library that aren't directly used, they're dropped when linking, even when they export symbols that would be exported in the final linked binary. In most cases in the code base, files are not dropped that way. The most notable counter-example is xpcomglue, where actually not dropping files leads to link failure because of missing symbols those files reference (yes, that would tend to say the glue is broken in some way). On the opposite side, there is mozglue, which does have both a descriptor and a static library (the latter being necessary for the SDK), and that linking as a static library drops files that shouldn't be dropped (like jemalloc). We're currently relying on -Wl,--whole-archive for those files not to be dropped, but that won't really be possible without much hassle in a world where mozglue dependencies live in moz.build land. Switching expandlibs to use descriptors when they exist, even when there is a static library (so, the opposite of the current behavior) allows to drop -Wl,--whole-archive and prepare for a better future. However, as mentioned, xpcomglue does still require to be linked through the static library, so we need to make it a static library only. To achieve that, we make NO_EXPAND_LIBS now actually mean no expandlibs and use that to build the various different xpcomglues.

/* 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/. */

"use strict";

this.EXPORTED_SYMBOLS = ["MobileIdentityVerificationFlow"];

const { utils: Cu, classes: Cc, interfaces: Ci } = Components;

Cu.import("resource://gre/modules/MobileIdentityCommon.jsm");
Cu.import("resource://gre/modules/Promise.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");

this.MobileIdentityVerificationFlow = function(aVerificationOptions,
                                               aUI,
                                               aClient,
                                               aVerifyStrategy,
                                               aCleanupStrategy) {
  this.verificationOptions = aVerificationOptions;
  this.ui = aUI;
  this.client = aClient;
  this.retries = VERIFICATIONCODE_RETRIES;
  this.verifyStrategy = aVerifyStrategy;
  this.cleanupStrategy = aCleanupStrategy;
};

MobileIdentityVerificationFlow.prototype = {

  doVerification: function() {
    log.debug("Start verification flow");
    return this.register()
    .then(
      (registerResult) => {
        log.debug("Register result ${}", registerResult);
        if (!registerResult || !registerResult.msisdnSessionToken) {
          return Promise.reject(ERROR_INTERNAL_UNEXPECTED);
        }
        this.sessionToken = registerResult.msisdnSessionToken;
        return this._doVerification();
      }
    )
  },

  _doVerification: function() {
    log.debug("_doVerification");
    // We save the timestamp of the start of the verification timeout to be
    // able to provide to the UI the remaining time on each retry.
    if (!this.timer) {
      log.debug("Creating verification code timer");
      this.timerCreation = Date.now();
      this.timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
      this.timer.initWithCallback(this.onVerificationCodeTimeout.bind(this),
                                  VERIFICATIONCODE_TIMEOUT,
                                  this.timer.TYPE_ONE_SHOT);
    }

    if (!this.verifyStrategy) {
      return Promise.reject(ERROR_INTERNAL_INVALID_VERIFICATION_FLOW);
    }

    this.verificationCodeDeferred = Promise.defer();

    this.verifyStrategy()
    .then(
      () => {
        // If the verification flow can be for an external phone number,
        // we need to ask the user for the verification code.
        // In that case we don't do a notification about the verification
        // process being done until the user enters the verification code
        // in the UI.
        if (this.verificationOptions.external) {
          let timeLeft = 0;
          if (this.timer) {
            timeLeft = this.timerCreation + VERIFICATIONCODE_TIMEOUT -
                       Date.now();
          }
          this.ui.verificationCodePrompt(this.retries,
                                         VERIFICATIONCODE_TIMEOUT / 1000,
                                         timeLeft / 1000)
          .then(
            (verificationCode) => {
              if (!verificationCode) {
                return this.verificationCodeDeferred.reject(
                  ERROR_INTERNAL_INVALID_PROMPT_RESULT);
              }
              // If the user got the verification code that means that the
              // introduced phone number didn't belong to any of the inserted
              // SIMs.
              this.ui.verify();
              this.verificationCodeDeferred.resolve(verificationCode);
            }
          );
        } else {
          this.ui.verify();
        }
      },
      (reason) => {
        this.verificationCodeDeferred.reject(reason);
      }
    );
    return this.verificationCodeDeferred.promise.then(
      this.onVerificationCode.bind(this)
    );
  },

  // When we receive a verification code from the UI, we check it against
  // the server. If the verification code is incorrect, we decrease the
  // number of retries left and allow the user to try again. If there is no
  // possible retry left, we notify about this error so the UI can allow the
  // user to request the resend of a new verification code.
  onVerificationCode: function(aVerificationCode) {
    log.debug("onVerificationCode " + aVerificationCode);
    if (!aVerificationCode) {
      this.ui.error(ERROR_INVALID_VERIFICATION_CODE);
      return this._doVerification();
    }

    // Before checking the verification code against the server we set the
    // "verifying" flag to queue timeout expiration events received before
    // the server request is completed. If the server request is positive
    // we will discard the timeout event, otherwise we will progress the
    // event to the UI to allow the user to retry.
    this.verifying = true;

    return this.verifyCode(aVerificationCode)
    .then(
      (result) => {
        if (!result) {
          return Promise.reject(INTERNAL_UNEXPECTED);
        }
        // The code was correct!
        // At this point the phone number is verified.
        // We return the given verification options with the session token
        // to be stored in the credentials store. With this data we will be
        // asking the server to give us a certificate to generate assertions.
        this.verificationOptions.sessionToken = this.sessionToken;
        this.verificationOptions.msisdn = result.msisdn ||
                                          this.verificationOptions.msisdn;
        return this.verificationOptions;
      },
      (error) => {
        log.error("Verification code error " + error);
        this.retries--;
        log.error("Retries left " + this.retries);
        if (!this.retries) {
          this.ui.error(ERROR_NO_RETRIES_LEFT);
          return Promise.reject(ERROR_NO_RETRIES_LEFT);
        }
        this.verifying = false;
        if (this.queuedTimeout) {
          this.onVerificationCodeTimeout();
        }
        return this._doVerification();
      }
    );
  },

  onVerificationCodeTimeout: function() {
    // It is possible that we get the timeout when we are checking a
    // verification code with the server. In that case, we queue the
    // timeout to be triggered after we receive the reply from the server
    // if needed.
    if (this.verifying) {
      this.queuedTimeout = true;
      return;
    }

    // When the verification process times out we do a clean up, reject
    // the corresponding promise and notify the UI about the timeout.
    if (this.verificationCodeDeferred) {
      this.verificationCodeDeferred.reject(ERROR_VERIFICATION_CODE_TIMEOUT);
    }
    this.ui.error(ERROR_VERIFICATION_CODE_TIMEOUT);
  },

  register: function() {
    return this.client.register();
  },

  verifyCode: function(aVerificationCode) {
    return this.client.verifyCode(this.sessionToken, aVerificationCode);
  },

  unregister: function() {
    return this.client.unregister(this.sessionToken);
  },

  cleanup: function(aUnregister = false) {
    log.debug("Verification flow cleanup");

    this.queuedTimeout = false;
    this.retries = VERIFICATIONCODE_RETRIES;

    if (this.timer) {
      this.timer.cancel();
      this.timer = null;
    }

    if (aUnregister) {
      this.unregister().
      then(
        () => {
          this.sessionToken = null;
        }
      );
    }

    if (this.cleanupStrategy) {
      this.cleanupStrategy();
    }
  }
};