mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 1028398 - FxA will silently provide user's email to privileged apps in 2.0. Part 2: Trigger forceAuth when new privileged app tries to get a FxA assertion. r=jedp"
This commit is contained in:
parent
4c2613d1f3
commit
4643d65bf1
@ -103,7 +103,7 @@ IDPAuthenticationContext.prototype = {
|
||||
}
|
||||
};
|
||||
|
||||
function RPWatchContext(aOptions, aTargetMM) {
|
||||
function RPWatchContext(aOptions, aTargetMM, aPrincipal) {
|
||||
objectCopy(aOptions, this);
|
||||
|
||||
// id and origin are required
|
||||
@ -111,6 +111,8 @@ function RPWatchContext(aOptions, aTargetMM) {
|
||||
throw new Error("id and origin are required for RP watch context");
|
||||
}
|
||||
|
||||
this.principal = aPrincipal;
|
||||
|
||||
// default for no loggedInUser is undefined, not null
|
||||
this.loggedInUser = aOptions.loggedInUser;
|
||||
|
||||
@ -187,8 +189,8 @@ this.DOMIdentity = {
|
||||
/*
|
||||
* Create a new RPWatchContext, and update the context maps.
|
||||
*/
|
||||
newContext: function(message, targetMM) {
|
||||
let context = new RPWatchContext(message, targetMM);
|
||||
newContext: function(message, targetMM, principal) {
|
||||
let context = new RPWatchContext(message, targetMM, principal);
|
||||
this._serviceContexts.set(message.id, context);
|
||||
this._mmContexts.set(targetMM, message.id);
|
||||
return context;
|
||||
@ -276,16 +278,16 @@ this.DOMIdentity = {
|
||||
switch (aMessage.name) {
|
||||
// RP
|
||||
case "Identity:RP:Watch":
|
||||
this._watch(msg, targetMM);
|
||||
this._watch(msg, targetMM, aMessage.principal);
|
||||
break;
|
||||
case "Identity:RP:Unwatch":
|
||||
this._unwatch(msg, targetMM);
|
||||
break;
|
||||
case "Identity:RP:Request":
|
||||
this._request(msg, targetMM);
|
||||
this._request(msg);
|
||||
break;
|
||||
case "Identity:RP:Logout":
|
||||
this._logout(msg, targetMM);
|
||||
this._logout(msg);
|
||||
break;
|
||||
// IDP
|
||||
case "Identity:IDP:BeginProvisioning":
|
||||
@ -359,9 +361,9 @@ this.DOMIdentity = {
|
||||
ppmm = null;
|
||||
},
|
||||
|
||||
_watch: function DOMIdentity__watch(message, targetMM) {
|
||||
log("DOMIdentity__watch: " + message.id);
|
||||
let context = this.newContext(message, targetMM);
|
||||
_watch: function DOMIdentity__watch(message, targetMM, principal) {
|
||||
log("DOMIdentity__watch: " + message.id + " - " + principal);
|
||||
let context = this.newContext(message, targetMM, principal);
|
||||
this.getService(message).RP.watch(context);
|
||||
},
|
||||
|
||||
|
@ -60,6 +60,8 @@ XPCOMUtils.defineLazyGetter(this, 'logPII', function() {
|
||||
}
|
||||
});
|
||||
|
||||
this.FXACCOUNTS_PERMISSION = "firefox-accounts";
|
||||
|
||||
this.DATA_FORMAT_VERSION = 1;
|
||||
this.DEFAULT_STORAGE_FILENAME = "signedInUser.json";
|
||||
|
||||
@ -141,6 +143,7 @@ this.ERROR_NO_TOKEN_SESSION = "NO_TOKEN_SESSION";
|
||||
this.ERROR_NO_SILENT_REFRESH_AUTH = "NO_SILENT_REFRESH_AUTH";
|
||||
this.ERROR_NOT_VALID_JSON_BODY = "NOT_VALID_JSON_BODY";
|
||||
this.ERROR_OFFLINE = "OFFLINE";
|
||||
this.ERROR_PERMISSION_DENIED = "PERMISSION_DENIED";
|
||||
this.ERROR_REQUEST_BODY_TOO_LARGE = "REQUEST_BODY_TOO_LARGE";
|
||||
this.ERROR_SERVER_ERROR = "SERVER_ERROR";
|
||||
this.ERROR_TOO_MANY_CLIENT_REQUESTS = "TOO_MANY_CLIENT_REQUESTS";
|
||||
|
@ -21,6 +21,10 @@ Cu.import("resource://gre/modules/FxAccounts.jsm");
|
||||
Cu.import("resource://gre/modules/Promise.jsm");
|
||||
Cu.import("resource://gre/modules/FxAccountsCommon.js");
|
||||
|
||||
XPCOMUtils.defineLazyServiceGetter(this, "permissionManager",
|
||||
"@mozilla.org/permissionmanager;1",
|
||||
"nsIPermissionManager");
|
||||
|
||||
this.FxAccountsManager = {
|
||||
|
||||
init: function() {
|
||||
@ -175,7 +179,7 @@ this.FxAccountsManager = {
|
||||
* FxAccountsClient.signCertificate()
|
||||
* See the latter method for possible (error code, errno) pairs.
|
||||
*/
|
||||
_handleGetAssertionError: function(reason, aAudience) {
|
||||
_handleGetAssertionError: function(reason, aAudience, aPrincipal) {
|
||||
let errno = (reason ? reason.errno : NaN) || NaN;
|
||||
// If the previously valid email/password pair is no longer valid ...
|
||||
if (errno == ERRNO_INVALID_AUTH_TOKEN) {
|
||||
@ -186,34 +190,42 @@ this.FxAccountsManager = {
|
||||
if (exists) {
|
||||
return this.getAccount().then(
|
||||
(user) => {
|
||||
return this._refreshAuthentication(aAudience, user.email, true);
|
||||
}
|
||||
);
|
||||
// ... otherwise, the account was deleted, so ask for Sign In/Up
|
||||
} else {
|
||||
return this._localSignOut().then(
|
||||
() => {
|
||||
return this._uiRequest(UI_REQUEST_SIGN_IN_FLOW, aAudience);
|
||||
},
|
||||
(reason) => { // reject primary problem, not signout failure
|
||||
log.error("Signing out in response to server error threw: " + reason);
|
||||
return this._error(reason);
|
||||
return this._refreshAuthentication(aAudience, user.email,
|
||||
aPrincipal,
|
||||
true /* logoutOnFailure */);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// Otherwise, the account was deleted, so ask for Sign In/Up
|
||||
return this._localSignOut().then(
|
||||
() => {
|
||||
return this._uiRequest(UI_REQUEST_SIGN_IN_FLOW, aAudience,
|
||||
aPrincipal);
|
||||
},
|
||||
(reason) => {
|
||||
// reject primary problem, not signout failure
|
||||
log.error("Signing out in response to server error threw: " +
|
||||
reason);
|
||||
return this._error(reason);
|
||||
}
|
||||
);
|
||||
}
|
||||
return Promise.reject(reason);
|
||||
},
|
||||
|
||||
_getAssertion: function(aAudience) {
|
||||
_getAssertion: function(aAudience, aPrincipal) {
|
||||
return this._fxAccounts.getAssertion(aAudience).then(
|
||||
(result) => {
|
||||
if (aPrincipal) {
|
||||
this._addPermission(aPrincipal);
|
||||
}
|
||||
return result;
|
||||
},
|
||||
(reason) => {
|
||||
return this._handleGetAssertionError(reason, aAudience);
|
||||
return this._handleGetAssertionError(reason, aAudience, aPrincipal);
|
||||
}
|
||||
);
|
||||
},
|
||||
@ -228,10 +240,11 @@ this.FxAccountsManager = {
|
||||
* 2) The person typing can't prove knowledge of the password used
|
||||
* to log in. Failure should do nothing.
|
||||
*/
|
||||
_refreshAuthentication: function(aAudience, aEmail, logoutOnFailure=false) {
|
||||
_refreshAuthentication: function(aAudience, aEmail, aPrincipal,
|
||||
logoutOnFailure=false) {
|
||||
this._refreshing = true;
|
||||
return this._uiRequest(UI_REQUEST_REFRESH_AUTH,
|
||||
aAudience, aEmail).then(
|
||||
aAudience, aPrincipal, aEmail).then(
|
||||
(assertion) => {
|
||||
this._refreshing = false;
|
||||
return assertion;
|
||||
@ -293,7 +306,7 @@ this.FxAccountsManager = {
|
||||
);
|
||||
},
|
||||
|
||||
_uiRequest: function(aRequest, aAudience, aParams) {
|
||||
_uiRequest: function(aRequest, aAudience, aPrincipal, aParams) {
|
||||
let ui = Cc["@mozilla.org/fxaccounts/fxaccounts-ui-glue;1"]
|
||||
.createInstance(Ci.nsIFxAccountsUIGlue);
|
||||
if (!ui[aRequest]) {
|
||||
@ -309,7 +322,7 @@ this.FxAccountsManager = {
|
||||
// Even if we get a successful result from the UI, the account will
|
||||
// most likely be unverified, so we cannot get an assertion.
|
||||
if (result && result.verified) {
|
||||
return this._getAssertion(aAudience);
|
||||
return this._getAssertion(aAudience, aPrincipal);
|
||||
}
|
||||
|
||||
return this._error(ERROR_UNVERIFIED_ACCOUNT, {
|
||||
@ -322,6 +335,17 @@ this.FxAccountsManager = {
|
||||
);
|
||||
},
|
||||
|
||||
_addPermission: function(aPrincipal) {
|
||||
// This will fail from tests cause we are running them in the child
|
||||
// process until we have chrome tests in b2g. Bug 797164.
|
||||
try {
|
||||
permissionManager.addFromPrincipal(aPrincipal, FXACCOUNTS_PERMISSION,
|
||||
Ci.nsIPermissionManager.ALLOW_ACTION);
|
||||
} catch (e) {
|
||||
log.warn("Could not add permission " + e);
|
||||
}
|
||||
},
|
||||
|
||||
// -- API --
|
||||
|
||||
signIn: function(aEmail, aPassword) {
|
||||
@ -469,22 +493,31 @@ this.FxAccountsManager = {
|
||||
* trigger the sign in flow.
|
||||
* else if we were asked to refresh and the grace period is up:
|
||||
* trigger the refresh flow.
|
||||
* else ask the core code for an assertion, which might itself
|
||||
* trigger either the sign in or refresh flows (if our account
|
||||
* changed on the server).
|
||||
* else:
|
||||
* request user permission to share an assertion if we don't have it
|
||||
* already and ask the core code for an assertion, which might itself
|
||||
* trigger either the sign in or refresh flows (if our account
|
||||
* changed on the server).
|
||||
*
|
||||
* aOptions can include:
|
||||
* refreshAuthentication - (bool) Force re-auth.
|
||||
* silent - (bool) Prevent any UI interaction.
|
||||
* I.e., try to get an automatic assertion.
|
||||
*/
|
||||
getAssertion: function(aAudience, aOptions) {
|
||||
getAssertion: function(aAudience, aPrincipal, aOptions) {
|
||||
if (!aAudience) {
|
||||
return this._error(ERROR_INVALID_AUDIENCE);
|
||||
}
|
||||
if (Services.io.offline) {
|
||||
return this._error(ERROR_OFFLINE);
|
||||
}
|
||||
|
||||
let secMan = Cc["@mozilla.org/scriptsecuritymanager;1"]
|
||||
.getService(Ci.nsIScriptSecurityManager);
|
||||
let uri = Services.io.newURI(aPrincipal.origin, null, null);
|
||||
let principal = secMan.getAppCodebasePrincipal(uri,
|
||||
aPrincipal.appId, aPrincipal.isInBrowserElement);
|
||||
|
||||
return this.getAccount().then(
|
||||
user => {
|
||||
if (user) {
|
||||
@ -506,21 +539,42 @@ this.FxAccountsManager = {
|
||||
if (aOptions.silent) {
|
||||
return this._error(ERROR_NO_SILENT_REFRESH_AUTH);
|
||||
}
|
||||
let secondsSinceAuth = (Date.now() / 1000) - this._activeSession.authAt;
|
||||
let secondsSinceAuth = (Date.now() / 1000) -
|
||||
this._activeSession.authAt;
|
||||
if (secondsSinceAuth > gracePeriod) {
|
||||
return this._refreshAuthentication(aAudience, user.email);
|
||||
return this._refreshAuthentication(aAudience, user.email,
|
||||
principal,
|
||||
false /* logoutOnFailure */);
|
||||
}
|
||||
}
|
||||
// Third case: we are all set *locally*. Probably we just return
|
||||
// the assertion, but the attempt might lead to the server saying
|
||||
// we are deleted or have a new password, which will trigger a flow.
|
||||
return this._getAssertion(aAudience);
|
||||
// Also we need to check if we have permission to get the assertion,
|
||||
// otherwise we need to show the forceAuth UI to let the user know
|
||||
// that the RP with no fxa permissions is trying to obtain an
|
||||
// assertion. Once the user authenticates herself in the forceAuth UI
|
||||
// the permission will be remembered by default.
|
||||
let permission = permissionManager.testPermissionFromPrincipal(
|
||||
principal,
|
||||
FXACCOUNTS_PERMISSION
|
||||
);
|
||||
if (permission == Ci.nsIPermissionManager.PROMPT_ACTION &&
|
||||
!this._refreshing) {
|
||||
return this._refreshAuthentication(aAudience, user.email,
|
||||
principal,
|
||||
false /* logoutOnFailure */);
|
||||
} else if (permission == Ci.nsIPermissionManager.DENY_ACTION &&
|
||||
!this._refreshing) {
|
||||
return this._error(ERROR_PERMISSION_DENIED);
|
||||
}
|
||||
return this._getAssertion(aAudience, principal);
|
||||
}
|
||||
log.debug("No signed in user");
|
||||
if (aOptions && aOptions.silent) {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
return this._uiRequest(UI_REQUEST_SIGN_IN_FLOW, aAudience);
|
||||
return this._uiRequest(UI_REQUEST_SIGN_IN_FLOW, aAudience, principal);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
@ -102,11 +102,16 @@ FxAccountsService.prototype = {
|
||||
}
|
||||
},
|
||||
|
||||
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 aCaller
|
||||
* @param aRPCaller
|
||||
* (Object) an object that represents the caller document, and
|
||||
* is expected to have properties:
|
||||
* - id (unique, e.g. uuid)
|
||||
@ -128,7 +133,9 @@ FxAccountsService.prototype = {
|
||||
// Log the user in, if possible, and then call ready().
|
||||
let runnable = {
|
||||
run: () => {
|
||||
this.fxAccountsManager.getAssertion(aRpCaller.audience, {silent:true}).then(
|
||||
this.fxAccountsManager.getAssertion(aRpCaller.audience,
|
||||
aRpCaller.principal,
|
||||
{ silent:true }).then(
|
||||
data => {
|
||||
if (data) {
|
||||
this.doLogin(aRpCaller.id, data);
|
||||
@ -161,10 +168,10 @@ FxAccountsService.prototype = {
|
||||
* navigator.id.request().
|
||||
*
|
||||
* @param aRPId
|
||||
* (integer) the id of the doc object obtained in .watch()
|
||||
* (integer) the id of the doc object obtained in .watch()
|
||||
*
|
||||
* @param aOptions
|
||||
* (Object) options including privacyPolicy, termsOfService
|
||||
* (Object) options including privacyPolicy, termsOfService
|
||||
*/
|
||||
request: function request(aRPId, aOptions) {
|
||||
aOptions = aOptions || {};
|
||||
@ -174,12 +181,25 @@ FxAccountsService.prototype = {
|
||||
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, options).then(
|
||||
this.fxAccountsManager.getAssertion(rp.audience, rp.principal, options)
|
||||
.then(
|
||||
data => {
|
||||
log.debug("got assertion for " + rp.audience + ": " + data);
|
||||
this.doLogin(aRPId, data);
|
||||
@ -193,6 +213,16 @@ FxAccountsService.prototype = {
|
||||
}
|
||||
this.doError(aRPId, error);
|
||||
}
|
||||
)
|
||||
.then(
|
||||
() => {
|
||||
this.cleanupRPRequest(rp);
|
||||
}
|
||||
)
|
||||
.catch(
|
||||
() => {
|
||||
this.cleanupRPRequest(rp);
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user