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

Tagging: serialize object permissions to REST API [FC-0036] #138

Merged
merged 20 commits into from
Jan 24, 2024

Conversation

pomegranited
Copy link
Contributor

@pomegranited pomegranited commented Jan 4, 2024

Description

Adds "user permissions" to the Tagging REST API to show what permissions the current request.user has on the objects serialized in the response.

Supporting information

Part of openedx/modular-learning#160

Testing instructions

See openedx/frontend-app-authoring#787 for manual testing instructions.

Deadline

None

Other information

TO DO (Re comment:)

  • Please include a test that loads a bunch of taxonomies/tags/objecttags as a non-superuser and makes sure the query count is only increased by at most 1 when permissions are included in the response.

    Done, see test_views.py.

  • Do we want to allow API consumers to choose whether the perms info is included in the REST API responses or not?
    It's probably simpler to just always include them, unless there is some performance or response size penalty.

    I decided to add an include_perms parameter which triggers the permission checks. These checks are cheap to run in this library, but users of the library may customize their permissions (like we did with content_tagging) and so the checks may not always be this quick to run.

  • bump version

Private ref: FAL-3577

to get test coverage back to 100% for this file.
@openedx-webhooks openedx-webhooks added the open-source-contribution PR author is not from Axim or 2U label Jan 4, 2024
@openedx-webhooks
Copy link

openedx-webhooks commented Jan 4, 2024

Thanks for the pull request, @pomegranited! Please note that it may take us up to several weeks or months to complete a review and merge your PR.

Feel free to add as much of the following information to the ticket as you can:

  • supporting documentation
  • Open edX discussion forum threads
  • timeline information ("this must be merged by XX date", and why that is)
  • partner information ("this is a course on edx.org")
  • any other information that can help Product understand the context for the PR

All technical communication about the code itself will be done via the GitHub pull request interface. As a reminder, our process documentation is here.

Please let us know once your PR is ready for our review and all tests are green.

@bradenmacdonald
Copy link
Contributor

A few thoughts:

  • please include a test that loads a bunch of taxonomies/tags/objecttags as a non-superuser and makes sure the query count is only increased by at most 1 when permissions are included in the response
  • The permissions for all tags in a taxonomy are always the same, right? And the permissions for an ObjectTag always are the same as the permissions for the Object itself? So I'm tempted to say don't include permissions for them - just include perms at the taxonomy level or course component level. But I realize that's making an assumption that this won't change in the future. If we include the perms now with every Taxonomy/Tag/ObjectTag as you're proposing, and make the frontend work accordingly, then we can be super flexible about how permissions work in the future if we want to, and won't need to make any changes to the frontend. So that makes sense.
  • Do we want to allow API consumers to choose whether the perms info is included in the REST API responses or not? It's probably simpler to just always include them, unless there is some performance or response size penalty.

These permissions reflect the current request.user's allowed actions
on the instances in the results list.
frontend will use userPermissions instead
…APIs

Uses a custom paginator to add this extra data into the response.
* model-level permissions: only need can_add
* instance-level permissions: only need can_change and can_delete
Also flattens the serialized user permissions so the flags appear
directly on the instance instead of in a "user_permissions" dict.
to tell REST API users whether the user may add object tags for this
object_id + taxonomy.
* `can_<action>` field will be None unless ?include_perms requested
* adds tests to verify query counts are unchanged when perms requested
This permission is needed by the Content Tag Drawer, which fetches the
list of taxonomies available for tagging a given content item, and shows
an "Add tags" button for the ones the user may add Object Tags for.
fixes type/testing issues with previous commit
to alleviate confusion about what permissions they reflect.
for a given taxonomy + object_id, because we are adding and deleting
ObjectTags here, not editing the individual instances.
@pomegranited pomegranited force-pushed the jill/serialize-permissions branch 4 times, most recently from b9797ab to bb1a0a2 Compare January 15, 2024 06:22
@pomegranited pomegranited marked this pull request as ready for review January 15, 2024 06:31
Comment on lines 266 to 268
if isinstance(instance, Tag):
return super()._can('change', instance)
return None
Copy link
Contributor Author

Choose a reason for hiding this comment

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

from comment:

The permissions for all tags in a taxonomy are always the same, right? ... So I'm tempted to say don't include permissions for them - just include perms at the taxonomy level or course component level. But I realize that's making an assumption that this won't change in the future. If we include the perms now with every Taxonomy/Tag/ObjectTag as you're proposing, and make the frontend work accordingly, then we can be super flexible about how permissions work in the future if we want to, and won't need to make any changes to the frontend. So that makes sense.

Here is where that plan breaks down, because we don't currently have a way to check use permissions for TagData instances (rather than Tag instances). I could create a dummy Tag here from the TagData instance and use that to check the permissions, but that will likely trigger another database query (for the Tag.taxonomy field).

But we could use the UserPermissionsPagination class to put permissions for the taxonomy itself into the top level like we did for can_add?

Copy link
Contributor

@bradenmacdonald bradenmacdonald Jan 15, 2024

Choose a reason for hiding this comment

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

@pomegranited For now, the permissions of every tag in the taxonomy are the same. But we want to design the REST API to be flexible in the future, as I mentioned. So I would suggest this:

When you initialize a TagDataSerializer, pass in the can_change and can_delete permissions as context, just the same way that we currently pass in taxonomy_id and request as context. Then just change get_can_change and get_can_delete to return the value from the context, or omit those fields if the data is not available in the context. This will be simple, complete, and works with both TagData and Tag. It also doesn't use any per-instance queries and maintains a flexible API so that if we need perms that vary per-tag in the future, we can change this to do that somehow, without needing to change the REST API or the frontend.


You could also use the self.context["taxonomy_id"] to look up the taxonomy and thus the permissions for each TagData instance, but I think that will result in too many queries unless you add some caching. And then you have to deal with caching.

Copy link
Contributor

@rpenido rpenido Jan 15, 2024

Choose a reason for hiding this comment

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

I'm failing to find a hypothetical case where we need different Tag and Taxonomy permissions. Do you see any @pomegranited and @bradenmacdonald?

If we can't foresee a use for different permissions, I think we could move closer to removing the Tag permissions and keeping it simple instead of making it more flexible. Of course, only if we can't see a use for it in the future.

So, now, I'm more inclined to use the Taxonomy canTagObject permission for Tagging and the Taxonomy canChange for Tag editing (that we don't have frontend yet), skipping serializing permission at TagDataSerializer.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@rpenido

So, now, I'm more inclined to use the Taxonomy canTagObject permission for Tagging and the Taxonomy canChange for Tag editing (that we don't have frontend yet), skipping serializing permission at TagDataSerializer.

I ended up going with @bradenmacdonald 's suggestion for the TagData:

When you initialize a TagDataSerializer, pass in the can_change and can_delete permissions as context, just the same way that we currently pass in taxonomy_id and request as context.

...so we have the permissions data in the REST API whenever we start editing taxonomy tags in the UI.

But I'm equally ok with removing this work if we feel like it's more unused code we shouldn't be maintaining yet.

Copy link
Contributor

Choose a reason for hiding this comment

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

Hi, @pomegranited!
While reviewing, it took me some time to get the permission context again, and I felt we were overengineering a bit with some "to be used in the future" features.

If you (who wrote this change) didn't feel this way, we probably don't need to simplify it. If this is the case, I'm ok with the current solutions!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thank you for your thoughts here @rpenido ! I'll leave this comment thread unresolved so upstream/CCs can see it too, and will see what they say.

@pomegranited pomegranited requested a review from rpenido January 15, 2024 06:40
Comment on lines 118 to 121
perm_name = f'{app_label}.add_objecttag'
perm_object = ObjectTagPermissionItem(taxonomy=instance, object_id="")
return request.user.has_perm(perm_name, perm_object) # type: ignore[arg-type]

Copy link
Contributor

Choose a reason for hiding this comment

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

We have change_objecttag_taxonomy rule that checks only the Taxonomy leg of the permission.

Suggested change
perm_name = f'{app_label}.add_objecttag'
perm_object = ObjectTagPermissionItem(taxonomy=instance, object_id="")
return request.user.has_perm(perm_name, perm_object) # type: ignore[arg-type]
perm_name = f'{app_label}.change_objecttag_taxonomy'
return request.user.has_perm(perm_name, instance) # type: ignore[arg-type]

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@rpenido

Since people may override these rules when they use the library, I'd rather call the canonical permission, and let the rule itself call change_objecttag_taxonomy.

Copy link
Contributor

Choose a reason for hiding this comment

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

Sure! Either way is acceptable to me.

Comment on lines 266 to 268
if isinstance(instance, Tag):
return super()._can('change', instance)
return None
Copy link
Contributor

@rpenido rpenido Jan 15, 2024

Choose a reason for hiding this comment

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

I'm failing to find a hypothetical case where we need different Tag and Taxonomy permissions. Do you see any @pomegranited and @bradenmacdonald?

If we can't foresee a use for different permissions, I think we could move closer to removing the Tag permissions and keeping it simple instead of making it more flexible. Of course, only if we can't see a use for it in the future.

So, now, I'm more inclined to use the Taxonomy canTagObject permission for Tagging and the Taxonomy canChange for Tag editing (that we don't have frontend yet), skipping serializing permission at TagDataSerializer.

@rpenido rpenido self-requested a review January 16, 2024 14:33
Copy link
Contributor

@rpenido rpenido left a comment

Choose a reason for hiding this comment

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

@pomegranited LGTM 👍

which means a user may add or delete ObjectTags using a given Taxonomy + object_id.

This permission is used in the bulk tag update REST API endpoint, and
when serializing Taxonomies in the REST API.
@pomegranited
Copy link
Contributor Author

@bradenmacdonald could I get your review here?

Copy link
Contributor

@bradenmacdonald bradenmacdonald left a comment

Choose a reason for hiding this comment

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

Just a couple minor things to clarify...

openedx_tagging/core/tagging/rest_api/v1/views.py Outdated Show resolved Hide resolved
# Enabled taxonomy can be modified by taxonomy admins
"can_change_taxonomy": is_admin,
"can_delete_taxonomy": is_admin,
"can_tag_object": False,
Copy link
Contributor

Choose a reason for hiding this comment

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

Why is this False? Shouldn't an admin (at least) be able to use this taxonomy to tag objects?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You'd think so, wouldn't you? But the base oel_tagging.can_change_object_tag_objectid always returns False, and so nobody has permission to add base ObjectTags.

I'm more than happy to change this, though, I think it's over cautious.

Copy link
Contributor

Choose a reason for hiding this comment

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

So it's False because the admin could add a ContentObjectTag but not an ObjectTag? Is that what you mean?

If so, is there a similar API that includes can_tag_content_object or something? This field just doesn't seem useful at the moment.

Copy link
Contributor

Choose a reason for hiding this comment

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

More context here: #74 (comment)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@bradenmacdonald

So it's False because the admin could add a ContentObjectTag but not an ObjectTag? Is that what you mean?

Yes.

If so, is there a similar API that includes can_tag_content_object or something? This field just doesn't seem useful at the moment.

openedx/edx-platform#34004 shows where can_tag_object is True in the content_tagging context, does that help?

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, that helps :)
Could you please just add a comment here along those lines? Something like "This is False because we default to not allowing users to tag arbitrary objects. But specific uses of this code like content_tagging in edx-platform will override this perm to be true for their use cases"

…views

We've demonstrated that adding permissions to the REST API does not add
more database queries, so we can always return permissions.
* removes can_change_objecttag
* replaces Optional[bool] with bool | None
* adds comment about can_tag_object in tests
@bradenmacdonald
Copy link
Contributor

@rpenido @pomegranited Are you both happy with this PR and ready for final review + merge from me?

Copy link
Contributor

@bradenmacdonald bradenmacdonald left a comment

Choose a reason for hiding this comment

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

I had a question here but I'm fine to merge either way. Code LGTM.

openedx_tagging/core/tagging/rest_api/v1/serializers.py Outdated Show resolved Hide resolved
tests/openedx_tagging/core/tagging/test_views.py Outdated Show resolved Hide resolved
@pomegranited
Copy link
Contributor Author

Thanks for your reviews @bradenmacdonald ! I'm going to merge & tag this and get openedx/edx-platform#34004 ready for merge too.

@pomegranited pomegranited merged commit 2508f13 into main Jan 24, 2024
7 checks passed
@pomegranited pomegranited deleted the jill/serialize-permissions branch January 24, 2024 00:27
@openedx-webhooks
Copy link

@pomegranited 🎉 Your pull request was merged! Please take a moment to answer a two question survey so we can improve your experience in the future.

xitij2000 pushed a commit to openedx/frontend-app-authoring that referenced this pull request Jan 29, 2024
Uses the permissions added to the Tagging REST API by openedx/openedx-learning#138 to decide what actions (e.g. import, export, edit, delete) to present to the current user when viewing Tagging-related content.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
open-source-contribution PR author is not from Axim or 2U
Projects
Archived in project
Development

Successfully merging this pull request may close these issues.

4 participants