Skip to content

Permissions

Authentication and authorization are a must in every application. Managing those via dependencies is extremely possible and also widely used but with Esmerald you have a clear separation of permissions although still allowing inject to happen as well.

Inspired by the same author of Django Rest Framework, Esmerald permissions are as simple as you want them to be and as complex as you design. For all tastes.

Note

This permission system is not the same as the Lilya permission system.

BasePermission and custom permissions

If you are used to Django Rest Framework, the way Esmerald designed was very much within the same lines and with all the huge community of developer in mind to make a transition almost immediate and simple as possible.

All the permission classes must derive from BasePermission.

The permissions must implement the has_permission and return True or False.

The permissions can also be async in case you need to run awaitables.

An example of a permission for an user admin.

from esmerald import Request
from esmerald.permissions.base import BaseAbstractUserPermission
from esmerald.types import APIGateHandler


class IsUserAdmin(BaseAbstractUserPermission):
    """
    Simply check if a user has admin access or not.

    BaseAbstractUserPermission inherits from BasePermission.
    """

    def is_user_authenticated(self, request: "Request") -> bool:
        """
        Logic to check if the user is authenticated
        """
        ...

    def is_user_staff(self, request: "Request") -> bool:
        """
        Logic to check if user is staff
        """
        ...

    def has_permission(self, request: "Request", apiview: "APIGateHandler"):
        super().has_permission(request, apiview)
        return bool(request.user and self.is_user_staff(request))

An example of a permission for an user admin with async.

from esmerald import Request
from esmerald.permissions.base import BaseAbstractUserPermission
from esmerald.types import APIGateHandler


class IsUserAdmin(BaseAbstractUserPermission):
    """
    Simply check if a user has admin access or not.

    BaseAbstractUserPermission inherits from BasePermission.
    """

    def is_user_authenticated(self, request: "Request") -> bool:
        """
        Logic to check if the user is authenticated
        """
        ...

    def is_user_staff(self, request: "Request") -> bool:
        """
        Logic to check if user is staff
        """
        ...

    async def has_permission(self, request: "Request", apiview: "APIGateHandler"):
        super().has_permission(request, apiview)
        return bool(request.user and self.is_user_staff(request))

An example of a permission for a project

from esmerald import BasePermission, Request
from esmerald.types import APIGateHandler


class IsProjectAllowed(BasePermission):
    """
    Permission to validate if has access to a given project
    """

    def has_permission(self, request: "Request", apiview: "APIGateHandler"):
        allow_project = request.headers.get("allow_access")
        return bool(allow_project)

An example of a permission for a project with async

from esmerald import BasePermission, Request
from esmerald.types import APIGateHandler


class IsProjectAllowed(BasePermission):
    """
    Permission to validate if has access to a given project
    """

    async def has_permission(self, request: "Request", apiview: "APIGateHandler"):
        allow_project = request.headers.get("allow_access")
        return bool(allow_project)

Esmerald and permissions

Esmerald giving support to Saffier ORM and Edgy also provides some default permissions that can be linked to the models also provided by Esmerald.

IsAdminUser and example of provided permissions

This is a simple permission that extends the BaseAbstractUserPermission and checks if a user is authenticated or not. The functionality of verifying if a user might be or not authenticated was separated from the Saffier and instead you must implement the is_user_authenticated() function when inheriting from BaseAbstractUserPermission or IsAdminUser.

Esmerald and provided permissions

Esmerald provides some default permissions that can be used in your projects but not too many as every project has special needs and rules.

  • DenyAll - As the name suggests, blocks access to anyone. Can be useful if an API is under maintenance.
  • AllowAny - The opposite of DenyAll. Allows access to everyone. Useful as permission for the top Esmerald application.
  • IsAdminUser - Checks if a user is admin or not by inheriting the object and implementing is_user_staff function.
  • IsAuthenticated - Checks if a user is authenticated by inheriting from the object and implementing the logic for is_user_authenticated function.
  • IsAuthenticatedOrReadOnly - Checks if a user is authenticated or a read only request. For the autenticated part the is_user_authenticated needs to be implemented.

How to use

To use the IsAdminUser, IsAuthenticated and IsAuthenticatedOrReadOnly is as simple as the example below.

from esmerald import APIView, Esmerald, Gateway, JSONResponse, Request, get
from esmerald.permissions import AllowAny, IsAdminUser, IsAuthenticated, IsAuthenticatedOrReadOnly


class IsAdmin(IsAdminUser):
    def is_user_staff(self, request: "Request") -> bool:
        """
        Add logic to verify if a user is staff
        """


class IsUserAuthenticated(IsAuthenticated):
    def is_user_authenticated(self, request: "Request") -> bool:
        """
        Add logic to verify if a user is staff
        """


class IsAuthOrReadOnly(IsAuthenticatedOrReadOnly):
    def is_user_authenticated(self, request: "Request") -> bool:
        """
        Add logic to verify if a user is staff
        """


@get()
async def home() -> None: ...


class UserAPIView(APIView):
    path = "/users"
    permissions = [IsUserAuthenticated]

    @get("/admin", permissions=[IsAdmin])
    async def admin(self, request: Request) -> JSONResponse:
        return JSONResponse({"message": "ok"})


app = Esmerald(
    routes=[Gateway("/home", handler=home), Gateway(handler=UserAPIView)],
    permissions=[AllowAny],
)
  1. The main app Esmerald has an AllowAny permission for the top level
  2. The UserAPIView object has a IsUserAuthenticated allowing only authenticated users to access any of the endpoints under the class (endpoints under /users).
  3. The /users/admin has a permission IsAdmin allowing only admin users to access the specific endpoint

More on permissions

The permissions internally are checked from top down which means you can place permissios at any given part of the level making it more dynamic and allowing to narrow it down to a granular level that is managenable.

Internally, Esmerald runs all the validations and checks and on an application level, the only thing you need to make sure is to implement the has_permission on any derived class of BasePermission.

Permissions summary

  1. All permissions must inherit from BasePermission.
  2. BasePermission has the has_permission(request Request, apiview: "APIGateHandler") and it can beasync` or not.
  3. The handlers, Gateway, WebSocketGateway, Include and Esmerald can have as many permissions as you want.

API Reference

Check out the API Reference for Permissions for more details.