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(ProjectHistoryLogs): create ph logs for permissions changes TASK-944 #5297

Merged
merged 9 commits into from
Dec 2, 2024

Conversation

rgraber
Copy link
Contributor

@rgraber rgraber commented Nov 25, 2024

🗒️ Checklist

  1. run linter locally
  2. update all related docs (API, README, inline, etc.), if any
  3. draft PR with a title <type>(<scope>)<!>: <title> TASK-1234
  4. tag PR: at least frontend or backend unless it's global
  5. fill in the template below and delete template comments
  6. review thyself: read the diff and repro the preview as written
  7. open PR & confirm that CI passes
  8. request reviewers, if needed
  9. delete this section before merging

📣 Summary

Create project history logs when users are assigned permissions or have permissions revoked for a project.

👀 Preview steps

Note: for this test, all PH logs should have metadata['log_subtype']='permission' and metadata['asset_uid']=<the uid of the project you're updating>

  1. ℹ️ have 3 accounts (at least 2 non-superuser). For this test we'll call them user1, user2, and user3 (user2 and user3 are not super)
  2. ℹ️ have a project owned by user1 with at least 1 submission
  3. Log in as user1
  4. Go to Project > Settings > Sharing
  5. Toggle 'Allow submissions to this form without a username and password'
  6. Go to /api/v2/audit-logs/?q=log_type:project-history AND metadata__asset_uid:<Project1.uid>
  7. 🟢 There should be a new PH log with action='allow-anonymous-submissions' (usual metadata)
  8. Toggle 'Allow submissions...' off again
  9. 🟢 Reload the endpoint. There should be a new PH log with action='disallow-anonymous-submissions'
  10. Check "Anyone can view this form"
  11. 🟢 Reload the endpoint. There should be a new PH log with action='share-form-publicly'
  12. Check "Anyone can view submissions made to this form"
  13. 🟢 Reload the endpoint. There should be a new PH log with action='share-data-publicly'
  14. Uncheck "Anyone can view submissions made to this form"
  15. 🟢 Reload the endpoint. There should be a new PH log with action='unshare-data-publicly'
  16. Uncheck "Anyone can view this form"
  17. 🟢 Reload the endpoint. There should be a new PH log with action='unshare-form-publicly'
  18. Click 'Add User'. Grant user2 permission to Edit Form
  19. 🟢 Reload the endpoint. There should be a new PH log with action='modify-user-permissions' and
metadata['permissions'] =
{
"username": "user2",
"added": ["change_asset", "view_asset"],  # order doesn't matter
"removed":[]
}
  1. Uncheck "Edit form" and check "Edit submissions only from specific users" -> "user2"
  2. Click "Update permissions"
  3. 🟢 Reload the endpoint. There should be a new PH log with action='modify-user-permissions' and
metadata['permissions'] =
{
"username": "user2",
"added": [             # order doesn't matter
    "partial_submissions",
    "add_submissions", 
    {
         "code": "view_submissions",
          "filters": [
                {"_submitted_by": "user2"}
          ],
    },
    {
         "code": "add_submissions",
          "filters": [
                {"_submitted_by": "user2"}
          ],
    },
    {
         "code": "change_submissions",
          "filters": [
                {"_submitted_by": "user2"}
          ],
    },
], 
"removed":["change_asset"]
}
  1. Click the trashcan next to "user2" to remove all permissions
  2. 🟢 Reload the endpoint. Deletion will actually be 2 delete, requests, so you'll end up with 2 new ph logs with
metadata['permissions'] =
{
"username": "user2",
"added": [],  
"removed": ["partial_submissions", "view_asset"]    # order doesn't matter
}

and

metadata['permissions'] =
{
"username": "user2",
"added": [],  
"removed":["add_submissions"]
}
  1. Copy the following into a tmp.json file (unless you really want to type the whole json into the command line):
[
    {
	"user":"http://localhost/api/v2/users/user2/",
	"permission": "http://localhost/api/v2/permissions/add_submissions/"
    },
    {
	"user": "http://localhost/api/v2/users/user3/",
	"permission": "http://localhost/api/v2/permissions/view_asset/"
    }
 
]
  1. In a terminal, run curl -v -X POST -H "Content-Type: application/json" -H "Authorization: Token <your token>" http://localhost/api/v2/assets/<asset_uid>/permission-assignments/bulk/?format=json -d @/tmp.json
  2. 🟢 Reload the endpoint. You should see 2 new PH logs, one with the new permissions for user2 and one for user3.

💭 Notes

This PR does a lot:

  1. Update assign_perm to call post_assign_perm right after we create the ObjectPermission object. This way it is called when we assign implied permissions as well. There's only one other thing listening to the signal and it only cares about when we grant anonymous users the 'add_submission' permission.
  2. Update remove_perm to call post_remove_perm iff we actually delete anything
  3. Add new signals for adding/removing partial permissions objects
  4. Listen for permission signals. Add permissions added/removed to the request in a dictionary keyed by username. We get the request from get_current_request because passing the request around wherever permissions might change is too complicated and would require at least one major refactor.
  5. At the end of the request, assuming it succeeded, use the dictionaries stored on the request object to create PH logs for each user whose permissions were updated

Additional notes:
Partial permissions are handled a bit differently than normal ones since they are not additive. Every time we set partial permissions, we're wiping away any old partial permissions.

I tried to be thorough in the unit tests but there are a LOT of different ways permissions can change, especially with a bulk request.

This PR purposely leaves out changes that result from collection actions. Those will be dealt with later. Similarly, the PR does not handle the v1 endpoint or requests to transfer ownership.

@rgraber rgraber changed the title feat: create ph logs for permissions changes feat(ProjectHistoryLogs): create ph logs for permissions changes Nov 25, 2024
@rgraber rgraber force-pushed the rsgraber/TASK-944-permissions-better branch from 5a181da to d0c0d94 Compare November 26, 2024 16:32
@rgraber rgraber force-pushed the rsgraber/TASK-944-permissions-better branch from 7c0f25f to 8be6200 Compare November 26, 2024 19:18
@rgraber rgraber changed the title feat(ProjectHistoryLogs): create ph logs for permissions changes feat(ProjectHistoryLogs): create ph logs for permissions changes TASK 944 Nov 26, 2024
@rgraber rgraber changed the title feat(ProjectHistoryLogs): create ph logs for permissions changes TASK 944 feat(ProjectHistoryLogs): create ph logs for permissions changes TASK-944 Nov 26, 2024
@rgraber rgraber self-assigned this Nov 26, 2024
@rgraber rgraber requested a review from Guitlle November 26, 2024 21:23
@rgraber rgraber marked this pull request as ready for review November 26, 2024 21:23
Copy link
Contributor

@noliveleger noliveleger left a comment

Choose a reason for hiding this comment

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

LGTM, although I haven't done an in-depth review. I leave that to Guillermo.
Just left some aesthetic comments.

# remove each permission as it is logged so we can see if there
# are any unusual ones left over
for combination, action in ANONYMOUS_USER_PERMISSION_ACTIONS.items():
# ANONYMOUS_USER_PERMIISSION_ACTIONS has tuples as keys
Copy link
Contributor

Choose a reason for hiding this comment

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

typo ;-)

Comment on lines +55 to +93
@receiver(post_assign_perm, sender=Asset)
def add_assigned_perms(sender, instance, user, codename, deny, **kwargs):
request = initialize_request()
if not request or instance.asset_type != ASSET_TYPE_SURVEY or deny:
return
request.permissions_added[user.username].add(codename)
request.permissions_removed[user.username].discard(codename)


@receiver(post_remove_perm, sender=Asset)
def add_removed_perms(sender, instance, user, codename, **kwargs):
request = initialize_request()
if not request or instance.asset_type != ASSET_TYPE_SURVEY:
return
request.permissions_removed[user.username].add(codename)
request.permissions_added[user.username].discard(codename)


@receiver(post_assign_partial_perm, sender=Asset)
def add_assigned_partial_perms(sender, instance, user, perms, **kwargs):
request = initialize_request()
if not request or instance.asset_type != ASSET_TYPE_SURVEY:
return
perms_as_list_of_dicts = [{'code': k, 'filters': v} for k, v in perms.items()]
# partial permissions are replaced rather than added
request.partial_permissions_added[user.username] = perms_as_list_of_dicts


@receiver(post_remove_partial_perms, sender=Asset)
def remove_partial_perms(sender, instance, user, **kwargs):
request = initialize_request()
if not request or instance.asset_type != ASSET_TYPE_SURVEY:
return
request.partial_permissions_added[user.username] = []
# in case we somehow deleted partial permissions
# without deleting 'partial_submissions', take care of that now since
# we can't have one without the other
request.permissions_added[user.username].discard(PERM_PARTIAL_SUBMISSIONS)
request.permissions_removed[user.username].add(PERM_PARTIAL_SUBMISSIONS)
Copy link
Contributor

Choose a reason for hiding this comment

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

A -> Z 🙏

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'm going to alphabetize everything in one big PR after all the functionality is done

sender=Asset,
instance=self.asset,
user=self.user,
codename=PERM_VIEW_ASSET,
Copy link
Contributor

@noliveleger noliveleger Nov 30, 2024

Choose a reason for hiding this comment

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

Should it be PERM_PARTIAL_SUBMISSIONS? or you did it in purpose because of your test?
If it is the latter, you should add a comment because PERM_VIEW_ASSET should never be added with partial perms.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In this case it doesn't matter since I'm just testing the signal, not actually adding permissions to anything.

Copy link
Contributor

@Guitlle Guitlle left a comment

Choose a reason for hiding this comment

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

Made some minor suggestions via chat, the rest of functionality and code looks great, and it works like a charm.

@rgraber rgraber merged commit 4c3da9b into main Dec 2, 2024
5 checks passed
@rgraber rgraber deleted the rsgraber/TASK-944-permissions-better branch December 2, 2024 20:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants