--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1282,16 +1282,20 @@ pref("identity.fxaccounts.remote.signup.
pref("identity.fxaccounts.remote.force_auth.uri", "https://accounts.firefox.com/force_auth?service=sync&context=fx_desktop_v3");
// The remote content URL shown for signin in. Must use HTTPS.
pref("identity.fxaccounts.remote.signin.uri", "https://accounts.firefox.com/signin?service=sync&context=fx_desktop_v3");
// The remote content URL where FxAccountsWebChannel messages originate.
pref("identity.fxaccounts.remote.webchannel.uri", "https://accounts.firefox.com/");
+// The value of the context query parameter passed in some fxa requests when config
+// discovery is enabled.
+pref("identity.fxaccounts.contextParam", "fx_desktop_v3");
+
// The URL we take the user to when they opt to "manage" their Firefox Account.
// Note that this will always need to be in the same TLD as the
// "identity.fxaccounts.remote.signup.uri" pref.
pref("identity.fxaccounts.settings.uri", "https://accounts.firefox.com/settings?service=sync&context=fx_desktop_v3");
// The remote URL of the FxA Profile Server
pref("identity.fxaccounts.remote.profile.uri", "https://profile.accounts.firefox.com/v1");
--- a/browser/base/content/aboutaccounts/aboutaccounts.css
+++ b/browser/base/content/aboutaccounts/aboutaccounts.css
@@ -4,17 +4,17 @@ html, body {
#remote {
width: 100%;
height: 100%;
border: 0;
display: none;
}
-#networkError, #manage, #intro, #stage {
+#networkError, #manage, #intro, #stage, #configError {
display: none;
}
#oldsync {
background: none;
border: 0;
color: #0095dd;
}
--- a/browser/base/content/aboutaccounts/aboutaccounts.js
+++ b/browser/base/content/aboutaccounts/aboutaccounts.js
@@ -159,24 +159,24 @@ var wrapper = {
}
}
}
// Calling cancel() will raise some OnStateChange notifications by itself,
// so avoid doing that more than once
if (failure && aStatus != Components.results.NS_BINDING_ABORTED) {
aRequest.cancel(Components.results.NS_BINDING_ABORTED);
- setErrorPage();
+ setErrorPage("networkError");
}
},
onLocationChange: function(aWebProgress, aRequest, aLocation, aFlags) {
if (aRequest && aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_ERROR_PAGE) {
aRequest.cancel(Components.results.NS_BINDING_ABORTED);
- setErrorPage();
+ setErrorPage("networkError");
}
},
onProgressChange: function() {},
onStatusChange: function() {},
onSecurityChange: function() {},
},
@@ -289,28 +289,27 @@ var wrapper = {
break;
default:
log("Unexpected remote command received: " + evt.detail.command + ". Ignoring command.");
break;
}
},
injectData: function (type, content) {
- let authUrl;
- try {
- authUrl = fxAccounts.getAccountsSignUpURI();
- } catch (e) {
- error("Couldn't inject data: " + e.message);
- return;
- }
- let data = {
- type: type,
- content: content
- };
- this.iframe.contentWindow.postMessage(data, authUrl);
+ return fxAccounts.promiseAccountsSignUpURI().then(authUrl => {
+ let data = {
+ type: type,
+ content: content
+ };
+ this.iframe.contentWindow.postMessage(data, authUrl);
+ })
+ .catch(e => {
+ console.log("Failed to inject data", e);
+ setErrorPage("configError");
+ });
},
};
// Button onclick handlers
function handleOldSync() {
let chromeWin = window
.QueryInterface(Ci.nsIInterfaceRequestor)
@@ -339,80 +338,87 @@ function openPrefs() {
window.location = "about:preferences#sync";
}
function init() {
fxAccounts.getSignedInUser().then(user => {
// tests in particular might cause the window to start closing before
// getSignedInUser has returned.
if (window.closed) {
- return;
+ return Promise.resolve();
}
updateDisplayedEmail(user);
// Ideally we'd use new URL(document.URL).searchParams, but for about: URIs,
// searchParams is empty.
let urlParams = new URLSearchParams(document.URL.split("?")[1] || "");
let action = urlParams.get(ACTION_URL_PARAM);
urlParams.delete(ACTION_URL_PARAM);
switch (action) {
case "signin":
if (user) {
// asking to sign-in when already signed in just shows manage.
show("stage", "manage");
} else {
- show("remote");
- wrapper.init(fxAccounts.getAccountsSignInURI(), urlParams);
+ return fxAccounts.promiseAccountsSignInURI().then(url => {
+ show("remote");
+ wrapper.init(url, urlParams);
+ });
}
break;
case "signup":
if (user) {
// asking to sign-up when already signed in just shows manage.
show("stage", "manage");
} else {
- show("remote");
- wrapper.init(fxAccounts.getAccountsSignUpURI(), urlParams);
+ return fxAccounts.promiseAccountsSignUpURI().then(url => {
+ show("remote");
+ wrapper.init(url, urlParams);
+ });
}
break;
case "reauth":
// ideally we would only show this when we know the user is in a
// "must reauthenticate" state - but we don't.
// As the email address will be included in the URL returned from
// promiseAccountsForceSigninURI, just always show it.
- fxAccounts.promiseAccountsForceSigninURI().then(url => {
+ return fxAccounts.promiseAccountsForceSigninURI().then(url => {
show("remote");
wrapper.init(url, urlParams);
});
- break;
default:
// No action specified.
if (user) {
show("stage", "manage");
} else {
// Attempt a migration if enabled or show the introductory page
// otherwise.
- migrateToDevEdition(urlParams).then(migrated => {
+ return migrateToDevEdition(urlParams).then(migrated => {
if (!migrated) {
show("stage", "intro");
// load the remote frame in the background
- wrapper.init(fxAccounts.getAccountsSignUpURI(), urlParams);
+ return fxAccounts.promiseAccountsSignUpURI().then(uri =>
+ wrapper.init(uri, urlParams));
}
+ return Promise.resolve();
});
}
break;
}
+ return Promise.resolve();
}).catch(err => {
- error("Failed to get the signed in user: " + err);
+ console.log("Configuration or sign in error", err);
+ setErrorPage("configError");
});
}
-function setErrorPage() {
- show("stage", "networkError");
+function setErrorPage(errorType) {
+ show("stage", errorType);
}
// Causes the "top-level" element with |id| to be shown - all other top-level
// elements are hidden. Optionally, ensures that only 1 "second-level" element
// inside the top-level one is shown.
function show(id, childId) {
// top-level items are either <div> or <iframe>
let allTop = document.querySelectorAll("body > div, iframe");
@@ -465,17 +471,22 @@ function migrateToDevEdition(urlParams)
return fxAccounts.promiseAccountsForceSigninURI().then(url => {
show("remote");
wrapper.init(url, urlParams);
});
}).then(null, error => {
log("Failed to migrate FX Account: " + error);
show("stage", "intro");
// load the remote frame in the background
- wrapper.init(fxAccounts.getAccountsSignUpURI(), urlParams);
+ fxAccounts.promiseAccountsSignUpURI().then(uri => {
+ wrapper.init(uri, urlParams)
+ }).catch(e => {
+ console.log("Failed to load signup page", e);
+ setErrorPage("configError");
+ });
}).then(() => {
// Reset the pref after migration.
Services.prefs.setBoolPref("identity.fxaccounts.migrateToDevEdition", false);
return true;
}).then(null, err => {
Cu.reportError("Failed to reset the migrateToDevEdition pref: " + err);
return false;
});
--- a/browser/base/content/aboutaccounts/aboutaccounts.xhtml
+++ b/browser/base/content/aboutaccounts/aboutaccounts.xhtml
@@ -82,16 +82,29 @@
<div class="description">&aboutAccounts.noConnection.description;</div>
<div class="button-row">
<button id="buttonRetry" class="button" tabindex="3">&aboutAccounts.noConnection.retry;</button>
</div>
</section>
</div>
+ <div id="configError">
+ <header>
+ <h1>&aboutAccounts.badConfig.title;</h1>
+ </header>
+
+ <section>
+ <div class="graphic graphic-sync-intro"> </div>
+
+ <div class="description">&aboutAccounts.badConfig.description;</div>
+
+ </section>
+ </div>
+
</div>
<iframe mozframetype="content" id="remote" />
<script type="application/javascript;version=1.8"
src="chrome://browser/content/utilityOverlay.js"/>
<script type="text/javascript;version=1.8"
src="chrome://browser/content/aboutaccounts/aboutaccounts.js" />
--- a/browser/base/content/test/general/browser_aboutAccounts.js
+++ b/browser/base/content/test/general/browser_aboutAccounts.js
@@ -313,17 +313,17 @@ var gTests = [
let mm = tab.linkedBrowser.messageManager;
mm.sendAsyncMessage("test:load-with-mocked-profile-path", {
url: "about:accounts",
profilePath: mockDir.path,
});
let response = yield readyPromise;
// We are expecting the iframe to be on the "signup" URL
- let expected = fxAccounts.getAccountsSignUpURI();
+ let expected = yield fxAccounts.promiseAccountsSignUpURI();
is(response.data.url, expected);
// and expect no signed in user.
let userData = yield fxAccounts.getSignedInUser();
is(userData, null);
// The migration pref should have still been switched off.
is(Services.prefs.getBoolPref(pref), false, pref + " got the expected value");
yield OS.File.removeEmptyDir(mockDir.path);
--- a/browser/locales/en-US/chrome/browser/aboutAccounts.dtd
+++ b/browser/locales/en-US/chrome/browser/aboutAccounts.dtd
@@ -7,8 +7,10 @@
<!ENTITY aboutAccountsConfig.description "Sign in to sync your tabs, bookmarks, passwords & more.">
<!ENTITY aboutAccountsConfig.startButton.label "Get started">
<!ENTITY aboutAccountsConfig.useOldSync.label "Using an older version of Sync?">
<!ENTITY aboutAccountsConfig.syncPreferences.label "Sync preferences">
<!ENTITY aboutAccounts.noConnection.title "No connection">
<!ENTITY aboutAccounts.noConnection.description "You must be connected to the Internet to sign in.">
<!ENTITY aboutAccounts.noConnection.retry "Try again">
+<!ENTITY aboutAccounts.badConfig.title "Bad configuration">
+<!ENTITY aboutAccounts.badConfig.description "Unable to determine your Firefox Account server configuration. Please try again later.">
--- a/services/fxaccounts/FxAccounts.jsm
+++ b/services/fxaccounts/FxAccounts.jsm
@@ -5,27 +5,31 @@
this.EXPORTED_SYMBOLS = ["fxAccounts", "FxAccounts"];
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
Cu.import("resource://gre/modules/Log.jsm");
Cu.import("resource://gre/modules/Promise.jsm");
Cu.import("resource://services-common/utils.js");
+Cu.import("resource://services-common/rest.js");
Cu.import("resource://services-crypto/utils.js");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Timer.jsm");
Cu.import("resource://gre/modules/Task.jsm");
Cu.import("resource://gre/modules/FxAccountsStorage.jsm");
Cu.import("resource://gre/modules/FxAccountsCommon.js");
XPCOMUtils.defineLazyModuleGetter(this, "FxAccountsClient",
"resource://gre/modules/FxAccountsClient.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "FxAccountsConfig",
+ "resource://gre/modules/FxAccountsConfig.jsm");
+
XPCOMUtils.defineLazyModuleGetter(this, "jwcrypto",
"resource://gre/modules/identity/jwcrypto.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "FxAccountsOAuthGrantClient",
"resource://gre/modules/FxAccountsOAuthGrantClient.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "FxAccountsProfile",
"resource://gre/modules/FxAccountsProfile.jsm");
@@ -33,34 +37,35 @@ XPCOMUtils.defineLazyModuleGetter(this,
XPCOMUtils.defineLazyModuleGetter(this, "Utils",
"resource://services-sync/util.js");
// All properties exposed by the public FxAccounts API.
var publicProperties = [
"accountStatus",
"checkVerificationStatus",
"getAccountsClient",
- "getAccountsSignInURI",
- "getAccountsSignUpURI",
"getAssertion",
"getDeviceId",
"getKeys",
"getOAuthToken",
"getSignedInUser",
"getSignedInUserProfile",
"handleDeviceDisconnection",
"invalidateCertificate",
"loadAndPoll",
"localtimeOffsetMsec",
"notifyDevices",
"now",
"promiseAccountsChangeProfileURI",
"promiseAccountsForceSigninURI",
"promiseAccountsManageURI",
+ "promiseAccountsSignUpURI",
+ "promiseAccountsSignInURI",
"removeCachedOAuthToken",
+ "requiresHttps",
"resendVerificationEmail",
"resetCredentials",
"sessionStatus",
"setSignedInUser",
"signOut",
"updateDeviceRegistration",
"updateUserAccountData",
"whenVerified",
@@ -773,19 +778,24 @@ FxAccountsInternal.prototype = {
log.warn("Missing session token; skipping remote sign out");
}).catch(err => {
log.error("Error during remote sign out of Firefox Accounts", err);
}).then(() => {
return this._destroyAllOAuthTokens(tokensToRevoke);
}).catch(err => {
log.error("Error during destruction of oauth tokens during signout", err);
}).then(() => {
+ FxAccountsConfig.resetConfigURLs();
// just for testing - notifications are cheap when no observers.
this.notifyObservers("testhelper-fxa-signout-complete");
- });
+ })
+ } else {
+ // We want to do this either way -- but if we're signing out remotely we
+ // need to wait until we destroy the oauth tokens if we want that to succeed.
+ FxAccountsConfig.resetConfigURLs();
}
}).then(() => {
this.notifyObservers(ONLOGOUT_NOTIFICATION);
});
},
/**
* This function should be called in conjunction with a server-side
@@ -1223,76 +1233,67 @@ FxAccountsInternal.prototype = {
: this.VERIFICATION_POLL_TIMEOUT_SUBSEQUENT;
}
log.debug("polling with timeout = " + timeoutMs);
this.currentTimer = setTimeout(() => {
this.pollEmailStatus(currentState, sessionToken, "timer");
}, timeoutMs);
},
- _requireHttps: function() {
+ requiresHttps: function() {
let allowHttp = false;
try {
allowHttp = Services.prefs.getBoolPref("identity.fxaccounts.allowHttp");
} catch(e) {
// Pref doesn't exist
}
return allowHttp !== true;
},
- // Return the URI of the remote UI flows.
- getAccountsSignUpURI: function() {
- let url = Services.urlFormatter.formatURLPref("identity.fxaccounts.remote.signup.uri");
- if (this._requireHttps() && !/^https:/.test(url)) { // Comment to un-break emacs js-mode highlighting
- throw new Error("Firefox Accounts server must use HTTPS");
- }
- return url;
+ promiseAccountsSignUpURI() {
+ return FxAccountsConfig.promiseAccountsSignUpURI();
},
- // Return the URI of the remote UI flows.
- getAccountsSignInURI: function() {
- let url = Services.urlFormatter.formatURLPref("identity.fxaccounts.remote.signin.uri");
- if (this._requireHttps() && !/^https:/.test(url)) { // Comment to un-break emacs js-mode highlighting
- throw new Error("Firefox Accounts server must use HTTPS");
- }
- return url;
+ promiseAccountsSignInURI() {
+ return FxAccountsConfig.promiseAccountsSignInURI();
},
// Returns a promise that resolves with the URL to use to force a re-signin
// of the current account.
- promiseAccountsForceSigninURI: function() {
+ promiseAccountsForceSigninURI: Task.async(function *() {
+ yield FxAccountsConfig.ensureConfigured();
let url = Services.urlFormatter.formatURLPref("identity.fxaccounts.remote.force_auth.uri");
- if (this._requireHttps() && !/^https:/.test(url)) { // Comment to un-break emacs js-mode highlighting
+ if (this.requiresHttps() && !/^https:/.test(url)) { // Comment to un-break emacs js-mode highlighting
throw new Error("Firefox Accounts server must use HTTPS");
}
let currentState = this.currentAccountState;
// but we need to append the email address onto a query string.
return this.getSignedInUser().then(accountData => {
if (!accountData) {
return null;
}
let newQueryPortion = url.indexOf("?") == -1 ? "?" : "&";
newQueryPortion += "email=" + encodeURIComponent(accountData.email);
return url + newQueryPortion;
}).then(result => currentState.resolve(result));
- },
+ }),
// Returns a promise that resolves with the URL to use to change
// the current account's profile image.
// if settingToEdit is set, the profile page should hightlight that setting
// for the user to edit.
promiseAccountsChangeProfileURI: function(entrypoint, settingToEdit = null) {
let url = Services.urlFormatter.formatURLPref("identity.fxaccounts.settings.uri");
if (settingToEdit) {
url += (url.indexOf("?") == -1 ? "?" : "&") +
"setting=" + encodeURIComponent(settingToEdit);
}
- if (this._requireHttps() && !/^https:/.test(url)) { // Comment to un-break emacs js-mode highlighting
+ if (this.requiresHttps() && !/^https:/.test(url)) { // Comment to un-break emacs js-mode highlighting
throw new Error("Firefox Accounts server must use HTTPS");
}
let currentState = this.currentAccountState;
// but we need to append the email address onto a query string.
return this.getSignedInUser().then(accountData => {
if (!accountData) {
return null;
}
@@ -1305,17 +1306,17 @@ FxAccountsInternal.prototype = {
return url + newQueryPortion;
}).then(result => currentState.resolve(result));
},
// Returns a promise that resolves with the URL to use to manage the current
// user's FxA acct.
promiseAccountsManageURI: function(entrypoint) {
let url = Services.urlFormatter.formatURLPref("identity.fxaccounts.settings.uri");
- if (this._requireHttps() && !/^https:/.test(url)) { // Comment to un-break emacs js-mode highlighting
+ if (this.requiresHttps() && !/^https:/.test(url)) { // Comment to un-break emacs js-mode highlighting
throw new Error("Firefox Accounts server must use HTTPS");
}
let currentState = this.currentAccountState;
// but we need to append the uid and email address onto a query string
// (if the server has no matching uid it will offer to sign in with the
// email address)
return this.getSignedInUser().then(accountData => {
if (!accountData) {
--- a/services/fxaccounts/FxAccountsClient.jsm
+++ b/services/fxaccounts/FxAccountsClient.jsm
@@ -11,22 +11,22 @@ Cu.import("resource://gre/modules/Promis
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://services-common/utils.js");
Cu.import("resource://services-common/hawkclient.js");
Cu.import("resource://services-common/hawkrequest.js");
Cu.import("resource://services-crypto/utils.js");
Cu.import("resource://gre/modules/FxAccountsCommon.js");
Cu.import("resource://gre/modules/Credentials.jsm");
-const HOST = Services.prefs.getCharPref("identity.fxaccounts.auth.uri");
+const HOST_PREF = "identity.fxaccounts.auth.uri";
const SIGNIN = "/account/login";
const SIGNUP = "/account/create";
-this.FxAccountsClient = function(host = HOST) {
+this.FxAccountsClient = function(host = Services.prefs.getCharPref(HOST_PREF)) {
this.host = host;
// The FxA auth server expects requests to certain endpoints to be authorized
// using Hawk.
this.hawk = new HawkClient(host);
this.hawk.observerPrefix = "FxA:hawk";
// Manage server backoff state. C.f.
new file mode 100644
--- /dev/null
+++ b/services/fxaccounts/FxAccountsConfig.jsm
@@ -0,0 +1,175 @@
+/* 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)
+ Services.prefs.setCharPref("identity.fxaccounts.auth.uri", config.auth_server_base_url);
+ 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;
+ }
+ }),
+
+};
--- a/services/fxaccounts/FxAccountsWebChannel.jsm
+++ b/services/fxaccounts/FxAccountsWebChannel.jsm
@@ -445,19 +445,23 @@ this.FxAccountsWebChannelHelpers.prototy
var singleton;
// The entry-point for this module, which ensures only one of our channels is
// ever created - we require this because the WebChannel is global in scope
// (eg, it uses the observer service to tell interested parties of interesting
// things) and allowing multiple channels would cause such notifications to be
// sent multiple times.
this.EnsureFxAccountsWebChannel = function() {
+ let contentUri = Services.urlFormatter.formatURLPref("identity.fxaccounts.remote.webchannel.uri");
+ if (singleton && singleton._contentUri !== contentUri) {
+ singleton.tearDown();
+ singleton = null;
+ }
if (!singleton) {
try {
- let contentUri = Services.urlFormatter.formatURLPref("identity.fxaccounts.remote.webchannel.uri");
if (contentUri) {
// The FxAccountsWebChannel listens for events and updates
// the state machine accordingly.
singleton = new this.FxAccountsWebChannel({
content_uri: contentUri,
channel_id: WEBCHANNEL_ID,
});
} else {
--- a/services/fxaccounts/moz.build
+++ b/services/fxaccounts/moz.build
@@ -15,16 +15,17 @@ EXTRA_COMPONENTS += [
'FxAccountsPush.js',
]
EXTRA_JS_MODULES += [
'Credentials.jsm',
'FxAccounts.jsm',
'FxAccountsClient.jsm',
'FxAccountsCommon.js',
+ 'FxAccountsConfig.jsm',
'FxAccountsOAuthClient.jsm',
'FxAccountsOAuthGrantClient.jsm',
'FxAccountsProfile.jsm',
'FxAccountsProfileClient.jsm',
'FxAccountsPush.js',
'FxAccountsStorage.jsm',
'FxAccountsWebChannel.jsm',
]
--- a/services/fxaccounts/tests/xpcshell/test_accounts.js
+++ b/services/fxaccounts/tests/xpcshell/test_accounts.js
@@ -187,42 +187,36 @@ function MakeFxAccounts(internal = {}) {
internal._signOutServer = () => Promise.resolve();
}
if (!internal._registerOrUpdateDevice) {
internal._registerOrUpdateDevice = () => Promise.resolve();
}
return new FxAccounts(internal);
}
-add_test(function test_non_https_remote_server_uri_with_requireHttps_false() {
+add_task(function* test_non_https_remote_server_uri_with_requireHttps_false() {
Services.prefs.setBoolPref(
"identity.fxaccounts.allowHttp",
true);
Services.prefs.setCharPref(
"identity.fxaccounts.remote.signup.uri",
"http://example.com/browser/browser/base/content/test/general/accounts_testRemoteCommands.html");
- do_check_eq(fxAccounts.getAccountsSignUpURI(),
+ do_check_eq(yield fxAccounts.promiseAccountsSignUpURI(),
"http://example.com/browser/browser/base/content/test/general/accounts_testRemoteCommands.html");
Services.prefs.clearUserPref("identity.fxaccounts.remote.signup.uri");
Services.prefs.clearUserPref("identity.fxaccounts.allowHttp");
- run_next_test();
});
-add_test(function test_non_https_remote_server_uri() {
+add_task(function* test_non_https_remote_server_uri() {
Services.prefs.setCharPref(
"identity.fxaccounts.remote.signup.uri",
"http://example.com/browser/browser/base/content/test/general/accounts_testRemoteCommands.html");
- do_check_throws_message(function () {
- fxAccounts.getAccountsSignUpURI();
- }, "Firefox Accounts server must use HTTPS");
-
+ rejects(fxAccounts.promiseAccountsSignUpURI(), null, "Firefox Accounts server must use HTTPS");
Services.prefs.clearUserPref("identity.fxaccounts.remote.signup.uri");
-
- run_next_test();
});
add_task(function* test_get_signed_in_user_initially_unset() {
_("Check getSignedInUser initially and after signout reports no user");
let account = MakeFxAccounts();
let credentials = {
email: "foo@example.com",
uid: "1234@lcip.org",
--- a/services/sync/tps/extensions/tps/resource/auth/fxaccounts.jsm
+++ b/services/sync/tps/extensions/tps/resource/auth/fxaccounts.jsm
@@ -7,16 +7,17 @@
this.EXPORTED_SYMBOLS = [
"Authentication",
];
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
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");
/**
* Helper object for Firefox Accounts authentication
*/
@@ -64,16 +65,19 @@ var Authentication = {
signIn: function signIn(account) {
let cb = Async.makeSpinningCallback();
Logger.AssertTrue(account["username"], "Username has been found");
Logger.AssertTrue(account["password"], "Password has been found");
Logger.logInfo("Login user: " + account["username"]);
+ // 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(() => {
cb(null, true);
}, error => {
cb(error, false);
});