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:
committed by
GitHub
parent
37ceae0812
commit
7944e2bd2d
@@ -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);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -1261,79 +1261,83 @@ 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)));
|
||||||
Object.keys(schema.fields).forEach(fieldName => {
|
}
|
||||||
if (schema.fields[fieldName].type === 'Pointer' && object[fieldName]) {
|
|
||||||
object[fieldName] = { objectId: object[fieldName], __type: 'Pointer', className: schema.fields[fieldName].targetClass };
|
// Converts from a postgres-format object to a REST-format object.
|
||||||
}
|
// Does not strip out anything based on a lack of authentication.
|
||||||
if (schema.fields[fieldName].type === 'Relation') {
|
postgresObjectToParseObject(className, object, schema) {
|
||||||
object[fieldName] = {
|
Object.keys(schema.fields).forEach(fieldName => {
|
||||||
__type: "Relation",
|
if (schema.fields[fieldName].type === 'Pointer' && object[fieldName]) {
|
||||||
className: schema.fields[fieldName].targetClass
|
object[fieldName] = { objectId: object[fieldName], __type: 'Pointer', className: schema.fields[fieldName].targetClass };
|
||||||
}
|
}
|
||||||
}
|
if (schema.fields[fieldName].type === 'Relation') {
|
||||||
if (object[fieldName] && schema.fields[fieldName].type === 'GeoPoint') {
|
object[fieldName] = {
|
||||||
object[fieldName] = {
|
__type: "Relation",
|
||||||
__type: "GeoPoint",
|
className: schema.fields[fieldName].targetClass
|
||||||
latitude: object[fieldName].y,
|
}
|
||||||
longitude: object[fieldName].x
|
}
|
||||||
}
|
if (object[fieldName] && schema.fields[fieldName].type === 'GeoPoint') {
|
||||||
}
|
object[fieldName] = {
|
||||||
if (object[fieldName] && schema.fields[fieldName].type === 'Polygon') {
|
__type: "GeoPoint",
|
||||||
let coords = object[fieldName];
|
latitude: object[fieldName].y,
|
||||||
coords = coords.substr(2, coords.length - 4).split('),(');
|
longitude: object[fieldName].x
|
||||||
coords = coords.map((point) => {
|
}
|
||||||
return [
|
}
|
||||||
parseFloat(point.split(',')[1]),
|
if (object[fieldName] && schema.fields[fieldName].type === 'Polygon') {
|
||||||
parseFloat(point.split(',')[0])
|
let coords = object[fieldName];
|
||||||
];
|
coords = coords.substr(2, coords.length - 4).split('),(');
|
||||||
});
|
coords = coords.map((point) => {
|
||||||
object[fieldName] = {
|
return [
|
||||||
__type: "Polygon",
|
parseFloat(point.split(',')[1]),
|
||||||
coordinates: coords
|
parseFloat(point.split(',')[0])
|
||||||
}
|
];
|
||||||
}
|
|
||||||
if (object[fieldName] && schema.fields[fieldName].type === 'File') {
|
|
||||||
object[fieldName] = {
|
|
||||||
__type: 'File',
|
|
||||||
name: object[fieldName]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
//TODO: remove this reliance on the mongo format. DB adapter shouldn't know there is a difference between created at and any other date field.
|
object[fieldName] = {
|
||||||
if (object.createdAt) {
|
__type: "Polygon",
|
||||||
object.createdAt = object.createdAt.toISOString();
|
coordinates: coords
|
||||||
}
|
}
|
||||||
if (object.updatedAt) {
|
}
|
||||||
object.updatedAt = object.updatedAt.toISOString();
|
if (object[fieldName] && schema.fields[fieldName].type === 'File') {
|
||||||
}
|
object[fieldName] = {
|
||||||
if (object.expiresAt) {
|
__type: 'File',
|
||||||
object.expiresAt = { __type: 'Date', iso: object.expiresAt.toISOString() };
|
name: object[fieldName]
|
||||||
}
|
|
||||||
if (object._email_verify_token_expires_at) {
|
|
||||||
object._email_verify_token_expires_at = { __type: 'Date', iso: object._email_verify_token_expires_at.toISOString() };
|
|
||||||
}
|
|
||||||
if (object._account_lockout_expires_at) {
|
|
||||||
object._account_lockout_expires_at = { __type: 'Date', iso: object._account_lockout_expires_at.toISOString() };
|
|
||||||
}
|
|
||||||
if (object._perishable_token_expires_at) {
|
|
||||||
object._perishable_token_expires_at = { __type: 'Date', iso: object._perishable_token_expires_at.toISOString() };
|
|
||||||
}
|
|
||||||
if (object._password_changed_at) {
|
|
||||||
object._password_changed_at = { __type: 'Date', iso: object._password_changed_at.toISOString() };
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
//TODO: remove this reliance on the mongo format. DB adapter shouldn't know there is a difference between created at and any other date field.
|
||||||
|
if (object.createdAt) {
|
||||||
|
object.createdAt = object.createdAt.toISOString();
|
||||||
|
}
|
||||||
|
if (object.updatedAt) {
|
||||||
|
object.updatedAt = object.updatedAt.toISOString();
|
||||||
|
}
|
||||||
|
if (object.expiresAt) {
|
||||||
|
object.expiresAt = { __type: 'Date', iso: object.expiresAt.toISOString() };
|
||||||
|
}
|
||||||
|
if (object._email_verify_token_expires_at) {
|
||||||
|
object._email_verify_token_expires_at = { __type: 'Date', iso: object._email_verify_token_expires_at.toISOString() };
|
||||||
|
}
|
||||||
|
if (object._account_lockout_expires_at) {
|
||||||
|
object._account_lockout_expires_at = { __type: 'Date', iso: object._account_lockout_expires_at.toISOString() };
|
||||||
|
}
|
||||||
|
if (object._perishable_token_expires_at) {
|
||||||
|
object._perishable_token_expires_at = { __type: 'Date', iso: object._perishable_token_expires_at.toISOString() };
|
||||||
|
}
|
||||||
|
if (object._password_changed_at) {
|
||||||
|
object._password_changed_at = { __type: 'Date', iso: object._password_changed_at.toISOString() };
|
||||||
|
}
|
||||||
|
|
||||||
for (const fieldName in object) {
|
for (const fieldName in object) {
|
||||||
if (object[fieldName] === null) {
|
if (object[fieldName] === null) {
|
||||||
delete object[fieldName];
|
delete object[fieldName];
|
||||||
}
|
}
|
||||||
if (object[fieldName] instanceof Date) {
|
if (object[fieldName] instanceof Date) {
|
||||||
object[fieldName] = { __type: 'Date', iso: object[fieldName].toISOString() };
|
object[fieldName] = { __type: 'Date', iso: object[fieldName].toISOString() };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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,17 +1502,19 @@ 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)
|
||||||
if (countField) {
|
.then(results => results.map(object => this.postgresObjectToParseObject(className, object, schema)))
|
||||||
results[0][countField] = parseInt(results[0][countField], 10);
|
.then(results => {
|
||||||
}
|
if (countField) {
|
||||||
results.forEach(result => {
|
results[0][countField] = parseInt(results[0][countField], 10);
|
||||||
if (!result.hasOwnProperty('objectId')) {
|
|
||||||
result.objectId = null;
|
|
||||||
}
|
}
|
||||||
|
results.forEach(result => {
|
||||||
|
if (!result.hasOwnProperty('objectId')) {
|
||||||
|
result.objectId = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return results;
|
||||||
});
|
});
|
||||||
return results;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
performInitialization({ VolatileClassesSchemas }) {
|
performInitialization({ VolatileClassesSchemas }) {
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
Reference in New Issue
Block a user