(project) add CRUD API endpoints for Rooms and ResourceAccess models

Introduce CRUD API endpoints for the Rooms and ResourceAccess models.
The code follows the Magnify logic, with the exception that token generation
has been removed and replaced by a TODO item with a mocked value.

Proper integration of LiveKit will be added in future commits.

With the removal of group logic, some complex query sets can be simplified.
Previously, we checked for both direct and indirect access to a room.
Indirect access meant a room was shared with a group, and the user was a
member of that group. I haven’t simplified those query set, as I preferred
isolate changes in dedicated commits.

Additionally, all previous tests are still passing, although tests related
to groups have been removed.
This commit is contained in:
lebaudantoine
2024-06-25 00:21:36 +02:00
parent 2e6feede31
commit c90a92d5c9
13 changed files with 2033 additions and 0 deletions

View File

@@ -62,3 +62,17 @@ class UserAdmin(auth_admin.UserAdmin):
ordering = ("is_active", "-is_superuser", "-is_staff", "-is_device", "-updated_at")
readonly_fields = ("id", "sub", "email", "created_at", "updated_at")
search_fields = ("id", "sub", "admin_email", "email")
class ResourceAccessInline(admin.TabularInline):
"""Admin class for the room user access model"""
model = models.ResourceAccess
extra = 0
@admin.register(models.Room)
class RoomAdmin(admin.ModelAdmin):
"""Room admin interface declaration."""
inlines = (ResourceAccessInline,)

View File

@@ -1,6 +1,8 @@
"""Permission handlers for the impress core app."""
from rest_framework import permissions
from ..models import RoleChoices
ACTION_FOR_METHOD_TO_PERMISSION = {
"versions_detail": {"DELETE": "versions_destroy", "GET": "versions_retrieve"}
}
@@ -34,3 +36,49 @@ class IsSelf(IsAuthenticated):
def has_object_permission(self, request, view, obj):
"""Write permissions are only allowed to the user itself."""
return obj == request.user
class RoomPermissions(permissions.BasePermission):
"""
Permissions applying to the room API endpoint.
"""
def has_permission(self, request, view):
"""Only allow authenticated users for unsafe methods."""
if request.method in permissions.SAFE_METHODS:
return True
return request.user.is_authenticated
def has_object_permission(self, request, view, obj):
"""Object permissions are only given to administrators of the room."""
if request.method in permissions.SAFE_METHODS:
return True
user = request.user
if request.method == "DELETE":
return obj.is_owner(user)
return obj.is_administrator(user)
class ResourceAccessPermission(permissions.BasePermission):
"""
Permissions for a room that can only be updated by room administrators.
"""
def has_permission(self, request, view):
"""Only allow authenticated users."""
return request.user.is_authenticated
def has_object_permission(self, request, view, obj):
"""
Check that the logged-in user is administrator of the linked room.
"""
user = request.user
if request.method == "DELETE" and obj.role == RoleChoices.OWNER:
return obj.user == user
return obj.resource.is_administrator(user)

View File

@@ -1,5 +1,8 @@
"""Client serializers for the impress core app."""
from django.utils.translation import gettext_lazy as _
from rest_framework import serializers
from rest_framework.exceptions import PermissionDenied
from core import models
@@ -11,3 +14,120 @@ class UserSerializer(serializers.ModelSerializer):
model = models.User
fields = ["id", "email"]
read_only_fields = ["id", "email"]
class ResourceAccessSerializerMixin:
"""
A serializer mixin to share controlling that the logged-in user submitting a room access object
is administrator on the targeted room.
"""
# pylint: disable=too-many-boolean-expressions
def validate(self, data):
"""
Check access rights specific to writing (create/update)
"""
request = self.context.get("request", None)
user = getattr(request, "user", None)
if (
# Update
self.instance
and (
data["role"] == models.RoleChoices.OWNER
and not self.instance.resource.is_owner(user)
or self.instance.role == models.RoleChoices.OWNER
and not self.instance.user == user
)
) or (
# Create
not self.instance
and data.get("role") == models.RoleChoices.OWNER
and not data["resource"].is_owner(user)
):
raise PermissionDenied(
"Only owners of a room can assign other users as owners."
)
return data
def validate_resource(self, resource):
"""The logged-in user must be administrator of the resource."""
request = self.context.get("request", None)
user = getattr(request, "user", None)
if not (user and user.is_authenticated and resource.is_administrator(user)):
raise PermissionDenied(
_("You must be administrator or owner of a room to add accesses to it.")
)
return resource
class ResourceAccessSerializer(
ResourceAccessSerializerMixin, serializers.ModelSerializer
):
"""Serialize Room to User accesses for the API."""
class Meta:
model = models.ResourceAccess
fields = ["id", "user", "resource", "role"]
read_only_fields = ["id"]
def update(self, instance, validated_data):
"""Make "user" and "resource" fields readonly but only on update."""
validated_data.pop("resource", None)
validated_data.pop("user", None)
return super().update(instance, validated_data)
class NestedResourceAccessSerializer(ResourceAccessSerializer):
"""Serialize Room accesses for the API with full nested user."""
user = UserSerializer(read_only=True)
class RoomSerializer(serializers.ModelSerializer):
"""Serialize Room model for the API."""
class Meta:
model = models.Room
fields = ["id", "name", "slug", "configuration", "is_public"]
read_only_fields = ["id", "slug"]
def to_representation(self, instance):
"""
Add users only for administrator users.
Add LiveKit credentials for public instance or related users/groups
"""
output = super().to_representation(instance)
request = self.context.get("request")
if not request:
return output
role = instance.get_role(request.user)
is_admin = models.RoleChoices.check_administrator_role(role)
if role is not None:
access_serializer = NestedResourceAccessSerializer(
instance.accesses.select_related("resource", "user").all(),
context=self.context,
many=True,
)
output["accesses"] = access_serializer.data
if not is_admin:
del output["configuration"]
if role is not None or instance.is_public:
output["livekit"] = {
# todo - generate a proper livekit name
"room": "foo",
# todo - generate a proper token
"token": "foo",
}
output["is_administrable"] = is_admin
# todo - pass properly livekit configuration
return output

View File

@@ -1,4 +1,12 @@
"""API endpoints"""
import uuid
from django.conf import settings
from django.db.models import Q
from django.http import Http404
from django.shortcuts import get_object_or_404
from django.utils.text import slugify
from rest_framework import (
decorators,
mixins,
@@ -140,3 +148,131 @@ class UserViewSet(
return drf_response.Response(
self.serializer_class(request.user, context=context).data
)
class RoomViewSet(
mixins.CreateModelMixin,
mixins.DestroyModelMixin,
mixins.UpdateModelMixin,
viewsets.GenericViewSet,
):
"""
API endpoints to access and perform actions on rooms.
"""
permission_classes = [permissions.RoomPermissions]
queryset = models.Room.objects.all()
serializer_class = serializers.RoomSerializer
def get_object(self):
"""Allow getting a room by its slug."""
try:
uuid.UUID(self.kwargs["pk"])
filter_kwargs = {"pk": self.kwargs["pk"]}
except ValueError:
filter_kwargs = {"slug": slugify(self.kwargs["pk"])}
queryset = self.filter_queryset(self.get_queryset())
obj = get_object_or_404(queryset, **filter_kwargs)
# May raise a permission denied
self.check_object_permissions(self.request, obj)
return obj
def retrieve(self, request, *args, **kwargs):
"""
Allow unregistered rooms when activated.
For unregistered rooms we only return a null id and the livekit room and token.
"""
try:
instance = self.get_object()
except Http404:
if not settings.ALLOW_UNREGISTERED_ROOMS:
raise
slug = slugify(self.kwargs["pk"])
data = {
"id": None,
"livekit": {
"room": slug,
# todo - generate a proper token
"token": "foo",
},
}
else:
data = self.get_serializer(instance).data
return drf_response.Response(data)
def list(self, request, *args, **kwargs):
"""Limit listed rooms to the ones related to the authenticated user."""
user = self.request.user
if user.is_authenticated:
# todo - simplify this queryset
queryset = (
self.filter_queryset(self.get_queryset())
.filter(Q(users=user))
.distinct()
)
else:
queryset = self.get_queryset().none()
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(queryset, many=True)
return drf_response.Response(serializer.data)
def perform_create(self, serializer):
"""Set the current user as owner of the newly created room."""
room = serializer.save()
models.ResourceAccess.objects.create(
resource=room,
user=self.request.user,
role=models.RoleChoices.OWNER,
)
class ResourceAccessListModelMixin:
"""List mixin for resource access API."""
def get_permissions(self):
"""User only needs to be authenticated to list rooms access"""
if self.action == "list":
permission_classes = [permissions.IsAuthenticated]
else:
return super().get_permissions()
return [permission() for permission in permission_classes]
def get_queryset(self):
"""Return the queryset according to the action."""
queryset = super().get_queryset()
if self.action == "list":
user = self.request.user
queryset = queryset.filter(
Q(resource__accesses__user=user),
resource__accesses__role__in=[
models.RoleChoices.ADMIN,
models.RoleChoices.OWNER,
],
).distinct()
return queryset
class ResourceAccessViewSet(
ResourceAccessListModelMixin,
mixins.CreateModelMixin,
mixins.DestroyModelMixin,
mixins.ListModelMixin,
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
viewsets.GenericViewSet,
):
"""
API endpoints to access and perform actions on resource accesses.
"""
permission_classes = [permissions.ResourceAccessPermission]
queryset = models.ResourceAccess.objects.all()
serializer_class = serializers.ResourceAccessSerializer

View File

View File

@@ -0,0 +1,71 @@
"""
Test rooms API endpoints in the impress core app: create.
"""
import pytest
from rest_framework.test import APIClient
from ...factories import RoomFactory, UserFactory
from ...models import Room
pytestmark = pytest.mark.django_db
def test_api_rooms_create_anonymous():
"""Anonymous users should not be allowed to create rooms."""
client = APIClient()
response = client.post(
"/api/v1.0/rooms/",
{
"name": "my room",
},
)
assert response.status_code == 401
assert Room.objects.exists() is False
def test_api_rooms_create_authenticated():
"""
Authenticated users should be able to create rooms and should automatically be declared
as owner of the newly created room.
"""
user = UserFactory()
client = APIClient()
client.force_login(user)
response = client.post(
"/api/v1.0/rooms/",
{
"name": "my room",
},
)
assert response.status_code == 201
room = Room.objects.get()
assert room.name == "my room"
assert room.slug == "my-room"
assert room.accesses.filter(role="owner", user=user).exists() is True
def test_api_rooms_create_authenticated_existing_slug():
"""
A user trying to create a room with a name that translates to a slug that already exists
should receive a 400 error.
"""
RoomFactory(name="my room")
user = UserFactory()
client = APIClient()
client.force_login(user)
response = client.post(
"/api/v1.0/rooms/",
{
"name": "My Room!",
},
)
assert response.status_code == 400
assert response.json() == {"slug": ["Room with this Slug already exists."]}

View File

@@ -0,0 +1,101 @@
"""
Test rooms API endpoints in the impress core app: delete.
"""
import pytest
from rest_framework.test import APIClient
from ...factories import RoomFactory, UserFactory
from ...models import Room
pytestmark = pytest.mark.django_db
def test_api_rooms_delete_anonymous():
"""Anonymous users should not be allowed to destroy a room."""
room = RoomFactory()
client = APIClient()
response = client.delete(
f"/api/v1.0/rooms/{room.id!s}/",
)
assert response.status_code == 401
assert Room.objects.count() == 1
def test_api_rooms_delete_authenticated():
"""
Authenticated users should not be allowed to delete a room to which they are not
related.
"""
room = RoomFactory()
user = UserFactory()
client = APIClient()
client.force_login(user)
response = client.delete(
f"/api/v1.0/rooms/{room.id!s}/",
)
assert response.status_code == 403
assert Room.objects.count() == 1
def test_api_rooms_delete_members():
"""
Authenticated users should not be allowed to delete a room for which they are
only a member.
"""
user = UserFactory()
room = RoomFactory(
users=[(user, "member")]
) # as user declared in the room but not administrator
client = APIClient()
client.force_login(user)
response = client.delete(
f"/api/v1.0/rooms/{room.id}/",
)
assert response.status_code == 403
assert Room.objects.count() == 1
def test_api_rooms_delete_administrators():
"""
Authenticated users should not be allowed to delete a room for which they are
administrator.
"""
user = UserFactory()
room = RoomFactory(users=[(user, "administrator")])
client = APIClient()
client.force_login(user)
response = client.delete(
f"/api/v1.0/rooms/{room.id}/",
)
assert response.status_code == 403
assert Room.objects.count() == 1
def test_api_rooms_delete_owners():
"""
Authenticated users should be able to delete a room for which they are directly
owner.
"""
user = UserFactory()
room = RoomFactory(users=[(user, "owner")])
client = APIClient()
client.force_login(user)
response = client.delete(
f"/api/v1.0/rooms/{room.id}/",
)
assert response.status_code == 204
assert Room.objects.exists() is False

View File

@@ -0,0 +1,116 @@
"""
Test rooms API endpoints in the impress core app: list.
"""
from unittest import mock
import pytest
from rest_framework.pagination import PageNumberPagination
from rest_framework.test import APIClient
from ...factories import RoomFactory, UserFactory
pytestmark = pytest.mark.django_db
def test_api_rooms_list_anonymous():
"""Anonymous users should not be able to list rooms."""
RoomFactory(is_public=False)
RoomFactory(is_public=True)
client = APIClient()
response = client.get("/api/v1.0/rooms/")
assert response.status_code == 200
results = response.json()["results"]
assert len(results) == 0
def test_api_rooms_list_authenticated():
"""
Authenticated users listing rooms, should only see the rooms
to which they are related.
"""
user = UserFactory()
client = APIClient()
client.force_login(user)
other_user = UserFactory()
RoomFactory(is_public=False)
RoomFactory(is_public=True)
room_user_accesses = RoomFactory(is_public=False, users=[user])
RoomFactory(is_public=False, users=[other_user])
response = client.get(
"/api/v1.0/rooms/",
)
assert response.status_code == 200
results = response.json()["results"]
assert len(results) == 1
expected_ids = {
str(room_user_accesses.id),
}
results_id = {result["id"] for result in results}
assert expected_ids == results_id
@mock.patch.object(PageNumberPagination, "get_page_size", return_value=2)
def test_api_rooms_list_pagination(_mock_page_size):
"""Pagination should work as expected."""
user = UserFactory()
client = APIClient()
client.force_login(user)
rooms = RoomFactory.create_batch(3, users=[user])
room_ids = [str(room.id) for room in rooms]
response = client.get(
"/api/v1.0/rooms/",
)
assert response.status_code == 200
content = response.json()
assert content["count"] == 3
assert content["next"] == "http://testserver/api/v1.0/rooms/?page=2"
assert content["previous"] is None
assert len(content["results"]) == 2
for item in content["results"]:
room_ids.remove(item["id"])
# Get page 2
response = client.get(
"/api/v1.0/rooms/?page=2",
)
assert response.status_code == 200
content = response.json()
assert content["count"] == 3
assert content["next"] is None
assert content["previous"], "http://testserver/api/v1.0/rooms/"
assert len(content["results"]) == 1
room_ids.remove(content["results"][0]["id"])
assert room_ids == []
def test_api_rooms_list_authenticated_distinct():
"""A public room with several related users should only be listed once."""
user = UserFactory()
other_user = UserFactory()
client = APIClient()
client.force_login(user)
room = RoomFactory(is_public=True, users=[user, other_user])
response = client.get(
"/api/v1.0/rooms/",
)
assert response.status_code == 200
content = response.json()
assert len(content["results"]) == 1
assert content["results"][0]["id"] == str(room.id)

View File

@@ -0,0 +1,344 @@
"""
Test rooms API endpoints in the impress core app: retrieve.
"""
import random
from django.test.utils import override_settings
import pytest
from rest_framework.test import APIClient
from ...factories import RoomFactory, UserFactory, UserResourceAccessFactory
pytestmark = pytest.mark.django_db
def test_api_rooms_retrieve_anonymous_private_pk():
"""
Anonymous users should be allowed to retrieve a private room but should not be
given any token.
"""
room = RoomFactory(is_public=False)
client = APIClient()
response = client.get(f"/api/v1.0/rooms/{room.id!s}/")
assert response.status_code == 200
assert response.json() == {
"id": str(room.id),
"is_administrable": False,
"is_public": False,
"name": room.name,
"slug": room.slug,
}
def test_api_rooms_retrieve_anonymous_private_pk_no_dashes():
"""It should be possible to get a room by its id stripped of its dashes."""
room = RoomFactory(is_public=False)
id_no_dashes = str(room.id).replace("-", "")
client = APIClient()
response = client.get(f"/api/v1.0/rooms/{id_no_dashes:s}/")
assert response.status_code == 200
assert response.json() == {
"id": str(room.id),
"is_administrable": False,
"is_public": False,
"name": room.name,
"slug": room.slug,
}
def test_api_rooms_retrieve_anonymous_private_slug():
"""It should be possible to get a room by its slug."""
room = RoomFactory(is_public=False)
client = APIClient()
response = client.get(f"/api/v1.0/rooms/{room.slug!s}/")
assert response.status_code == 200
assert response.json() == {
"id": str(room.id),
"is_administrable": False,
"is_public": False,
"name": room.name,
"slug": room.slug,
}
def test_api_rooms_retrieve_anonymous_private_slug_not_normalized():
"""Getting a room by a slug that is not normalized should work."""
room = RoomFactory(name="Réunion", is_public=False)
client = APIClient()
response = client.get("/api/v1.0/rooms/Réunion/")
assert response.status_code == 200
assert response.json() == {
"id": str(room.id),
"is_administrable": False,
"is_public": False,
"name": room.name,
"slug": room.slug,
}
@override_settings(ALLOW_UNREGISTERED_ROOMS=True)
def test_api_rooms_retrieve_anonymous_unregistered_allowed():
"""
Retrieving an unregistered room should return a Livekit token
if unregistered rooms are allowed.
"""
client = APIClient()
response = client.get("/api/v1.0/rooms/unregistered-room/")
assert response.status_code == 200
assert response.json() == {
"id": None,
"livekit": {
"room": "unregistered-room",
"token": "foo",
},
}
# todo - assert generate_token has been called
@override_settings(ALLOW_UNREGISTERED_ROOMS=True)
def test_api_rooms_retrieve_anonymous_unregistered_allowed_not_normalized():
"""
Getting an unregistered room by a slug that is not normalized should work
and use the Livekit room on the url-safe name.
"""
client = APIClient()
response = client.get("/api/v1.0/rooms/Réunion/")
assert response.status_code == 200
assert response.json() == {
"id": None,
"livekit": {
"room": "reunion",
"token": "foo",
},
}
# todo - assert generate_token has been called
@override_settings(ALLOW_UNREGISTERED_ROOMS=False)
def test_api_rooms_retrieve_anonymous_unregistered_not_allowed():
"""
Retrieving an unregistered room should return a 404 if unregistered rooms are not allowed.
"""
client = APIClient()
response = client.get("/api/v1.0/rooms/unregistered-room/")
assert response.status_code == 404
assert response.json() == {"detail": "Not found."}
def test_api_rooms_retrieve_anonymous_public():
"""
Anonymous users should be able to retrieve a room with a token provided it is public.
"""
room = RoomFactory(is_public=True)
client = APIClient()
response = client.get(f"/api/v1.0/rooms/{room.id!s}/")
assert response.status_code == 200
assert response.json() == {
"id": str(room.id),
"is_administrable": False,
"is_public": True,
"livekit": {
"room": "foo",
"token": "foo",
},
"name": room.name,
"slug": room.slug,
}
# todo - assert generate_token has been called
def test_api_rooms_retrieve_authenticated_public():
"""
Authenticated users should be allowed to retrieve a room and get a token for a room to
which they are not related, provided the room is public.
They should not see related users.
"""
room = RoomFactory(is_public=True)
user = UserFactory()
client = APIClient()
client.force_login(user)
response = client.get(
f"/api/v1.0/rooms/{room.id!s}/",
)
assert response.status_code == 200
assert response.json() == {
"id": str(room.id),
"is_administrable": False,
"is_public": True,
"livekit": {
"room": "foo",
"token": "foo",
},
"name": room.name,
"slug": room.slug,
}
# todo - assert generate_token has been called
def test_api_rooms_retrieve_authenticated():
"""
Authenticated users should be allowed to retrieve a private room to which they
are not related but should not be given any token.
"""
room = RoomFactory(is_public=False)
user = UserFactory()
client = APIClient()
client.force_login(user)
response = client.get(
f"/api/v1.0/rooms/{room.id!s}/",
)
assert response.status_code == 200
assert response.json() == {
"id": str(room.id),
"is_administrable": False,
"is_public": False,
"name": room.name,
"slug": room.slug,
}
def test_api_rooms_retrieve_members(django_assert_num_queries):
"""
Users who are members of a room should be allowed to see related users.
"""
user = UserFactory()
other_user = UserFactory()
room = RoomFactory()
user_access = UserResourceAccessFactory(resource=room, user=user, role="member")
other_user_access = UserResourceAccessFactory(
resource=room, user=other_user, role="member"
)
client = APIClient()
client.force_login(user)
with django_assert_num_queries(4):
response = client.get(
f"/api/v1.0/rooms/{room.id!s}/",
)
assert response.status_code == 200
content_dict = response.json()
assert sorted(content_dict.pop("accesses"), key=lambda x: x["id"]) == sorted(
[
{
"id": str(user_access.id),
"user": {
"id": str(user_access.user.id),
"email": user_access.user.email,
},
"resource": str(room.id),
"role": user_access.role,
},
{
"id": str(other_user_access.id),
"user": {
"id": str(other_user_access.user.id),
"email": other_user_access.user.email,
},
"resource": str(room.id),
"role": other_user_access.role,
},
],
key=lambda x: x["id"],
)
assert content_dict == {
"id": str(room.id),
"is_administrable": False,
"is_public": room.is_public,
"livekit": {
"room": "foo",
"token": "foo",
},
"name": room.name,
"slug": room.slug,
}
# todo - assert generate_token has been called
def test_api_rooms_retrieve_administrators(django_assert_num_queries):
"""
A user who is an administrator or owner of a room should be allowed
to see related users.
"""
user = UserFactory()
other_user = UserFactory()
room = RoomFactory()
user_access = UserResourceAccessFactory(
resource=room, user=user, role=random.choice(["administrator", "owner"])
)
other_user_access = UserResourceAccessFactory(
resource=room, user=other_user, role="member"
)
client = APIClient()
client.force_login(user)
with django_assert_num_queries(4):
response = client.get(
f"/api/v1.0/rooms/{room.id!s}/",
)
assert response.status_code == 200
content_dict = response.json()
assert sorted(content_dict.pop("accesses"), key=lambda x: x["id"]) == sorted(
[
{
"id": str(other_user_access.id),
"user": {
"id": str(other_user_access.user.id),
"email": other_user_access.user.email,
},
"resource": str(room.id),
"role": other_user_access.role,
},
{
"id": str(user_access.id),
"user": {
"id": str(user_access.user.id),
"email": user_access.user.email,
},
"resource": str(room.id),
"role": user_access.role,
},
],
key=lambda x: x["id"],
)
assert content_dict == {
"id": str(room.id),
"is_administrable": True,
"is_public": room.is_public,
"configuration": {},
"livekit": {
"room": "foo",
"token": "foo",
},
"name": room.name,
"slug": room.slug,
}
# todo - assert generate_token has been called

View File

@@ -0,0 +1,123 @@
"""
Test rooms API endpoints in the impress core app: update.
"""
import random
import pytest
from rest_framework.test import APIClient
from ...factories import RoomFactory, UserFactory
pytestmark = pytest.mark.django_db
def test_api_rooms_update_anonymous():
"""Anonymous users should not be allowed to update a room."""
room = RoomFactory(name="Old name")
client = APIClient()
response = client.put(
f"/api/v1.0/rooms/{room.id!s}/",
{
"name": "New name",
},
)
assert response.status_code == 401
room.refresh_from_db()
assert room.name == "Old name"
assert room.slug == "old-name"
def test_api_rooms_update_authenticated():
"""Authenticated users should not be allowed to update a room."""
user = UserFactory()
room = RoomFactory(name="Old name")
client = APIClient()
client.force_login(user)
response = client.put(
f"/api/v1.0/rooms/{room.id!s}/",
{
"name": "New name",
},
)
assert response.status_code == 403
room.refresh_from_db()
assert room.name == "Old name"
assert room.slug == "old-name"
def test_api_rooms_update_members():
"""
Users who are members of a room but not administrators should
not be allowed to update it.
"""
user = UserFactory()
room = RoomFactory(name="Old name", users=[(user, "member")])
client = APIClient()
client.force_login(user)
new_is_public = not room.is_public
response = client.put(
f"/api/v1.0/rooms/{room.id!s}/",
{
"name": "New name",
"slug": "should-be-ignored",
"is_public": new_is_public,
"configuration": {"the_key": "the_value"},
},
format="json",
)
assert response.status_code, 403
room.refresh_from_db()
assert room.name == "Old name"
assert room.slug == "old-name"
assert room.is_public != new_is_public
assert room.configuration == {}
def test_api_rooms_update_administrators():
"""Administrators or owners of a room should be allowed to update it."""
user = UserFactory()
room = RoomFactory(users=[(user, random.choice(["administrator", "owner"]))])
client = APIClient()
client.force_login(user)
new_is_public = not room.is_public
response = client.put(
f"/api/v1.0/rooms/{room.id!s}/",
{
"name": "New name",
"slug": "should-be-ignored",
"is_public": new_is_public,
"configuration": {"the_key": "the_value"},
},
format="json",
)
assert response.status_code == 200
room.refresh_from_db()
assert room.name == "New name"
assert room.slug == "new-name"
assert room.is_public == new_is_public
assert room.configuration == {"the_key": "the_value"}
def test_api_rooms_update_administrators_of_another():
"""
Being administrator or owner of a room should not grant authorization to update
another room.
"""
user = UserFactory()
RoomFactory(users=[(user, random.choice(["administrator", "owner"]))])
other_room = RoomFactory(name="Old name")
client = APIClient()
client.force_login(user)
response = client.put(
f"/api/v1.0/rooms/{other_room.id!s}/",
{"name": "New name", "slug": "should-be-ignored"},
)
assert response.status_code, 403
other_room.refresh_from_db()
assert other_room.name == "Old name"
assert other_room.slug == "old-name"

View File

@@ -0,0 +1,953 @@
"""
Test resource accesses API endpoints in the impress core app.
"""
import random
from unittest import mock
from uuid import uuid4
import pytest
from rest_framework.pagination import PageNumberPagination
from rest_framework.test import APIClient
from ..api.serializers import ResourceAccessSerializer
from ..factories import (
RoomFactory,
UserFactory,
UserResourceAccessFactory,
)
from ..models import ResourceAccess, RoleChoices
pytestmark = pytest.mark.django_db
def test_api_room_user_accesses_list_anonymous():
"""Anonymous users should not be allowed to list room user accesses."""
UserResourceAccessFactory()
client = APIClient()
response = client.get("/api/v1.0/resource-accesses/")
assert response.status_code == 401
assert response.json() == {
"detail": "Authentication credentials were not provided."
}
def test_api_room_user_accesses_list_authenticated_not_related():
"""
Authenticated users should not be allowed to list room user accesses for a room
to which they are not related, be it public or private.
"""
user = UserFactory()
client = APIClient()
client.force_login(user)
public_room = RoomFactory(is_public=True)
UserResourceAccessFactory(resource=public_room)
UserResourceAccessFactory(resource=public_room, role="member")
UserResourceAccessFactory(resource=public_room, role="administrator")
UserResourceAccessFactory(resource=public_room, role="owner")
private_room = RoomFactory(is_public=False)
UserResourceAccessFactory(resource=private_room)
UserResourceAccessFactory(resource=private_room, role="member")
UserResourceAccessFactory(resource=private_room, role="administrator")
UserResourceAccessFactory(resource=private_room, role="owner")
response = client.get(
"/api/v1.0/resource-accesses/",
)
assert response.status_code == 200
assert response.json()["results"] == []
def test_api_room_user_accesses_list_authenticated_member():
"""
Authenticated users should not be allowed to list room user accesses for a room
in which they are a simple member.
"""
user = UserFactory()
client = APIClient()
client.force_login(user)
public_room = RoomFactory(is_public=True, users=[(user, "member")])
UserResourceAccessFactory(resource=public_room)
UserResourceAccessFactory(resource=public_room, role="member")
UserResourceAccessFactory(resource=public_room, role="administrator")
UserResourceAccessFactory(resource=public_room, role="owner")
private_room = RoomFactory(is_public=False, users=[(user, "member")])
UserResourceAccessFactory(resource=private_room)
UserResourceAccessFactory(resource=private_room, role="member")
UserResourceAccessFactory(resource=private_room, role="administrator")
UserResourceAccessFactory(resource=private_room, role="owner")
response = client.get(
"/api/v1.0/resource-accesses/",
)
assert response.status_code == 200
assert response.json()["results"] == []
def test_api_room_user_accesses_list_authenticated_administrator():
"""
Authenticated users should be allowed to list room user accesses for a room
in which they are an administrator.
"""
user = UserFactory()
client = APIClient()
client.force_login(user)
public_room = RoomFactory(is_public=True)
public_room_accesses = (
# Access for the logged-in user
UserResourceAccessFactory(
resource=public_room, user=user, role="administrator"
),
# Accesses for other users
UserResourceAccessFactory(resource=public_room),
UserResourceAccessFactory(resource=public_room, role="member"),
UserResourceAccessFactory(resource=public_room, role="administrator"),
UserResourceAccessFactory(resource=public_room, role="owner"),
)
private_room = RoomFactory(is_public=False)
private_room_accesses = (
# Access for the logged-in user
UserResourceAccessFactory(
resource=private_room, user=user, role="administrator"
),
# Accesses for other users
UserResourceAccessFactory(resource=private_room),
UserResourceAccessFactory(resource=private_room, role="member"),
UserResourceAccessFactory(resource=private_room, role="administrator"),
UserResourceAccessFactory(resource=private_room, role="owner"),
)
response = client.get(
"/api/v1.0/resource-accesses/",
)
assert response.status_code == 200
results = response.json()["results"]
assert len(results) == 10
assert [item["id"] for item in results].sort() == [
str(access.id) for access in public_room_accesses + private_room_accesses
].sort()
def test_api_room_user_accesses_list_authenticated_owner():
"""
Authenticated users should be allowed to list room user accesses for a room
in which they are owner.
"""
user = UserFactory()
client = APIClient()
client.force_login(user)
public_room = RoomFactory(is_public=True)
public_room_accesses = (
# Access for the logged-in user
UserResourceAccessFactory(resource=public_room, user=user, role="owner"),
# Accesses for other users
UserResourceAccessFactory(resource=public_room),
UserResourceAccessFactory(resource=public_room, role="member"),
UserResourceAccessFactory(resource=public_room, role="administrator"),
UserResourceAccessFactory(resource=public_room, role="owner"),
)
private_room = RoomFactory(is_public=False)
private_room_accesses = (
# Access for the logged-in user
UserResourceAccessFactory(resource=private_room, user=user, role="owner"),
# Accesses for other users
UserResourceAccessFactory(resource=private_room),
UserResourceAccessFactory(resource=private_room, role="member"),
UserResourceAccessFactory(resource=private_room, role="administrator"),
UserResourceAccessFactory(resource=private_room, role="owner"),
)
response = client.get("/api/v1.0/resource-accesses/")
assert response.status_code == 200
results = response.json()["results"]
assert len(results) == 10
assert [item["id"] for item in results].sort() == [
str(access.id) for access in public_room_accesses + private_room_accesses
].sort()
@mock.patch.object(PageNumberPagination, "get_page_size", return_value=2)
def test_api_room_user_accesses_list_pagination(_mock_page_size):
"""Pagination should work as expected."""
user = UserFactory()
client = APIClient()
client.force_login(user)
room = RoomFactory()
accesses = [
UserResourceAccessFactory(
resource=room, user=user, role=random.choice(["administrator", "owner"])
),
*UserResourceAccessFactory.create_batch(2, resource=room),
]
access_ids = [str(access.id) for access in accesses]
response = client.get(
"/api/v1.0/resource-accesses/",
)
assert response.status_code == 200
content = response.json()
assert content["count"] == 3
assert content["next"] == "http://testserver/api/v1.0/resource-accesses/?page=2"
assert content["previous"] is None
assert len(content["results"]) == 2
for item in content["results"]:
access_ids.remove(item["id"])
# Get page 2
response = client.get("/api/v1.0/resource-accesses/?page=2")
assert response.status_code == 200
content = response.json()
assert content["count"] == 3
assert content["next"] is None
assert content["previous"] == "http://testserver/api/v1.0/resource-accesses/"
assert len(content["results"]) == 1
access_ids.remove(content["results"][0]["id"])
assert access_ids == []
# Retrieve
def test_api_room_user_accesses_retrieve_anonymous():
"""
Anonymous users should not be allowed to retrieve a room user access.
"""
access = UserResourceAccessFactory()
client = APIClient()
response = client.get(
f"/api/v1.0/resource-accesses/{access.id!s}/",
)
assert response.status_code == 401
assert response.json() == {
"detail": "Authentication credentials were not provided."
}
def test_api_room_user_accesses_retrieve_authenticated_not_related():
"""
Authenticated users should not be allowed to retrieve a room user access for
a room to which they are not related, be it public or private.
"""
user = UserFactory()
client = APIClient()
client.force_login(user)
for is_public in [True, False]:
room = RoomFactory(is_public=is_public)
assert len(RoleChoices.choices) == 3
for role, _name in RoleChoices.choices:
access = UserResourceAccessFactory(resource=room, role=role)
response = client.get(
f"/api/v1.0/resource-accesses/{access.id!s}/",
)
assert response.status_code == 403
assert response.json() == {
"detail": "You do not have permission to perform this action."
}
def test_api_room_user_accesses_retrieve_authenticated_member():
"""
Authenticated users should not be allowed to retrieve a room user access for a room in
which they are a simple member, be it public or private.
"""
user = UserFactory()
client = APIClient()
client.force_login(user)
for is_public in [True, False]:
room = RoomFactory(
is_public=is_public,
users=[(user, "member")],
)
assert len(RoleChoices.choices) == 3
for role, _name in RoleChoices.choices:
access = UserResourceAccessFactory(resource=room, role=role)
response = client.get(
f"/api/v1.0/resource-accesses/{access.id!s}/",
)
assert response.status_code == 403
assert response.json() == {
"detail": "You do not have permission to perform this action."
}
def test_api_room_user_accesses_retrieve_authenticated_administrator():
"""
A user who is an administrator of a room should be allowed to retrieve the
associated room user accesses
"""
user = UserFactory()
client = APIClient()
client.force_login(user)
for is_public in [True, False]:
room = RoomFactory(is_public=is_public, users=[(user, "administrator")])
assert len(RoleChoices.choices) == 3
for role, _name in RoleChoices.choices:
access = UserResourceAccessFactory(resource=room, role=role)
response = client.get(
f"/api/v1.0/resource-accesses/{access.id!s}/",
)
assert response.status_code == 200
assert response.json() == {
"id": str(access.id),
"user": str(access.user.id),
"resource": str(access.resource_id),
"role": access.role,
}
def test_api_room_user_accesses_retrieve_authenticated_owner():
"""
A user who is an owner of a room should be allowed to retrieve the
associated room user accesses
"""
user = UserFactory()
client = APIClient()
client.force_login(user)
for is_public in [True, False]:
room = RoomFactory(is_public=is_public, users=[(user, "owner")])
assert len(RoleChoices.choices) == 3
for role, _name in RoleChoices.choices:
access = UserResourceAccessFactory(resource=room, role=role)
response = client.get(
f"/api/v1.0/resource-accesses/{access.id!s}/",
)
assert response.status_code == 200
assert response.json() == {
"id": str(access.id),
"user": str(access.user.id),
"resource": str(access.resource_id),
"role": access.role,
}
# Create
def test_api_room_user_accesses_create_anonymous():
"""Anonymous users should not be allowed to create room user accesses."""
user = UserFactory()
room = RoomFactory()
client = APIClient()
response = client.post(
"/api/v1.0/resource-accesses/",
{
"user": str(user.id),
"resource": str(room.id),
"role": random.choice(["member", "administrator", "owner"]),
},
)
assert response.status_code == 401
assert response.json() == {
"detail": "Authentication credentials were not provided."
}
assert ResourceAccess.objects.exists() is False
def test_api_room_user_accesses_create_authenticated():
"""Authenticated users should not be allowed to create room user accesses."""
user, other_user = UserFactory.create_batch(2)
room = RoomFactory()
client = APIClient()
client.force_login(user)
response = client.post(
"/api/v1.0/resource-accesses/",
{
"user": str(other_user.id),
"resource": str(room.id),
"role": random.choice(["member", "administrator", "owner"]),
},
)
assert response.status_code == 403
assert response.json() == {
"detail": "You must be administrator or owner of a room to add accesses to it."
}
assert ResourceAccess.objects.filter(user=other_user).exists() is False
def test_api_room_user_accesses_create_members():
"""
A user who is a simple member in a room should not be allowed to create
room user accesses in this room.
"""
user, other_user = UserFactory.create_batch(2)
room = RoomFactory(users=[(user, "member")])
client = APIClient()
client.force_login(user)
response = client.post(
"/api/v1.0/resource-accesses/",
{
"user": str(other_user.id),
"resource": str(room.id),
"role": random.choice(["member", "administrator", "owner"]),
},
)
assert response.status_code == 403
assert response.json() == {
"detail": "You must be administrator or owner of a room to add accesses to it."
}
assert ResourceAccess.objects.filter(user=other_user).exists() is False
def test_api_room_user_accesses_create_administrators_except_owner():
"""
A user who is administrator in a room should be allowed to create
room user accesses in this room for roles other than owner (which is tested in the
subsequent test).
"""
user, other_user = UserFactory.create_batch(2)
room = RoomFactory(users=[(user, "administrator")])
client = APIClient()
client.force_login(user)
response = client.post(
"/api/v1.0/resource-accesses/",
{
"user": str(other_user.id),
"resource": str(room.id),
"role": random.choice(["member", "administrator"]),
},
)
assert response.status_code == 201
assert ResourceAccess.objects.count() == 2
assert ResourceAccess.objects.filter(user=other_user).exists() is True
def test_api_room_user_accesses_create_administrators_owner():
"""
A user who is administrator in a room should not be allowed to create room
user accesses in this room for the owner role.
"""
user, other_user = UserFactory.create_batch(2)
room = RoomFactory(users=[(user, "administrator")])
client = APIClient()
client.force_login(user)
response = client.post(
"/api/v1.0/resource-accesses/",
{
"user": str(other_user.id),
"resource": str(room.id),
"role": "owner",
},
)
assert response.status_code == 403
assert ResourceAccess.objects.filter(user=other_user).exists() is False
def test_api_room_user_accesses_create_owner_all_roles():
"""
A user who is owner in a room should be allowed to create
room user accesses in this room for all roles.
"""
user = UserFactory()
room = RoomFactory(users=[(user, "owner")])
client = APIClient()
client.force_login(user)
for i, role in enumerate(["member", "administrator", "owner"]):
other_user = UserFactory()
response = client.post(
"/api/v1.0/resource-accesses/",
{
"user": str(other_user.id),
"resource": str(room.id),
"role": role,
},
)
assert response.status_code == 201
assert ResourceAccess.objects.count() == i + 2
assert ResourceAccess.objects.filter(user=other_user).exists() is True
# Update
def test_api_room_user_accesses_update_anonymous():
"""Anonymous users should not be allowed to update a room user access."""
access = UserResourceAccessFactory()
old_values = ResourceAccessSerializer(instance=access).data
client = APIClient()
new_values = {
"id": uuid4(),
"resource": RoomFactory().id,
"user": UserFactory().id,
"role": random.choice(RoleChoices.choices)[0],
}
for field, value in new_values.items():
response = client.put(
f"/api/v1.0/resource-accesses/{access.id!s}/",
{**old_values, field: value},
format="json",
)
assert response.status_code == 401
access.refresh_from_db()
updated_values = ResourceAccessSerializer(instance=access).data
assert updated_values == old_values
def test_api_room_user_accesses_update_authenticated():
"""Authenticated users should not be allowed to update a room user access."""
user = UserFactory()
client = APIClient()
client.force_login(user)
access = UserResourceAccessFactory()
old_values = ResourceAccessSerializer(instance=access).data
new_values = {
"id": uuid4(),
"resource": RoomFactory(users=[(user, "member")]).id,
"user": UserFactory().id,
"role": random.choice(RoleChoices.choices)[0],
}
for field, value in new_values.items():
response = client.put(
f"/api/v1.0/resource-accesses/{access.id!s}/",
{**old_values, field: value},
format="json",
)
assert response.status_code == 403
access.refresh_from_db()
updated_values = ResourceAccessSerializer(instance=access).data
assert updated_values == old_values
def test_api_room_user_accesses_update_member():
"""
A user who is a simple member in a room should not be allowed to update
a user access for this room.
"""
user = UserFactory()
client = APIClient()
client.force_login(user)
room = RoomFactory(users=[(user, "member")])
access = UserResourceAccessFactory(resource=room)
old_values = ResourceAccessSerializer(instance=access).data
new_values = {
"id": uuid4(),
"resource": RoomFactory(users=[(user, "member")]).id,
"user": UserFactory().id,
"role": random.choice(RoleChoices.choices)[0],
}
for field, value in new_values.items():
response = client.put(
f"/api/v1.0/resource-accesses/{access.id!s}/",
{**old_values, field: value},
format="json",
)
assert response.status_code == 403
access.refresh_from_db()
updated_values = ResourceAccessSerializer(instance=access).data
assert updated_values == old_values
def test_api_room_user_accesses_update_administrator_except_owner():
"""
A user who is an administrator in a room should be allowed to update
a user access for this room, as long as she does not try to set the role to owner.
"""
user = UserFactory()
client = APIClient()
client.force_login(user)
room = RoomFactory(users=[(user, "administrator")])
access = UserResourceAccessFactory(
resource=room, role=random.choice(["member", "administrator"])
)
old_values = ResourceAccessSerializer(instance=access).data
new_values = {
"id": uuid4(),
"resource": RoomFactory(users=[(user, "administrator")]).id,
"user": UserFactory().id,
"role": random.choice(["member", "administrator"]),
}
for field, value in new_values.items():
response = client.put(
f"/api/v1.0/resource-accesses/{access.id!s}/",
{**old_values, field: value},
format="json",
)
assert response.status_code, 200
access.refresh_from_db()
updated_values = ResourceAccessSerializer(instance=access).data
if field == "role":
assert updated_values == {**old_values, "role": new_values["role"]}
else:
assert updated_values == old_values
def test_api_room_user_accesses_update_administrator_from_owner():
"""
A user who is an administrator in a room, should not be allowed to update
the user access of an owner for this room.
"""
user, other_user = UserFactory.create_batch(2)
client = APIClient()
client.force_login(user)
room = RoomFactory(users=[(user, "administrator")])
access = UserResourceAccessFactory(resource=room, user=other_user, role="owner")
old_values = ResourceAccessSerializer(instance=access).data
new_values = {
"id": uuid4(),
"resource": RoomFactory(users=[(user, "administrator")]).id,
"user": UserFactory().id,
"role": random.choice(RoleChoices.choices)[0],
}
for field, value in new_values.items():
response = client.put(
f"/api/v1.0/resource-accesses/{access.id!s}/",
{**old_values, field: value},
format="json",
)
assert response.status_code == 403
access.refresh_from_db()
updated_values = ResourceAccessSerializer(instance=access).data
assert updated_values == old_values
def test_api_room_user_accesses_update_administrator_to_owner():
"""
A user who is an administrator in a room should not be allowed to update
the user access of another user when granting ownership.
"""
user, other_user = UserFactory.create_batch(2)
client = APIClient()
client.force_login(user)
room = RoomFactory(users=[(user, "administrator")])
access = UserResourceAccessFactory(
resource=room,
user=other_user,
role=random.choice(["member", "administrator"]),
)
old_values = ResourceAccessSerializer(instance=access).data
new_values = {
"id": uuid4(),
"resource": RoomFactory(users=[(user, "administrator")]).id,
"user": UserFactory().id,
"role": "owner",
}
for field, value in new_values.items():
response = client.put(
f"/api/v1.0/resource-accesses/{access.id!s}/",
{**old_values, field: value},
format="json",
)
if field == "role":
assert response.status_code == 403
else:
assert response.status_code == 200
access.refresh_from_db()
updated_values = ResourceAccessSerializer(instance=access).data
assert updated_values == old_values
def test_api_room_user_accesses_update_owner_except_owner():
"""
A user who is an owner in a room should be allowed to update
a user access for this room except for existing owner accesses.
"""
user = UserFactory()
client = APIClient()
client.force_login(user)
room = RoomFactory(users=[(user, "owner")])
access = UserResourceAccessFactory(
resource=room, role=random.choice(["member", "administrator"])
)
old_values = ResourceAccessSerializer(instance=access).data
new_values = {
"id": uuid4(),
"resource": RoomFactory(users=[(user, "administrator")]).id,
"user": UserFactory().id,
"role": random.choice(RoleChoices.choices)[0],
}
for field, value in new_values.items():
response = client.put(
f"/api/v1.0/resource-accesses/{access.id!s}/",
{**old_values, field: value},
format="json",
)
assert response.status_code == 200
access.refresh_from_db()
updated_values = ResourceAccessSerializer(instance=access).data
if field == "role":
assert updated_values == {**old_values, "role": new_values["role"]}
else:
assert updated_values == old_values
def test_api_room_user_accesses_update_owner_for_owners():
"""
A user who is an owner in a room should not be allowed to update
an existing owner user access for this room.
"""
user = UserFactory()
client = APIClient()
client.force_login(user)
room = RoomFactory(users=[(user, "owner")])
access = UserResourceAccessFactory(resource=room, role="owner")
old_values = ResourceAccessSerializer(instance=access).data
new_values = {
"id": uuid4(),
"resource": RoomFactory(users=[(user, "administrator")]).id,
"user": UserFactory().id,
"role": random.choice(RoleChoices.choices)[0],
}
for field, value in new_values.items():
response = client.put(
f"/api/v1.0/resource-accesses/{access.id!s}/",
{**old_values, field: value},
format="json",
)
assert response.status_code == 403
access.refresh_from_db()
updated_values = ResourceAccessSerializer(instance=access).data
assert updated_values == old_values
def test_api_room_user_accesses_update_owner_self():
"""
A user who is an owner in a room should be allowed to update
her own user access provided there are other owners in the room.
"""
user = UserFactory()
client = APIClient()
client.force_login(user)
room = RoomFactory()
access = UserResourceAccessFactory(resource=room, user=user, role="owner")
old_values = ResourceAccessSerializer(instance=access).data
new_role = random.choice(["member", "administrator"])
response = client.put(
f"/api/v1.0/resource-accesses/{access.id!s}/",
{**old_values, "role": new_role},
format="json",
)
assert response.status_code == 403
access.refresh_from_db()
assert access.role == "owner"
# Add another owner and it should now work
UserResourceAccessFactory(resource=room, role="owner")
response = client.put(
f"/api/v1.0/resource-accesses/{access.id!s}/",
{**old_values, "role": new_role},
format="json",
)
assert response.status_code == 200
access.refresh_from_db()
assert access.role == new_role
# Delete
def test_api_room_user_access_delete_anonymous():
"""Anonymous users should not be allowed to destroy a room user access."""
access = UserResourceAccessFactory()
client = APIClient()
response = client.delete(
f"/api/v1.0/resource-accesses/{access.id!s}/",
)
assert response.status_code == 401
assert ResourceAccess.objects.count() == 1
def test_api_room_user_access_delete_authenticated():
"""
Authenticated users should not be allowed to delete a room user access for a room in
which they are not administrator.
"""
access = UserResourceAccessFactory()
user = UserFactory()
client = APIClient()
client.force_login(user)
response = client.delete(
f"/api/v1.0/resource-accesses/{access.id!s}/",
)
assert response.status_code == 403
assert ResourceAccess.objects.count() == 1
def test_api_room_user_access_delete_members():
"""
Authenticated users should not be allowed to delete a room user access for a room in
which they are a simple member.
"""
user = UserFactory()
room = RoomFactory(users=[(user, "member")])
access = UserResourceAccessFactory(resource=room)
client = APIClient()
client.force_login(user)
assert ResourceAccess.objects.count() == 2
assert ResourceAccess.objects.filter(user=access.user).exists() is True
response = client.delete(
f"/api/v1.0/resource-accesses/{access.id!s}/",
)
assert response.status_code == 403
assert ResourceAccess.objects.count() == 2
def test_api_room_user_access_delete_administrators():
"""
Users who are administrators in a room should be allowed to delete a user access
from the room provided it is not ownership.
"""
user = UserFactory()
room = RoomFactory(users=[(user, "administrator")])
access = UserResourceAccessFactory(
resource=room, role=random.choice(["member", "administrator"])
)
client = APIClient()
client.force_login(user)
assert ResourceAccess.objects.count() == 2
assert ResourceAccess.objects.filter(user=access.user).exists() is True
response = client.delete(
f"/api/v1.0/resource-accesses/{access.id!s}/",
)
assert response.status_code == 204
assert ResourceAccess.objects.count() == 1
def test_api_room_user_access_delete_owners_except_owners():
"""
Users should be able to delete the room user access of another user
for a room in which they are owner except for owners.
"""
user = UserFactory()
room = RoomFactory(users=[(user, "owner")])
access = UserResourceAccessFactory(
resource=room, role=random.choice(["member", "administrator"])
)
client = APIClient()
client.force_login(user)
assert ResourceAccess.objects.count() == 2
assert ResourceAccess.objects.filter(user=access.user).exists() is True
response = client.delete(
f"/api/v1.0/resource-accesses/{access.id!s}/",
)
assert response.status_code == 204
assert ResourceAccess.objects.count() == 1
def test_api_room_user_access_delete_owners_for_owners():
"""
Users should not be able to delete the room user access of another owner
even for a room in which they are owners.
"""
user = UserFactory()
room = RoomFactory(users=[(user, "owner")])
access = UserResourceAccessFactory(resource=room, role="owner")
client = APIClient()
client.force_login(user)
assert ResourceAccess.objects.count() == 2
assert ResourceAccess.objects.filter(user=access.user).exists() is True
response = client.delete(
f"/api/v1.0/resource-accesses/{access.id!s}/",
)
assert response.status_code == 403
assert ResourceAccess.objects.count() == 2
def test_api_room_user_access_delete_owners_last_owner():
"""
It should not be possible to delete the last owner access from a room
"""
user = UserFactory()
room = RoomFactory()
access = UserResourceAccessFactory(resource=room, user=user, role="owner")
client = APIClient()
client.force_login(user)
assert ResourceAccess.objects.count() == 1
response = client.delete(
f"/api/v1.0/resource-accesses/{access.id!s}/",
)
assert response.status_code == 403
assert ResourceAccess.objects.count() == 1

View File

@@ -10,6 +10,10 @@ from core.authentication.urls import urlpatterns as oidc_urls
# - Main endpoints
router = DefaultRouter()
router.register("users", viewsets.UserViewSet, basename="users")
router.register("rooms", viewsets.RoomViewSet, basename="rooms")
router.register(
"resource-accesses", viewsets.ResourceAccessViewSet, basename="resource_accesses"
)
urlpatterns = [
path(

View File

@@ -372,6 +372,9 @@ class Base(Configuration):
DEFAULT_ROOM_IS_PUBLIC = values.BooleanValue(
True, environ_name="MAGNIFY_DEFAULT_ROOM_IS_PUBLIC", environ_prefix=None
)
ALLOW_UNREGISTERED_ROOMS = values.BooleanValue(
True, environ_name="MAGNIFY_ALLOW_UNREGISTERED_ROOMS", environ_prefix=None
)
# pylint: disable=invalid-name
@property