Skip to content

Commit ca1b782

Browse files
authored
Prevent invalid column names (className and length) (#7053)
* Prevent invalid column names * remove className as invalid * remove className from beforeSave hook response * improve tests
1 parent b398894 commit ca1b782

File tree

6 files changed

+49
-42
lines changed

6 files changed

+49
-42
lines changed

spec/CloudCode.spec.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,21 @@ describe('Cloud Code', () => {
216216
);
217217
});
218218

219+
it('test beforeSave with invalid field', async () => {
220+
Parse.Cloud.beforeSave('BeforeSaveChanged', function (req) {
221+
req.object.set('length', 0);
222+
});
223+
224+
const obj = new Parse.Object('BeforeSaveChanged');
225+
obj.set('foo', 'bar');
226+
try {
227+
await obj.save();
228+
fail('should not succeed');
229+
} catch (e) {
230+
expect(e.message).toBe('Invalid field name: length.');
231+
}
232+
});
233+
219234
it("test beforeSave changed object fail doesn't change object", async function () {
220235
Parse.Cloud.beforeSave('BeforeSaveChanged', function (req) {
221236
if (req.object.has('fail')) {

spec/ParseObject.spec.js

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -701,29 +701,24 @@ describe('Parse.Object testing', () => {
701701
});
702702
});
703703

704-
it('length attribute', function (done) {
704+
it('acl attribute', function (done) {
705705
Parse.User.signUp('bob', 'password').then(function (user) {
706706
const TestObject = Parse.Object.extend('TestObject');
707707
const obj = new TestObject({
708-
length: 5,
709708
ACL: new Parse.ACL(user), // ACLs cause things like validation to run
710709
});
711-
equal(obj.get('length'), 5);
712710
ok(obj.get('ACL') instanceof Parse.ACL);
713711

714712
obj.save().then(function (obj) {
715-
equal(obj.get('length'), 5);
716713
ok(obj.get('ACL') instanceof Parse.ACL);
717714

718715
const query = new Parse.Query(TestObject);
719716
query.get(obj.id).then(function (obj) {
720-
equal(obj.get('length'), 5);
721717
ok(obj.get('ACL') instanceof Parse.ACL);
722718

723719
const query = new Parse.Query(TestObject);
724720
query.find().then(function (results) {
725721
obj = results[0];
726-
equal(obj.get('length'), 5);
727722
ok(obj.get('ACL') instanceof Parse.ACL);
728723

729724
done();
@@ -733,6 +728,21 @@ describe('Parse.Object testing', () => {
733728
});
734729
});
735730

731+
it('cannot save object with invalid field', async () => {
732+
const invalidFields = ['className', 'length'];
733+
const promises = invalidFields.map(async field => {
734+
const obj = new TestObject();
735+
obj.set(field, 'bar');
736+
try {
737+
await obj.save();
738+
fail('should not succeed');
739+
} catch (e) {
740+
expect(e.message).toBe(`Invalid field name: ${field}.`);
741+
}
742+
});
743+
await Promise.all(promises);
744+
});
745+
736746
it('old attribute unset then unset', function (done) {
737747
const TestObject = Parse.Object.extend('TestObject');
738748
const obj = new TestObject();

spec/ParseQuery.spec.js

Lines changed: 0 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2860,33 +2860,6 @@ describe('Parse.Query testing', () => {
28602860
});
28612861
});
28622862

2863-
it('object with length', function (done) {
2864-
const TestObject = Parse.Object.extend('TestObject');
2865-
const obj = new TestObject();
2866-
obj.set('length', 5);
2867-
equal(obj.get('length'), 5);
2868-
obj.save().then(
2869-
function () {
2870-
const query = new Parse.Query(TestObject);
2871-
query.find().then(
2872-
function (results) {
2873-
equal(results.length, 1);
2874-
equal(results[0].get('length'), 5);
2875-
done();
2876-
},
2877-
function (error) {
2878-
ok(false, error.message);
2879-
done();
2880-
}
2881-
);
2882-
},
2883-
function (error) {
2884-
ok(false, error.message);
2885-
done();
2886-
}
2887-
);
2888-
});
2889-
28902863
it('include user', function (done) {
28912864
Parse.User.signUp('bob', 'password', { age: 21 }).then(function (user) {
28922865
const TestObject = Parse.Object.extend('TestObject');

src/Controllers/DatabaseController.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -564,7 +564,7 @@ class DatabaseController {
564564
}
565565
const rootFieldName = getRootFieldName(fieldName);
566566
if (
567-
!SchemaController.fieldNameIsValid(rootFieldName) &&
567+
!SchemaController.fieldNameIsValid(rootFieldName, className) &&
568568
!isSpecialUpdateKey(rootFieldName)
569569
) {
570570
throw new Parse.Error(
@@ -1213,7 +1213,7 @@ class DatabaseController {
12131213
throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, `Cannot sort by ${fieldName}`);
12141214
}
12151215
const rootFieldName = getRootFieldName(fieldName);
1216-
if (!SchemaController.fieldNameIsValid(rootFieldName)) {
1216+
if (!SchemaController.fieldNameIsValid(rootFieldName, className)) {
12171217
throw new Parse.Error(
12181218
Parse.Error.INVALID_KEY_NAME,
12191219
`Invalid field name: ${fieldName}.`

src/Controllers/HooksController.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,7 @@ function wrapToHTTPRequest(hook, key) {
242242
if (typeof result === 'object') {
243243
delete result.createdAt;
244244
delete result.updatedAt;
245+
delete result.className;
245246
}
246247
return { object: result };
247248
} else {

src/Controllers/SchemaController.js

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,8 @@ const requiredColumns = Object.freeze({
155155
_Role: ['name', 'ACL'],
156156
});
157157

158+
const invalidColumns = ['length'];
159+
158160
const systemClasses = Object.freeze([
159161
'_User',
160162
'_Installation',
@@ -422,18 +424,24 @@ function classNameIsValid(className: string): boolean {
422424
// Be a join table OR
423425
joinClassRegex.test(className) ||
424426
// Include only alpha-numeric and underscores, and not start with an underscore or number
425-
fieldNameIsValid(className)
427+
fieldNameIsValid(className, className)
426428
);
427429
}
428430

429431
// Valid fields must be alpha-numeric, and not start with an underscore or number
430-
function fieldNameIsValid(fieldName: string): boolean {
431-
return classAndFieldRegex.test(fieldName);
432+
// must not be a reserved key
433+
function fieldNameIsValid(fieldName: string, className: string): boolean {
434+
if (className && className !== '_Hooks') {
435+
if (fieldName === 'className') {
436+
return false;
437+
}
438+
}
439+
return classAndFieldRegex.test(fieldName) && !invalidColumns.includes(fieldName);
432440
}
433441

434442
// Checks that it's not trying to clobber one of the default fields of the class.
435443
function fieldNameIsValidForClass(fieldName: string, className: string): boolean {
436-
if (!fieldNameIsValid(fieldName)) {
444+
if (!fieldNameIsValid(fieldName, className)) {
437445
return false;
438446
}
439447
if (defaultColumns._Default[fieldName]) {
@@ -976,7 +984,7 @@ export default class SchemaController {
976984
) {
977985
for (const fieldName in fields) {
978986
if (existingFieldNames.indexOf(fieldName) < 0) {
979-
if (!fieldNameIsValid(fieldName)) {
987+
if (!fieldNameIsValid(fieldName, className)) {
980988
return {
981989
code: Parse.Error.INVALID_KEY_NAME,
982990
error: 'invalid field name: ' + fieldName,
@@ -1060,7 +1068,7 @@ export default class SchemaController {
10601068
fieldName = fieldName.split('.')[0];
10611069
type = 'Object';
10621070
}
1063-
if (!fieldNameIsValid(fieldName)) {
1071+
if (!fieldNameIsValid(fieldName, className)) {
10641072
throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, `Invalid field name: ${fieldName}.`);
10651073
}
10661074

@@ -1154,7 +1162,7 @@ export default class SchemaController {
11541162
}
11551163

11561164
fieldNames.forEach(fieldName => {
1157-
if (!fieldNameIsValid(fieldName)) {
1165+
if (!fieldNameIsValid(fieldName, className)) {
11581166
throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, `invalid field name: ${fieldName}`);
11591167
}
11601168
//Don't allow deleting the default fields.

0 commit comments

Comments
 (0)