/* 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 = ["FxAccountsConfig"];

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

Cu.import("resource://services-common/rest.js");
Cu.import("resource://gre/modules/FxAccountsCommon.js");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Task.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "fxAccounts",
                                  "resource://gre/modules/FxAccounts.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "EnsureFxAccountsWebChannel",
                                  "resource://gre/modules/FxAccountsWebChannel.jsm");

const CONFIG_PREFS = [
  "identity.fxaccounts.auth.uri",
  "identity.fxaccounts.remote.oauth.uri",
  "identity.fxaccounts.remote.profile.uri",
  "identity.sync.tokenserver.uri",
  "identity.fxaccounts.remote.webchannel.uri",
  "identity.fxaccounts.settings.uri",
  "identity.fxaccounts.remote.signup.uri",
  "identity.fxaccounts.remote.signin.uri",
  "identity.fxaccounts.remote.force_auth.uri",
];

this.FxAccountsConfig = {

  // Returns a promise that resolves with the URI of the remote UI flows.
  promiseAccountsSignUpURI: Task.async(function*() {
    yield this.ensureConfigured();
    let url = Services.urlFormatter.formatURLPref("identity.fxaccounts.remote.signup.uri");
    if (fxAccounts.requiresHttps() && !/^https:/.test(url)) { // Comment to un-break emacs js-mode highlighting
      throw new Error("Firefox Accounts server must use HTTPS");
    }
    return url;
  }),

  // Returns a promise that resolves with the URI of the remote UI flows.
  promiseAccountsSignInURI: Task.async(function*() {
    yield this.ensureConfigured();
    let url = Services.urlFormatter.formatURLPref("identity.fxaccounts.remote.signin.uri");
    if (fxAccounts.requiresHttps() && !/^https:/.test(url)) { // Comment to un-break emacs js-mode highlighting
      throw new Error("Firefox Accounts server must use HTTPS");
    }
    return url;
  }),

  resetConfigURLs() {
    let autoconfigURL = this.getAutoConfigURL();
    if (!autoconfigURL) {
      return;
    }
    // They have the autoconfig uri pref set, so we clear all the prefs that we
    // will have initialized, which will leave them pointing at production.
    for (let pref of CONFIG_PREFS) {
      Services.prefs.clearUserPref(pref);
    }
    // Reset the webchannel.
    EnsureFxAccountsWebChannel();
    if (!Services.prefs.prefHasUserValue("webchannel.allowObject.urlWhitelist")) {
      return;
    }
    let whitelistValue = Services.prefs.getCharPref("webchannel.allowObject.urlWhitelist");
    if (whitelistValue.startsWith(autoconfigURL + " ")) {
      whitelistValue = whitelistValue.slice(autoconfigURL.length + 1);
      // Check and see if the value will be the default, and just clear the pref if it would
      // to avoid it showing up as changed in about:config.
      let defaultWhitelist;
      try {
        defaultWhitelist = Services.prefs.getDefaultBranch("webchannel.allowObject.").getCharPref("urlWhitelist");
      } catch (e) {
        // No default value ...
      }

      if (defaultWhitelist === whitelistValue) {
        Services.prefs.clearUserPref("webchannel.allowObject.urlWhitelist");
      } else {
        Services.prefs.setCharPref("webchannel.allowObject.urlWhitelist", whitelistValue);
      }
    }
  },

  getAutoConfigURL() {
    let pref;
    try {
      pref = Services.prefs.getCharPref("identity.fxaccounts.autoconfig.uri");
    } catch (e) { /* no pref */ }
    if (!pref) {
      // no pref / empty pref means we don't bother here.
      return "";
    }
    let rootURL = Services.urlFormatter.formatURL(pref);
    if (rootURL.endsWith("/")) {
      rootURL.slice(0, -1);
    }
    return rootURL;
  },

  ensureConfigured: Task.async(function*() {
    let isSignedIn = !!(yield fxAccounts.getSignedInUser());
    if (!isSignedIn) {
      yield this.fetchConfigURLs();
    }
  }),

  // Read expected client configuration from the fxa auth server
  // (from `identity.fxaccounts.autoconfig.uri`/.well-known/fxa-client-configuration)
  // and replace all the relevant our prefs with the information found there.
  // This is only done before sign-in and sign-up, and even then only if the
  // `identity.fxaccounts.autoconfig.uri` preference is set.
  fetchConfigURLs: Task.async(function*() {
    let rootURL = this.getAutoConfigURL();
    if (!rootURL) {
      return;
    }
    let configURL = rootURL + "/.well-known/fxa-client-configuration";
    let jsonStr = yield new Promise((resolve, reject) => {
      let request = new RESTRequest(configURL);
      request.setHeader("Accept", "application/json");
      request.get(error => {
        if (error) {
          log.error(`Failed to get configuration object from "${configURL}"`, error);
          return reject(error);
        }
        if (!request.response.success) {
          log.error(`Received HTTP response code ${request.response.status} from configuration object request`);
          if (request.response && request.response.body) {
            log.debug("Got error response", request.response.body);
          }
          return reject(request.response.status);
        }
        resolve(request.response.body);
      });
    });

    log.debug("Got successful configuration response", jsonStr);
    try {
      // Update the prefs directly specified by the config.
      let config = JSON.parse(jsonStr)
      let authServerBase = config.auth_server_base_url;
      if (!authServerBase.endsWith("/v1")) {
        authServerBase += "/v1";
      }
      Services.prefs.setCharPref("identity.fxaccounts.auth.uri", authServerBase);
      Services.prefs.setCharPref("identity.fxaccounts.remote.oauth.uri", config.oauth_server_base_url + "/v1");
      Services.prefs.setCharPref("identity.fxaccounts.remote.profile.uri", config.profile_server_base_url + "/v1");
      Services.prefs.setCharPref("identity.sync.tokenserver.uri", config.sync_tokenserver_base_url + "/1.0/sync/1.5");
      // Update the prefs that are based off of the autoconfig url

      let contextParam = encodeURIComponent(
        Services.prefs.getCharPref("identity.fxaccounts.contextParam"));

      Services.prefs.setCharPref("identity.fxaccounts.remote.webchannel.uri", rootURL);
      Services.prefs.setCharPref("identity.fxaccounts.settings.uri", rootURL + "/settings?service=sync&context=" + contextParam);
      Services.prefs.setCharPref("identity.fxaccounts.remote.signup.uri", rootURL + "/signup?service=sync&context=" + contextParam);
      Services.prefs.setCharPref("identity.fxaccounts.remote.signin.uri", rootURL + "/signin?service=sync&context=" + contextParam);
      Services.prefs.setCharPref("identity.fxaccounts.remote.force_auth.uri", rootURL + "/force_auth?service=sync&context=" + contextParam);

      let whitelistValue = Services.prefs.getCharPref("webchannel.allowObject.urlWhitelist");
      if (!whitelistValue.includes(rootURL)) {
        whitelistValue = `${rootURL} ${whitelistValue}`;
        Services.prefs.setCharPref("webchannel.allowObject.urlWhitelist", whitelistValue);
      }
      // Ensure the webchannel is pointed at the correct uri
      EnsureFxAccountsWebChannel();
    } catch (e) {
      log.error("Failed to initialize configuration preferences from autoconfig object", e);
      throw e;
    }
  }),

};