feat: Add default ACL (#8701)

This commit is contained in:
Daniel
2025-03-25 01:15:27 +11:00
committed by GitHub
parent b9917dd734
commit 12b5d781dc
10 changed files with 245 additions and 5 deletions

View File

@@ -18,6 +18,12 @@ describe('MongoSchemaCollection', () => {
}, },
_metadata: { _metadata: {
class_permissions: { class_permissions: {
ACL: {
'*': {
read: true,
write: true,
},
},
get: { '*': true }, get: { '*': true },
find: { '*': true }, find: { '*': true },
count: { '*': true }, count: { '*': true },
@@ -69,6 +75,12 @@ describe('MongoSchemaCollection', () => {
objectId: { type: 'String' }, objectId: { type: 'String' },
}, },
classLevelPermissions: { classLevelPermissions: {
ACL: {
'*': {
read: true,
write: true,
},
},
find: { '*': true }, find: { '*': true },
get: { '*': true }, get: { '*': true },
count: { '*': true }, count: { '*': true },

View File

@@ -923,4 +923,32 @@ describe('Parse.ACL', () => {
rest.create(config, auth.nobody(config), '_User', anonUser); rest.create(config, auth.nobody(config), '_User', anonUser);
}); });
it('support defaultACL in schema', async () => {
await new Parse.Object('TestObject').save();
const schema = await Parse.Server.database.loadSchema();
await schema.updateClass(
'TestObject',
{},
{
create: {
'*': true,
},
ACL: {
'*': { read: true },
currentUser: { read: true, write: true },
},
}
);
const acls = new Parse.ACL();
acls.setPublicReadAccess(true);
const user = await Parse.User.signUp('testuser', 'p@ssword');
const obj = new Parse.Object('TestObject');
await obj.save(null, { sessionToken: user.getSessionToken() });
expect(obj.getACL()).toBeDefined();
const acl = obj.getACL().toJSON();
expect(acl['*']).toEqual({ read: true });
expect(acl[user.id].write).toBeTrue();
expect(acl[user.id].read).toBeTrue();
});
}); });

View File

@@ -309,6 +309,12 @@ describe('SchemaController', () => {
foo: { type: 'String' }, foo: { type: 'String' },
}, },
classLevelPermissions: { classLevelPermissions: {
ACL: {
'*': {
read: true,
write: true,
},
},
find: { '*': true }, find: { '*': true },
get: { '*': true }, get: { '*': true },
count: { '*': true }, count: { '*': true },
@@ -329,6 +335,12 @@ describe('SchemaController', () => {
it('can update classes without needing an object', done => { it('can update classes without needing an object', done => {
const levelPermissions = { const levelPermissions = {
ACL: {
'*': {
read: true,
write: true,
},
},
find: { '*': true }, find: { '*': true },
get: { '*': true }, get: { '*': true },
count: { '*': true }, count: { '*': true },
@@ -489,6 +501,12 @@ describe('SchemaController', () => {
foo: { type: 'String' }, foo: { type: 'String' },
}, },
classLevelPermissions: { classLevelPermissions: {
ACL: {
'*': {
read: true,
write: true,
},
},
find: { '*': true }, find: { '*': true },
get: { '*': true }, get: { '*': true },
count: { '*': true }, count: { '*': true },
@@ -694,6 +712,12 @@ describe('SchemaController', () => {
it('refuses to add CLP with incorrect find', done => { it('refuses to add CLP with incorrect find', done => {
const levelPermissions = { const levelPermissions = {
ACL: {
'*': {
read: true,
write: true,
},
},
find: { '*': false }, find: { '*': false },
get: { '*': true }, get: { '*': true },
create: { '*': true }, create: { '*': true },
@@ -717,6 +741,12 @@ describe('SchemaController', () => {
it('refuses to add CLP when incorrectly sending a string to protectedFields object value instead of an array', done => { it('refuses to add CLP when incorrectly sending a string to protectedFields object value instead of an array', done => {
const levelPermissions = { const levelPermissions = {
ACL: {
'*': {
read: true,
write: true,
},
},
find: { '*': true }, find: { '*': true },
get: { '*': true }, get: { '*': true },
create: { '*': true }, create: { '*': true },
@@ -785,6 +815,12 @@ describe('SchemaController', () => {
aPolygon: { type: 'Polygon' }, aPolygon: { type: 'Polygon' },
}, },
classLevelPermissions: { classLevelPermissions: {
ACL: {
'*': {
read: true,
write: true,
},
},
find: { '*': true }, find: { '*': true },
get: { '*': true }, get: { '*': true },
count: { '*': true }, count: { '*': true },
@@ -832,6 +868,12 @@ describe('SchemaController', () => {
parseVersion: { type: 'String' }, parseVersion: { type: 'String' },
}, },
classLevelPermissions: { classLevelPermissions: {
ACL: {
'*': {
read: true,
write: true,
},
},
find: { '*': true }, find: { '*': true },
get: { '*': true }, get: { '*': true },
count: { '*': true }, count: { '*': true },
@@ -866,6 +908,12 @@ describe('SchemaController', () => {
roles: { type: 'Relation', targetClass: '_Role' }, roles: { type: 'Relation', targetClass: '_Role' },
}, },
classLevelPermissions: { classLevelPermissions: {
ACL: {
'*': {
read: true,
write: true,
},
},
find: { '*': true }, find: { '*': true },
get: { '*': true }, get: { '*': true },
count: { '*': true }, count: { '*': true },
@@ -900,6 +948,12 @@ describe('SchemaController', () => {
ACL: { type: 'ACL' }, ACL: { type: 'ACL' },
}, },
classLevelPermissions: { classLevelPermissions: {
ACL: {
'*': {
read: true,
write: true,
},
},
find: { '*': true }, find: { '*': true },
get: { '*': true }, get: { '*': true },
count: { '*': true }, count: { '*': true },
@@ -1070,6 +1124,12 @@ describe('SchemaController', () => {
relationField: { type: 'Relation', targetClass: '_User' }, relationField: { type: 'Relation', targetClass: '_User' },
}, },
classLevelPermissions: { classLevelPermissions: {
ACL: {
'*': {
read: true,
write: true,
},
},
find: { '*': true }, find: { '*': true },
get: { '*': true }, get: { '*': true },
count: { '*': true }, count: { '*': true },

View File

@@ -167,6 +167,12 @@ describe('Schema Performance', function () {
await schema.reloadData(); await schema.reloadData();
const levelPermissions = { const levelPermissions = {
ACL: {
'*': {
read: true,
write: true,
},
},
find: { '*': true }, find: { '*': true },
get: { '*': true }, get: { '*': true },
create: { '*': true }, create: { '*': true },

View File

@@ -26,6 +26,12 @@ const hasAllPODobject = () => {
}; };
const defaultClassLevelPermissions = { const defaultClassLevelPermissions = {
ACL: {
'*': {
read: true,
write: true,
},
},
find: { find: {
'*': true, '*': true,
}, },
@@ -2058,12 +2064,70 @@ describe('schemas', () => {
}, },
}).then(fail, response => { }).then(fail, response => {
expect(response.data.error).toEqual( expect(response.data.error).toEqual(
"'1' is not a valid value for class level permissions find:*:1" "'1' is not a valid value for class level permissions acl find:*"
); );
done(); done();
}); });
}); });
it('should validate defaultAcl with class level permissions when request is not an object', async () => {
const response = await request({
method: 'POST',
url: 'http://localhost:8378/1/schemas/AClass',
headers: masterKeyHeaders,
json: true,
body: {
classLevelPermissions: {
ACL: {
'*': true,
},
},
},
}).catch(error => error.data);
expect(response.error).toEqual(`'true' is not a valid value for class level permissions acl`);
});
it('should validate defaultAcl with class level permissions when request is an object and invalid key', async () => {
const response = await request({
method: 'POST',
url: 'http://localhost:8378/1/schemas/AClass',
headers: masterKeyHeaders,
json: true,
body: {
classLevelPermissions: {
ACL: {
'*': {
foo: true,
},
},
},
},
}).catch(error => error.data);
expect(response.error).toEqual(`'foo' is not a valid key for class level permissions acl`);
});
it('should validate defaultAcl with class level permissions when request is an object and invalid value', async () => {
const response = await request({
method: 'POST',
url: 'http://localhost:8378/1/schemas/AClass',
headers: masterKeyHeaders,
json: true,
body: {
classLevelPermissions: {
ACL: {
'*': {
read: 1,
},
},
},
},
}).catch(error => error.data);
expect(response.error).toEqual(`'1' is not a valid value for class level permissions acl`);
});
it('should throw if permission is empty string', done => { it('should throw if permission is empty string', done => {
request({ request({
method: 'POST', method: 'POST',
@@ -2079,7 +2143,7 @@ describe('schemas', () => {
}, },
}).then(fail, response => { }).then(fail, response => {
expect(response.data.error).toEqual( expect(response.data.error).toEqual(
"'' is not a valid value for class level permissions find:*:" `'' is not a valid value for class level permissions acl find:*`
); );
done(); done();
}); });
@@ -2690,6 +2754,12 @@ describe('schemas', () => {
setPermissionsOnClass( setPermissionsOnClass(
'_Role', '_Role',
{ {
ACL: {
'*': {
read: true,
write: true,
},
},
get: { '*': true }, get: { '*': true },
find: { '*': true }, find: { '*': true },
count: { '*': true }, count: { '*': true },
@@ -2710,6 +2780,12 @@ describe('schemas', () => {
}) })
.then(res => { .then(res => {
expect(res.data.classLevelPermissions).toEqual({ expect(res.data.classLevelPermissions).toEqual({
ACL: {
'*': {
read: true,
write: true,
},
},
get: { '*': true }, get: { '*': true },
find: { '*': true }, find: { '*': true },
count: { '*': true }, count: { '*': true },

View File

@@ -76,6 +76,12 @@ const emptyCLPS = Object.freeze({
}); });
const defaultCLPS = Object.freeze({ const defaultCLPS = Object.freeze({
ACL: {
'*': {
read: true,
write: true,
},
},
find: { '*': true }, find: { '*': true },
count: { '*': true }, count: { '*': true },
get: { '*': true }, get: { '*': true },

View File

@@ -127,6 +127,12 @@ const emptyCLPS = Object.freeze({
}); });
const defaultCLPS = Object.freeze({ const defaultCLPS = Object.freeze({
ACL: {
'*': {
read: true,
write: true,
},
},
find: { '*': true }, find: { '*': true },
get: { '*': true }, get: { '*': true },
count: { '*': true }, count: { '*': true },

View File

@@ -255,6 +255,7 @@ function validateProtectedFieldsKey(key, userIdRegExp) {
} }
const CLPValidKeys = Object.freeze([ const CLPValidKeys = Object.freeze([
'ACL',
'find', 'find',
'count', 'count',
'get', 'get',
@@ -364,13 +365,34 @@ function validateCLP(perms: ClassLevelPermissions, fields: SchemaFields, userIdR
continue; continue;
} }
// or [entity]: boolean
const permit = operation[entity]; const permit = operation[entity];
if (permit !== true) { if (operationKey === 'ACL') {
if (Object.prototype.toString.call(permit) !== '[object Object]') {
throw new Parse.Error(
Parse.Error.INVALID_JSON,
`'${permit}' is not a valid value for class level permissions acl`
);
}
const invalidKeys = Object.keys(permit).filter(key => !['read', 'write'].includes(key));
const invalidValues = Object.values(permit).filter(key => typeof key !== 'boolean');
if (invalidKeys.length) {
throw new Parse.Error(
Parse.Error.INVALID_JSON,
`'${invalidKeys.join(',')}' is not a valid key for class level permissions acl`
);
}
if (invalidValues.length) {
throw new Parse.Error(
Parse.Error.INVALID_JSON,
`'${invalidValues.join(',')}' is not a valid value for class level permissions acl`
);
}
} else if (permit !== true) {
throw new Parse.Error( throw new Parse.Error(
Parse.Error.INVALID_JSON, Parse.Error.INVALID_JSON,
`'${permit}' is not a valid value for class level permissions ${operationKey}:${entity}:${permit}` `'${permit}' is not a valid value for class level permissions acl ${operationKey}:${entity}`
); );
} }
} }

View File

@@ -19,6 +19,11 @@ export type Schema = {
}; };
export type ClassLevelPermissions = { export type ClassLevelPermissions = {
ACL?: {
[string]: {
[string]: boolean,
},
},
find?: { [string]: boolean }, find?: { [string]: boolean },
count?: { [string]: boolean }, count?: { [string]: boolean },
get?: { [string]: boolean }, get?: { [string]: boolean },

View File

@@ -367,6 +367,25 @@ RestWrite.prototype.setRequiredFieldsIfNeeded = function () {
} }
}; };
// add default ACL
if (
schema?.classLevelPermissions?.ACL &&
!this.data.ACL &&
JSON.stringify(schema.classLevelPermissions.ACL) !==
JSON.stringify({ '*': { read: true, write: true } })
) {
const acl = deepcopy(schema.classLevelPermissions.ACL);
if (acl.currentUser) {
if (this.auth.user?.id) {
acl[this.auth.user?.id] = deepcopy(acl.currentUser);
}
delete acl.currentUser;
}
this.data.ACL = acl;
this.storage.fieldsChangedByTrigger = this.storage.fieldsChangedByTrigger || [];
this.storage.fieldsChangedByTrigger.push('ACL');
}
// Add default fields // Add default fields
if (!this.query) { if (!this.query) {
// allow customizing createdAt and updatedAt when using maintenance key // allow customizing createdAt and updatedAt when using maintenance key