Skip to content
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

feat: respect field level permissions on bulk-update #120

Draft
wants to merge 7 commits into
base: master
Choose a base branch
from

Conversation

tomwwinter
Copy link
Contributor

@tomwwinter tomwwinter commented Jan 17, 2024

@tomwwinter tomwwinter marked this pull request as ready for review January 18, 2024 14:21
Copy link
Contributor

@TheSlimvReal TheSlimvReal left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Functionality seems got. But lets

  • add a test for removing some properties
  • merge the logic for the singe doc and the bulk docs request so they behave the same
  • also add a check for posting attachments (/{db}/{docId}/{property}) this should only work if the users has permissions for the property a attachment refers to.

A

};

jest.spyOn(mockRulesService, 'getRulesForUser').mockReturnValue([
{ action: 'delete', subject: 'Child', fields: ['name'] },
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure whether this kind of rule makes sense for us to support. I am not even sure how I should understand the rule here: Does it mean I am allowed to delete the name property or does it mean that I am allowed to delete the entity and change the name property?. How we (or PouchDB) currently delete is by only sending a doc with the _deleted property set without the remaining properties (see screenshot below). So what you are describing here is more like a update and delete. I think it would make sense that for delete rule we do not implement any field level logic but only use this for read and update (and also create?) rules.

Screenshot 2024-01-22 at 10 39 42

// User deletes a document, permissions can't be checked directly
const deletedPublicSchool: DatabaseDocument = {
_id: publicSchool._id,
_rev: publicSchool._rev,
_revisions: publicSchool._revisions,
_deleted: true,
anotherProperty: 'anotherValue',
privateSchool: false,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We explicitly implemented the _delete endpoint so it does not keep the other properties to support GDPR regulations. The document needs to be kept for synchronisation reasons but all other info on the doc should be removed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was not aware of this regulation, thanks.

updatedDoc[fieldKeys[i]] === null
) {
delete existingDoc.doc[fieldKeys[i]];
delete updatedDoc[fieldKeys[i]];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a test for this behavior?

return Object.assign(existingDoc ? existingDoc.doc : {}, updatedDoc);
}

private removeFieldsWithoutPermissions(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we move this logic into a separate service so that it can be used by all endpoints that send changes to the CouchDB?

if (existingDoc) {
if (updatedDoc._deleted) {
existingDoc.doc._deleted = true;
return existingDoc.doc;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's make sure to delete all properties on the doc too (maybe keeping updated and created)

Comment on lines +166 to +170
for (let i = 0; i < fieldKeys.length; i++) {
if (!ability.can(action, updatedDoc, fieldKeys[i])) {
delete updatedDoc[fieldKeys[i]];
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
for (let i = 0; i < fieldKeys.length; i++) {
if (!ability.can(action, updatedDoc, fieldKeys[i])) {
delete updatedDoc[fieldKeys[i]];
}
}
for (const key of fieldKeys) {
if (!ability.can(action, updatedDoc, key)) {
delete updatedDoc[key];
}
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, any reason why you did not got with the approach we already implemented in the applyPermissions function inside the DocumentController? Using permittedFieldsOf and pick the code seems much shorter and clearer.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this function in a Controller? Did not saw it there.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I showed it to you initially when we talked about the requirements for this feature. It was only in the controller because we did not yet use it in other places.

action = 'create';
}

const fieldKeys = this.getCustomFieldKeys(updatedDoc);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think using the CASL function permittedFieldsOf and combining the results with the DEFAULT_FIELDS would make this a bit more explicit.

updatedDoc: DatabaseDocument,
existingDoc: DocMetaInf,
) {
const fieldKeys = this.getCustomFieldKeys(updatedDoc);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Its a bit weird that you have to call this again. You could just pass the values from the other function.

};
}

private deleteEmptyValues(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the review and later following the code I think it would help to sort the functions in a file according to their importance and call order. Having this function before removeFieldsWithoutPermissions doesn't really make sense.

@sleidig sleidig added the Status: Core Team Priority Prioritized tasks that will be handled by the core team. (Community volunteers, please do not pick) label Jan 26, 2024
@sleidig sleidig marked this pull request as draft March 20, 2024 12:58
@tomwwinter tomwwinter force-pushed the master branch 3 times, most recently from 660be7f to 189badc Compare April 16, 2024 19:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Status: Core Team Priority Prioritized tasks that will be handled by the core team. (Community volunteers, please do not pick)
Projects
Status: Todo (ready for work)
Development

Successfully merging this pull request may close these issues.

3 participants