Skip to content

Commit

Permalink
Release 5.11.1
Browse files Browse the repository at this point in the history
### Changelog:
* Fix(backend): Monkey patching json functions by orjson for Authlib components.
* Fix(backend): Other performance enhances.
* Fix(backend): h11 exception for 304 responses with content.
* Fix(frontend): Security dependabot alerts.

Dependabot alerts:
- DOM Clobbering Gadget found in rollup bundled scripts that leads to XSS.
- DOMPurify allows tampering by prototype pollution.
- Regular Expression Denial of Service (ReDoS) in micromatch.

See merge request vst/vst-utils!671
  • Loading branch information
onegreyonewhite committed Sep 27, 2024
2 parents 10b695a + 86469f4 commit dd3ebf6
Show file tree
Hide file tree
Showing 11 changed files with 175 additions and 230 deletions.
2 changes: 1 addition & 1 deletion requirements-prod.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@
# PyMySQL>=0.9.2,<=0.9.3; python_version<'3.0'
# mysql-connector-python==8.0.15; python_version>'3.4'
# Advanced cache support
redis[hiredis]~=5.0.8
redis[hiredis]~=5.1.0
tarantool~=1.2.0
pywebpush~=2.0.0
2 changes: 1 addition & 1 deletion requirements-test.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ coverage~=7.6.1
fakeldap~=0.6.6
tblib==3.0.0
beautifulsoup4~=4.12.3
httpx~=0.27.0
httpx~=0.27.2
3 changes: 1 addition & 2 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,12 @@ ormsgpack~=1.5.0
pyyaml~=6.0.2

# web server
uvicorn~=0.30.6
uvicorn~=0.31.0
pyuwsgi~=2.0.26
# Restore it if some problems with pyuwsgi
# uwsgi==2.0.23
fastapi-slim~=0.115.0
aiofiles~=24.1.0
asgiref>=3.8.1

# Notifications
cent~=5.0.0
Expand Down
2 changes: 1 addition & 1 deletion vstutils/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
# pylint: disable=django-not-available
__version__: str = '5.11.0'
__version__: str = '5.11.1'
3 changes: 3 additions & 0 deletions vstutils/api/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,9 @@ def exception_handler(exc, context):
if isinstance(exc, exceptions.NotAuthenticated): # nocv
default_response["X-Anonymous"] = "true" # type: ignore

if default_response.status_code == status.HTTP_304_NOT_MODIFIED:
default_response.data = None

return default_response


Expand Down
2 changes: 1 addition & 1 deletion vstutils/api/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ class Language(ListModel):
@classmethod
def get_etag_value(cls, pk=None):
hashable_str = '_'.join(c for c, _ in settings.LANGUAGES) + (f'_{pk}' if pk is not None else '')
return hashlib.md5(hashable_str.encode('utf-8')).hexdigest() # nosec
return hashlib.blake2s(hashable_str.encode('utf-8'), digest_size=4).hexdigest() # nosec

def _get_translation_data(self, module_path_string, code, for_server=False):
data = {}
Expand Down
6 changes: 3 additions & 3 deletions vstutils/oauth2/authentication.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@
from rest_framework.request import Request
from rest_framework.response import Response
from vstutils.exceptions import HttpResponseException
from vstutils.utils import get_session_store, add_in_vary
from vstutils.utils import add_in_vary

from .authorization_server import protector
from .authorization_server import protector, SESSION_STORE


class OAuth2ErrorWrapper(HttpResponseException, AuthenticationFailed):
Expand Down Expand Up @@ -46,7 +46,7 @@ def _get_request_token(request: "Request"):


def get_session(session_key):
session = get_session_store()(session_key)
session = SESSION_STORE(session_key)
session._from_jwt = True # pylint: disable=protected-access
return session

Expand Down
32 changes: 27 additions & 5 deletions vstutils/oauth2/authorization_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@
from time import time
from typing import Callable, Optional, TYPE_CHECKING

import orjson
from authlib.integrations.django_oauth2 import (
AuthorizationServer as BaseAuthorizationServer,
ResourceProtector,
)
from authlib.common import encoding
from authlib.jose import jwt
from authlib.jose.rfc7519 import jwt as jwt_rfc7519
from authlib.oauth2.rfc6749 import (
InvalidRequestError,
RefreshTokenGrant as BaseRefreshTokenGrant,
Expand All @@ -26,6 +29,7 @@
JWTBearerTokenValidator as BaseJWTBearerTokenValidator,
)
from authlib.oauth2.rfc9068.claims import JWTAccessTokenClaims, JWTClaims
from authlib.integrations.django_oauth2 import requests as django_oauth2_requests
from django.conf import settings
from django.contrib.auth import authenticate, login, get_user
from django.utils.module_loading import import_string
Expand All @@ -42,11 +46,29 @@
from django.contrib.auth.models import AbstractBaseUser


SESSION_STORE = get_session_store()
extra_claims_provider: 'Optional[Callable[[AbstractBaseUser], Optional[dict]]]' = (
import_string(settings.OAUTH_SERVER_JWT_EXTRA_CLAIMS_PROVIDER)
if settings.OAUTH_SERVER_JWT_EXTRA_CLAIMS_PROVIDER
else None
)
_json_options = (
orjson.OPT_SERIALIZE_NUMPY |
orjson.OPT_NON_STR_KEYS |
orjson.OPT_SERIALIZE_DATACLASS |
orjson.OPT_SERIALIZE_UUID
)


def json_dumps(data, ensure_ascii=False):
result = orjson.dumps(data, option=_json_options)
if ensure_ascii:
return result.decode('utf-8').encode('ascii', 'backslashreplace').decode('ascii') # nocv
return result.decode('utf-8')


def expires_generator(client, grant_type):
return settings.OAUTH_SERVER_TOKEN_EXPIRES_IN


class MissingOrInvalidSecondFactorError(InvalidRequestError):
Expand All @@ -59,10 +81,6 @@ def get_body(self):
return body


def expires_generator(client, grant_type):
return settings.OAUTH_SERVER_TOKEN_EXPIRES_IN


class JWTBearerTokenGenerator(BaseJWTBearerTokenGenerator):
def __init__(self, refresh_token_generator=None):
super().__init__(
Expand Down Expand Up @@ -237,7 +255,7 @@ def decode(cls, token: str):

@cached_property
def _session(self):
session = get_session_store()(self.claims['jti'])
session = SESSION_STORE(self.claims['jti'])
if session.exists(session.session_key):
return session

Expand Down Expand Up @@ -358,3 +376,7 @@ def save_token(self, token, request):

protector = ResourceProtector()
protector.register_token_validator(JWTBearerTokenValidator())

# Monkey-patching for use orjson
encoding.json_dumps = jwt_rfc7519.json_dumps = json_dumps
encoding.json_loads = jwt_rfc7519.json_loads = django_oauth2_requests.json_loads = orjson.loads
1 change: 1 addition & 0 deletions vstutils/oauth2/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@


class UserWrapper:
__slots__ = ("django_user", "pk")
request: 'Optional[DjangoOAuth2Request]' = None

def __init__(self, user: 'Union[AbstractBaseUser, AnonymousUser]'):
Expand Down
10 changes: 8 additions & 2 deletions vstutils/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
import sys
import tempfile
import time
import json
import traceback
import types
import typing as tp
Expand All @@ -22,6 +21,7 @@
from enum import Enum, EnumMeta
from importlib import import_module

import orjson
from asgiref.sync import sync_to_async, async_to_sync
from django.conf import settings
from django.middleware.gzip import GZipMiddleware
Expand Down Expand Up @@ -107,6 +107,12 @@ def list_to_choices(items_list, response_type=list):
return response_type(((x, x) for x in items_list))


def json_dumps(obj):
return orjson\
.dumps(obj, option=orjson.OPT_NON_STR_KEYS | orjson.OPT_SERIALIZE_DATACLASS | orjson.OPT_SERIALIZE_UUID)\
.decode('utf-8')


def is_member_descriptor(obj):
try:
return type(obj).__name__ == 'member_descriptor'
Expand Down Expand Up @@ -513,7 +519,7 @@ def __repr__(self): # nocv
return self.__str__()

def __str__(self):
return json.dumps(self.copy())
return json_dumps(self.copy())


class tmp_file:
Expand Down
Loading

0 comments on commit dd3ebf6

Please sign in to comment.