Skip to content

feat: Add default ACL #8701

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Mar 24, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions spec/MongoSchemaCollectionAdapter.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ describe('MongoSchemaCollection', () => {
},
_metadata: {
class_permissions: {
ACL: {
'*': {
read: true,
write: true,
},
},
get: { '*': true },
find: { '*': true },
count: { '*': true },
Expand Down Expand Up @@ -69,6 +75,12 @@ describe('MongoSchemaCollection', () => {
objectId: { type: 'String' },
},
classLevelPermissions: {
ACL: {
'*': {
read: true,
write: true,
},
},
find: { '*': true },
get: { '*': true },
count: { '*': true },
Expand Down
28 changes: 28 additions & 0 deletions spec/ParseACL.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -923,4 +923,32 @@ describe('Parse.ACL', () => {

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();
});
});
60 changes: 60 additions & 0 deletions spec/Schema.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,12 @@ describe('SchemaController', () => {
foo: { type: 'String' },
},
classLevelPermissions: {
ACL: {
'*': {
read: true,
write: true,
},
},
find: { '*': true },
get: { '*': true },
count: { '*': true },
Expand All @@ -329,6 +335,12 @@ describe('SchemaController', () => {

it('can update classes without needing an object', done => {
const levelPermissions = {
ACL: {
'*': {
read: true,
write: true,
},
},
find: { '*': true },
get: { '*': true },
count: { '*': true },
Expand Down Expand Up @@ -489,6 +501,12 @@ describe('SchemaController', () => {
foo: { type: 'String' },
},
classLevelPermissions: {
ACL: {
'*': {
read: true,
write: true,
},
},
find: { '*': true },
get: { '*': true },
count: { '*': true },
Expand Down Expand Up @@ -694,6 +712,12 @@ describe('SchemaController', () => {

it('refuses to add CLP with incorrect find', done => {
const levelPermissions = {
ACL: {
'*': {
read: true,
write: true,
},
},
find: { '*': false },
get: { '*': true },
create: { '*': true },
Expand All @@ -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 => {
const levelPermissions = {
ACL: {
'*': {
read: true,
write: true,
},
},
find: { '*': true },
get: { '*': true },
create: { '*': true },
Expand Down Expand Up @@ -785,6 +815,12 @@ describe('SchemaController', () => {
aPolygon: { type: 'Polygon' },
},
classLevelPermissions: {
ACL: {
'*': {
read: true,
write: true,
},
},
find: { '*': true },
get: { '*': true },
count: { '*': true },
Expand Down Expand Up @@ -832,6 +868,12 @@ describe('SchemaController', () => {
parseVersion: { type: 'String' },
},
classLevelPermissions: {
ACL: {
'*': {
read: true,
write: true,
},
},
find: { '*': true },
get: { '*': true },
count: { '*': true },
Expand Down Expand Up @@ -866,6 +908,12 @@ describe('SchemaController', () => {
roles: { type: 'Relation', targetClass: '_Role' },
},
classLevelPermissions: {
ACL: {
'*': {
read: true,
write: true,
},
},
find: { '*': true },
get: { '*': true },
count: { '*': true },
Expand Down Expand Up @@ -900,6 +948,12 @@ describe('SchemaController', () => {
ACL: { type: 'ACL' },
},
classLevelPermissions: {
ACL: {
'*': {
read: true,
write: true,
},
},
find: { '*': true },
get: { '*': true },
count: { '*': true },
Expand Down Expand Up @@ -1070,6 +1124,12 @@ describe('SchemaController', () => {
relationField: { type: 'Relation', targetClass: '_User' },
},
classLevelPermissions: {
ACL: {
'*': {
read: true,
write: true,
},
},
find: { '*': true },
get: { '*': true },
count: { '*': true },
Expand Down
6 changes: 6 additions & 0 deletions spec/SchemaPerformance.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,12 @@ describe('Schema Performance', function () {
await schema.reloadData();

const levelPermissions = {
ACL: {
'*': {
read: true,
write: true,
},
},
find: { '*': true },
get: { '*': true },
create: { '*': true },
Expand Down
80 changes: 78 additions & 2 deletions spec/schemas.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ const hasAllPODobject = () => {
};

const defaultClassLevelPermissions = {
ACL: {
'*': {
read: true,
write: true,
},
},
find: {
'*': true,
},
Expand Down Expand Up @@ -2058,12 +2064,70 @@ describe('schemas', () => {
},
}).then(fail, response => {
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();
});
});

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 => {
request({
method: 'POST',
Expand All @@ -2079,7 +2143,7 @@ describe('schemas', () => {
},
}).then(fail, response => {
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();
});
Expand Down Expand Up @@ -2690,6 +2754,12 @@ describe('schemas', () => {
setPermissionsOnClass(
'_Role',
{
ACL: {
'*': {
read: true,
write: true,
},
},
get: { '*': true },
find: { '*': true },
count: { '*': true },
Expand All @@ -2710,6 +2780,12 @@ describe('schemas', () => {
})
.then(res => {
expect(res.data.classLevelPermissions).toEqual({
ACL: {
'*': {
read: true,
write: true,
},
},
get: { '*': true },
find: { '*': true },
count: { '*': true },
Expand Down
6 changes: 6 additions & 0 deletions src/Adapters/Storage/Mongo/MongoSchemaCollection.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,12 @@ const emptyCLPS = Object.freeze({
});

const defaultCLPS = Object.freeze({
ACL: {
'*': {
read: true,
write: true,
},
},
find: { '*': true },
count: { '*': true },
get: { '*': true },
Expand Down
6 changes: 6 additions & 0 deletions src/Adapters/Storage/Postgres/PostgresStorageAdapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,12 @@ const emptyCLPS = Object.freeze({
});

const defaultCLPS = Object.freeze({
ACL: {
'*': {
read: true,
write: true,
},
},
find: { '*': true },
get: { '*': true },
count: { '*': true },
Expand Down
28 changes: 25 additions & 3 deletions src/Controllers/SchemaController.js
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,7 @@ function validateProtectedFieldsKey(key, userIdRegExp) {
}

const CLPValidKeys = Object.freeze([
'ACL',
'find',
'count',
'get',
Expand Down Expand Up @@ -364,13 +365,34 @@ function validateCLP(perms: ClassLevelPermissions, fields: SchemaFields, userIdR
continue;
}

// or [entity]: boolean
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(
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}`
);
}
}
Expand Down
Loading
Loading