Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Permissions for payment orders #808

Merged
merged 5 commits into from
Jun 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions app/common/permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down
10 changes: 9 additions & 1 deletion app/content/serializers/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
14 changes: 10 additions & 4 deletions app/content/views/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
62 changes: 49 additions & 13 deletions app/payment/models/order.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
)
Expand All @@ -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()
57 changes: 54 additions & 3 deletions app/payment/views/order.py
Original file line number Diff line number Diff line change
@@ -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 (
Expand Down Expand Up @@ -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
)
Expand Down Expand Up @@ -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<event_id>\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,
)
71 changes: 62 additions & 9 deletions app/tests/payment/test_order_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -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/"
Expand All @@ -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)
Expand All @@ -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))
Expand All @@ -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
Expand All @@ -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
Loading