diff --git a/app/common/permissions.py b/app/common/permissions.py index 2214c1cc..a7aa169a 100644 --- a/app/common/permissions.py +++ b/app/common/permissions.py @@ -47,6 +47,9 @@ def check_has_access(groups_with_access, request): set_user_id(request) user = request.user + if not user: + return False + try: groups = map(str, groups_with_access) return ( diff --git a/app/content/serializers/user.py b/app/content/serializers/user.py index d1bd1c54..b92ebbe2 100644 --- a/app/content/serializers/user.py +++ b/app/content/serializers/user.py @@ -177,7 +177,15 @@ def get_fields(self): class UserPermissionsSerializer(serializers.ModelSerializer): permissions = DRYGlobalPermissionsField( - actions=["write", "write_all", "read", "destroy", "update", "retrieve"] + actions=[ + "write", + "write_all", + "read", + "read_all", + "destroy", + "update", + "retrieve", + ] ) class Meta: diff --git a/app/content/views/user.py b/app/content/views/user.py index 90d787b2..9db67ca8 100644 --- a/app/content/views/user.py +++ b/app/content/views/user.py @@ -170,10 +170,16 @@ def connect_to_slack(self, request, *args, **kwargs): @action(detail=False, methods=["get"], url_path="me/permissions") def get_user_permissions(self, request, *args, **kwargs): - serializer = UserPermissionsSerializer( - request.user, context={"request": request} - ) - return Response(serializer.data, status=status.HTTP_200_OK) + try: + serializer = UserPermissionsSerializer( + request.user, context={"request": request} + ) + return Response(serializer.data, status=status.HTTP_200_OK) + except Exception: + return Response( + {"detail": "Kunne ikke hente brukerens tillatelser"}, + status=status.HTTP_500_INTERNAL_SERVER_ERROR, + ) @action(detail=True, methods=["get"], url_path="memberships") def get_user_memberships(self, request, pk, *args, **kwargs): diff --git a/app/payment/models/order.py b/app/payment/models/order.py index f5c2454d..dd221467 100644 --- a/app/payment/models/order.py +++ b/app/payment/models/order.py @@ -2,12 +2,11 @@ from django.db import models -from app.common.enums import AdminGroup +from app.common.enums import Groups from app.common.permissions import ( BasePermissionModel, - is_admin_group_user, + check_has_access, is_admin_user, - is_index_user, ) from app.content.models.event import Event from app.content.models.user import User @@ -16,7 +15,8 @@ class Order(BaseModel, BasePermissionModel): - access = AdminGroup.admin() + read_access = (Groups.TIHLDE,) + order_id = models.UUIDField( auto_created=True, default=uuid.uuid4, primary_key=True, serialize=False ) @@ -40,28 +40,64 @@ def __str__(self): @classmethod def has_update_permission(cls, request): - return is_admin_user(request) + return False @classmethod def has_destroy_permission(cls, request): - return is_index_user(request) + return False @classmethod def has_retrieve_permission(cls, request): - return is_admin_group_user(request) + if not request.user: + return False + + return ( + check_has_access(cls.read_access, request) + or is_admin_user(request) + or request.user.memberships_with_events_access.exists() + ) @classmethod def has_read_permission(cls, request): - return is_admin_group_user(request) + if not request.user: + return False - def has_object_read_permission(self, request): - return self.has_read_permission(request) + return ( + check_has_access(cls.read_access, request) + or request.user.memberships_with_events_access.exists() + ) + + @classmethod + def has_list_permission(cls, request): + return is_admin_user(request) + + @classmethod + def has_read_all_permission(cls, request): + return is_admin_user(request) def has_object_update_permission(self, request): - return self.has_update_permission(request) + return False def has_object_destroy_permission(self, request): - return self.has_destroy_permission(request) + return False def has_object_retrieve_permission(self, request): - return self.has_retrieve_permission(request) + if not request.user: + return False + + organizer = self.event.organizer + + return ( + self.check_request_user_has_access_through_organizer( + request.user, organizer + ) + or is_admin_user(request) + or self.user == request.user + ) + + def check_request_user_has_access_through_organizer(self, user, organizer): + # All memberships that have access to events will also have access to orders + if not organizer: + return False + + return user.memberships_with_events_access.filter(group=organizer).exists() diff --git a/app/payment/views/order.py b/app/payment/views/order.py index ebe4f685..9c3b765e 100644 --- a/app/payment/views/order.py +++ b/app/payment/views/order.py @@ -1,14 +1,15 @@ from django_filters.rest_framework import DjangoFilterBackend from rest_framework import filters, status +from rest_framework.decorators import action from rest_framework.response import Response from sentry_sdk import capture_exception from app.common.mixins import ActionMixin from app.common.pagination import BasePagination -from app.common.permissions import BasicViewPermission +from app.common.permissions import BasicViewPermission, is_admin_user from app.common.viewsets import BaseViewSet -from app.content.models import Registration, User +from app.content.models import Event, Registration, User from app.payment.filters.order import OrderFilter from app.payment.models import Order from app.payment.serializers import ( @@ -38,7 +39,7 @@ class OrderViewSet(BaseViewSet, ActionMixin): def retrieve(self, request, pk): try: - order = Order.objects.get(order_id=pk) + order = self.get_object() serializer = OrderSerializer( order, context={"request": request}, many=False ) @@ -103,3 +104,53 @@ def create(self, request, *args, **kwargs): {"detail": "Fant ikke bruker."}, status=status.HTTP_404_NOT_FOUND, ) + + @action(detail=False, methods=["GET"], url_path=r"event/(?P\d+)") + def event_orders(self, request, event_id): + try: + if is_admin_user(request): + orders = Order.objects.filter(event=event_id) + serializer = OrderListSerializer( + orders, context={"request": request}, many=True + ) + return Response(serializer.data, status.HTTP_200_OK) + + event = Event.objects.filter(id=event_id).first() + + if not event: + return Response( + {"detail": "Fant ikke arrangement."}, + status=status.HTTP_404_NOT_FOUND, + ) + + organizer = event.organizer + + if not organizer: + return Response( + {"detail": "Du har ikke tilgang til disse betalingsordrene."}, + status=status.HTTP_403_FORBIDDEN, + ) + + has_access_through_organizer = ( + request.user.memberships_with_events_access.filter( + group=organizer + ).exists() + ) + + if not has_access_through_organizer: + return Response( + {"detail": "Du har ikke tilgang til disse betalingsordrene."}, + status=status.HTTP_403_FORBIDDEN, + ) + + orders = Order.objects.filter(event=event) + + serializer = OrderListSerializer( + orders, context={"request": request}, many=True + ) + return Response(serializer.data, status.HTTP_200_OK) + except Exception: + return Response( + {"detail": "Det skjedde en feil på serveren."}, + status=status.HTTP_500_INTERNAL_SERVER_ERROR, + ) diff --git a/app/tests/payment/test_order_integration.py b/app/tests/payment/test_order_integration.py index df2cc14c..f3ef2461 100644 --- a/app/tests/payment/test_order_integration.py +++ b/app/tests/payment/test_order_integration.py @@ -3,7 +3,10 @@ import pytest from app.common.enums import AdminGroup +from app.group.factories import GroupFactory +from app.group.models import Group from app.payment.enums import OrderStatus +from app.payment.factories import OrderFactory from app.util.test_utils import add_user_to_group_with_name, get_api_client API_ORDERS_BASE_URL = "/payments/" @@ -29,9 +32,9 @@ def test_list_orders_as_user(member): @pytest.mark.django_db -@pytest.mark.parametrize("group_name", AdminGroup.all()) +@pytest.mark.parametrize("group_name", AdminGroup.admin()) def test_list_orders_as_admin_user(member, group_name): - """A member of an admin group should be able to list orders.""" + """An admin or index user should be able to list orders.""" add_user_to_group_with_name(member, group_name) client = get_api_client(user=member) response = client.get(API_ORDERS_BASE_URL) @@ -54,9 +57,19 @@ def test_retrieve_order_as_member(member, order): @pytest.mark.django_db -@pytest.mark.parametrize("group_name", AdminGroup.all()) +def test_retrieve_own_order_as_member(member, order): + """A user should be able to retrieve their own order.""" + order.user = member + order.save() + client = get_api_client(user=member) + response = client.get(get_orders_url_detail(order.order_id)) + assert response.status_code == status.HTTP_200_OK + + +@pytest.mark.django_db +@pytest.mark.parametrize("group_name", AdminGroup.admin()) def test_retrieve_order_as_admin_user(member, order, group_name): - """A member of an adming group should be able to retrieve an order.""" + """An admin or member of Index should be able to retrieve an order.""" add_user_to_group_with_name(member, group_name) client = get_api_client(user=member) response = client.get(get_orders_url_detail(order.order_id)) @@ -81,11 +94,11 @@ def test_delete_order_as_member(member, order): @pytest.mark.django_db @pytest.mark.parametrize("group_name", [AdminGroup.INDEX]) def test_delete_order_as_index_user(member, order, group_name): - """An index user should be able to delete an order.""" + """An index user should not be able to delete an order.""" add_user_to_group_with_name(member, group_name) client = get_api_client(user=member) response = client.delete(get_orders_url_detail(order.order_id)) - assert response.status_code == status.HTTP_204_NO_CONTENT + assert response.status_code == status.HTTP_403_FORBIDDEN @pytest.mark.django_db @@ -106,13 +119,53 @@ def test_update_order_as_member(member, order): @pytest.mark.django_db @pytest.mark.parametrize("group_name", [*AdminGroup.admin()]) def test_update_order_as_admin_user(member, order, group_name): - """An index and HS user should be able to update an order.""" + """An index and HS user should not be able to update an order.""" add_user_to_group_with_name(member, group_name) client = get_api_client(user=member) data = {"status": OrderStatus.SALE} response = client.put(get_orders_url_detail(order.order_id), data=data) + assert response.status_code == status.HTTP_403_FORBIDDEN + + +@pytest.mark.django_db +def test_list_all_orders_for_event_as_organizer(member, event): + """ + A member of an organizer group should be able to list all orders for an event. + """ + add_user_to_group_with_name(member, AdminGroup.SOSIALEN) + organizer = Group.objects.get(name=AdminGroup.SOSIALEN) + + event.organizer = organizer + event.save() + + orders = [OrderFactory(event=event) for _ in range(3)] + + url = f"{API_ORDERS_BASE_URL}event/{event.id}/" + client = get_api_client(user=member) + + response = client.get(url) + assert response.status_code == status.HTTP_200_OK + assert len(response.data) == len(orders) + + +@pytest.mark.django_db +def test_list_all_orders_for_event_as_non_organizer(member, event): + """ + A member of a group that is not the organizer should not be able to list all orders for an event. + """ + add_user_to_group_with_name(member, AdminGroup.NOK) + GroupFactory(name=AdminGroup.KOK) + organizer = Group.objects.get(name=AdminGroup.KOK) + + event.organizer = organizer + event.save() + + [OrderFactory(event=event) for _ in range(3)] - order.refresh_from_db() + url = f"{API_ORDERS_BASE_URL}event/{event.id}/" + client = get_api_client(user=member) + + response = client.get(url) - assert order.status == OrderStatus.SALE + assert response.status_code == status.HTTP_403_FORBIDDEN