Strip personally identifiable information from user table for unauthorized users.

- add a config option to explicitly enumerate pii fields beyond email
- in query controller, strip pii of user table results before sending out the door.
This commit is contained in:
Arthur Cinader
2016-12-01 17:04:09 -08:00
committed by Florent Vilmart
parent a270632570
commit 01b05b060f
6 changed files with 546 additions and 14 deletions

View File

@@ -34,6 +34,7 @@ export class Config {
this.fileKey = cacheInfo.fileKey;
this.facebookAppIds = cacheInfo.facebookAppIds;
this.allowClientClassCreation = cacheInfo.allowClientClassCreation;
this.userSensitiveFields = cacheInfo.userSensitiveFields;
// Create a new DatabaseController per request
if (cacheInfo.databaseController) {

View File

@@ -113,6 +113,7 @@ class ParseServer {
webhookKey,
fileKey,
facebookAppIds = [],
userSensitiveFields = [],
enableAnonymousUsers = defaults.enableAnonymousUsers,
allowClientClassCreation = defaults.allowClientClassCreation,
oauth = {},
@@ -155,6 +156,11 @@ class ParseServer {
throw 'When using an explicit database adapter, you must also use an explicit filesAdapter.';
}
userSensitiveFields = Array.from(new Set(userSensitiveFields.concat(
defaults.userSensitiveFields,
userSensitiveFields
)));
const loggerControllerAdapter = loadAdapter(loggerAdapter, WinstonLoggerAdapter, { jsonLogs, logsFolder, verbose, logLevel, silent });
const loggerController = new LoggerController(loggerControllerAdapter, appId);
logging.setLogger(loggerController);
@@ -222,7 +228,8 @@ class ParseServer {
revokeSessionOnPasswordReset,
databaseController,
schemaCacheTTL,
enableSingleSchemaCache
enableSingleSchemaCache,
userSensitiveFields
});
// To maintain compatibility. TODO: Remove in some version that breaks backwards compatibility

View File

@@ -386,6 +386,32 @@ RestQuery.prototype.replaceDontSelect = function() {
})
};
const cleanResultOfSensitiveUserInfo = function (result, auth, config) {
delete result.password;
if (auth.isMaster || (auth.user && auth.user.id === result.objectId)) {
return;
}
for (const field of config.userSensitiveFields) {
delete result[field];
}
};
const cleanResultAuthData = function (result) {
if (result.authData) {
Object.keys(result.authData).forEach((provider) => {
if (result.authData[provider] === null) {
delete result.authData[provider];
}
});
if (Object.keys(result.authData).length == 0) {
delete result.authData;
}
}
};
// Returns a promise for whether it was successful.
// Populates this.response with an object that only has 'results'.
RestQuery.prototype.runFind = function(options = {}) {
@@ -406,18 +432,8 @@ RestQuery.prototype.runFind = function(options = {}) {
this.className, this.restWhere, findOptions).then((results) => {
if (this.className === '_User') {
for (var result of results) {
delete result.password;
if (result.authData) {
Object.keys(result.authData).forEach((provider) => {
if (result.authData[provider] === null) {
delete result.authData[provider];
}
});
if (Object.keys(result.authData).length == 0) {
delete result.authData;
}
}
cleanResultOfSensitiveUserInfo(result, this.auth, this.config);
cleanResultAuthData(result);
}
}

View File

@@ -160,6 +160,10 @@ export default {
help: "Max file size for uploads.",
default: "20mb"
},
"userSensitiveFields": {
help: "Personally identifiable information fields in the user table the should be removed for non-authorized users.",
default: "email"
},
"sessionLength": {
env: "PARSE_SERVER_SESSION_LENGTH",
help: "Session duration, defaults to 1 year",

View File

@@ -31,5 +31,6 @@ export default {
sessionLength: 31536000,
expireInactiveSessions: true,
revokeSessionOnPasswordReset: true,
schemaCacheTTL: 5000 // in ms
schemaCacheTTL: 5000, // in ms
userSensitiveFields: ['email']
}