diff --git a/api/CHANGELOG.md b/api/CHANGELOG.md index 4d0fbce45b..dd3446550a 100644 --- a/api/CHANGELOG.md +++ b/api/CHANGELOG.md @@ -17,6 +17,13 @@ All notable changes to the **Prowler API** are documented in this file. --- +## [v1.8.3] (Prowler v5.7.3) + +### Fixed +- Fixed transaction persistence with RLS operations [(#7916)](https://github.com/prowler-cloud/prowler/pull/7916). + +--- + ## [v1.8.2] (Prowler v5.7.2) ### Fixed @@ -30,7 +37,7 @@ All notable changes to the **Prowler API** are documented in this file. ## [v1.8.1] (Prowler v5.7.1) ### Fixed -- Added database index to improve performance on finding lookup. [(#7800)](https://github.com/prowler-cloud/prowler/pull/7800) +- Added database index to improve performance on finding lookup [(#7800)](https://github.com/prowler-cloud/prowler/pull/7800). --- diff --git a/api/src/backend/api/base_views.py b/api/src/backend/api/base_views.py index 605afe332d..54b020597f 100644 --- a/api/src/backend/api/base_views.py +++ b/api/src/backend/api/base_views.py @@ -1,5 +1,4 @@ from django.core.exceptions import ObjectDoesNotExist -from django.db import transaction from rest_framework import permissions from rest_framework.exceptions import NotAuthenticated from rest_framework.filters import SearchFilter @@ -47,11 +46,9 @@ class BaseViewSet(ModelViewSet): class BaseRLSViewSet(BaseViewSet): - def dispatch(self, request, *args, **kwargs): - with transaction.atomic(): - return super().dispatch(request, *args, **kwargs) - def initial(self, request, *args, **kwargs): + super().initial(request, *args, **kwargs) + # Ideally, this logic would be in the `.setup()` method but DRF view sets don't call it # https://docs.djangoproject.com/en/5.1/ref/class-based-views/base/#django.views.generic.base.View.setup if request.auth is None: @@ -61,9 +58,19 @@ class BaseRLSViewSet(BaseViewSet): if tenant_id is None: raise NotAuthenticated("Tenant ID is not present in token") - with rls_transaction(tenant_id): - self.request.tenant_id = tenant_id - return super().initial(request, *args, **kwargs) + self.request.tenant_id = tenant_id + + self._rls_cm = rls_transaction(tenant_id) + self._rls_cm.__enter__() + + def finalize_response(self, request, response, *args, **kwargs): + response = super().finalize_response(request, response, *args, **kwargs) + + if hasattr(self, "_rls_cm"): + self._rls_cm.__exit__(None, None, None) + del self._rls_cm + + return response def get_serializer_context(self): context = super().get_serializer_context() @@ -73,8 +80,7 @@ class BaseRLSViewSet(BaseViewSet): class BaseTenantViewset(BaseViewSet): def dispatch(self, request, *args, **kwargs): - with transaction.atomic(): - tenant = super().dispatch(request, *args, **kwargs) + tenant = super().dispatch(request, *args, **kwargs) try: # If the request is a POST, create the admin role @@ -109,6 +115,8 @@ class BaseTenantViewset(BaseViewSet): pass # Tenant might not exist, handle gracefully def initial(self, request, *args, **kwargs): + super().initial(request, *args, **kwargs) + if request.auth is None: raise NotAuthenticated @@ -117,19 +125,27 @@ class BaseTenantViewset(BaseViewSet): raise NotAuthenticated("Tenant ID is not present in token") user_id = str(request.user.id) - with rls_transaction(value=user_id, parameter=POSTGRES_USER_VAR): - return super().initial(request, *args, **kwargs) + + self._rls_cm = rls_transaction(value=user_id, parameter=POSTGRES_USER_VAR) + self._rls_cm.__enter__() + + def finalize_response(self, request, response, *args, **kwargs): + response = super().finalize_response(request, response, *args, **kwargs) + + if hasattr(self, "_rls_cm"): + self._rls_cm.__exit__(None, None, None) + del self._rls_cm + + return response class BaseUserViewset(BaseViewSet): - def dispatch(self, request, *args, **kwargs): - with transaction.atomic(): - return super().dispatch(request, *args, **kwargs) - def initial(self, request, *args, **kwargs): + super().initial(request, *args, **kwargs) + # TODO refactor after improving RLS on users if request.stream is not None and request.stream.method == "POST": - return super().initial(request, *args, **kwargs) + return if request.auth is None: raise NotAuthenticated @@ -137,6 +153,16 @@ class BaseUserViewset(BaseViewSet): if tenant_id is None: raise NotAuthenticated("Tenant ID is not present in token") - with rls_transaction(tenant_id): - self.request.tenant_id = tenant_id - return super().initial(request, *args, **kwargs) + self.request.tenant_id = tenant_id + + self._rls_cm = rls_transaction(tenant_id) + self._rls_cm.__enter__() + + def finalize_response(self, request, response, *args, **kwargs): + response = super().finalize_response(request, response, *args, **kwargs) + + if hasattr(self, "_rls_cm"): + self._rls_cm.__exit__(None, None, None) + del self._rls_cm + + return response