Skip to content
Merged
20 changes: 10 additions & 10 deletions spec/MongoTransform.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -121,18 +121,18 @@ describe('transformWhere', () => {
});
});

describe('untransformObject', () => {
describe('mongoObjectToParseObject', () => {
it('built-in timestamps', (done) => {
var input = {createdAt: new Date(), updatedAt: new Date()};
var output = transform.untransformObject(dummySchema, null, input);
var output = transform.mongoObjectToParseObject(dummySchema, null, input);
expect(typeof output.createdAt).toEqual('string');
expect(typeof output.updatedAt).toEqual('string');
done();
});

it('pointer', (done) => {
var input = {_p_userPointer: '_User$123'};
var output = transform.untransformObject(dummySchema, null, input);
var output = transform.mongoObjectToParseObject(dummySchema, null, input);
expect(typeof output.userPointer).toEqual('object');
expect(output.userPointer).toEqual(
{__type: 'Pointer', className: '_User', objectId: '123'}
Expand All @@ -142,22 +142,22 @@ describe('untransformObject', () => {

it('null pointer', (done) => {
var input = {_p_userPointer: null};
var output = transform.untransformObject(dummySchema, null, input);
var output = transform.mongoObjectToParseObject(dummySchema, null, input);
expect(output.userPointer).toBeUndefined();
done();
});

it('file', (done) => {
var input = {picture: 'pic.jpg'};
var output = transform.untransformObject(dummySchema, null, input);
var output = transform.mongoObjectToParseObject(dummySchema, null, input);
expect(typeof output.picture).toEqual('object');
expect(output.picture).toEqual({__type: 'File', name: 'pic.jpg'});
done();
});

it('geopoint', (done) => {
var input = {location: [180, -180]};
var output = transform.untransformObject(dummySchema, null, input);
var output = transform.mongoObjectToParseObject(dummySchema, null, input);
expect(typeof output.location).toEqual('object');
expect(output.location).toEqual(
{__type: 'GeoPoint', longitude: 180, latitude: -180}
Expand All @@ -167,7 +167,7 @@ describe('untransformObject', () => {

it('nested array', (done) => {
var input = {arr: [{_testKey: 'testValue' }]};
var output = transform.untransformObject(dummySchema, null, input);
var output = transform.mongoObjectToParseObject(dummySchema, null, input);
expect(Array.isArray(output.arr)).toEqual(true);
expect(output.arr).toEqual([{ _testKey: 'testValue'}]);
done();
Expand All @@ -185,7 +185,7 @@ describe('untransformObject', () => {
},
regularKey: "some data",
}]}
let output = transform.untransformObject(dummySchema, null, input);
let output = transform.mongoObjectToParseObject(dummySchema, null, input);
expect(dd(output, input)).toEqual(undefined);
done();
});
Expand Down Expand Up @@ -253,7 +253,7 @@ describe('transform schema key changes', () => {
_rperm: ["*"],
_wperm: ["Kevin"]
};
var output = transform.untransformObject(dummySchema, null, input);
var output = transform.mongoObjectToParseObject(dummySchema, null, input);
expect(typeof output.ACL).toEqual('object');
expect(output._rperm).toBeUndefined();
expect(output._wperm).toBeUndefined();
Expand All @@ -267,7 +267,7 @@ describe('transform schema key changes', () => {
long: mongodb.Long.fromNumber(Number.MAX_SAFE_INTEGER),
double: new mongodb.Double(Number.MAX_VALUE)
}
var output = transform.untransformObject(dummySchema, null, input);
var output = transform.mongoObjectToParseObject(dummySchema, null, input);
expect(output.long).toBe(Number.MAX_SAFE_INTEGER);
expect(output.double).toBe(Number.MAX_VALUE);
done();
Expand Down
50 changes: 50 additions & 0 deletions spec/ParseAPI.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -1120,4 +1120,54 @@ describe('miscellaneous', function() {
done();
})
});

it('does not change inner object key names _auth_data_something', done => {
new Parse.Object('O').save({ innerObj: {_auth_data_facebook: 7}})
.then(object => new Parse.Query('O').get(object.id))
.then(object => {
expect(object.get('innerObj')).toEqual({_auth_data_facebook: 7});
done();
});
});

it('does not change inner object key names _p_somethign', done => {
new Parse.Object('O').save({ innerObj: {_p_data: 7}})
.then(object => new Parse.Query('O').get(object.id))
.then(object => {
expect(object.get('innerObj')).toEqual({_p_data: 7});
done();
});
});

it('does not change inner object key names _rperm, _wperm', done => {
new Parse.Object('O').save({ innerObj: {_rperm: 7, _wperm: 8}})
.then(object => new Parse.Query('O').get(object.id))
.then(object => {
expect(object.get('innerObj')).toEqual({_rperm: 7, _wperm: 8});
done();
});
});

it('does not change inner objects if the key has the same name as a geopoint field on the class, and the value is an array of length 2, or if the key has the same name as a file field on the class, and the value is a string', done => {
let file = new Parse.File('myfile.txt', { base64: 'eAo=' });
file.save()
.then(f => {
let obj = new Parse.Object('O');
obj.set('fileField', f);
obj.set('geoField', new Parse.GeoPoint(0, 0));
obj.set('innerObj', {
fileField: "data",
geoField: [1,2],
});
return obj.save();
})
.then(object => object.fetch())
.then(object => {
expect(object.get('innerObj')).toEqual({
fileField: "data",
geoField: [1,2],
});
done();
});
});
});
8 changes: 8 additions & 0 deletions src/Adapters/Storage/Mongo/MongoStorageAdapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,14 @@ export class MongoStorageAdapter {
});
}

// Executes a find. Accepts: className, query in Parse format, and { skip, limit, sort }.
// Accepts the schemaController for legacy reasons.
find(className, query, { skip, limit, sort }, schemaController) {
return this.adaptiveCollection(className)
.then(collection => collection.find(query, { skip, limit, sort }))
.then(objects => objects.map(object => transform.mongoObjectToParseObject(schemaController, className, object)));
}

get transform() {
return transform;
}
Expand Down
142 changes: 46 additions & 96 deletions src/Adapters/Storage/Mongo/MongoTransform.js
Original file line number Diff line number Diff line change
Expand Up @@ -713,25 +713,49 @@ function transformUpdateOperator({
}
}

const specialKeysForUntransform = [
'_id',
'_hashed_password',
'_acl',
'_email_verify_token',
'_perishable_token',
'_tombstone',
'_session_token',
'updatedAt',
'_updated_at',
'createdAt',
'_created_at',
'expiresAt',
'_expiresAt',
];
const nestedMongoObjectToNestedParseObject = mongoObject => {
switch(typeof mongoObject) {
case 'string':
case 'number':
case 'boolean':
return mongoObject;
case 'undefined':
case 'symbol':
case 'function':
throw 'bad value in mongoObjectToParseObject';
case 'object':
if (mongoObject === null) {
return null;
}
if (mongoObject instanceof Array) {
return mongoObject.map(nestedMongoObjectToNestedParseObject);
}

if (mongoObject instanceof Date) {
return Parse._encode(mongoObject);
}

if (mongoObject instanceof mongodb.Long) {
return mongoObject.toNumber();
}

if (mongoObject instanceof mongodb.Double) {
return mongoObject.value;
}

if (BytesCoder.isValidDatabaseObject(mongoObject)) {
return BytesCoder.databaseToJSON(mongoObject);
}

return _.mapValues(mongoObject, nestedMongoObjectToNestedParseObject);
default:
throw 'unknown js type';
}
}

// Converts from a mongo-format object to a REST-format object.
// Does not strip out anything based on a lack of authentication.
function untransformObject(schema, className, mongoObject, isNestedObject = false) {
const mongoObjectToParseObject = (schema, className, mongoObject) => {
switch(typeof mongoObject) {
case 'string':
case 'number':
Expand All @@ -740,15 +764,13 @@ function untransformObject(schema, className, mongoObject, isNestedObject = fals
case 'undefined':
case 'symbol':
case 'function':
throw 'bad value in untransformObject';
throw 'bad value in mongoObjectToParseObject';
case 'object':
if (mongoObject === null) {
return null;
}
if (mongoObject instanceof Array) {
return mongoObject.map(arrayEntry => {
return untransformObject(schema, className, arrayEntry, true);
});
return mongoObject.map(nestedMongoObjectToNestedParseObject);
}

if (mongoObject instanceof Date) {
Expand All @@ -769,10 +791,6 @@ function untransformObject(schema, className, mongoObject, isNestedObject = fals

var restObject = untransformACL(mongoObject);
for (var key in mongoObject) {
if (isNestedObject && _.includes(specialKeysForUntransform, key)) {
restObject[key] = untransformObject(schema, className, mongoObject[key], true);
continue;
}
switch(key) {
case '_id':
restObject['objectId'] = '' + mongoObject[key];
Expand Down Expand Up @@ -840,7 +858,7 @@ function untransformObject(schema, className, mongoObject, isNestedObject = fals
objectId: objData[1]
};
break;
} else if (!isNestedObject && key[0] == '_' && key != '__type') {
} else if (key[0] == '_' && key != '__type') {
throw ('bad key in untransform: ' + key);
} else {
var expectedType = schema.getExpectedType(className, key);
Expand All @@ -854,80 +872,16 @@ function untransformObject(schema, className, mongoObject, isNestedObject = fals
break;
}
}
restObject[key] = untransformObject(schema, className, mongoObject[key], true);
restObject[key] = nestedMongoObjectToNestedParseObject(mongoObject[key]);
}
}

if (!isNestedObject) {
let relationFields = schema.getRelationFields(className);
Object.assign(restObject, relationFields);
}
return restObject;
return { ...restObject, ...schema.getRelationFields(className) };
default:
throw 'unknown js type';
}
}

function transformSelect(selectObject, key ,objects) {
var values = [];
for (var result of objects) {
values.push(result[key]);
}
delete selectObject['$select'];
if (Array.isArray(selectObject['$in'])) {
selectObject['$in'] = selectObject['$in'].concat(values);
} else {
selectObject['$in'] = values;
}
}

function transformDontSelect(dontSelectObject, key, objects) {
var values = [];
for (var result of objects) {
values.push(result[key]);
}
delete dontSelectObject['$dontSelect'];
if (Array.isArray(dontSelectObject['$nin'])) {
dontSelectObject['$nin'] = dontSelectObject['$nin'].concat(values);
} else {
dontSelectObject['$nin'] = values;
}
}

function transformInQuery(inQueryObject, className, results) {
var values = [];
for (var result of results) {
values.push({
__type: 'Pointer',
className: className,
objectId: result.objectId
});
}
delete inQueryObject['$inQuery'];
if (Array.isArray(inQueryObject['$in'])) {
inQueryObject['$in'] = inQueryObject['$in'].concat(values);
} else {
inQueryObject['$in'] = values;
}
}

function transformNotInQuery(notInQueryObject, className, results) {
var values = [];
for (var result of results) {
values.push({
__type: 'Pointer',
className: className,
objectId: result.objectId
});
}
delete notInQueryObject['$notInQuery'];
if (Array.isArray(notInQueryObject['$nin'])) {
notInQueryObject['$nin'] = notInQueryObject['$nin'].concat(values);
} else {
notInQueryObject['$nin'] = values;
}
}

var DateCoder = {
JSONToDatabase(json) {
return new Date(json.iso);
Expand Down Expand Up @@ -1021,9 +975,5 @@ module.exports = {
parseObjectToMongoObjectForCreate,
transformUpdate,
transformWhere,
transformSelect,
transformDontSelect,
transformInQuery,
transformNotInQuery,
untransformObject
mongoObjectToParseObject,
};
14 changes: 3 additions & 11 deletions src/Controllers/DatabaseController.js
Original file line number Diff line number Diff line change
Expand Up @@ -146,12 +146,8 @@ DatabaseController.prototype.validateObject = function(className, object, query,
});
};

// Like transform.untransformObject but you need to provide a className.
// Filters out any data that shouldn't be on this REST-formatted object.
DatabaseController.prototype.untransformObject = function(
schema, isMaster, aclGroup, className, mongoObject) {
var object = this.transform.untransformObject(schema, className, mongoObject);

const filterSensitiveData = (isMaster, aclGroup, className, object) => {
if (className !== '_User') {
return object;
}
Expand Down Expand Up @@ -705,12 +701,8 @@ DatabaseController.prototype.find = function(className, query, {
delete mongoOptions.limit;
return collection.count(mongoWhere, mongoOptions);
} else {
return collection.find(mongoWhere, mongoOptions)
.then(mongoResults => {
return mongoResults.map(result => {
return this.untransformObject(schemaController, isMaster, aclGroup, className, result);
});
});
return this.adapter.find(className, mongoWhere, mongoOptions, schemaController)
.then(objects => objects.map(object => filterSensitiveData(isMaster, aclGroup, className, object)));
}
});
});
Expand Down
Loading