1
0

Initial public releases

* `bthlabs-jsonrpc-aiohttp` v1.0.0
* `bthlabs-jsonrpc-core` v1.0.0
* `bthlabs-jsonrpc-django` v1.0.0
This commit is contained in:
2022-06-04 10:41:53 +02:00
commit c75ea4ea9d
111 changed files with 7193 additions and 0 deletions

View File

@@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
# django-jsonrpc-django | (c) 2022-present Tomek Wójcik | MIT License
from .auth_checks import ( # noqa
has_perms,
is_authenticated,
is_staff,
)
from .views import JSONRPCView # noqa
__version__ = '1.0.0'

View File

@@ -0,0 +1,17 @@
# -*- coding: utf-8 -*-
# django-jsonrpc-django | (c) 2022-present Tomek Wójcik | MIT License
import importlib
from django.apps import AppConfig
class BTHLabsJSONRPCConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'bthlabs_jsonrpc_django'
verbose_name = 'BTHLabs JSONRPC'
def ready(self):
from django.conf import settings
for module_path in settings.JSONRPC_METHOD_MODULES:
_ = importlib.import_module(module_path)

View File

@@ -0,0 +1,27 @@
# -*- coding: utf-8 -*-
# django-jsonrpc-django | (c) 2022-present Tomek Wójcik | MIT License
import typing
from django.http import HttpRequest
def is_authenticated(request: HttpRequest) -> bool:
"""Checks if the request user is authenticated and active."""
return all((
request.user.is_anonymous is False,
request.user.is_active is True,
))
def is_staff(request: HttpRequest) -> bool:
"""Checks if the request user is a staff user."""
return request.user.is_staff
def has_perms(perms: list[str]) -> typing.Callable:
"""Checks if the request user has the specified permissions."""
def internal_has_perms(request: HttpRequest) -> bool:
return request.user.has_perms(perms)
return internal_has_perms

View File

@@ -0,0 +1,28 @@
# -*- coding: utf-8 -*-
# django-jsonrpc-django | (c) 2022-present Tomek Wójcik | MIT License
import typing
from bthlabs_jsonrpc_core import Executor, JSONRPCAccessDeniedError
from django.http import HttpRequest
from bthlabs_jsonrpc_django.serializer import DjangoJSONRPCSerializer
class DjangoExecutor(Executor):
serializer = DjangoJSONRPCSerializer
def __init__(self,
request: HttpRequest,
can_call: typing.Callable,
namespace: typing.Optional[str] = None):
super().__init__(namespace=namespace)
self.request: HttpRequest = request
self.can_call: typing.Callable = can_call
def enrich_args(self, args):
return [self.request, *super().enrich_args(args)]
def before_call(self, method, args, kwargs):
can_call = self.can_call(self.request, method, args, kwargs)
if can_call is False:
raise JSONRPCAccessDeniedError(data='can_call')

View File

@@ -0,0 +1,8 @@
# -*- coding: utf-8 -*-
# django-jsonrpc-django | (c) 2022-present Tomek Wójcik | MIT License
from bthlabs_jsonrpc_core import JSONRPCSerializer
from django.db.models import QuerySet
class DjangoJSONRPCSerializer(JSONRPCSerializer):
SEQUENCE_TYPES = (QuerySet, *JSONRPCSerializer.SEQUENCE_TYPES)

View File

@@ -0,0 +1,122 @@
# -*- coding: utf-8 -*-
# django-jsonrpc-django | (c) 2022-present Tomek Wójcik | MIT License
import typing
from bthlabs_jsonrpc_core import Executor
from django.core.exceptions import PermissionDenied
from django.http import HttpRequest, HttpResponse, JsonResponse
from django.utils.decorators import classonlymethod
from django.views.decorators.csrf import csrf_exempt
from django.views.generic.base import View
from bthlabs_jsonrpc_django.executor import DjangoExecutor
class JSONRPCView(View):
"""
The JSONRPC View. This is the main JSONRPC entry point. Use it to register
your JSONRPC endpoints.
Example:
.. code-block:: python
from bthlabs_jsonrpc_django import JSONRPCView, is_authenticated
urlpatterns = [
path(
'rpc/private',
JSONRPCView.as_view(
auth_checks=[is_authenticated],
namespace='admin',
),
)
path('rpc', JSONRPCView.as_view()),
]
"""
# pragma mark - Private class attributes
# The executor class.
executor: Executor = DjangoExecutor
# pragma mark - Public class attributes
#: List of auth check functions.
auth_checks: list[typing.Callable] = []
#: Namespace of this endpoint.
namespace: typing.Optional[str] = None
# pragma mark - Private interface
def ensure_auth(self, request: HttpRequest) -> None:
"""
Runs auth checks (if any) and raises
:py:exc:`django.core.exceptions.PermissionDenied` if any of them
returns ``False``.
:meta private:
"""
if len(self.auth_checks) == []:
return
has_auth = all((
auth_check(request)
for auth_check
in self.auth_checks
))
if has_auth is False:
raise PermissionDenied('This RPC endpoint requires auth.')
def dispatch(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
"""
Dispatches the *request*.
:meta private:
"""
if request.method.lower() in self.http_method_names:
handler = getattr(
self, request.method.lower(), self.http_method_not_allowed,
)
else:
handler = self.http_method_not_allowed
self.ensure_auth(request)
return handler(request, *args, **kwargs)
def post(self, request: HttpRequest) -> HttpResponse:
"""
The POST handler.
:meta private:
"""
executor = self.executor(
request, self.can_call, self.namespace,
)
serializer = executor.execute(request.body)
if serializer is None:
return HttpResponse('')
return JsonResponse(serializer.data, safe=False)
# pragma mark - Public interface
@classonlymethod
def as_view(cls, **initkwargs):
result = super().as_view(**initkwargs)
return csrf_exempt(result)
def can_call(self,
request: HttpRequest,
method: str,
args: list,
kwargs: dict) -> bool:
"""
Hook for subclasses to perform additional per-call permissions checks
etc. The default implementation returns ``True``.
"""
return True