Skip to content
Merged
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
16 changes: 13 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,16 @@ Repository = "https://github.com/kitware-resonant/django-resonant-utils"
allauth = [
"django-allauth",
]
rest_framework = [
"djangorestframework",
]
minio_storage = [
"django-minio-storage",
]
ninja = [
"django-ninja",
"django-oauth-toolkit",
]
rest_framework = [
"djangorestframework",
]
s3_storage = [
"django-storages[s3]>=1.14",
]
Expand Down Expand Up @@ -152,3 +156,9 @@ warn_unused_ignores = true
mypy_path = [
"$MYPY_CONFIG_FILE_DIR/stubs",
]

[[tool.mypy.overrides]]
module = [
"oauth2_provider.*",
]
ignore_missing_imports = true
44 changes: 44 additions & 0 deletions resonant_utils/ninja.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
from __future__ import annotations

from typing import TYPE_CHECKING, Any

from ninja.security.http import HttpAuthBase
from oauth2_provider.oauth2_backends import get_oauthlib_core

if TYPE_CHECKING:
from django.http import HttpRequest
from oauth2_provider.models import AbstractAccessToken


# TODO: Remove this once https://github.com/django-oauth/django-oauth-toolkit/pull/1646 is released.
# Don't inherit from `HttpBearer`, since we have our own header extraction logic
class HttpOAuth2(HttpAuthBase):
"""Perform OAuth2 authentication, for use with Django Ninja."""

openapi_scheme: str = "bearer"

def __init__(self, *, scopes: list[str] | None = None) -> None:
super().__init__()
self.scopes = scopes if scopes is not None else []

def __call__(self, request: HttpRequest) -> Any | None:
oauthlib_core = get_oauthlib_core()
# This also sets `request.user`,
# which Ninja does not: https://github.com/vitalik/django-ninja/issues/76
valid, r = oauthlib_core.verify_request(request, scopes=self.scopes)

if not valid:
return None

return self.authenticate(request, r.access_token)

def authenticate(self, request: HttpRequest, access_token: AbstractAccessToken) -> Any | None:
"""
Determine whether authentication succeeds.

If this returns a truthy value, authentication will succeed.
Django Ninja will set the return value as `request.auth`.

Subclasses may override this to implement additional authorization logic.
"""
return access_token
4 changes: 2 additions & 2 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ env_list =
runner = uv-venv-lock-runner
extras =
allauth
rest_framework
minio_storage
ninja
rest_framework
s3_storage

[testenv:lint]
Expand All @@ -35,4 +36,3 @@ dependency_groups =
type
commands =
mypy {posargs}

Loading