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
@@ -182,11 +182,14 @@ const publicRegex = /^\*$/;
|
||||
|
||||
const requireAuthenticationRegex = /^requiresAuthentication$/;
|
||||
|
||||
const pointerFieldsRegex = /^pointerFields$/;
|
||||
|
||||
const permissionKeyRegex = Object.freeze([
|
||||
roleRegex,
|
||||
pointerPermissionRegex,
|
||||
publicRegex,
|
||||
requireAuthenticationRegex,
|
||||
pointerFieldsRegex,
|
||||
]);
|
||||
|
||||
function validatePermissionKey(key, userIdRegExp) {
|
||||
@@ -238,26 +241,19 @@ function validateCLP(
|
||||
}
|
||||
|
||||
const operation = perms[operationKey];
|
||||
if (!operation) {
|
||||
// proceed with next operationKey
|
||||
continue;
|
||||
}
|
||||
// proceed with next operationKey
|
||||
|
||||
// throws when root fields are of wrong type
|
||||
validateCLPjson(operation, operationKey);
|
||||
|
||||
// validate grouped pointer permissions
|
||||
if (
|
||||
operationKey === 'readUserFields' ||
|
||||
operationKey === 'writeUserFields'
|
||||
) {
|
||||
// validate grouped pointer permissions
|
||||
// must be an array with field names
|
||||
if (!Array.isArray(operation)) {
|
||||
throw new Parse.Error(
|
||||
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);
|
||||
}
|
||||
for (const fieldName of operation) {
|
||||
validatePointerPermission(fieldName, fields, operationKey);
|
||||
}
|
||||
// readUserFields and writerUserFields do not have nesdted fields
|
||||
// proceed with next operationKey
|
||||
@@ -299,11 +295,29 @@ function validateCLP(
|
||||
// "*" - Public,
|
||||
// "requiresAuthentication" - authenticated users,
|
||||
// "objectId" - _User id,
|
||||
// "role:objectId",
|
||||
// "role:rolename",
|
||||
// "pointerFields" - array of field names containing pointers to users
|
||||
for (const entity in operation) {
|
||||
// throws on unexpected key
|
||||
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];
|
||||
|
||||
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(
|
||||
fieldName: string,
|
||||
fields: Object,
|
||||
operation: string
|
||||
) {
|
||||
// Uses collection schema to ensure the field is of type:
|
||||
// - Pointer<_User> (pointers/relations)
|
||||
// - Pointer<_User> (pointers)
|
||||
// - Array
|
||||
//
|
||||
// It's not possible to enforce type on Array's items in schema
|
||||
@@ -1340,7 +1375,8 @@ export default class SchemaController {
|
||||
classPermissions: ?any,
|
||||
className: string,
|
||||
aclGroup: string[],
|
||||
operation: string
|
||||
operation: string,
|
||||
action?: string
|
||||
) {
|
||||
if (
|
||||
SchemaController.testPermissions(classPermissions, aclGroup, operation)
|
||||
@@ -1394,6 +1430,16 @@ export default class SchemaController {
|
||||
) {
|
||||
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(
|
||||
Parse.Error.OPERATION_FORBIDDEN,
|
||||
`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
|
||||
validatePermission(className: string, aclGroup: string[], operation: string) {
|
||||
validatePermission(
|
||||
className: string,
|
||||
aclGroup: string[],
|
||||
operation: string,
|
||||
action?: string
|
||||
) {
|
||||
return SchemaController.validatePermission(
|
||||
this.getClassLevelPermissions(className),
|
||||
className,
|
||||
aclGroup,
|
||||
operation
|
||||
operation,
|
||||
action
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user