mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-26 14:22:01 +00:00
321 lines
9.6 KiB
JavaScript
321 lines
9.6 KiB
JavaScript
/* 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 = ["FirefoxAccounts"];
|
|
|
|
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
|
|
|
|
Cu.import("resource://gre/modules/Log.jsm");
|
|
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
|
Cu.import("resource://gre/modules/Services.jsm");
|
|
Cu.import("resource://gre/modules/identity/LogUtils.jsm");
|
|
|
|
XPCOMUtils.defineLazyModuleGetter(this, "objectCopy",
|
|
"resource://gre/modules/identity/IdentityUtils.jsm");
|
|
|
|
XPCOMUtils.defineLazyModuleGetter(this, "makeMessageObject",
|
|
"resource://gre/modules/identity/IdentityUtils.jsm");
|
|
|
|
// loglevel preference should be one of: "FATAL", "ERROR", "WARN", "INFO",
|
|
// "CONFIG", "DEBUG", "TRACE" or "ALL". We will be logging error messages by
|
|
// default.
|
|
const PREF_LOG_LEVEL = "identity.fxaccounts.loglevel";
|
|
try {
|
|
this.LOG_LEVEL =
|
|
Services.prefs.getPrefType(PREF_LOG_LEVEL) == Ci.nsIPrefBranch.PREF_STRING
|
|
&& Services.prefs.getCharPref(PREF_LOG_LEVEL);
|
|
} catch (e) {
|
|
this.LOG_LEVEL = Log.Level.Error;
|
|
}
|
|
|
|
var log = Log.repository.getLogger("Identity.FxAccounts");
|
|
log.level = LOG_LEVEL;
|
|
log.addAppender(new Log.ConsoleAppender(new Log.BasicFormatter()));
|
|
|
|
#ifdef MOZ_B2G
|
|
XPCOMUtils.defineLazyModuleGetter(this, "FxAccountsManager",
|
|
"resource://gre/modules/FxAccountsManager.jsm",
|
|
"FxAccountsManager");
|
|
Cu.import("resource://gre/modules/FxAccountsCommon.js");
|
|
#else
|
|
log.warn("The FxAccountsManager is only functional in B2G at this time.");
|
|
var FxAccountsManager = null;
|
|
var ONVERIFIED_NOTIFICATION = null;
|
|
var ONLOGIN_NOTIFICATION = null;
|
|
var ONLOGOUT_NOTIFICATION = null;
|
|
#endif
|
|
|
|
function FxAccountsService() {
|
|
Services.obs.addObserver(this, "quit-application-granted", false);
|
|
if (ONVERIFIED_NOTIFICATION) {
|
|
Services.obs.addObserver(this, ONVERIFIED_NOTIFICATION, false);
|
|
Services.obs.addObserver(this, ONLOGIN_NOTIFICATION, false);
|
|
Services.obs.addObserver(this, ONLOGOUT_NOTIFICATION, false);
|
|
}
|
|
|
|
// Maintain interface parity with Identity.jsm and MinimalIdentity.jsm
|
|
this.RP = this;
|
|
|
|
this._rpFlows = new Map();
|
|
|
|
// Enable us to mock FxAccountsManager service in testing
|
|
this.fxAccountsManager = FxAccountsManager;
|
|
}
|
|
|
|
FxAccountsService.prototype = {
|
|
QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports, Ci.nsIObserver]),
|
|
|
|
observe: function observe(aSubject, aTopic, aData) {
|
|
switch (aTopic) {
|
|
case null:
|
|
// Guard against matching null ON*_NOTIFICATION
|
|
break;
|
|
case ONVERIFIED_NOTIFICATION:
|
|
log.debug("Received " + ONVERIFIED_NOTIFICATION + "; firing request()s");
|
|
for (let [rpId,] of this._rpFlows) {
|
|
this.request(rpId);
|
|
}
|
|
break;
|
|
case ONLOGIN_NOTIFICATION:
|
|
log.debug("Received " + ONLOGIN_NOTIFICATION + "; doLogin()s fired");
|
|
for (let [rpId,] of this._rpFlows) {
|
|
this.request(rpId);
|
|
}
|
|
break;
|
|
case ONLOGOUT_NOTIFICATION:
|
|
log.debug("Received " + ONLOGOUT_NOTIFICATION + "; doLogout()s fired");
|
|
for (let [rpId,] of this._rpFlows) {
|
|
this.doLogout(rpId);
|
|
}
|
|
break;
|
|
case "quit-application-granted":
|
|
Services.obs.removeObserver(this, "quit-application-granted");
|
|
if (ONVERIFIED_NOTIFICATION) {
|
|
Services.obs.removeObserver(this, ONVERIFIED_NOTIFICATION);
|
|
Services.obs.removeObserver(this, ONLOGIN_NOTIFICATION);
|
|
Services.obs.removeObserver(this, ONLOGOUT_NOTIFICATION);
|
|
}
|
|
break;
|
|
}
|
|
},
|
|
|
|
cleanupRPRequest: function(aRp) {
|
|
aRp.pendingRequest = false;
|
|
this._rpFlows.set(aRp.id, aRp);
|
|
},
|
|
|
|
/**
|
|
* Register a listener for a given windowID as a result of a call to
|
|
* navigator.id.watch().
|
|
*
|
|
* @param aRPCaller
|
|
* (Object) an object that represents the caller document, and
|
|
* is expected to have properties:
|
|
* - id (unique, e.g. uuid)
|
|
* - origin (string)
|
|
*
|
|
* and a bunch of callbacks
|
|
* - doReady()
|
|
* - doLogin()
|
|
* - doLogout()
|
|
* - doError()
|
|
* - doCancel()
|
|
*
|
|
*/
|
|
watch: function watch(aRpCaller) {
|
|
this._rpFlows.set(aRpCaller.id, aRpCaller);
|
|
log.debug("watch: " + aRpCaller.id);
|
|
log.debug("Current rp flows: " + this._rpFlows.size);
|
|
|
|
// Log the user in, if possible, and then call ready().
|
|
let runnable = {
|
|
run: () => {
|
|
this.fxAccountsManager.getAssertion(aRpCaller.audience,
|
|
aRpCaller.principal,
|
|
{ silent:true }).then(
|
|
data => {
|
|
if (data) {
|
|
this.doLogin(aRpCaller.id, data);
|
|
} else {
|
|
this.doLogout(aRpCaller.id);
|
|
}
|
|
this.doReady(aRpCaller.id);
|
|
},
|
|
error => {
|
|
log.error("get silent assertion failed: " + JSON.stringify(error));
|
|
this.doError(aRpCaller.id, error);
|
|
}
|
|
);
|
|
}
|
|
};
|
|
Services.tm.currentThread.dispatch(runnable,
|
|
Ci.nsIThread.DISPATCH_NORMAL);
|
|
},
|
|
|
|
/**
|
|
* Delete the flow when the screen is unloaded
|
|
*/
|
|
unwatch: function(aRpCallerId, aTargetMM) {
|
|
log.debug("unwatching: " + aRpCallerId);
|
|
this._rpFlows.delete(aRpCallerId);
|
|
},
|
|
|
|
/**
|
|
* Initiate a login with user interaction as a result of a call to
|
|
* navigator.id.request().
|
|
*
|
|
* @param aRPId
|
|
* (integer) the id of the doc object obtained in .watch()
|
|
*
|
|
* @param aOptions
|
|
* (Object) options including privacyPolicy, termsOfService
|
|
*/
|
|
request: function request(aRPId, aOptions) {
|
|
aOptions = aOptions || {};
|
|
let rp = this._rpFlows.get(aRPId);
|
|
if (!rp) {
|
|
log.error("request() called before watch()");
|
|
return;
|
|
}
|
|
|
|
// We check if we already have a pending request for this RP and in that
|
|
// case we just bail out. We don't want duplicated onlogin or oncancel
|
|
// events.
|
|
if (rp.pendingRequest) {
|
|
log.debug("request() already called");
|
|
return;
|
|
}
|
|
|
|
// Otherwise, we set the RP flow with the pending request flag.
|
|
rp.pendingRequest = true;
|
|
this._rpFlows.set(rp.id, rp);
|
|
|
|
let options = makeMessageObject(rp);
|
|
objectCopy(aOptions, options);
|
|
|
|
log.debug("get assertion for " + rp.audience);
|
|
|
|
this.fxAccountsManager.getAssertion(rp.audience, rp.principal, options)
|
|
.then(
|
|
data => {
|
|
log.debug("got assertion for " + rp.audience + ": " + data);
|
|
this.doLogin(aRPId, data);
|
|
},
|
|
error => {
|
|
log.debug("get assertion failed: " + JSON.stringify(error));
|
|
// Cancellation is passed through an error channel; here we reroute.
|
|
if ((error.error && (error.error.details == "DIALOG_CLOSED_BY_USER")) ||
|
|
(error.details == "DIALOG_CLOSED_BY_USER")) {
|
|
return this.doCancel(aRPId);
|
|
}
|
|
this.doError(aRPId, error);
|
|
}
|
|
)
|
|
.then(
|
|
() => {
|
|
this.cleanupRPRequest(rp);
|
|
}
|
|
)
|
|
.catch(
|
|
() => {
|
|
this.cleanupRPRequest(rp);
|
|
}
|
|
);
|
|
},
|
|
|
|
/**
|
|
* Invoked when a user wishes to logout of a site (for instance, when clicking
|
|
* on an in-content logout button).
|
|
*
|
|
* @param aRpCallerId
|
|
* (integer) the id of the doc object obtained in .watch()
|
|
*
|
|
*/
|
|
logout: function logout(aRpCallerId) {
|
|
// XXX Bug 945363 - Resolve the SSO story for FXA and implement
|
|
// logout accordingly.
|
|
//
|
|
// For now, it makes no sense to logout from a specific RP in
|
|
// Firefox Accounts, so just directly call the logout callback.
|
|
if (!this._rpFlows.has(aRpCallerId)) {
|
|
log.error("logout() called before watch()");
|
|
return;
|
|
}
|
|
|
|
// Call logout() on the next tick
|
|
let runnable = {
|
|
run: () => {
|
|
this.fxAccountsManager.signOut().then(() => {
|
|
this.doLogout(aRpCallerId);
|
|
});
|
|
}
|
|
};
|
|
Services.tm.currentThread.dispatch(runnable,
|
|
Ci.nsIThread.DISPATCH_NORMAL);
|
|
},
|
|
|
|
childProcessShutdown: function childProcessShutdown(messageManager) {
|
|
for (let [key,] of this._rpFlows) {
|
|
if (this._rpFlows.get(key)._mm === messageManager) {
|
|
this._rpFlows.delete(key);
|
|
}
|
|
}
|
|
},
|
|
|
|
doLogin: function doLogin(aRpCallerId, aAssertion) {
|
|
let rp = this._rpFlows.get(aRpCallerId);
|
|
if (!rp) {
|
|
log.warn("doLogin found no rp to go with callerId " + aRpCallerId);
|
|
return;
|
|
}
|
|
|
|
rp.doLogin(aAssertion);
|
|
},
|
|
|
|
doLogout: function doLogout(aRpCallerId) {
|
|
let rp = this._rpFlows.get(aRpCallerId);
|
|
if (!rp) {
|
|
log.warn("doLogout found no rp to go with callerId " + aRpCallerId);
|
|
return;
|
|
}
|
|
|
|
rp.doLogout();
|
|
},
|
|
|
|
doReady: function doReady(aRpCallerId) {
|
|
let rp = this._rpFlows.get(aRpCallerId);
|
|
if (!rp) {
|
|
log.warn("doReady found no rp to go with callerId " + aRpCallerId);
|
|
return;
|
|
}
|
|
|
|
rp.doReady();
|
|
},
|
|
|
|
doCancel: function doCancel(aRpCallerId) {
|
|
let rp = this._rpFlows.get(aRpCallerId);
|
|
if (!rp) {
|
|
log.warn("doCancel found no rp to go with callerId " + aRpCallerId);
|
|
return;
|
|
}
|
|
|
|
rp.doCancel();
|
|
},
|
|
|
|
doError: function doError(aRpCallerId, aError) {
|
|
let rp = this._rpFlows.get(aRpCallerId);
|
|
if (!rp) {
|
|
log.warn("doError found no rp to go with callerId " + aRpCallerId);
|
|
return;
|
|
}
|
|
|
|
rp.doError(aError);
|
|
}
|
|
};
|
|
|
|
this.FirefoxAccounts = new FxAccountsService();
|
|
|