Granular CLP pointer permissions (#6352)

* set pointer permissions per operatioon; tests

* more tests

* fixes addField permission; tests
This commit is contained in:
Old Grandpa
2020-01-28 09:21:30 +03:00
committed by Antonio Davi Macedo Coelho de Castro
parent 4beb89fc2e
commit 3c46117d9b
10 changed files with 1380 additions and 37 deletions

View File

@@ -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
);
}