Granular CLP pointer permissions (#6352)
* set pointer permissions per operatioon; tests * more tests * fixes addField permission; tests
This commit is contained in:
committed by
Antonio Davi Macedo Coelho de Castro
parent
4beb89fc2e
commit
3c46117d9b
@@ -313,7 +313,15 @@ describe('Parse.Object testing', () => {
|
|||||||
|
|
||||||
it('invalid __type', function(done) {
|
it('invalid __type', function(done) {
|
||||||
const item = new Parse.Object('Item');
|
const item = new Parse.Object('Item');
|
||||||
const types = ['Pointer', 'File', 'Date', 'GeoPoint', 'Bytes', 'Polygon'];
|
const types = [
|
||||||
|
'Pointer',
|
||||||
|
'File',
|
||||||
|
'Date',
|
||||||
|
'GeoPoint',
|
||||||
|
'Bytes',
|
||||||
|
'Polygon',
|
||||||
|
'Relation',
|
||||||
|
];
|
||||||
const tests = types.map(type => {
|
const tests = types.map(type => {
|
||||||
const test = new Parse.Object('Item');
|
const test = new Parse.Object('Item');
|
||||||
test.set('foo', {
|
test.set('foo', {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -758,4 +758,37 @@ describe('ProtectedFields', function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('schema setup', () => {
|
||||||
|
const className = 'AObject';
|
||||||
|
async function updateCLP(clp) {
|
||||||
|
const config = Config.get(Parse.applicationId);
|
||||||
|
const schemaController = await config.database.loadSchema();
|
||||||
|
|
||||||
|
await schemaController.updateClass(className, {}, clp);
|
||||||
|
}
|
||||||
|
|
||||||
|
it('should fail setting non-existing protected field', async () => {
|
||||||
|
const object = new Parse.Object(className, {
|
||||||
|
revision: 0,
|
||||||
|
});
|
||||||
|
await object.save();
|
||||||
|
|
||||||
|
const field = 'non-existing';
|
||||||
|
const entity = '*';
|
||||||
|
|
||||||
|
await expectAsync(
|
||||||
|
updateCLP({
|
||||||
|
protectedFields: {
|
||||||
|
[entity]: [field],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
).toBeRejectedWith(
|
||||||
|
new Parse.Error(
|
||||||
|
Parse.Error.INVALID_JSON,
|
||||||
|
`Field '${field}' in protectedFields:${entity} does not exist`
|
||||||
|
)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1665,7 +1665,7 @@ describe('Class Level Permissions for requiredAuth', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('required auth test get not authenitcated', done => {
|
it('required auth test get not authenticated', done => {
|
||||||
config.database
|
config.database
|
||||||
.loadSchema()
|
.loadSchema()
|
||||||
.then(schema => {
|
.then(schema => {
|
||||||
@@ -1704,7 +1704,7 @@ describe('Class Level Permissions for requiredAuth', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('required auth test find not authenitcated', done => {
|
it('required auth test find not authenticated', done => {
|
||||||
config.database
|
config.database
|
||||||
.loadSchema()
|
.loadSchema()
|
||||||
.then(schema => {
|
.then(schema => {
|
||||||
|
|||||||
@@ -2752,6 +2752,115 @@ describe('schemas', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should reject creating class schema with field with invalid key', async done => {
|
||||||
|
const config = Config.get(Parse.applicationId);
|
||||||
|
const schemaController = await config.database.loadSchema();
|
||||||
|
|
||||||
|
const fieldName = '1invalid';
|
||||||
|
|
||||||
|
const schemaCreation = () =>
|
||||||
|
schemaController.addClassIfNotExists('AnObject', {
|
||||||
|
[fieldName]: { __type: 'String' },
|
||||||
|
});
|
||||||
|
|
||||||
|
await expectAsync(schemaCreation()).toBeRejectedWith(
|
||||||
|
new Parse.Error(
|
||||||
|
Parse.Error.INVALID_KEY_NAME,
|
||||||
|
`invalid field name: ${fieldName}`
|
||||||
|
)
|
||||||
|
);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reject creating invalid field name', async done => {
|
||||||
|
const object = new Parse.Object('AnObject');
|
||||||
|
|
||||||
|
await expectAsync(
|
||||||
|
object.save({
|
||||||
|
'!12field': 'field',
|
||||||
|
})
|
||||||
|
).toBeRejectedWith(new Parse.Error(Parse.Error.INVALID_KEY_NAME));
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be rejected if CLP operation is not an object', async done => {
|
||||||
|
const config = Config.get(Parse.applicationId);
|
||||||
|
const schemaController = await config.database.loadSchema();
|
||||||
|
|
||||||
|
const operationKey = 'get';
|
||||||
|
const operation = true;
|
||||||
|
|
||||||
|
const schemaSetup = async () =>
|
||||||
|
await schemaController.addClassIfNotExists(
|
||||||
|
'AnObject',
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
[operationKey]: operation,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
await expectAsync(schemaSetup()).toBeRejectedWith(
|
||||||
|
new Parse.Error(
|
||||||
|
Parse.Error.INVALID_JSON,
|
||||||
|
`'${operation}' is not a valid value for class level permissions ${operationKey} - must be an object`
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be rejected if CLP protectedFields is not an object', async done => {
|
||||||
|
const config = Config.get(Parse.applicationId);
|
||||||
|
const schemaController = await config.database.loadSchema();
|
||||||
|
|
||||||
|
const operationKey = 'get';
|
||||||
|
const operation = 'wrongtype';
|
||||||
|
|
||||||
|
const schemaSetup = async () =>
|
||||||
|
await schemaController.addClassIfNotExists(
|
||||||
|
'AnObject',
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
[operationKey]: operation,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
await expectAsync(schemaSetup()).toBeRejectedWith(
|
||||||
|
new Parse.Error(
|
||||||
|
Parse.Error.INVALID_JSON,
|
||||||
|
`'${operation}' is not a valid value for class level permissions ${operationKey} - must be an object`
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be rejected if CLP read/writeUserFields is not an array', async done => {
|
||||||
|
const config = Config.get(Parse.applicationId);
|
||||||
|
const schemaController = await config.database.loadSchema();
|
||||||
|
|
||||||
|
const operationKey = 'readUserFields';
|
||||||
|
const operation = true;
|
||||||
|
|
||||||
|
const schemaSetup = async () =>
|
||||||
|
await schemaController.addClassIfNotExists(
|
||||||
|
'AnObject',
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
[operationKey]: operation,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
await expectAsync(schemaSetup()).toBeRejectedWith(
|
||||||
|
new Parse.Error(
|
||||||
|
Parse.Error.INVALID_JSON,
|
||||||
|
`'${operation}' is not a valid value for class level permissions ${operationKey} - must be an array`
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
describe('index management', () => {
|
describe('index management', () => {
|
||||||
beforeEach(() => require('../lib/TestUtils').destroyAllDataPermanently());
|
beforeEach(() => require('../lib/TestUtils').destroyAllDataPermanently());
|
||||||
it('cannot create index if field does not exist', done => {
|
it('cannot create index if field does not exist', done => {
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ export type QueryOptions = {
|
|||||||
readPreference?: ?string,
|
readPreference?: ?string,
|
||||||
hint?: ?mixed,
|
hint?: ?mixed,
|
||||||
explain?: Boolean,
|
explain?: Boolean,
|
||||||
|
action?: string,
|
||||||
|
addsField?: boolean,
|
||||||
};
|
};
|
||||||
|
|
||||||
export type UpdateQueryOptions = {
|
export type UpdateQueryOptions = {
|
||||||
|
|||||||
@@ -553,9 +553,10 @@ class DatabaseController {
|
|||||||
className: string,
|
className: string,
|
||||||
object: any,
|
object: any,
|
||||||
query: any,
|
query: any,
|
||||||
{ acl }: QueryOptions
|
runOptions: QueryOptions
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
let schema;
|
let schema;
|
||||||
|
const acl = runOptions.acl;
|
||||||
const isMaster = acl === undefined;
|
const isMaster = acl === undefined;
|
||||||
var aclGroup: string[] = acl || [];
|
var aclGroup: string[] = acl || [];
|
||||||
return this.loadSchema()
|
return this.loadSchema()
|
||||||
@@ -564,7 +565,13 @@ class DatabaseController {
|
|||||||
if (isMaster) {
|
if (isMaster) {
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
return this.canAddField(schema, className, object, aclGroup);
|
return this.canAddField(
|
||||||
|
schema,
|
||||||
|
className,
|
||||||
|
object,
|
||||||
|
aclGroup,
|
||||||
|
runOptions
|
||||||
|
);
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
return schema.validateObject(className, object, query);
|
return schema.validateObject(className, object, query);
|
||||||
@@ -575,7 +582,7 @@ class DatabaseController {
|
|||||||
className: string,
|
className: string,
|
||||||
query: any,
|
query: any,
|
||||||
update: any,
|
update: any,
|
||||||
{ acl, many, upsert }: FullQueryOptions = {},
|
{ acl, many, upsert, addsField }: FullQueryOptions = {},
|
||||||
skipSanitization: boolean = false,
|
skipSanitization: boolean = false,
|
||||||
validateOnly: boolean = false,
|
validateOnly: boolean = false,
|
||||||
validSchemaController: SchemaController.SchemaController
|
validSchemaController: SchemaController.SchemaController
|
||||||
@@ -608,6 +615,21 @@ class DatabaseController {
|
|||||||
query,
|
query,
|
||||||
aclGroup
|
aclGroup
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (addsField) {
|
||||||
|
query = {
|
||||||
|
$and: [
|
||||||
|
query,
|
||||||
|
this.addPointerPermissions(
|
||||||
|
schemaController,
|
||||||
|
className,
|
||||||
|
'addField',
|
||||||
|
query,
|
||||||
|
aclGroup
|
||||||
|
),
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (!query) {
|
if (!query) {
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
@@ -994,7 +1016,8 @@ class DatabaseController {
|
|||||||
schema: SchemaController.SchemaController,
|
schema: SchemaController.SchemaController,
|
||||||
className: string,
|
className: string,
|
||||||
object: any,
|
object: any,
|
||||||
aclGroup: string[]
|
aclGroup: string[],
|
||||||
|
runOptions: QueryOptions
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const classSchema = schema.schemaData[className];
|
const classSchema = schema.schemaData[className];
|
||||||
if (!classSchema) {
|
if (!classSchema) {
|
||||||
@@ -1014,7 +1037,11 @@ class DatabaseController {
|
|||||||
return schemaFields.indexOf(field) < 0;
|
return schemaFields.indexOf(field) < 0;
|
||||||
});
|
});
|
||||||
if (newKeys.length > 0) {
|
if (newKeys.length > 0) {
|
||||||
return schema.validatePermission(className, aclGroup, 'addField');
|
// adds a marker that new field is being adding during update
|
||||||
|
runOptions.addsField = true;
|
||||||
|
|
||||||
|
const action = runOptions.action;
|
||||||
|
return schema.validatePermission(className, aclGroup, 'addField', action);
|
||||||
}
|
}
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
@@ -1525,28 +1552,50 @@ class DatabaseController {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Constraints query using CLP's pointer permissions (PP) if any.
|
||||||
|
// 1. Etract the user id from caller's ACLgroup;
|
||||||
|
// 2. Exctract a list of field names that are PP for target collection and operation;
|
||||||
|
// 3. Constraint the original query so that each PP field must
|
||||||
|
// point to caller's id (or contain it in case of PP field being an array)
|
||||||
addPointerPermissions(
|
addPointerPermissions(
|
||||||
schema: SchemaController.SchemaController,
|
schema: SchemaController.SchemaController,
|
||||||
className: string,
|
className: string,
|
||||||
operation: string,
|
operation: string,
|
||||||
query: any,
|
query: any,
|
||||||
aclGroup: any[] = []
|
aclGroup: any[] = []
|
||||||
) {
|
): any {
|
||||||
// Check if class has public permission for operation
|
// Check if class has public permission for operation
|
||||||
// If the BaseCLP pass, let go through
|
// If the BaseCLP pass, let go through
|
||||||
if (schema.testPermissionsForClassName(className, aclGroup, operation)) {
|
if (schema.testPermissionsForClassName(className, aclGroup, operation)) {
|
||||||
return query;
|
return query;
|
||||||
}
|
}
|
||||||
const perms = schema.getClassLevelPermissions(className);
|
const perms = schema.getClassLevelPermissions(className);
|
||||||
const field =
|
|
||||||
['get', 'find'].indexOf(operation) > -1
|
|
||||||
? 'readUserFields'
|
|
||||||
: 'writeUserFields';
|
|
||||||
const userACL = aclGroup.filter(acl => {
|
const userACL = aclGroup.filter(acl => {
|
||||||
return acl.indexOf('role:') != 0 && acl != '*';
|
return acl.indexOf('role:') != 0 && acl != '*';
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const groupKey =
|
||||||
|
['get', 'find', 'count'].indexOf(operation) > -1
|
||||||
|
? 'readUserFields'
|
||||||
|
: 'writeUserFields';
|
||||||
|
|
||||||
|
const permFields = [];
|
||||||
|
|
||||||
|
if (perms[operation] && perms[operation].pointerFields) {
|
||||||
|
permFields.push(...perms[operation].pointerFields);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (perms[groupKey]) {
|
||||||
|
for (const field of perms[groupKey]) {
|
||||||
|
if (!permFields.includes(field)) {
|
||||||
|
permFields.push(field);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
// the ACL should have exactly 1 user
|
// the ACL should have exactly 1 user
|
||||||
if (perms && perms[field] && perms[field].length > 0) {
|
if (permFields.length > 0) {
|
||||||
|
// the ACL should have exactly 1 user
|
||||||
// No user set return undefined
|
// No user set return undefined
|
||||||
// If the length is > 1, that means we didn't de-dupe users correctly
|
// If the length is > 1, that means we didn't de-dupe users correctly
|
||||||
if (userACL.length != 1) {
|
if (userACL.length != 1) {
|
||||||
@@ -1559,7 +1608,6 @@ class DatabaseController {
|
|||||||
objectId: userId,
|
objectId: userId,
|
||||||
};
|
};
|
||||||
|
|
||||||
const permFields = perms[field];
|
|
||||||
const ors = permFields.flatMap(key => {
|
const ors = permFields.flatMap(key => {
|
||||||
// constraint for single pointer setup
|
// constraint for single pointer setup
|
||||||
const q = {
|
const q = {
|
||||||
@@ -1588,7 +1636,7 @@ class DatabaseController {
|
|||||||
query: any = {},
|
query: any = {},
|
||||||
aclGroup: any[] = [],
|
aclGroup: any[] = [],
|
||||||
auth: any = {}
|
auth: any = {}
|
||||||
) {
|
): null | string[] {
|
||||||
const perms = schema.getClassLevelPermissions(className);
|
const perms = schema.getClassLevelPermissions(className);
|
||||||
if (!perms) return null;
|
if (!perms) return null;
|
||||||
|
|
||||||
|
|||||||
@@ -182,11 +182,14 @@ const publicRegex = /^\*$/;
|
|||||||
|
|
||||||
const requireAuthenticationRegex = /^requiresAuthentication$/;
|
const requireAuthenticationRegex = /^requiresAuthentication$/;
|
||||||
|
|
||||||
|
const pointerFieldsRegex = /^pointerFields$/;
|
||||||
|
|
||||||
const permissionKeyRegex = Object.freeze([
|
const permissionKeyRegex = Object.freeze([
|
||||||
roleRegex,
|
roleRegex,
|
||||||
pointerPermissionRegex,
|
pointerPermissionRegex,
|
||||||
publicRegex,
|
publicRegex,
|
||||||
requireAuthenticationRegex,
|
requireAuthenticationRegex,
|
||||||
|
pointerFieldsRegex,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
function validatePermissionKey(key, userIdRegExp) {
|
function validatePermissionKey(key, userIdRegExp) {
|
||||||
@@ -238,26 +241,19 @@ function validateCLP(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const operation = perms[operationKey];
|
const operation = perms[operationKey];
|
||||||
if (!operation) {
|
// proceed with next operationKey
|
||||||
// proceed with next operationKey
|
|
||||||
continue;
|
// throws when root fields are of wrong type
|
||||||
}
|
validateCLPjson(operation, operationKey);
|
||||||
|
|
||||||
// validate grouped pointer permissions
|
|
||||||
if (
|
if (
|
||||||
operationKey === 'readUserFields' ||
|
operationKey === 'readUserFields' ||
|
||||||
operationKey === 'writeUserFields'
|
operationKey === 'writeUserFields'
|
||||||
) {
|
) {
|
||||||
|
// validate grouped pointer permissions
|
||||||
// must be an array with field names
|
// must be an array with field names
|
||||||
if (!Array.isArray(operation)) {
|
for (const fieldName of operation) {
|
||||||
throw new Parse.Error(
|
validatePointerPermission(fieldName, fields, operationKey);
|
||||||
Parse.Error.INVALID_JSON,
|
|
||||||
`'${operation}' is not a valid value for class level permissions ${operationKey}`
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
for (const fieldName of operation) {
|
|
||||||
validatePointerPermission(fieldName, fields, operationKey);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// readUserFields and writerUserFields do not have nesdted fields
|
// readUserFields and writerUserFields do not have nesdted fields
|
||||||
// proceed with next operationKey
|
// proceed with next operationKey
|
||||||
@@ -299,11 +295,29 @@ function validateCLP(
|
|||||||
// "*" - Public,
|
// "*" - Public,
|
||||||
// "requiresAuthentication" - authenticated users,
|
// "requiresAuthentication" - authenticated users,
|
||||||
// "objectId" - _User id,
|
// "objectId" - _User id,
|
||||||
// "role:objectId",
|
// "role:rolename",
|
||||||
|
// "pointerFields" - array of field names containing pointers to users
|
||||||
for (const entity in operation) {
|
for (const entity in operation) {
|
||||||
// throws on unexpected key
|
// throws on unexpected key
|
||||||
validatePermissionKey(entity, userIdRegExp);
|
validatePermissionKey(entity, userIdRegExp);
|
||||||
|
|
||||||
|
if (entity === 'pointerFields') {
|
||||||
|
const pointerFields = operation[entity];
|
||||||
|
|
||||||
|
if (Array.isArray(pointerFields)) {
|
||||||
|
for (const pointerField of pointerFields) {
|
||||||
|
validatePointerPermission(pointerField, fields, operation);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new Parse.Error(
|
||||||
|
Parse.Error.INVALID_JSON,
|
||||||
|
`'${pointerFields}' is not a valid value for protectedFields[${entity}] - expected an array.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// proceed with next entity key
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
const permit = operation[entity];
|
const permit = operation[entity];
|
||||||
|
|
||||||
if (permit !== true) {
|
if (permit !== true) {
|
||||||
@@ -316,13 +330,34 @@ function validateCLP(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function validateCLPjson(operation: any, operationKey: string) {
|
||||||
|
if (operationKey === 'readUserFields' || operationKey === 'writeUserFields') {
|
||||||
|
if (!Array.isArray(operation)) {
|
||||||
|
throw new Parse.Error(
|
||||||
|
Parse.Error.INVALID_JSON,
|
||||||
|
`'${operation}' is not a valid value for class level permissions ${operationKey} - must be an array`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (typeof operation === 'object' && operation !== null) {
|
||||||
|
// ok to proceed
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
throw new Parse.Error(
|
||||||
|
Parse.Error.INVALID_JSON,
|
||||||
|
`'${operation}' is not a valid value for class level permissions ${operationKey} - must be an object`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function validatePointerPermission(
|
function validatePointerPermission(
|
||||||
fieldName: string,
|
fieldName: string,
|
||||||
fields: Object,
|
fields: Object,
|
||||||
operation: string
|
operation: string
|
||||||
) {
|
) {
|
||||||
// Uses collection schema to ensure the field is of type:
|
// Uses collection schema to ensure the field is of type:
|
||||||
// - Pointer<_User> (pointers/relations)
|
// - Pointer<_User> (pointers)
|
||||||
// - Array
|
// - Array
|
||||||
//
|
//
|
||||||
// It's not possible to enforce type on Array's items in schema
|
// It's not possible to enforce type on Array's items in schema
|
||||||
@@ -1340,7 +1375,8 @@ export default class SchemaController {
|
|||||||
classPermissions: ?any,
|
classPermissions: ?any,
|
||||||
className: string,
|
className: string,
|
||||||
aclGroup: string[],
|
aclGroup: string[],
|
||||||
operation: string
|
operation: string,
|
||||||
|
action?: string
|
||||||
) {
|
) {
|
||||||
if (
|
if (
|
||||||
SchemaController.testPermissions(classPermissions, aclGroup, operation)
|
SchemaController.testPermissions(classPermissions, aclGroup, operation)
|
||||||
@@ -1394,6 +1430,16 @@ export default class SchemaController {
|
|||||||
) {
|
) {
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const pointerFields = classPermissions[operation].pointerFields;
|
||||||
|
if (Array.isArray(pointerFields) && pointerFields.length > 0) {
|
||||||
|
// any op except 'addField as part of create' is ok.
|
||||||
|
if (operation !== 'addField' || action === 'update') {
|
||||||
|
// We can allow adding field on update flow only.
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
throw new Parse.Error(
|
throw new Parse.Error(
|
||||||
Parse.Error.OPERATION_FORBIDDEN,
|
Parse.Error.OPERATION_FORBIDDEN,
|
||||||
`Permission denied for action ${operation} on class ${className}.`
|
`Permission denied for action ${operation} on class ${className}.`
|
||||||
@@ -1401,12 +1447,18 @@ export default class SchemaController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Validates an operation passes class-level-permissions set in the schema
|
// Validates an operation passes class-level-permissions set in the schema
|
||||||
validatePermission(className: string, aclGroup: string[], operation: string) {
|
validatePermission(
|
||||||
|
className: string,
|
||||||
|
aclGroup: string[],
|
||||||
|
operation: string,
|
||||||
|
action?: string
|
||||||
|
) {
|
||||||
return SchemaController.validatePermission(
|
return SchemaController.validatePermission(
|
||||||
this.getClassLevelPermissions(className),
|
this.getClassLevelPermissions(className),
|
||||||
className,
|
className,
|
||||||
aclGroup,
|
aclGroup,
|
||||||
operation
|
operation,
|
||||||
|
action
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,8 @@ function RestWrite(
|
|||||||
query,
|
query,
|
||||||
data,
|
data,
|
||||||
originalData,
|
originalData,
|
||||||
clientSDK
|
clientSDK,
|
||||||
|
action
|
||||||
) {
|
) {
|
||||||
if (auth.isReadOnly) {
|
if (auth.isReadOnly) {
|
||||||
throw new Parse.Error(
|
throw new Parse.Error(
|
||||||
@@ -47,6 +48,10 @@ function RestWrite(
|
|||||||
this.runOptions = {};
|
this.runOptions = {};
|
||||||
this.context = {};
|
this.context = {};
|
||||||
|
|
||||||
|
if (action) {
|
||||||
|
this.runOptions.action = action;
|
||||||
|
}
|
||||||
|
|
||||||
if (!query) {
|
if (!query) {
|
||||||
if (this.config.allowCustomObjectId) {
|
if (this.config.allowCustomObjectId) {
|
||||||
if (
|
if (
|
||||||
|
|||||||
@@ -251,7 +251,8 @@ function update(config, auth, className, restWhere, restObject, clientSDK) {
|
|||||||
restWhere,
|
restWhere,
|
||||||
restObject,
|
restObject,
|
||||||
originalRestObject,
|
originalRestObject,
|
||||||
clientSDK
|
clientSDK,
|
||||||
|
'update'
|
||||||
).execute();
|
).execute();
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
|
|||||||
Reference in New Issue
Block a user