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

Default has_object_permission implementation in BaseHasAPIKey is redundant #150

Closed
sparkyb opened this issue Oct 16, 2020 · 5 comments · Fixed by #240
Closed

Default has_object_permission implementation in BaseHasAPIKey is redundant #150

sparkyb opened this issue Oct 16, 2020 · 5 comments · Fixed by #240
Labels
bug Something isn't working
Milestone

Comments

@sparkyb
Copy link

sparkyb commented Oct 16, 2020

def has_object_permission(
self, request: HttpRequest, view: typing.Any, obj: AbstractAPIKey
) -> bool:
return self.has_permission(request, view)

Right now the permission BaseHasAPIKey implement has_object_permission and just calls has_permission. This is an incorrect implementation of BasePermission. has_object_permission is only called after has_permission has already passed, so this implementation is redundant (and inefficient since it will cause a redundant database lookup for the API key).

Also, the type of obj is annotated as AbstractAPIKey, but the object passed to has_object_permission would be an instance of whatever model the view is acting on where this permission is used, not the API key.

@florimondmanca
Copy link
Owner

Also, the type of obj is annotated as AbstractAPIKey, but the object passed to has_object_permission would be an instance of whatever model the view is acting on where this permission is used, not the API key.

Absolutely, sounds like a no brainer. I think a separate PR for this particular type hint tweak would be acceptable?

As for .has_object_permission() — I think your rationale makes sense. The current state of affairs comes from #21. Looks like at the time we added the default .has_object_permission() but OP's use case should have warranted them to create a custom HasAPIKey permission that happens to implement .has_object_permission() as we do currently (i.e. deferring to .has_permission()). Is this your analysis of the situation as well?

@sparkyb
Copy link
Author

sparkyb commented Oct 17, 2020

Ah, I did try to look why has_object_permission was implemented like that, but I failed to find that issue. That makes a lot of sense, and actually, though I haven't gotten there yet, I'm probably going to be doing something similar and will likely have the same issue. It does seem that what you suggest (omit has_object_permission and leave this workaround to a custom subclass for those who need it) is the right way to go based on the intent of BasePermission. This seems like it might be a common issue, but it is actually more of an issue for DRF itself. Any permission could have this problem when used with OR (for example, IsAdminUser|IsTheSameUser). I plan to report it to DRF because it really should be fixed there (either BasePermission could implement a default has_object_permission that delegates to has_permission like the workaround we have, or it could be handled in the OR class).

Any way it is handled, it's a shame that there's no good way to cache the result of has_permission so it doesn't have to do the DB lookup again. Since the request and view are parameters to the methods, not the constructor, it's not possible to save the result of has_permission on the permission class itself, and the same permission instance isn't reused anyway. Perhaps the looked up APIKey should be stored on the request? That would make checking has_permission a second time faster and would making getting the APIKey from within the view easier without a 2nd lookup.

@sparkyb
Copy link
Author

sparkyb commented Oct 17, 2020

Looks like there's already an issue for this against DRF encode/django-rest-framework#7117 . It looks like there's a PR to fix it exactly as I would have suggested (in the OR class) which didn't make it into the recent 3.12 release, but hopefully should make it into the next one. So I'd say, yes, djangorestframework-api-key should remove this workaround and anyone who needs it prior to the upstream DRF fix should do it themselves.

And yes, a separate PR for fixing the type annotation would be a simple fix, but it is moot if the method gets removed.

@florimondmanca florimondmanca added the external Relates to an external project label Mar 17, 2021
@florimondmanca florimondmanca removed the external Relates to an external project label Jun 4, 2022
@florimondmanca florimondmanca added this to the v2.3.0 milestone Jun 4, 2022
@florimondmanca
Copy link
Owner

florimondmanca commented Jun 4, 2022

@sparkyb As I come back to this repo to move a few things forward, I noticed I had incorrectly assumed this was only an external DRF issue. There is a problem with how DRF handles bitwise permission operations, but we still make an inappropriate use of .has_object_permissions() by duplicating API key checking there on top of .has_permissions(). I was able to confirm in #173 (comment) that this results in about 30-40% more time spent processing requests, on top of performing unnecessary CPU hash crunching.

I agree we should drop .has_object_permissions() and hint users to implement it in a custom permission class if they need it.

We basically need to undo #25*, and maybe add a note about encode/django-rest-framework#7117 in the docs that mention bitwise operations. (* That "fix" is 3 years old. I'm thinking -- all this time, computers running rest_framework_api_key have been doing 2x more CPU work than they should have been doing. Uh!)

@florimondmanca florimondmanca added the bug Something isn't working label Jun 4, 2022
@florimondmanca
Copy link
Owner

The PR encode/django-rest-framework#7117 was merged on the DRF side.

Now, bitwise OR checks for both .has_permission() and .has_object_permission(). Before this, and at the time of #25, DRF only called for .has_object_permission()which meant API key permissions (which only implemented.has_permission()` at the time) were not checked if used with a bitwise OR.

This does confirm that we can revert #25 and have folks rely on the new fix from the DRF.

@florimondmanca florimondmanca changed the title BaseHasAPIKey should not implement has_object_permission Default has_object_permission implementation in BaseHasAPIKey is redundant Aug 15, 2023
@florimondmanca florimondmanca modified the milestones: v2.3.0, v2.4.0 Aug 15, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
2 participants