Skip to content

Commit

Permalink
Distinguish between AuthenticationError and AuthorizationError (#1257)
Browse files Browse the repository at this point in the history
* 1218 Authorization vs Authentication

* AuthenticationError AuthorizationError inherited from HttpError

---------

Co-authored-by: Vitaliy Kucheryaviy <[email protected]>
  • Loading branch information
c4ffein and vitalik authored Nov 22, 2024
1 parent 63c0c6c commit c4aa496
Show file tree
Hide file tree
Showing 4 changed files with 32 additions and 15 deletions.
4 changes: 4 additions & 0 deletions docs/docs/guides/errors.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@ By default, **Django Ninja** initialized the following exception handlers:

Raised when authentication data is not valid

#### `ninja.errors.AuthorizationError`

Raised when authentication data is valid, but doesn't allow you to access the resource

#### `ninja.errors.ValidationError`

Raised when request data does not validate
Expand Down
1 change: 1 addition & 0 deletions docs/docs/guides/response/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,7 @@ In case of authentication, for example, you can return:
- **200** successful -> token
- **401** -> Unauthorized
- **402** -> Payment required
- **403** -> Forbidden
- etc..

In fact, the [OpenAPI specification](https://swagger.io/docs/specification/describing-responses/) allows you to pass multiple response schemas.
Expand Down
25 changes: 11 additions & 14 deletions ninja/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
__all__ = [
"ConfigError",
"AuthenticationError",
"AuthorizationError",
"ValidationError",
"HttpError",
"set_default_exc_handlers",
Expand All @@ -27,10 +28,6 @@ class ConfigError(Exception):
pass


class AuthenticationError(Exception):
pass


class ValidationError(Exception):
"""
This exception raised when operation params do not validate
Expand All @@ -53,6 +50,16 @@ def __str__(self) -> str:
return self.message


class AuthenticationError(HttpError):
def __init__(self, status_code: int = 401, message: str = "Unauthorized") -> None:
super().__init__(status_code=status_code, message=message)


class AuthorizationError(HttpError):
def __init__(self, status_code: int = 403, message: str = "Forbidden") -> None:
super().__init__(status_code=status_code, message=message)


class Throttled(HttpError):
def __init__(self, wait: Optional[int]) -> None:
self.wait = wait
Expand All @@ -76,10 +83,6 @@ def set_default_exc_handlers(api: "NinjaAPI") -> None:
ValidationError,
partial(_default_validation_error, api=api),
)
api.add_exception_handler(
AuthenticationError,
partial(_default_authentication_error, api=api),
)


def _default_404(request: HttpRequest, exc: Exception, api: "NinjaAPI") -> HttpResponse:
Expand All @@ -101,12 +104,6 @@ def _default_validation_error(
return api.create_response(request, {"detail": exc.errors}, status=422)


def _default_authentication_error(
request: HttpRequest, exc: AuthenticationError, api: "NinjaAPI"
) -> HttpResponse:
return api.create_response(request, {"detail": "Unauthorized"}, status=401)


def _default_exception(
request: HttpRequest, exc: Exception, api: "NinjaAPI"
) -> HttpResponse:
Expand Down
17 changes: 16 additions & 1 deletion tests/test_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import pytest

from ninja import NinjaAPI
from ninja.errors import ConfigError
from ninja.errors import AuthorizationError, ConfigError
from ninja.security import (
APIKeyCookie,
APIKeyHeader,
Expand Down Expand Up @@ -60,6 +60,8 @@ class BearerAuth(HttpBearer):
def authenticate(self, request, token):
if token == "bearertoken":
return token
if token == "nottherightone":
raise AuthorizationError


def demo_operation(request):
Expand Down Expand Up @@ -102,6 +104,7 @@ class MockSuperUser(str):


BODY_UNAUTHORIZED_DEFAULT = dict(detail="Unauthorized")
BODY_FORBIDDEN_DEFAULT = dict(detail="Forbidden")


@pytest.mark.parametrize(
Expand Down Expand Up @@ -178,6 +181,18 @@ class MockSuperUser(str):
401,
BODY_UNAUTHORIZED_DEFAULT,
),
(
"/bearer",
dict(headers={"Authorization": "Bearer nonexistingtoken"}),
401,
BODY_UNAUTHORIZED_DEFAULT,
),
(
"/bearer",
dict(headers={"Authorization": "Bearer nottherightone"}),
403,
BODY_FORBIDDEN_DEFAULT,
),
("/customexception", {}, 401, dict(custom=True)),
(
"/customexception",
Expand Down

0 comments on commit c4aa496

Please sign in to comment.