Remove hidden properties from aggregate responses (#4351)

* Remove hidden properties from aggregrate responses

* transform results from mongo & postgres

* Adjust ordering to comply with tests
This commit is contained in:
Benjamin Wilson Friedman
2017-11-22 23:07:45 -08:00
committed by GitHub
parent 37ceae0812
commit 7944e2bd2d
5 changed files with 145 additions and 85 deletions

View File

@@ -409,4 +409,49 @@ describe('Parse.Query Aggregate testing', () => {
done(); done();
}).catch(done.fail); }).catch(done.fail);
}); });
it('does not return sensitive hidden properties', (done) => {
const options = Object.assign({}, masterKeyOptions, {
body: {
match: {
score: {
$gt: 5
}
},
}
});
const username = 'leaky_user';
const score = 10;
const user = new Parse.User();
user.setUsername(username);
user.setPassword('password');
user.set('score', score);
user.signUp().then(function() {
return rp.get(Parse.serverURL + '/aggregate/_User', options);
}).then(function(resp) {
expect(resp.results.length).toBe(1);
const result = resp.results[0];
// verify server-side keys are not present...
expect(result._hashed_password).toBe(undefined);
expect(result._wperm).toBe(undefined);
expect(result._rperm).toBe(undefined);
expect(result._acl).toBe(undefined);
expect(result._created_at).toBe(undefined);
expect(result._updated_at).toBe(undefined);
// verify createdAt, updatedAt and others are present
expect(result.createdAt).not.toBe(undefined);
expect(result.updatedAt).not.toBe(undefined);
expect(result.objectId).not.toBe(undefined);
expect(result.username).toBe(username);
expect(result.score).toBe(score);
done();
}).catch(function(err) {
fail(err);
});
});
}); });

View File

@@ -409,10 +409,11 @@ export class MongoStorageAdapter {
distinct(className, schema, query, fieldName) { distinct(className, schema, query, fieldName) {
schema = convertParseSchemaToMongoSchema(schema); schema = convertParseSchemaToMongoSchema(schema);
return this._adaptiveCollection(className) return this._adaptiveCollection(className)
.then(collection => collection.distinct(fieldName, transformWhere(className, query, schema))); .then(collection => collection.distinct(fieldName, transformWhere(className, query, schema)))
.then(objects => objects.map(object => mongoObjectToParseObject(className, object, schema)));
} }
aggregate(className, pipeline, readPreference) { aggregate(className, schema, pipeline, readPreference) {
readPreference = this._parseReadPreference(readPreference); readPreference = this._parseReadPreference(readPreference);
return this._adaptiveCollection(className) return this._adaptiveCollection(className)
.then(collection => collection.aggregate(pipeline, { readPreference, maxTimeMS: this._maxTimeMS })) .then(collection => collection.aggregate(pipeline, { readPreference, maxTimeMS: this._maxTimeMS }))
@@ -424,7 +425,8 @@ export class MongoStorageAdapter {
} }
}); });
return results; return results;
}); })
.then(objects => objects.map(object => mongoObjectToParseObject(className, object, schema)));
} }
_parseReadPreference(readPreference) { _parseReadPreference(readPreference) {

View File

@@ -1261,7 +1261,12 @@ export class PostgresStorageAdapter {
} }
return Promise.reject(err); return Promise.reject(err);
}) })
.then(results => results.map(object => { .then(results => results.map(object => this.postgresObjectToParseObject(className, object, schema)));
}
// Converts from a postgres-format object to a REST-format object.
// Does not strip out anything based on a lack of authentication.
postgresObjectToParseObject(className, object, schema) {
Object.keys(schema.fields).forEach(fieldName => { Object.keys(schema.fields).forEach(fieldName => {
if (schema.fields[fieldName].type === 'Pointer' && object[fieldName]) { if (schema.fields[fieldName].type === 'Pointer' && object[fieldName]) {
object[fieldName] = { objectId: object[fieldName], __type: 'Pointer', className: schema.fields[fieldName].targetClass }; object[fieldName] = { objectId: object[fieldName], __type: 'Pointer', className: schema.fields[fieldName].targetClass };
@@ -1333,7 +1338,6 @@ export class PostgresStorageAdapter {
} }
return object; return object;
}));
} }
// Create a unique index. Unique indexes on nullable fields are not allowed. Since we don't // Create a unique index. Unique indexes on nullable fields are not allowed. Since we don't
@@ -1406,10 +1410,10 @@ export class PostgresStorageAdapter {
} }
const child = fieldName.split('.')[1]; const child = fieldName.split('.')[1];
return results.map(object => object[column][child]); return results.map(object => object[column][child]);
}); }).then(results => results.map(object => this.postgresObjectToParseObject(className, object, schema)));
} }
aggregate(className, pipeline) { aggregate(className, schema, pipeline) {
debug('aggregate', className, pipeline); debug('aggregate', className, pipeline);
const values = [className]; const values = [className];
let columns = []; let columns = [];
@@ -1498,7 +1502,9 @@ export class PostgresStorageAdapter {
const qs = `SELECT ${columns} FROM $1:name ${wherePattern} ${sortPattern} ${limitPattern} ${skipPattern} ${groupPattern}`; const qs = `SELECT ${columns} FROM $1:name ${wherePattern} ${sortPattern} ${limitPattern} ${skipPattern} ${groupPattern}`;
debug(qs, values); debug(qs, values);
return this._client.any(qs, values).then(results => { return this._client.any(qs, values)
.then(results => results.map(object => this.postgresObjectToParseObject(className, object, schema)))
.then(results => {
if (countField) { if (countField) {
results[0][countField] = parseInt(results[0][countField], 10); results[0][countField] = parseInt(results[0][countField], 10);
} }

View File

@@ -875,7 +875,7 @@ DatabaseController.prototype.find = function(className, query, {
if (!classExists) { if (!classExists) {
return []; return [];
} else { } else {
return this.adapter.aggregate(className, pipeline, readPreference); return this.adapter.aggregate(className, schema, pipeline, readPreference);
} }
} else { } else {
if (!classExists) { if (!classExists) {

View File

@@ -2,6 +2,7 @@ import ClassesRouter from './ClassesRouter';
import rest from '../rest'; import rest from '../rest';
import * as middleware from '../middlewares'; import * as middleware from '../middlewares';
import Parse from 'parse/node'; import Parse from 'parse/node';
import UsersRouter from './UsersRouter';
const ALLOWED_KEYS = [ const ALLOWED_KEYS = [
'where', 'where',
@@ -65,8 +66,14 @@ export class AggregateRouter extends ClassesRouter {
if (typeof body.where === 'string') { if (typeof body.where === 'string') {
body.where = JSON.parse(body.where); body.where = JSON.parse(body.where);
} }
return rest.find(req.config, req.auth, this.className(req), body.where, options, req.info.clientSDK) return rest.find(req.config, req.auth, this.className(req), body.where, options, req.info.clientSDK).then((response) => {
.then((response) => { return { response }; }); for(const result of response.results) {
if(typeof result === 'object') {
UsersRouter.removeHiddenProperties(result);
}
}
return { response };
});
} }
mountRoutes() { mountRoutes() {