APIController Permissions
The concept of this permission system came from Django DRF.
Permission checks are always run at the very start of the route function, before any other code is allowed to proceed.
Permission checks will typically use the authentication information in the request.user
and request.auth
properties to determine if the incoming request should be permitted.
Permissions are used to grant or deny access for different classes of users to different parts of the API.
The simplest style of permission would be to allow access to any authenticated user, and deny access to any unauthenticated user.
This corresponds to the IsAuthenticated
class in Django Ninja Extra.
A slightly less strict style of permission would be to allow full access to authenticated users, but allow read-only access to unauthenticated users.
This corresponds to the IsAuthenticatedOrReadOnly
class in Django Ninja Extra.
Limitations of object level permissions
During the handling of a request, the has_permission
method is automatically invoked for all the permissions specified
in the permission list of the route function. However, has_object_permission
is not triggered since
it requires an object for permission validation. As a result of that, has_object_permission
method for permissions are
invoked when attempting to retrieve an object using the get_object_or_exception
or get_object_or_none
methods within the controller.
Custom permissions
To implement a custom permission, override BasePermission
and implement either, or both, of the following methods:
.has_permission(self, request: HttpRequest, controller: "APIController")
.has_object_permission(self, request: HttpRequest, controller: "APIController", obj: Any)
Example
from ninja_extra import permissions, api_controller, http_get
class ReadOnly(permissions.BasePermission):
def has_permission(self, request, view):
return request.method in permissions.SAFE_METHODS
@api_controller(permissions=[permissions.IsAuthenticated | ReadOnly])
class PermissionController:
@http_get('/must_be_authenticated', permissions=[permissions.IsAuthenticated])
def must_be_authenticated(self, word: str):
return dict(says=word)
Permissions Supported Operands
- & (and) eg:
permissions.IsAuthenticated & ReadOnly
- | (or) eg:
permissions.IsAuthenticated | ReadOnly
- ~ (not) eg:
~(permissions.IsAuthenticated & ReadOnly)
Using Permission Object in Controllers
The Ninja-Extra permission system provides flexibility in defining permissions either as an instance of a permission class or as a type.
In the example below, the ReadOnly
class is defined as a subclass of permissions.BasePermission
and
its instance is then passed to the permissions
parameter within the api_controller
decorator.
from ninja_extra import permissions, api_controller, ControllerBase
class ReadOnly(permissions.BasePermission):
def has_permission(self, request, view):
return request.method in permissions.SAFE_METHODS
@api_controller(permissions=[permissions.IsAuthenticated | ReadOnly()])
class SampleController(ControllerBase):
pass
In the provided example, the UserWithPermission
class is utilized to assess different permissions for distinct controllers or route functions.
For instance:
from ninja_extra import permissions, api_controller, ControllerBase, http_post, http_delete
class UserWithPermission(permissions.BasePermission):
def __init__(self, permission: str) -> None:
self._permission = permission
def has_permission(self, request, view):
return request.user.has_perm(self._permission)
@api_controller('/blog')
class BlogController(ControllerBase):
@http_post('/', permissions=[permissions.IsAuthenticated & UserWithPermission('blog.add')])
def add_blog(self):
pass
@http_delete('/', permissions=[permissions.IsAuthenticated & UserWithPermission('blog.delete')])
def delete_blog(self):
pass
In this scenario, the UserWithPermission
class is employed to verify whether the user possesses the blog.add
permission to access the add_blog
action and blog.delete
permission for the delete_blog
action within the BlogController
.
The permissions are explicitly configured for each route function, allowing fine-grained control over user access based on specific permissions.
AllowAny
The AllowAny
permission class grants unrestricted access, irrespective of whether the request is authenticated or unauthenticated. While not mandatory, using this permission class is optional, as you can achieve the same outcome by employing an empty list or tuple for the permissions setting.
However, specifying the AllowAny
class can be beneficial as it explicitly communicates the intention of allowing unrestricted access.
IsAuthenticated
The IsAuthenticated
permission class denies permission to unauthenticated users and grants permission to authenticated users.
This permission is appropriate if you intend to restrict API access solely to registered users.
IsAdminUser
The IsAdminUser
permission class denies permission to any user, except when user.is_staff
is True
,
in which case permission is granted.
This permission is suitable if you intend to restrict API access to a specific subset of trusted administrators.
IsAuthenticatedOrReadOnly
The IsAuthenticatedOrReadOnly
permission class allows authenticated users to perform any request.
For unauthenticated users, requests will only be permitted if the method is one of the "safe" methods: GET, HEAD, or OPTIONS.
This permission is appropriate if you want your API to grant read permissions to anonymous users while restricting write permissions to authenticated users.