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
1ee91d533a
commit
2423e4fc3a
@ -103,7 +103,7 @@ IDPAuthenticationContext.prototype = {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
function RPWatchContext(aOptions, aTargetMM) {
|
function RPWatchContext(aOptions, aTargetMM, aPrincipal) {
|
||||||
objectCopy(aOptions, this);
|
objectCopy(aOptions, this);
|
||||||
|
|
||||||
// id and origin are required
|
// 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");
|
throw new Error("id and origin are required for RP watch context");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.principal = aPrincipal;
|
||||||
|
|
||||||
// default for no loggedInUser is undefined, not null
|
// default for no loggedInUser is undefined, not null
|
||||||
this.loggedInUser = aOptions.loggedInUser;
|
this.loggedInUser = aOptions.loggedInUser;
|
||||||
|
|
||||||
@ -187,8 +189,8 @@ this.DOMIdentity = {
|
|||||||
/*
|
/*
|
||||||
* Create a new RPWatchContext, and update the context maps.
|
* Create a new RPWatchContext, and update the context maps.
|
||||||
*/
|
*/
|
||||||
newContext: function(message, targetMM) {
|
newContext: function(message, targetMM, principal) {
|
||||||
let context = new RPWatchContext(message, targetMM);
|
let context = new RPWatchContext(message, targetMM, principal);
|
||||||
this._serviceContexts.set(message.id, context);
|
this._serviceContexts.set(message.id, context);
|
||||||
this._mmContexts.set(targetMM, message.id);
|
this._mmContexts.set(targetMM, message.id);
|
||||||
return context;
|
return context;
|
||||||
@ -276,16 +278,16 @@ this.DOMIdentity = {
|
|||||||
switch (aMessage.name) {
|
switch (aMessage.name) {
|
||||||
// RP
|
// RP
|
||||||
case "Identity:RP:Watch":
|
case "Identity:RP:Watch":
|
||||||
this._watch(msg, targetMM);
|
this._watch(msg, targetMM, aMessage.principal);
|
||||||
break;
|
break;
|
||||||
case "Identity:RP:Unwatch":
|
case "Identity:RP:Unwatch":
|
||||||
this._unwatch(msg, targetMM);
|
this._unwatch(msg, targetMM);
|
||||||
break;
|
break;
|
||||||
case "Identity:RP:Request":
|
case "Identity:RP:Request":
|
||||||
this._request(msg, targetMM);
|
this._request(msg);
|
||||||
break;
|
break;
|
||||||
case "Identity:RP:Logout":
|
case "Identity:RP:Logout":
|
||||||
this._logout(msg, targetMM);
|
this._logout(msg);
|
||||||
break;
|
break;
|
||||||
// IDP
|
// IDP
|
||||||
case "Identity:IDP:BeginProvisioning":
|
case "Identity:IDP:BeginProvisioning":
|
||||||
@ -359,9 +361,9 @@ this.DOMIdentity = {
|
|||||||
ppmm = null;
|
ppmm = null;
|
||||||
},
|
},
|
||||||
|
|
||||||
_watch: function DOMIdentity__watch(message, targetMM) {
|
_watch: function DOMIdentity__watch(message, targetMM, principal) {
|
||||||
log("DOMIdentity__watch: " + message.id);
|
log("DOMIdentity__watch: " + message.id + " - " + principal);
|
||||||
let context = this.newContext(message, targetMM);
|
let context = this.newContext(message, targetMM, principal);
|
||||||
this.getService(message).RP.watch(context);
|
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.DATA_FORMAT_VERSION = 1;
|
||||||
this.DEFAULT_STORAGE_FILENAME = "signedInUser.json";
|
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_NO_SILENT_REFRESH_AUTH = "NO_SILENT_REFRESH_AUTH";
|
||||||
this.ERROR_NOT_VALID_JSON_BODY = "NOT_VALID_JSON_BODY";
|
this.ERROR_NOT_VALID_JSON_BODY = "NOT_VALID_JSON_BODY";
|
||||||
this.ERROR_OFFLINE = "OFFLINE";
|
this.ERROR_OFFLINE = "OFFLINE";
|
||||||
|
this.ERROR_PERMISSION_DENIED = "PERMISSION_DENIED";
|
||||||
this.ERROR_REQUEST_BODY_TOO_LARGE = "REQUEST_BODY_TOO_LARGE";
|
this.ERROR_REQUEST_BODY_TOO_LARGE = "REQUEST_BODY_TOO_LARGE";
|
||||||
this.ERROR_SERVER_ERROR = "SERVER_ERROR";
|
this.ERROR_SERVER_ERROR = "SERVER_ERROR";
|
||||||
this.ERROR_TOO_MANY_CLIENT_REQUESTS = "TOO_MANY_CLIENT_REQUESTS";
|
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/Promise.jsm");
|
||||||
Cu.import("resource://gre/modules/FxAccountsCommon.js");
|
Cu.import("resource://gre/modules/FxAccountsCommon.js");
|
||||||
|
|
||||||
|
XPCOMUtils.defineLazyServiceGetter(this, "permissionManager",
|
||||||
|
"@mozilla.org/permissionmanager;1",
|
||||||
|
"nsIPermissionManager");
|
||||||
|
|
||||||
this.FxAccountsManager = {
|
this.FxAccountsManager = {
|
||||||
|
|
||||||
init: function() {
|
init: function() {
|
||||||
@ -175,7 +179,7 @@ this.FxAccountsManager = {
|
|||||||
* FxAccountsClient.signCertificate()
|
* FxAccountsClient.signCertificate()
|
||||||
* See the latter method for possible (error code, errno) pairs.
|
* 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;
|
let errno = (reason ? reason.errno : NaN) || NaN;
|
||||||
// If the previously valid email/password pair is no longer valid ...
|
// If the previously valid email/password pair is no longer valid ...
|
||||||
if (errno == ERRNO_INVALID_AUTH_TOKEN) {
|
if (errno == ERRNO_INVALID_AUTH_TOKEN) {
|
||||||
@ -186,34 +190,42 @@ this.FxAccountsManager = {
|
|||||||
if (exists) {
|
if (exists) {
|
||||||
return this.getAccount().then(
|
return this.getAccount().then(
|
||||||
(user) => {
|
(user) => {
|
||||||
return this._refreshAuthentication(aAudience, user.email, true);
|
return this._refreshAuthentication(aAudience, user.email,
|
||||||
}
|
aPrincipal,
|
||||||
);
|
true /* logoutOnFailure */);
|
||||||
// ... 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);
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// 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);
|
return Promise.reject(reason);
|
||||||
},
|
},
|
||||||
|
|
||||||
_getAssertion: function(aAudience) {
|
_getAssertion: function(aAudience, aPrincipal) {
|
||||||
return this._fxAccounts.getAssertion(aAudience).then(
|
return this._fxAccounts.getAssertion(aAudience).then(
|
||||||
(result) => {
|
(result) => {
|
||||||
|
if (aPrincipal) {
|
||||||
|
this._addPermission(aPrincipal);
|
||||||
|
}
|
||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
(reason) => {
|
(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
|
* 2) The person typing can't prove knowledge of the password used
|
||||||
* to log in. Failure should do nothing.
|
* to log in. Failure should do nothing.
|
||||||
*/
|
*/
|
||||||
_refreshAuthentication: function(aAudience, aEmail, logoutOnFailure=false) {
|
_refreshAuthentication: function(aAudience, aEmail, aPrincipal,
|
||||||
|
logoutOnFailure=false) {
|
||||||
this._refreshing = true;
|
this._refreshing = true;
|
||||||
return this._uiRequest(UI_REQUEST_REFRESH_AUTH,
|
return this._uiRequest(UI_REQUEST_REFRESH_AUTH,
|
||||||
aAudience, aEmail).then(
|
aAudience, aPrincipal, aEmail).then(
|
||||||
(assertion) => {
|
(assertion) => {
|
||||||
this._refreshing = false;
|
this._refreshing = false;
|
||||||
return assertion;
|
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"]
|
let ui = Cc["@mozilla.org/fxaccounts/fxaccounts-ui-glue;1"]
|
||||||
.createInstance(Ci.nsIFxAccountsUIGlue);
|
.createInstance(Ci.nsIFxAccountsUIGlue);
|
||||||
if (!ui[aRequest]) {
|
if (!ui[aRequest]) {
|
||||||
@ -309,7 +322,7 @@ this.FxAccountsManager = {
|
|||||||
// Even if we get a successful result from the UI, the account will
|
// Even if we get a successful result from the UI, the account will
|
||||||
// most likely be unverified, so we cannot get an assertion.
|
// most likely be unverified, so we cannot get an assertion.
|
||||||
if (result && result.verified) {
|
if (result && result.verified) {
|
||||||
return this._getAssertion(aAudience);
|
return this._getAssertion(aAudience, aPrincipal);
|
||||||
}
|
}
|
||||||
|
|
||||||
return this._error(ERROR_UNVERIFIED_ACCOUNT, {
|
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 --
|
// -- API --
|
||||||
|
|
||||||
signIn: function(aEmail, aPassword) {
|
signIn: function(aEmail, aPassword) {
|
||||||
@ -469,22 +493,31 @@ this.FxAccountsManager = {
|
|||||||
* trigger the sign in flow.
|
* trigger the sign in flow.
|
||||||
* else if we were asked to refresh and the grace period is up:
|
* else if we were asked to refresh and the grace period is up:
|
||||||
* trigger the refresh flow.
|
* trigger the refresh flow.
|
||||||
* else ask the core code for an assertion, which might itself
|
* else:
|
||||||
* trigger either the sign in or refresh flows (if our account
|
* request user permission to share an assertion if we don't have it
|
||||||
* changed on the server).
|
* 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:
|
* aOptions can include:
|
||||||
* refreshAuthentication - (bool) Force re-auth.
|
* refreshAuthentication - (bool) Force re-auth.
|
||||||
* silent - (bool) Prevent any UI interaction.
|
* silent - (bool) Prevent any UI interaction.
|
||||||
* I.e., try to get an automatic assertion.
|
* I.e., try to get an automatic assertion.
|
||||||
*/
|
*/
|
||||||
getAssertion: function(aAudience, aOptions) {
|
getAssertion: function(aAudience, aPrincipal, aOptions) {
|
||||||
if (!aAudience) {
|
if (!aAudience) {
|
||||||
return this._error(ERROR_INVALID_AUDIENCE);
|
return this._error(ERROR_INVALID_AUDIENCE);
|
||||||
}
|
}
|
||||||
if (Services.io.offline) {
|
if (Services.io.offline) {
|
||||||
return this._error(ERROR_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(
|
return this.getAccount().then(
|
||||||
user => {
|
user => {
|
||||||
if (user) {
|
if (user) {
|
||||||
@ -506,21 +539,42 @@ this.FxAccountsManager = {
|
|||||||
if (aOptions.silent) {
|
if (aOptions.silent) {
|
||||||
return this._error(ERROR_NO_SILENT_REFRESH_AUTH);
|
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) {
|
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
|
// Third case: we are all set *locally*. Probably we just return
|
||||||
// the assertion, but the attempt might lead to the server saying
|
// the assertion, but the attempt might lead to the server saying
|
||||||
// we are deleted or have a new password, which will trigger a flow.
|
// 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");
|
log.debug("No signed in user");
|
||||||
if (aOptions && aOptions.silent) {
|
if (aOptions && aOptions.silent) {
|
||||||
return Promise.resolve(null);
|
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
|
* Register a listener for a given windowID as a result of a call to
|
||||||
* navigator.id.watch().
|
* navigator.id.watch().
|
||||||
*
|
*
|
||||||
* @param aCaller
|
* @param aRPCaller
|
||||||
* (Object) an object that represents the caller document, and
|
* (Object) an object that represents the caller document, and
|
||||||
* is expected to have properties:
|
* is expected to have properties:
|
||||||
* - id (unique, e.g. uuid)
|
* - id (unique, e.g. uuid)
|
||||||
@ -128,7 +133,9 @@ FxAccountsService.prototype = {
|
|||||||
// Log the user in, if possible, and then call ready().
|
// Log the user in, if possible, and then call ready().
|
||||||
let runnable = {
|
let runnable = {
|
||||||
run: () => {
|
run: () => {
|
||||||
this.fxAccountsManager.getAssertion(aRpCaller.audience, {silent:true}).then(
|
this.fxAccountsManager.getAssertion(aRpCaller.audience,
|
||||||
|
aRpCaller.principal,
|
||||||
|
{ silent:true }).then(
|
||||||
data => {
|
data => {
|
||||||
if (data) {
|
if (data) {
|
||||||
this.doLogin(aRpCaller.id, data);
|
this.doLogin(aRpCaller.id, data);
|
||||||
@ -161,10 +168,10 @@ FxAccountsService.prototype = {
|
|||||||
* navigator.id.request().
|
* navigator.id.request().
|
||||||
*
|
*
|
||||||
* @param aRPId
|
* @param aRPId
|
||||||
* (integer) the id of the doc object obtained in .watch()
|
* (integer) the id of the doc object obtained in .watch()
|
||||||
*
|
*
|
||||||
* @param aOptions
|
* @param aOptions
|
||||||
* (Object) options including privacyPolicy, termsOfService
|
* (Object) options including privacyPolicy, termsOfService
|
||||||
*/
|
*/
|
||||||
request: function request(aRPId, aOptions) {
|
request: function request(aRPId, aOptions) {
|
||||||
aOptions = aOptions || {};
|
aOptions = aOptions || {};
|
||||||
@ -174,12 +181,25 @@ FxAccountsService.prototype = {
|
|||||||
return;
|
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);
|
let options = makeMessageObject(rp);
|
||||||
objectCopy(aOptions, options);
|
objectCopy(aOptions, options);
|
||||||
|
|
||||||
log.debug("get assertion for " + rp.audience);
|
log.debug("get assertion for " + rp.audience);
|
||||||
|
|
||||||
this.fxAccountsManager.getAssertion(rp.audience, options).then(
|
this.fxAccountsManager.getAssertion(rp.audience, rp.principal, options)
|
||||||
|
.then(
|
||||||
data => {
|
data => {
|
||||||
log.debug("got assertion for " + rp.audience + ": " + data);
|
log.debug("got assertion for " + rp.audience + ": " + data);
|
||||||
this.doLogin(aRPId, data);
|
this.doLogin(aRPId, data);
|
||||||
@ -193,6 +213,16 @@ FxAccountsService.prototype = {
|
|||||||
}
|
}
|
||||||
this.doError(aRPId, error);
|
this.doError(aRPId, error);
|
||||||
}
|
}
|
||||||
|
)
|
||||||
|
.then(
|
||||||
|
() => {
|
||||||
|
this.cleanupRPRequest(rp);
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.catch(
|
||||||
|
() => {
|
||||||
|
this.cleanupRPRequest(rp);
|
||||||
|
}
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user