"""
- ``validate_signed_request``: Function decorator. Validate request signature.
Applies appropriate validation mechanism to the request data. Assumes
``SKA_SECRET_KEY`` to be in ``settings`` module.
Arguments to be used with `ska.validate_signed_request_data` shortcut
function.
:param str secret_key: The shared secret key.
:param str signature_param: Name of the (for example GET or POST) param name
which holds the ``signature`` value.
:param str auth_user_param: Name of the (for example GET or POST) param name
which holds the ``auth_user`` value.
:param str valid_until_param: Name of the (foe example GET or POST) param
name which holds the ``valid_until`` value.
- ``sign_url``: Method decorator (to be used in models). Signs the URL.
Arguments to be used with `ska.sign_url` shortcut function.
:param str auth_user: Username of the user making the request.
:param str secret_key: The shared secret key.
:param float|str valid_until: Unix timestamp. If not given, generated
automatically (now + lifetime).
:param int lifetime: Signature lifetime in seconds.
:param str suffix: Suffix to add after the ``endpoint_url`` and before the
appended signature params.
:param str signature_param: Name of the GET param name which would hold the
generated signature value.
:param str auth_user_param: Name of the GET param name which would hold the
``auth_user`` value.
:param str valid_until_param: Name of the GET param name which would hold
the ``valid_until`` value.
"""
from typing import Callable, Dict, Optional, Union
from django.http import HttpRequest
from django.shortcuts import render
from django.utils.translation import gettext, gettext_lazy as _
from .... import sign_url as ska_sign_url, validate_signed_request_data
from ....defaults import (
DEFAULT_AUTH_USER_PARAM,
DEFAULT_EXTRA_PARAM,
DEFAULT_SIGNATURE_PARAM,
DEFAULT_URL_SUFFIX,
DEFAULT_VALID_UNTIL_PARAM,
SIGNATURE_LIFETIME,
)
from .http import HttpResponseUnauthorized
from .settings import (
AUTH_USER,
SECRET_KEY,
UNAUTHORISED_REQUEST_ERROR_MESSAGE,
UNAUTHORISED_REQUEST_ERROR_TEMPLATE,
)
__author__ = "Artur Barseghyan <artur.barseghyan@gmail.com>"
__copyright__ = "2013-2023 Artur Barseghyan"
__license__ = "GPL-2.0-only OR LGPL-2.1-or-later"
__all__ = (
"BaseValidateSignedRequest",
"MethodValidateSignedRequest",
"SignAbsoluteURL",
"ValidateSignedRequest",
"m_validate_signed_request",
"sign_url",
"validate_signed_request",
)
[docs]
class BaseValidateSignedRequest:
"""BaseValidateSignedRequest."""
def __init__(
self,
secret_key: str = SECRET_KEY,
signature_param: str = DEFAULT_SIGNATURE_PARAM,
auth_user_param: str = DEFAULT_AUTH_USER_PARAM,
valid_until_param: str = DEFAULT_VALID_UNTIL_PARAM,
extra_param: str = DEFAULT_EXTRA_PARAM,
) -> None:
"""Constructor."""
self.secret_key = secret_key
self.signature_param = signature_param
self.auth_user_param = auth_user_param
self.valid_until_param = valid_until_param
self.extra_param = extra_param
[docs]
def get_request_data(
self, request: HttpRequest, *args, **kwargs
) -> Dict[str, str]:
return request.GET.dict()
[docs]
class ValidateSignedRequest(BaseValidateSignedRequest):
"""ValidateSignedRequest.
Function decorator. Validate request signature. Applies appropriate
validation mechanism to the request data. Assumes ``SKA_SECRET_KEY`` to be
in ``settings`` module.
Arguments to be used with `ska.validate_signed_request_data` shortcut
function.
:attribute str secret_key: The shared secret key.
:attribute str signature_param: Name of the (for example GET or POST) param
name which holds the ``signature`` value.
:attribute str auth_user_param: Name of the (for example GET or POST) param
name which holds the ``auth_user`` value.
:attribute str valid_until_param: Name of the (foe example GET or POST)
param name which holds the ``valid_until`` value.
:attribute str extra_param: Name of the (foe example GET or POST) param
name which holds the ``extra`` value.
:example:
>>> from ska.contrib.django.ska.decorators import validate_signed_request
>>>
>>> @validate_signed_request()
>>> def detail(request, slug, template_name='foo/detail.html'):
>>> # Your code
"""
def __call__(self, func: Callable) -> Callable:
"""Call."""
def inner(request: HttpRequest, *args, **kwargs):
"""Inner."""
request_data = self.get_request_data(request, *args, **kwargs)
# Validating the request.
validation_result = validate_signed_request_data(
data=request_data,
secret_key=self.secret_key,
signature_param=self.signature_param,
auth_user_param=self.auth_user_param,
valid_until_param=self.valid_until_param,
extra_param=self.extra_param,
)
if validation_result.result is True:
# If validated, just return the func as is.
return func(request, *args, **kwargs)
else:
# Otherwise...
if UNAUTHORISED_REQUEST_ERROR_TEMPLATE:
# If template to display the error message is set in
# ska (django-ska) settings, use it to render the message
# and return ``HttpResponseUnauthorized`` response
# describing the error.
response_content = render(
request,
UNAUTHORISED_REQUEST_ERROR_TEMPLATE,
{"reason": "; ".join(validation_result.reason)},
)
return HttpResponseUnauthorized(response_content)
else:
# Otherwise, return plain text message with describing the
# error.
return HttpResponseUnauthorized(
gettext(UNAUTHORISED_REQUEST_ERROR_MESSAGE).format(
"; ".join(validation_result.reason)
)
)
return inner
validate_signed_request = ValidateSignedRequest
[docs]
class MethodValidateSignedRequest(BaseValidateSignedRequest):
"""MethodValidateSignedRequest.
Method decorator. Validate request signature. Applies appropriate
validation mechanism to the request data. Assumes ``SKA_SECRET_KEY`` to be
in ``settings`` module.
Arguments to be used with `ska.validate_signed_request_data` shortcut
function.
:attribute str secret_key: The shared secret key.
:attribute str signature_param: Name of the (for example GET or POST) param
name which holds the ``signature`` value.
:attribute str auth_user_param: Name of the (for example GET or POST) param
name which holds the ``auth_user`` value.
:attribute str valid_until_param: Name of the (foe example GET or POST)
param name which holds the ``valid_until`` value.
:attribute str extra_param: Name of the (foe example GET or POST) param
name which holds the ``extra`` value.
:example:
>>> from ska.contrib.django.ska.decorators import m_validate_signed_request
>>>
>>> class FooDetailView(View):
>>> @validate_signed_request()
>>> def get(self, request, slug, template_name='foo/detail.html'):
>>> # Your code
"""
def __call__(self, func: Callable) -> Callable:
"""Call."""
def inner(this, request: HttpRequest, *args, **kwargs):
"""Inner."""
request_data = self.get_request_data(request, *args, **kwargs)
# Validating the request.
validation_result = validate_signed_request_data(
data=request_data,
secret_key=self.secret_key,
signature_param=self.signature_param,
auth_user_param=self.auth_user_param,
valid_until_param=self.valid_until_param,
extra_param=self.extra_param,
)
if validation_result.result is True:
# If validated, just return the func as is.
return func(this, request, *args, **kwargs)
else:
# Otherwise...
if UNAUTHORISED_REQUEST_ERROR_TEMPLATE:
# If template to display the error message is set in
# ska (django-ska) settings, use it to render the message
# and return ``HttpResponseUnauthorized`` response
# describing the error.
response_content = render(
request,
UNAUTHORISED_REQUEST_ERROR_TEMPLATE,
{"reason": "; ".join(validation_result.reason)},
)
return HttpResponseUnauthorized(response_content)
else:
# Otherwise, return plain text message with describing the
# error.
return HttpResponseUnauthorized(
gettext(UNAUTHORISED_REQUEST_ERROR_MESSAGE).format(
"; ".join(validation_result.reason)
)
)
return inner
m_validate_signed_request = MethodValidateSignedRequest
[docs]
class SignAbsoluteURL:
"""SignAbsoluteURL.
Method decorator (to be used in models). Signs the URL.
Arguments to be used with `ska.sign_url` shortcut function.
:attribute str auth_user: Username of the user making the request.
:attribute str secret_key: The shared secret key.
:attribute float | str valid_until: Unix timestamp. If not given, generated
automatically (now + lifetime).
:attribute int lifetime: Signature lifetime in seconds.
:attribute str suffix: Suffix to add after the ``endpoint_url`` and before
the appended signature params.
:attribute str signature_param: Name of the GET param name which would hold
the generated signature value.
:attribute str auth_user_param: Name of the GET param name which would hold
the ``auth_user`` value.
:attribute str valid_until_param: Name of the GET param name which would
hold the ``valid_until`` value.
:attribute dict extra: Dict of extra params to append to signed URL.
:attribute str extra_param: Name of the GET param name which would hold
the ``extra`` value.
:example:
>>> from ska.contrib.django.ska.decorators import sign_url
>>>
>>> class FooItem(models.Model):
>>> title = models.CharField(_("Title"), max_length=100)
>>> slug = models.SlugField(unique=True, verbose_name=_("Slug"))
>>> body = models.TextField(_("Body"))
>>>
>>> @sign_url()
>>> def get_signed_absolute_url(self):
>>> return reverse('foo.detail', kwargs={'slug': self.slug})
"""
def __init__(
self,
auth_user: str = AUTH_USER,
secret_key: str = SECRET_KEY,
valid_until: Union[str, float] = None,
lifetime: int = SIGNATURE_LIFETIME,
suffix: str = DEFAULT_URL_SUFFIX,
signature_param: str = DEFAULT_SIGNATURE_PARAM,
auth_user_param: str = DEFAULT_AUTH_USER_PARAM,
valid_until_param: str = DEFAULT_VALID_UNTIL_PARAM,
extra: Optional[Dict[str, Union[bytes, str, float, int]]] = None,
extra_param: str = DEFAULT_EXTRA_PARAM,
):
"""Constructor."""
self.auth_user = auth_user
self.secret_key = secret_key
self.valid_until = valid_until
self.lifetime = lifetime
self.suffix = suffix
self.signature_param = signature_param
self.auth_user_param = auth_user_param
self.valid_until_param = valid_until_param
self.extra = extra if extra is not None else {}
self.extra_param = extra_param
def __call__(self, func: Callable):
"""Call."""
def inner(this, *args, **kwargs):
"""Inner."""
url = func(this, *args, **kwargs)
return ska_sign_url(
auth_user=self.auth_user,
secret_key=self.secret_key,
valid_until=self.valid_until,
lifetime=self.lifetime,
url=url,
suffix=self.suffix,
signature_param=self.signature_param,
auth_user_param=self.auth_user_param,
valid_until_param=self.valid_until_param,
extra=self.extra,
extra_param=self.extra_param,
)
return inner
sign_url = SignAbsoluteURL