Chore: type GlobalPermissionsByMethod.perms_map keys as HTTP methods

perms_map accepted any string key, so a misspelled method or a HEAD entry
would never match. Keys are now a Literal of the mappable methods, without
HEAD, and the map is typed read-only.
This commit is contained in:
stumpylog committed 2026-10-14 13:17:26 -07:00
1 parent 6b700393a0
commit 6c85e8877a
1 file changed
+12 -2
+12 -2
View File
@@ -1,7 +1,11 @@
from collections.abc import Mapping
from collections.abc import Sequence
from typing import Any
from typing import ClassVar
from typing import Literal
from typing import TypeGuard
from typing import TypeVar
from typing import cast
from django.contrib.auth.models import Group
from django.contrib.auth.models import Permission
@@ -702,6 +706,10 @@ def has_perms_owner_aware(user, perms, obj):
return obj.owner is None or obj.owner == user or checker.has_perm(perms, obj)
# HEAD is deliberately absent: GlobalPermissionsByMethod checks it against GET.
MappedMethod = Literal["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"]
class GlobalPermissionsByMethod(BasePermission):
"""
Requires the global (model-level) permissions listed in ``perms_map`` for
@@ -710,14 +718,16 @@ class GlobalPermissionsByMethod(BasePermission):
``perms_map`` is denied.
"""
perms_map: ClassVar[dict[str, list[str]]]
perms_map: ClassVar[Mapping[MappedMethod, Sequence[str]]]
def has_permission(self, request: Any, view: Any) -> bool:
if not request.user or not request.user.is_authenticated:
return False
method = "GET" if request.method == "HEAD" else request.method
perms = self.perms_map.get(method)
# An unknown method is not a MappedMethod, but .get() returns None for
# it, so it is still denied.
perms = self.perms_map.get(cast("MappedMethod", method))
return perms is not None and request.user.has_perms(perms)