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

Distinguish between AuthenticationError and AuthorizationError #1257

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
15 changes: 15 additions & 0 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 @@ -31,6 +32,10 @@ class AuthenticationError(Exception):
pass


class AuthorizationError(AuthenticationError):
pass


class ValidationError(Exception):
"""
This exception raised when operation params do not validate
Expand Down Expand Up @@ -80,6 +85,10 @@ def set_default_exc_handlers(api: "NinjaAPI") -> None:
AuthenticationError,
partial(_default_authentication_error, api=api),
)
api.add_exception_handler(
AuthorizationError,
partial(_default_authorization_error, api=api),
)


def _default_404(request: HttpRequest, exc: Exception, api: "NinjaAPI") -> HttpResponse:
Expand Down Expand Up @@ -107,6 +116,12 @@ def _default_authentication_error(
return api.create_response(request, {"detail": "Unauthorized"}, status=401)


def _default_authorization_error(
request: HttpRequest, exc: AuthorizationError, api: "NinjaAPI"
) -> HttpResponse:
return api.create_response(request, {"detail": "Forbidden"}, status=403)


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
Loading