✨(models/api) add RBAC on templates linking accesses to a team name
We want to be able to control who can access a template via roles. I added this feature on the TeamAccess model assuming that the teams to which a user belongs can be retrieved via a `get_teams` method on the user model. The idea is that this method will get the teams either via a call to an external API or directly from the OIDC token upon user login. This list of teams will probably have to be cached for each user.
This commit is contained in:
committed by
Samuel Paccoud
parent
a23118bee4
commit
f581eb8abd
@@ -77,8 +77,3 @@ class TemplateAdmin(admin.ModelAdmin):
|
||||
"""Template admin interface declaration."""
|
||||
|
||||
inlines = (TemplateAccessInline,)
|
||||
|
||||
|
||||
@admin.register(models.Team)
|
||||
class TeamAdmin(admin.ModelAdmin):
|
||||
"""Team admin interface declaration."""
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
"""Client serializers for the publish core app."""
|
||||
from django.db.models import Q
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from rest_framework import exceptions, serializers
|
||||
@@ -31,7 +32,7 @@ class TemplateAccessSerializer(serializers.ModelSerializer):
|
||||
|
||||
class Meta:
|
||||
model = models.TemplateAccess
|
||||
fields = ["id", "user", "role", "abilities"]
|
||||
fields = ["id", "user", "team", "role", "abilities"]
|
||||
read_only_fields = ["id", "abilities"]
|
||||
|
||||
def update(self, instance, validated_data):
|
||||
@@ -68,6 +69,7 @@ class TemplateAccessSerializer(serializers.ModelSerializer):
|
||||
|
||||
# Create
|
||||
else:
|
||||
teams = user.get_teams()
|
||||
try:
|
||||
template_id = self.context["template_id"]
|
||||
except KeyError as exc:
|
||||
@@ -76,8 +78,8 @@ class TemplateAccessSerializer(serializers.ModelSerializer):
|
||||
) from exc
|
||||
|
||||
if not models.TemplateAccess.objects.filter(
|
||||
Q(user=user) | Q(team__in=teams),
|
||||
template=template_id,
|
||||
user=user,
|
||||
role__in=[models.RoleChoices.OWNER, models.RoleChoices.ADMIN],
|
||||
).exists():
|
||||
raise exceptions.PermissionDenied(
|
||||
@@ -87,8 +89,8 @@ class TemplateAccessSerializer(serializers.ModelSerializer):
|
||||
if (
|
||||
role == models.RoleChoices.OWNER
|
||||
and not models.TemplateAccess.objects.filter(
|
||||
Q(user=user) | Q(team__in=teams),
|
||||
template=template_id,
|
||||
user=user,
|
||||
role=models.RoleChoices.OWNER,
|
||||
).exists()
|
||||
):
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"""API endpoints"""
|
||||
from io import BytesIO
|
||||
|
||||
from django.contrib.postgres.aggregates import ArrayAgg
|
||||
from django.db.models import (
|
||||
OuterRef,
|
||||
Q,
|
||||
@@ -156,14 +157,22 @@ class TemplateViewSet(
|
||||
if not self.request.user.is_authenticated:
|
||||
return models.Template.objects.filter(is_public=True)
|
||||
|
||||
user_role_query = models.TemplateAccess.objects.filter(
|
||||
user=self.request.user, template=OuterRef("pk")
|
||||
).values("role")[:1]
|
||||
user = self.request.user
|
||||
teams = user.get_teams()
|
||||
|
||||
user_roles_query = (
|
||||
models.TemplateAccess.objects.filter(
|
||||
Q(user=user) | Q(team__in=teams), template=OuterRef("pk")
|
||||
)
|
||||
.values("template")
|
||||
.annotate(roles_array=ArrayAgg("role"))
|
||||
.values("roles_array")
|
||||
)
|
||||
return (
|
||||
models.Template.objects.filter(
|
||||
Q(accesses__user=self.request.user) | Q(is_public=True)
|
||||
Q(accesses__user=user) | Q(accesses__team__in=teams) | Q(is_public=True)
|
||||
)
|
||||
.annotate(user_role=Subquery(user_role_query))
|
||||
.annotate(user_roles=Subquery(user_roles_query))
|
||||
.distinct()
|
||||
)
|
||||
|
||||
@@ -263,19 +272,30 @@ class TemplateAccessViewSet(
|
||||
queryset = queryset.filter(template=self.kwargs["template_id"])
|
||||
|
||||
if self.action == "list":
|
||||
user = self.request.user
|
||||
teams = user.get_teams()
|
||||
|
||||
user_roles_query = (
|
||||
models.TemplateAccess.objects.filter(
|
||||
Q(user=user) | Q(team__in=teams),
|
||||
template=self.kwargs["template_id"],
|
||||
)
|
||||
.values("template")
|
||||
.annotate(roles_array=ArrayAgg("role"))
|
||||
.values("roles_array")
|
||||
)
|
||||
|
||||
# Limit to template access instances related to a template THAT also has
|
||||
# a template access
|
||||
# instance for the logged-in user (we don't want to list only the template
|
||||
# access instances pointing to the logged-in user)
|
||||
user_role_query = models.TemplateAccess.objects.filter(
|
||||
template=self.kwargs["template_id"],
|
||||
template__accesses__user=self.request.user,
|
||||
).values("role")[:1]
|
||||
queryset = (
|
||||
queryset.filter(
|
||||
template__accesses__user=self.request.user,
|
||||
Q(template__accesses__user=user)
|
||||
| Q(template__accesses__team__in=teams),
|
||||
template=self.kwargs["template_id"],
|
||||
)
|
||||
.annotate(user_role=Subquery(user_role_query))
|
||||
.annotate(user_roles=Subquery(user_roles_query))
|
||||
.distinct()
|
||||
)
|
||||
return queryset
|
||||
|
||||
@@ -41,12 +41,12 @@ class TemplateFactory(factory.django.DjangoModelFactory):
|
||||
if create and extracted:
|
||||
for item in extracted:
|
||||
if isinstance(item, models.User):
|
||||
TemplateAccessFactory(template=self, user=item)
|
||||
UserTemplateAccessFactory(template=self, user=item)
|
||||
else:
|
||||
TemplateAccessFactory(template=self, user=item[0], role=item[1])
|
||||
UserTemplateAccessFactory(template=self, user=item[0], role=item[1])
|
||||
|
||||
|
||||
class TemplateAccessFactory(factory.django.DjangoModelFactory):
|
||||
class UserTemplateAccessFactory(factory.django.DjangoModelFactory):
|
||||
"""Create fake template user accesses for testing."""
|
||||
|
||||
class Meta:
|
||||
@@ -55,3 +55,14 @@ class TemplateAccessFactory(factory.django.DjangoModelFactory):
|
||||
template = factory.SubFactory(TemplateFactory)
|
||||
user = factory.SubFactory(UserFactory)
|
||||
role = factory.fuzzy.FuzzyChoice([r[0] for r in models.RoleChoices.choices])
|
||||
|
||||
|
||||
class TeamTemplateAccessFactory(factory.django.DjangoModelFactory):
|
||||
"""Create fake template team accesses for testing."""
|
||||
|
||||
class Meta:
|
||||
model = models.TemplateAccess
|
||||
|
||||
template = factory.SubFactory(TemplateFactory)
|
||||
team = factory.Sequence(lambda n: f"team{n}")
|
||||
role = factory.fuzzy.FuzzyChoice([r[0] for r in models.RoleChoices.choices])
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Generated by Django 5.0.2 on 2024-02-22 20:34
|
||||
# Generated by Django 5.0.2 on 2024-02-24 17:39
|
||||
|
||||
import django.contrib.auth.models
|
||||
import django.core.validators
|
||||
@@ -18,21 +18,6 @@ class Migration(migrations.Migration):
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Team',
|
||||
fields=[
|
||||
('id', models.UUIDField(default=uuid.uuid4, editable=False, help_text='primary key for the record as UUID', primary_key=True, serialize=False, verbose_name='id')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True, help_text='date and time at which a record was created', verbose_name='created on')),
|
||||
('updated_at', models.DateTimeField(auto_now=True, help_text='date and time at which a record was last updated', verbose_name='updated on')),
|
||||
('name', models.CharField(max_length=100, unique=True)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Team',
|
||||
'verbose_name_plural': 'Teams',
|
||||
'db_table': 'publish_role',
|
||||
'ordering': ('name',),
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Template',
|
||||
fields=[
|
||||
@@ -87,8 +72,8 @@ class Migration(migrations.Migration):
|
||||
('id', models.UUIDField(default=uuid.uuid4, editable=False, help_text='primary key for the record as UUID', primary_key=True, serialize=False, verbose_name='id')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True, help_text='date and time at which a record was created', verbose_name='created on')),
|
||||
('updated_at', models.DateTimeField(auto_now=True, help_text='date and time at which a record was last updated', verbose_name='updated on')),
|
||||
('team', models.CharField(blank=True, max_length=100)),
|
||||
('role', models.CharField(choices=[('member', 'Member'), ('administrator', 'Administrator'), ('owner', 'Owner')], default='member', max_length=20)),
|
||||
('team', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='accesses', to='core.team')),
|
||||
('template', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='accesses', to='core.template')),
|
||||
('user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='accesses', to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
@@ -100,10 +85,14 @@ class Migration(migrations.Migration):
|
||||
),
|
||||
migrations.AddConstraint(
|
||||
model_name='templateaccess',
|
||||
constraint=models.UniqueConstraint(fields=('user', 'template'), name='unique_template_user', violation_error_message='This user is already in this template.'),
|
||||
constraint=models.UniqueConstraint(condition=models.Q(('user__isnull', False)), fields=('user', 'template'), name='unique_template_user', violation_error_message='This user is already in this template.'),
|
||||
),
|
||||
migrations.AddConstraint(
|
||||
model_name='templateaccess',
|
||||
constraint=models.UniqueConstraint(fields=('team', 'template'), name='unique_template_team', violation_error_message='This team is already in this template.'),
|
||||
constraint=models.UniqueConstraint(condition=models.Q(('team__gt', '')), fields=('team', 'template'), name='unique_template_team', violation_error_message='This team is already in this template.'),
|
||||
),
|
||||
migrations.AddConstraint(
|
||||
model_name='templateaccess',
|
||||
constraint=models.CheckConstraint(check=models.Q(models.Q(('team', ''), ('user__isnull', False)), models.Q(('team__gt', ''), ('user__isnull', True)), _connector='OR'), name='check_either_user_or_team', violation_error_message='Either user or team must be set, not both.'),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -147,20 +147,12 @@ class User(AbstractBaseUser, BaseModel, auth_models.PermissionsMixin):
|
||||
raise ValueError("User has no email address.")
|
||||
mail.send_mail(subject, message, from_email, [self.email], **kwargs)
|
||||
|
||||
|
||||
class Team(BaseModel):
|
||||
"""Team used for role based access control when matched with templates in OIDC tokens."""
|
||||
|
||||
name = models.CharField(max_length=100, unique=True)
|
||||
|
||||
class Meta:
|
||||
db_table = "publish_role"
|
||||
ordering = ("name",)
|
||||
verbose_name = _("Team")
|
||||
verbose_name_plural = _("Teams")
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
def get_teams(self):
|
||||
"""
|
||||
Get list of teams in which the user is, as a list of strings.
|
||||
Must be cached if retrieved remotely.
|
||||
"""
|
||||
return []
|
||||
|
||||
|
||||
class Template(BaseModel):
|
||||
@@ -213,21 +205,25 @@ class Template(BaseModel):
|
||||
Compute and return abilities for a given user on the template.
|
||||
"""
|
||||
# Compute user role
|
||||
role = None
|
||||
roles = []
|
||||
if user.is_authenticated:
|
||||
try:
|
||||
role = self.user_role
|
||||
roles = self.user_roles or []
|
||||
except AttributeError:
|
||||
teams = user.get_teams()
|
||||
try:
|
||||
role = self.accesses.filter(user=user).values("role")[0]["role"]
|
||||
roles = self.accesses.filter(
|
||||
models.Q(user=user) | models.Q(team__in=teams)
|
||||
).values_list("role", flat=True)
|
||||
except (TemplateAccess.DoesNotExist, IndexError):
|
||||
role = None
|
||||
|
||||
is_owner_or_admin = role in [RoleChoices.OWNER, RoleChoices.ADMIN]
|
||||
can_get = self.is_public or role is not None
|
||||
roles = []
|
||||
is_owner_or_admin = bool(
|
||||
set(roles).intersection({RoleChoices.OWNER, RoleChoices.ADMIN})
|
||||
)
|
||||
can_get = self.is_public or bool(roles)
|
||||
|
||||
return {
|
||||
"destroy": role == RoleChoices.OWNER,
|
||||
"destroy": RoleChoices.OWNER in roles,
|
||||
"generate_document": can_get,
|
||||
"manage_accesses": is_owner_or_admin,
|
||||
"update": is_owner_or_admin,
|
||||
@@ -250,13 +246,7 @@ class TemplateAccess(BaseModel):
|
||||
null=True,
|
||||
blank=True,
|
||||
)
|
||||
team = models.ForeignKey(
|
||||
Team,
|
||||
on_delete=models.CASCADE,
|
||||
related_name="accesses",
|
||||
null=True,
|
||||
blank=True,
|
||||
)
|
||||
team = models.CharField(max_length=100, blank=True)
|
||||
role = models.CharField(
|
||||
max_length=20, choices=RoleChoices.choices, default=RoleChoices.MEMBER
|
||||
)
|
||||
@@ -268,14 +258,22 @@ class TemplateAccess(BaseModel):
|
||||
constraints = [
|
||||
models.UniqueConstraint(
|
||||
fields=["user", "template"],
|
||||
condition=models.Q(user__isnull=False), # Exclude null users
|
||||
name="unique_template_user",
|
||||
violation_error_message=_("This user is already in this template."),
|
||||
),
|
||||
models.UniqueConstraint(
|
||||
fields=["team", "template"],
|
||||
condition=models.Q(team__gt=""), # Exclude empty string teams
|
||||
name="unique_template_team",
|
||||
violation_error_message=_("This team is already in this template."),
|
||||
),
|
||||
models.CheckConstraint(
|
||||
check=models.Q(user__isnull=False, team="")
|
||||
| models.Q(user__isnull=True, team__gt=""),
|
||||
name="check_either_user_or_team",
|
||||
violation_error_message=_("Either user or team must be set, not both."),
|
||||
),
|
||||
]
|
||||
|
||||
def __str__(self):
|
||||
@@ -287,32 +285,34 @@ class TemplateAccess(BaseModel):
|
||||
the current state of the object.
|
||||
"""
|
||||
is_template_owner_or_admin = False
|
||||
role = None
|
||||
|
||||
roles = []
|
||||
if user.is_authenticated:
|
||||
teams = user.get_teams()
|
||||
try:
|
||||
role = self.user_role
|
||||
roles = self.user_roles or []
|
||||
except AttributeError:
|
||||
try:
|
||||
role = self._meta.model.objects.filter(
|
||||
roles = self._meta.model.objects.filter(
|
||||
models.Q(user=user) | models.Q(team__in=teams),
|
||||
template=self.template_id,
|
||||
user=user,
|
||||
).values("role")[0]["role"]
|
||||
).values_list("role", flat=True)
|
||||
except (self._meta.model.DoesNotExist, IndexError):
|
||||
role = None
|
||||
|
||||
is_template_owner_or_admin = role in [RoleChoices.OWNER, RoleChoices.ADMIN]
|
||||
roles = []
|
||||
|
||||
is_template_owner_or_admin = bool(
|
||||
set(roles).intersection({RoleChoices.OWNER, RoleChoices.ADMIN})
|
||||
)
|
||||
if self.role == RoleChoices.OWNER:
|
||||
can_delete = (
|
||||
role == RoleChoices.OWNER
|
||||
RoleChoices.OWNER in roles
|
||||
and self.template.accesses.filter(role=RoleChoices.OWNER).count() > 1
|
||||
)
|
||||
set_role_to = [RoleChoices.ADMIN, RoleChoices.MEMBER] if can_delete else []
|
||||
else:
|
||||
can_delete = is_template_owner_or_admin
|
||||
set_role_to = []
|
||||
if role == RoleChoices.OWNER:
|
||||
if RoleChoices.OWNER in roles:
|
||||
set_role_to.append(RoleChoices.OWNER)
|
||||
if is_template_owner_or_admin:
|
||||
set_role_to.extend([RoleChoices.ADMIN, RoleChoices.MEMBER])
|
||||
@@ -326,6 +326,6 @@ class TemplateAccess(BaseModel):
|
||||
return {
|
||||
"destroy": can_delete,
|
||||
"update": bool(set_role_to),
|
||||
"retrieve": bool(role),
|
||||
"retrieve": bool(roles),
|
||||
"set_role_to": set_role_to,
|
||||
}
|
||||
|
||||
15
src/backend/core/tests/conftest.py
Normal file
15
src/backend/core/tests/conftest.py
Normal file
@@ -0,0 +1,15 @@
|
||||
"""Fixtures for tests in the publish core application"""
|
||||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
|
||||
USER = "user"
|
||||
TEAM = "team"
|
||||
VIA = [USER, TEAM]
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_user_get_teams():
|
||||
"""Mock for the "get_teams" method on the User model."""
|
||||
with mock.patch("core.models.User.get_teams") as mock_get_teams:
|
||||
yield mock_get_teams
|
||||
@@ -7,6 +7,7 @@ import pytest
|
||||
from rest_framework.test import APIClient
|
||||
|
||||
from core import factories, models
|
||||
from core.tests.conftest import TEAM, USER, VIA
|
||||
|
||||
pytestmark = pytest.mark.django_db
|
||||
|
||||
@@ -45,17 +46,27 @@ def test_api_templates_delete_authenticated_unrelated():
|
||||
|
||||
|
||||
@pytest.mark.parametrize("role", ["member", "administrator"])
|
||||
def test_api_templates_delete_authenticated_member(role):
|
||||
@pytest.mark.parametrize("via", VIA)
|
||||
def test_api_templates_delete_authenticated_member_or_administrator(
|
||||
via, role, mock_user_get_teams
|
||||
):
|
||||
"""
|
||||
Authenticated users should not be allowed to delete a template for which they are
|
||||
only a member.
|
||||
only a member or administrator.
|
||||
"""
|
||||
user = factories.UserFactory()
|
||||
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
|
||||
template = factories.TemplateFactory(users=[(user, role)])
|
||||
template = factories.TemplateFactory()
|
||||
if via == USER:
|
||||
factories.UserTemplateAccessFactory(template=template, user=user, role=role)
|
||||
elif via == TEAM:
|
||||
mock_user_get_teams.return_value = ["lasuite", "unknown"]
|
||||
factories.TeamTemplateAccessFactory(
|
||||
template=template, team="lasuite", role=role
|
||||
)
|
||||
|
||||
response = client.delete(
|
||||
f"/api/v1.0/templates/{template.id}/",
|
||||
@@ -68,17 +79,24 @@ def test_api_templates_delete_authenticated_member(role):
|
||||
assert models.Template.objects.count() == 1
|
||||
|
||||
|
||||
def test_api_templates_delete_authenticated_owner():
|
||||
@pytest.mark.parametrize("via", VIA)
|
||||
def test_api_templates_delete_authenticated_owner(via, mock_user_get_teams):
|
||||
"""
|
||||
Authenticated users should be able to delete a template for which they are directly
|
||||
owner.
|
||||
Authenticated users should be able to delete a template they own.
|
||||
"""
|
||||
user = factories.UserFactory()
|
||||
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
|
||||
template = factories.TemplateFactory(users=[(user, "owner")])
|
||||
template = factories.TemplateFactory()
|
||||
if via == USER:
|
||||
factories.UserTemplateAccessFactory(template=template, user=user, role="owner")
|
||||
elif via == TEAM:
|
||||
mock_user_get_teams.return_value = ["lasuite", "unknown"]
|
||||
factories.TeamTemplateAccessFactory(
|
||||
template=template, team="lasuite", role="owner"
|
||||
)
|
||||
|
||||
response = client.delete(
|
||||
f"/api/v1.0/templates/{template.id}/",
|
||||
|
||||
@@ -5,6 +5,7 @@ import pytest
|
||||
from rest_framework.test import APIClient
|
||||
|
||||
from core import factories
|
||||
from core.tests.conftest import TEAM, USER, VIA
|
||||
|
||||
pytestmark = pytest.mark.django_db
|
||||
|
||||
@@ -89,14 +90,20 @@ def test_api_templates_generate_document_authenticated_not_public():
|
||||
assert response.json() == {"detail": "Not found."}
|
||||
|
||||
|
||||
def test_api_templates_generate_document_related():
|
||||
@pytest.mark.parametrize("via", VIA)
|
||||
def test_api_templates_generate_document_related(via, mock_user_get_teams):
|
||||
"""Users related to a template can generate pdf document."""
|
||||
user = factories.UserFactory()
|
||||
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
|
||||
access = factories.TemplateAccessFactory(user=user)
|
||||
if via == USER:
|
||||
access = factories.UserTemplateAccessFactory(user=user)
|
||||
elif via == TEAM:
|
||||
mock_user_get_teams.return_value = ["lasuite", "unknown"]
|
||||
access = factories.TeamTemplateAccessFactory(team="lasuite")
|
||||
|
||||
data = {"body": "# Test markdown body"}
|
||||
|
||||
response = client.post(
|
||||
|
||||
@@ -28,10 +28,10 @@ def test_api_templates_list_anonymous():
|
||||
assert expected_ids == results_id
|
||||
|
||||
|
||||
def test_api_templates_list_authenticated():
|
||||
def test_api_templates_list_authenticated_direct():
|
||||
"""
|
||||
Authenticated users should be able to list templates they are
|
||||
an owner/administrator/member of.
|
||||
Authenticated users should be able to list templates they are a direct
|
||||
owner/administrator/member of.
|
||||
"""
|
||||
user = factories.UserFactory()
|
||||
|
||||
@@ -40,7 +40,7 @@ def test_api_templates_list_authenticated():
|
||||
|
||||
related_templates = [
|
||||
access.template
|
||||
for access in factories.TemplateAccessFactory.create_batch(5, user=user)
|
||||
for access in factories.UserTemplateAccessFactory.create_batch(5, user=user)
|
||||
]
|
||||
public_templates = factories.TemplateFactory.create_batch(2, is_public=True)
|
||||
factories.TemplateFactory.create_batch(2, is_public=False)
|
||||
@@ -60,6 +60,43 @@ def test_api_templates_list_authenticated():
|
||||
assert expected_ids == results_id
|
||||
|
||||
|
||||
def test_api_templates_list_authenticated_via_team(mock_user_get_teams):
|
||||
"""
|
||||
Authenticated users should be able to list templates they are a
|
||||
owner/administrator/member of via a team.
|
||||
"""
|
||||
user = factories.UserFactory()
|
||||
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
|
||||
mock_user_get_teams.return_value = ["team1", "team2", "unknown"]
|
||||
|
||||
templates_team1 = [
|
||||
access.template
|
||||
for access in factories.TeamTemplateAccessFactory.create_batch(2, team="team1")
|
||||
]
|
||||
templates_team2 = [
|
||||
access.template
|
||||
for access in factories.TeamTemplateAccessFactory.create_batch(3, team="team2")
|
||||
]
|
||||
public_templates = factories.TemplateFactory.create_batch(2, is_public=True)
|
||||
factories.TemplateFactory.create_batch(2, is_public=False)
|
||||
|
||||
expected_ids = {
|
||||
str(template.id)
|
||||
for template in templates_team1 + templates_team2 + public_templates
|
||||
}
|
||||
|
||||
response = client.get("/api/v1.0/templates/")
|
||||
|
||||
assert response.status_code == HTTP_200_OK
|
||||
results = response.json()["results"]
|
||||
assert len(results) == 7
|
||||
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_templates_list_pagination(
|
||||
_mock_page_size,
|
||||
@@ -72,7 +109,7 @@ def test_api_templates_list_pagination(
|
||||
|
||||
template_ids = [
|
||||
str(access.template.id)
|
||||
for access in factories.TemplateAccessFactory.create_batch(3, user=user)
|
||||
for access in factories.UserTemplateAccessFactory.create_batch(3, user=user)
|
||||
]
|
||||
|
||||
# Get page 1
|
||||
|
||||
@@ -89,10 +89,10 @@ def test_api_templates_retrieve_authenticated_unrelated_not_public():
|
||||
assert response.json() == {"detail": "Not found."}
|
||||
|
||||
|
||||
def test_api_templates_retrieve_authenticated_related():
|
||||
def test_api_templates_retrieve_authenticated_related_direct():
|
||||
"""
|
||||
Authenticated users should be allowed to retrieve a template to which they
|
||||
are related whatever the role.
|
||||
are directly related whatever the role.
|
||||
"""
|
||||
user = factories.UserFactory()
|
||||
|
||||
@@ -100,8 +100,8 @@ def test_api_templates_retrieve_authenticated_related():
|
||||
client.force_login(user)
|
||||
|
||||
template = factories.TemplateFactory()
|
||||
access1 = factories.TemplateAccessFactory(template=template, user=user)
|
||||
access2 = factories.TemplateAccessFactory(template=template)
|
||||
access1 = factories.UserTemplateAccessFactory(template=template, user=user)
|
||||
access2 = factories.UserTemplateAccessFactory(template=template)
|
||||
|
||||
response = client.get(
|
||||
f"/api/v1.0/templates/{template.id!s}/",
|
||||
@@ -113,12 +113,14 @@ def test_api_templates_retrieve_authenticated_related():
|
||||
{
|
||||
"id": str(access1.id),
|
||||
"user": str(user.id),
|
||||
"team": "",
|
||||
"role": access1.role,
|
||||
"abilities": access1.get_abilities(user),
|
||||
},
|
||||
{
|
||||
"id": str(access2.id),
|
||||
"user": str(access2.user.id),
|
||||
"team": "",
|
||||
"role": access2.role,
|
||||
"abilities": access2.get_abilities(user),
|
||||
},
|
||||
@@ -130,3 +132,310 @@ def test_api_templates_retrieve_authenticated_related():
|
||||
"title": template.title,
|
||||
"abilities": template.get_abilities(user),
|
||||
}
|
||||
|
||||
|
||||
def test_api_templates_retrieve_authenticated_related_team_none(mock_user_get_teams):
|
||||
"""
|
||||
Authenticated users should not be able to retrieve a template related to teams in
|
||||
which the user is not.
|
||||
"""
|
||||
mock_user_get_teams.return_value = []
|
||||
|
||||
user = factories.UserFactory()
|
||||
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
|
||||
template = factories.TemplateFactory(is_public=False)
|
||||
|
||||
factories.TeamTemplateAccessFactory(
|
||||
template=template, team="members", role="member"
|
||||
)
|
||||
factories.TeamTemplateAccessFactory(
|
||||
template=template, team="administrators", role="administrator"
|
||||
)
|
||||
factories.TeamTemplateAccessFactory(template=template, team="owners", role="owner")
|
||||
factories.TeamTemplateAccessFactory(template=template)
|
||||
factories.TeamTemplateAccessFactory()
|
||||
|
||||
response = client.get(f"/api/v1.0/templates/{template.id!s}/")
|
||||
assert response.status_code == 404
|
||||
assert response.json() == {"detail": "Not found."}
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"teams",
|
||||
[
|
||||
["members"],
|
||||
["unknown", "members"],
|
||||
],
|
||||
)
|
||||
def test_api_templates_retrieve_authenticated_related_team_members(
|
||||
teams, mock_user_get_teams
|
||||
):
|
||||
"""
|
||||
Authenticated users should be allowed to retrieve a template to which they
|
||||
are related via a team whatever the role and see all its accesses.
|
||||
"""
|
||||
mock_user_get_teams.return_value = teams
|
||||
|
||||
user = factories.UserFactory()
|
||||
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
|
||||
template = factories.TemplateFactory(is_public=False)
|
||||
|
||||
access_member = factories.TeamTemplateAccessFactory(
|
||||
template=template, team="members", role="member"
|
||||
)
|
||||
access_administrator = factories.TeamTemplateAccessFactory(
|
||||
template=template, team="administrators", role="administrator"
|
||||
)
|
||||
access_owner = factories.TeamTemplateAccessFactory(
|
||||
template=template, team="owners", role="owner"
|
||||
)
|
||||
other_access = factories.TeamTemplateAccessFactory(template=template)
|
||||
factories.TeamTemplateAccessFactory()
|
||||
|
||||
response = client.get(f"/api/v1.0/templates/{template.id!s}/")
|
||||
assert response.status_code == 200
|
||||
content = response.json()
|
||||
expected_abilities = {
|
||||
"destroy": False,
|
||||
"retrieve": True,
|
||||
"set_role_to": [],
|
||||
"update": False,
|
||||
}
|
||||
assert sorted(content.pop("accesses"), key=lambda x: x["id"]) == sorted(
|
||||
[
|
||||
{
|
||||
"id": str(access_member.id),
|
||||
"user": None,
|
||||
"team": "members",
|
||||
"role": access_member.role,
|
||||
"abilities": expected_abilities,
|
||||
},
|
||||
{
|
||||
"id": str(access_administrator.id),
|
||||
"user": None,
|
||||
"team": "administrators",
|
||||
"role": access_administrator.role,
|
||||
"abilities": expected_abilities,
|
||||
},
|
||||
{
|
||||
"id": str(access_owner.id),
|
||||
"user": None,
|
||||
"team": "owners",
|
||||
"role": access_owner.role,
|
||||
"abilities": expected_abilities,
|
||||
},
|
||||
{
|
||||
"id": str(other_access.id),
|
||||
"user": None,
|
||||
"team": other_access.team,
|
||||
"role": other_access.role,
|
||||
"abilities": expected_abilities,
|
||||
},
|
||||
],
|
||||
key=lambda x: x["id"],
|
||||
)
|
||||
assert response.json() == {
|
||||
"id": str(template.id),
|
||||
"title": template.title,
|
||||
"abilities": template.get_abilities(user),
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"teams",
|
||||
[
|
||||
["administrators"],
|
||||
["members", "administrators"],
|
||||
["unknown", "administrators"],
|
||||
],
|
||||
)
|
||||
def test_api_templates_retrieve_authenticated_related_team_administrators(
|
||||
teams, mock_user_get_teams
|
||||
):
|
||||
"""
|
||||
Authenticated users should be allowed to retrieve a template to which they
|
||||
are related via a team whatever the role and see all its accesses.
|
||||
"""
|
||||
mock_user_get_teams.return_value = teams
|
||||
|
||||
user = factories.UserFactory()
|
||||
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
|
||||
template = factories.TemplateFactory(is_public=False)
|
||||
|
||||
access_member = factories.TeamTemplateAccessFactory(
|
||||
template=template, team="members", role="member"
|
||||
)
|
||||
access_administrator = factories.TeamTemplateAccessFactory(
|
||||
template=template, team="administrators", role="administrator"
|
||||
)
|
||||
access_owner = factories.TeamTemplateAccessFactory(
|
||||
template=template, team="owners", role="owner"
|
||||
)
|
||||
other_access = factories.TeamTemplateAccessFactory(template=template)
|
||||
factories.TeamTemplateAccessFactory()
|
||||
|
||||
response = client.get(f"/api/v1.0/templates/{template.id!s}/")
|
||||
|
||||
assert response.status_code == 200
|
||||
content = response.json()
|
||||
assert sorted(content.pop("accesses"), key=lambda x: x["id"]) == sorted(
|
||||
[
|
||||
{
|
||||
"id": str(access_member.id),
|
||||
"user": None,
|
||||
"team": "members",
|
||||
"role": "member",
|
||||
"abilities": {
|
||||
"destroy": True,
|
||||
"retrieve": True,
|
||||
"set_role_to": ["administrator"],
|
||||
"update": True,
|
||||
},
|
||||
},
|
||||
{
|
||||
"id": str(access_administrator.id),
|
||||
"user": None,
|
||||
"team": "administrators",
|
||||
"role": "administrator",
|
||||
"abilities": {
|
||||
"destroy": True,
|
||||
"retrieve": True,
|
||||
"set_role_to": ["member"],
|
||||
"update": True,
|
||||
},
|
||||
},
|
||||
{
|
||||
"id": str(access_owner.id),
|
||||
"user": None,
|
||||
"team": "owners",
|
||||
"role": "owner",
|
||||
"abilities": {
|
||||
"destroy": False,
|
||||
"retrieve": True,
|
||||
"set_role_to": [],
|
||||
"update": False,
|
||||
},
|
||||
},
|
||||
{
|
||||
"id": str(other_access.id),
|
||||
"user": None,
|
||||
"team": other_access.team,
|
||||
"role": other_access.role,
|
||||
"abilities": other_access.get_abilities(user),
|
||||
},
|
||||
],
|
||||
key=lambda x: x["id"],
|
||||
)
|
||||
assert response.json() == {
|
||||
"id": str(template.id),
|
||||
"title": template.title,
|
||||
"abilities": template.get_abilities(user),
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"teams",
|
||||
[
|
||||
["owners"],
|
||||
["owners", "administrators"],
|
||||
["members", "administrators", "owners"],
|
||||
["unknown", "owners"],
|
||||
],
|
||||
)
|
||||
def test_api_templates_retrieve_authenticated_related_team_owners(
|
||||
teams, mock_user_get_teams
|
||||
):
|
||||
"""
|
||||
Authenticated users should be allowed to retrieve a template to which they
|
||||
are related via a team whatever the role and see all its accesses.
|
||||
"""
|
||||
mock_user_get_teams.return_value = teams
|
||||
|
||||
user = factories.UserFactory()
|
||||
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
|
||||
template = factories.TemplateFactory(is_public=False)
|
||||
|
||||
access_member = factories.TeamTemplateAccessFactory(
|
||||
template=template, team="members", role="member"
|
||||
)
|
||||
access_administrator = factories.TeamTemplateAccessFactory(
|
||||
template=template, team="administrators", role="administrator"
|
||||
)
|
||||
access_owner = factories.TeamTemplateAccessFactory(
|
||||
template=template, team="owners", role="owner"
|
||||
)
|
||||
other_access = factories.TeamTemplateAccessFactory(template=template)
|
||||
factories.TeamTemplateAccessFactory()
|
||||
|
||||
response = client.get(f"/api/v1.0/templates/{template.id!s}/")
|
||||
|
||||
assert response.status_code == 200
|
||||
content = response.json()
|
||||
assert sorted(content.pop("accesses"), key=lambda x: x["id"]) == sorted(
|
||||
[
|
||||
{
|
||||
"id": str(access_member.id),
|
||||
"user": None,
|
||||
"team": "members",
|
||||
"role": "member",
|
||||
"abilities": {
|
||||
"destroy": True,
|
||||
"retrieve": True,
|
||||
"set_role_to": ["owner", "administrator"],
|
||||
"update": True,
|
||||
},
|
||||
},
|
||||
{
|
||||
"id": str(access_administrator.id),
|
||||
"user": None,
|
||||
"team": "administrators",
|
||||
"role": "administrator",
|
||||
"abilities": {
|
||||
"destroy": True,
|
||||
"retrieve": True,
|
||||
"set_role_to": ["owner", "member"],
|
||||
"update": True,
|
||||
},
|
||||
},
|
||||
{
|
||||
"id": str(access_owner.id),
|
||||
"user": None,
|
||||
"team": "owners",
|
||||
"role": "owner",
|
||||
"abilities": {
|
||||
# editable only if there is another owner role than the user's team...
|
||||
"destroy": other_access.role == "owner",
|
||||
"retrieve": True,
|
||||
"set_role_to": ["administrator", "member"]
|
||||
if other_access.role == "owner"
|
||||
else [],
|
||||
"update": other_access.role == "owner",
|
||||
},
|
||||
},
|
||||
{
|
||||
"id": str(other_access.id),
|
||||
"user": None,
|
||||
"team": other_access.team,
|
||||
"role": other_access.role,
|
||||
"abilities": other_access.get_abilities(user),
|
||||
},
|
||||
],
|
||||
key=lambda x: x["id"],
|
||||
)
|
||||
assert response.json() == {
|
||||
"id": str(template.id),
|
||||
"title": template.title,
|
||||
"abilities": template.get_abilities(user),
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ from rest_framework.test import APIClient
|
||||
|
||||
from core import factories
|
||||
from core.api import serializers
|
||||
from core.tests.conftest import TEAM, USER, VIA
|
||||
|
||||
pytestmark = pytest.mark.django_db
|
||||
|
||||
@@ -64,7 +65,8 @@ def test_api_templates_update_authenticated_unrelated():
|
||||
assert template_values == old_template_values
|
||||
|
||||
|
||||
def test_api_templates_update_authenticated_members():
|
||||
@pytest.mark.parametrize("via", VIA)
|
||||
def test_api_templates_update_authenticated_members(via, mock_user_get_teams):
|
||||
"""
|
||||
Users who are members of a template but not administrators should
|
||||
not be allowed to update it.
|
||||
@@ -74,7 +76,15 @@ def test_api_templates_update_authenticated_members():
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
|
||||
template = factories.TemplateFactory(users=[(user, "member")])
|
||||
template = factories.TemplateFactory()
|
||||
if via == USER:
|
||||
factories.UserTemplateAccessFactory(template=template, user=user, role="member")
|
||||
elif via == TEAM:
|
||||
mock_user_get_teams.return_value = ["lasuite", "unknown"]
|
||||
factories.TeamTemplateAccessFactory(
|
||||
template=template, team="lasuite", role="member"
|
||||
)
|
||||
|
||||
old_template_values = serializers.TemplateSerializer(instance=template).data
|
||||
|
||||
new_template_values = serializers.TemplateSerializer(
|
||||
@@ -97,14 +107,25 @@ def test_api_templates_update_authenticated_members():
|
||||
|
||||
|
||||
@pytest.mark.parametrize("role", ["administrator", "owner"])
|
||||
def test_api_templates_update_authenticated_administrators(role):
|
||||
"""Administrators of a template should be allowed to update it."""
|
||||
@pytest.mark.parametrize("via", VIA)
|
||||
def test_api_templates_update_authenticated_administrator_or_owner(
|
||||
via, role, mock_user_get_teams
|
||||
):
|
||||
"""Administrator or owner of a template should be allowed to update it."""
|
||||
user = factories.UserFactory()
|
||||
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
|
||||
template = factories.TemplateFactory(users=[(user, role)])
|
||||
template = factories.TemplateFactory()
|
||||
if via == USER:
|
||||
factories.UserTemplateAccessFactory(template=template, user=user, role=role)
|
||||
elif via == TEAM:
|
||||
mock_user_get_teams.return_value = ["lasuite", "unknown"]
|
||||
factories.TeamTemplateAccessFactory(
|
||||
template=template, team="lasuite", role=role
|
||||
)
|
||||
|
||||
old_template_values = serializers.TemplateSerializer(instance=template).data
|
||||
|
||||
new_template_values = serializers.TemplateSerializer(
|
||||
@@ -126,7 +147,47 @@ def test_api_templates_update_authenticated_administrators(role):
|
||||
assert value == new_template_values[key]
|
||||
|
||||
|
||||
def test_api_templates_update_administrator_or_owner_of_another():
|
||||
@pytest.mark.parametrize("via", VIA)
|
||||
def test_api_templates_update_authenticated_owners(via, mock_user_get_teams):
|
||||
"""Administrators of a template should be allowed to update it."""
|
||||
user = factories.UserFactory()
|
||||
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
|
||||
template = factories.TemplateFactory()
|
||||
if via == USER:
|
||||
factories.UserTemplateAccessFactory(template=template, user=user, role="owner")
|
||||
elif via == TEAM:
|
||||
mock_user_get_teams.return_value = ["lasuite", "unknown"]
|
||||
factories.TeamTemplateAccessFactory(
|
||||
template=template, team="lasuite", role="owner"
|
||||
)
|
||||
|
||||
old_template_values = serializers.TemplateSerializer(instance=template).data
|
||||
|
||||
new_template_values = serializers.TemplateSerializer(
|
||||
instance=factories.TemplateFactory()
|
||||
).data
|
||||
|
||||
response = client.put(
|
||||
f"/api/v1.0/templates/{template.id!s}/", new_template_values, format="json"
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
template.refresh_from_db()
|
||||
template_values = serializers.TemplateSerializer(instance=template).data
|
||||
for key, value in template_values.items():
|
||||
if key in ["id", "accesses"]:
|
||||
assert value == old_template_values[key]
|
||||
else:
|
||||
assert value == new_template_values[key]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("via", VIA)
|
||||
def test_api_templates_update_administrator_or_owner_of_another(
|
||||
via, mock_user_get_teams
|
||||
):
|
||||
"""
|
||||
Being administrator or owner of a template should not grant authorization to update
|
||||
another template.
|
||||
@@ -136,7 +197,19 @@ def test_api_templates_update_administrator_or_owner_of_another():
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
|
||||
factories.TemplateFactory(users=[(user, random.choice(["administrator", "owner"]))])
|
||||
template = factories.TemplateFactory()
|
||||
if via == USER:
|
||||
factories.UserTemplateAccessFactory(
|
||||
template=template, user=user, role=random.choice(["administrator", "owner"])
|
||||
)
|
||||
elif via == TEAM:
|
||||
mock_user_get_teams.return_value = ["lasuite", "unknown"]
|
||||
factories.TeamTemplateAccessFactory(
|
||||
template=template,
|
||||
team="lasuite",
|
||||
role=random.choice(["administrator", "owner"]),
|
||||
)
|
||||
|
||||
is_public = random.choice([True, False])
|
||||
template = factories.TemplateFactory(title="Old title", is_public=is_public)
|
||||
old_template_values = serializers.TemplateSerializer(instance=template).data
|
||||
|
||||
@@ -9,6 +9,7 @@ from rest_framework.test import APIClient
|
||||
|
||||
from core import factories, models
|
||||
from core.api import serializers
|
||||
from core.tests.conftest import TEAM, USER, VIA
|
||||
|
||||
pytestmark = pytest.mark.django_db
|
||||
|
||||
@@ -16,7 +17,7 @@ pytestmark = pytest.mark.django_db
|
||||
def test_api_template_accesses_list_anonymous():
|
||||
"""Anonymous users should not be allowed to list template accesses."""
|
||||
template = factories.TemplateFactory()
|
||||
factories.TemplateAccessFactory.create_batch(2, template=template)
|
||||
factories.UserTemplateAccessFactory.create_batch(2, template=template)
|
||||
|
||||
response = APIClient().get(f"/api/v1.0/templates/{template.id!s}/accesses/")
|
||||
assert response.status_code == 401
|
||||
@@ -36,11 +37,11 @@ def test_api_template_accesses_list_authenticated_unrelated():
|
||||
client.force_login(user)
|
||||
|
||||
template = factories.TemplateFactory()
|
||||
factories.TemplateAccessFactory.create_batch(3, template=template)
|
||||
factories.UserTemplateAccessFactory.create_batch(3, template=template)
|
||||
|
||||
# Accesses for other templates to which the user is related should not be listed either
|
||||
other_access = factories.TemplateAccessFactory(user=user)
|
||||
factories.TemplateAccessFactory(template=other_access.template)
|
||||
other_access = factories.UserTemplateAccessFactory(user=user)
|
||||
factories.UserTemplateAccessFactory(template=other_access.template)
|
||||
|
||||
response = client.get(
|
||||
f"/api/v1.0/templates/{template.id!s}/accesses/",
|
||||
@@ -54,10 +55,11 @@ def test_api_template_accesses_list_authenticated_unrelated():
|
||||
}
|
||||
|
||||
|
||||
def test_api_template_accesses_list_authenticated_related():
|
||||
@pytest.mark.parametrize("via", VIA)
|
||||
def test_api_template_accesses_list_authenticated_related(via, mock_user_get_teams):
|
||||
"""
|
||||
Authenticated users should be able to list template accesses for a template
|
||||
to which they are related, whatever their role in the template.
|
||||
to which they are directly related, whatever their role in the template.
|
||||
"""
|
||||
user = factories.UserFactory()
|
||||
|
||||
@@ -65,16 +67,26 @@ def test_api_template_accesses_list_authenticated_related():
|
||||
client.force_login(user)
|
||||
|
||||
template = factories.TemplateFactory()
|
||||
user_access = models.TemplateAccess.objects.create(
|
||||
template=template, user=user
|
||||
) # random role
|
||||
access1, access2 = factories.TemplateAccessFactory.create_batch(
|
||||
2, template=template
|
||||
)
|
||||
if via == USER:
|
||||
user_access = models.TemplateAccess.objects.create(
|
||||
template=template,
|
||||
user=user,
|
||||
role=random.choice(models.RoleChoices.choices)[0],
|
||||
)
|
||||
elif via == TEAM:
|
||||
mock_user_get_teams.return_value = ["lasuite", "unknown"]
|
||||
user_access = models.TemplateAccess.objects.create(
|
||||
template=template,
|
||||
team="lasuite",
|
||||
role=random.choice(models.RoleChoices.choices)[0],
|
||||
)
|
||||
|
||||
access1 = factories.TeamTemplateAccessFactory(template=template)
|
||||
access2 = factories.UserTemplateAccessFactory(template=template)
|
||||
|
||||
# Accesses for other templates to which the user is related should not be listed either
|
||||
other_access = factories.TemplateAccessFactory(user=user)
|
||||
factories.TemplateAccessFactory(template=other_access.template)
|
||||
other_access = factories.UserTemplateAccessFactory(user=user)
|
||||
factories.UserTemplateAccessFactory(template=other_access.template)
|
||||
|
||||
response = client.get(
|
||||
f"/api/v1.0/templates/{template.id!s}/accesses/",
|
||||
@@ -87,19 +99,22 @@ def test_api_template_accesses_list_authenticated_related():
|
||||
[
|
||||
{
|
||||
"id": str(user_access.id),
|
||||
"user": str(user.id),
|
||||
"user": str(user.id) if via == "user" else None,
|
||||
"team": "lasuite" if via == "team" else "",
|
||||
"role": user_access.role,
|
||||
"abilities": user_access.get_abilities(user),
|
||||
},
|
||||
{
|
||||
"id": str(access1.id),
|
||||
"user": str(access1.user.id),
|
||||
"user": None,
|
||||
"team": access1.team,
|
||||
"role": access1.role,
|
||||
"abilities": access1.get_abilities(user),
|
||||
},
|
||||
{
|
||||
"id": str(access2.id),
|
||||
"user": str(access2.user.id),
|
||||
"team": "",
|
||||
"role": access2.role,
|
||||
"abilities": access2.get_abilities(user),
|
||||
},
|
||||
@@ -112,7 +127,7 @@ def test_api_template_accesses_retrieve_anonymous():
|
||||
"""
|
||||
Anonymous users should not be allowed to retrieve a template access.
|
||||
"""
|
||||
access = factories.TemplateAccessFactory()
|
||||
access = factories.UserTemplateAccessFactory()
|
||||
|
||||
response = APIClient().get(
|
||||
f"/api/v1.0/templates/{access.template.id!s}/accesses/{access.id!s}/",
|
||||
@@ -135,7 +150,7 @@ def test_api_template_accesses_retrieve_authenticated_unrelated():
|
||||
client.force_login(user)
|
||||
|
||||
template = factories.TemplateFactory()
|
||||
access = factories.TemplateAccessFactory(template=template)
|
||||
access = factories.UserTemplateAccessFactory(template=template)
|
||||
|
||||
response = client.get(
|
||||
f"/api/v1.0/templates/{template.id!s}/accesses/{access.id!s}/",
|
||||
@@ -147,8 +162,8 @@ def test_api_template_accesses_retrieve_authenticated_unrelated():
|
||||
|
||||
# Accesses related to another template should be excluded even if the user is related to it
|
||||
for access in [
|
||||
factories.TemplateAccessFactory(),
|
||||
factories.TemplateAccessFactory(user=user),
|
||||
factories.UserTemplateAccessFactory(),
|
||||
factories.UserTemplateAccessFactory(user=user),
|
||||
]:
|
||||
response = client.get(
|
||||
f"/api/v1.0/templates/{template.id!s}/accesses/{access.id!s}/",
|
||||
@@ -158,7 +173,8 @@ def test_api_template_accesses_retrieve_authenticated_unrelated():
|
||||
assert response.json() == {"detail": "Not found."}
|
||||
|
||||
|
||||
def test_api_template_accesses_retrieve_authenticated_related():
|
||||
@pytest.mark.parametrize("via", VIA)
|
||||
def test_api_template_accesses_retrieve_authenticated_related(via, mock_user_get_teams):
|
||||
"""
|
||||
A user who is related to a template should be allowed to retrieve the
|
||||
associated template user accesses.
|
||||
@@ -168,8 +184,14 @@ def test_api_template_accesses_retrieve_authenticated_related():
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
|
||||
template = factories.TemplateFactory(users=[user])
|
||||
access = factories.TemplateAccessFactory(template=template)
|
||||
template = factories.TemplateFactory()
|
||||
if via == USER:
|
||||
factories.UserTemplateAccessFactory(template=template, user=user)
|
||||
elif via == TEAM:
|
||||
mock_user_get_teams.return_value = ["lasuite", "unknown"]
|
||||
factories.TeamTemplateAccessFactory(template=template, team="lasuite")
|
||||
|
||||
access = factories.UserTemplateAccessFactory(template=template)
|
||||
|
||||
response = client.get(
|
||||
f"/api/v1.0/templates/{template.id!s}/accesses/{access.id!s}/",
|
||||
@@ -179,6 +201,7 @@ def test_api_template_accesses_retrieve_authenticated_related():
|
||||
assert response.json() == {
|
||||
"id": str(access.id),
|
||||
"user": str(access.user.id),
|
||||
"team": "",
|
||||
"role": access.role,
|
||||
"abilities": access.get_abilities(user),
|
||||
}
|
||||
@@ -231,14 +254,23 @@ def test_api_template_accesses_create_authenticated_unrelated():
|
||||
assert not models.TemplateAccess.objects.filter(user=other_user).exists()
|
||||
|
||||
|
||||
def test_api_template_accesses_create_authenticated_member():
|
||||
@pytest.mark.parametrize("via", VIA)
|
||||
def test_api_template_accesses_create_authenticated_member(via, mock_user_get_teams):
|
||||
"""Members of a template should not be allowed to create template accesses."""
|
||||
user = factories.UserFactory()
|
||||
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
|
||||
template = factories.TemplateFactory(users=[(user, "member")])
|
||||
template = factories.TemplateFactory()
|
||||
if via == USER:
|
||||
factories.UserTemplateAccessFactory(template=template, user=user, role="member")
|
||||
elif via == TEAM:
|
||||
mock_user_get_teams.return_value = ["lasuite", "unknown"]
|
||||
factories.TeamTemplateAccessFactory(
|
||||
template=template, team="lasuite", role="member"
|
||||
)
|
||||
|
||||
other_user = factories.UserFactory()
|
||||
|
||||
for role in [role[0] for role in models.RoleChoices.choices]:
|
||||
@@ -256,7 +288,10 @@ def test_api_template_accesses_create_authenticated_member():
|
||||
assert not models.TemplateAccess.objects.filter(user=other_user).exists()
|
||||
|
||||
|
||||
def test_api_template_accesses_create_authenticated_administrator():
|
||||
@pytest.mark.parametrize("via", VIA)
|
||||
def test_api_template_accesses_create_authenticated_administrator(
|
||||
via, mock_user_get_teams
|
||||
):
|
||||
"""
|
||||
Administrators of a template should be able to create template accesses
|
||||
except for the "owner" role.
|
||||
@@ -266,10 +301,18 @@ def test_api_template_accesses_create_authenticated_administrator():
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
|
||||
template = factories.TemplateFactory(users=[(user, "administrator")])
|
||||
other_user = factories.UserFactory()
|
||||
template = factories.TemplateFactory()
|
||||
if via == USER:
|
||||
factories.UserTemplateAccessFactory(
|
||||
template=template, user=user, role="administrator"
|
||||
)
|
||||
elif via == TEAM:
|
||||
mock_user_get_teams.return_value = ["lasuite", "unknown"]
|
||||
factories.TeamTemplateAccessFactory(
|
||||
template=template, team="lasuite", role="administrator"
|
||||
)
|
||||
|
||||
api_client = APIClient()
|
||||
other_user = factories.UserFactory()
|
||||
|
||||
# It should not be allowed to create an owner access
|
||||
response = client.post(
|
||||
@@ -306,12 +349,14 @@ def test_api_template_accesses_create_authenticated_administrator():
|
||||
assert response.json() == {
|
||||
"abilities": new_template_access.get_abilities(user),
|
||||
"id": str(new_template_access.id),
|
||||
"team": "",
|
||||
"role": role,
|
||||
"user": str(other_user.id),
|
||||
}
|
||||
|
||||
|
||||
def test_api_template_accesses_create_authenticated_owner():
|
||||
@pytest.mark.parametrize("via", VIA)
|
||||
def test_api_template_accesses_create_authenticated_owner(via, mock_user_get_teams):
|
||||
"""
|
||||
Owners of a template should be able to create template accesses whatever the role.
|
||||
"""
|
||||
@@ -320,7 +365,15 @@ def test_api_template_accesses_create_authenticated_owner():
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
|
||||
template = factories.TemplateFactory(users=[(user, "owner")])
|
||||
template = factories.TemplateFactory()
|
||||
if via == USER:
|
||||
factories.UserTemplateAccessFactory(template=template, user=user, role="owner")
|
||||
elif via == TEAM:
|
||||
mock_user_get_teams.return_value = ["lasuite", "unknown"]
|
||||
factories.TeamTemplateAccessFactory(
|
||||
template=template, team="lasuite", role="owner"
|
||||
)
|
||||
|
||||
other_user = factories.UserFactory()
|
||||
|
||||
role = random.choice([role[0] for role in models.RoleChoices.choices])
|
||||
@@ -338,16 +391,17 @@ def test_api_template_accesses_create_authenticated_owner():
|
||||
assert models.TemplateAccess.objects.filter(user=other_user).count() == 1
|
||||
new_template_access = models.TemplateAccess.objects.filter(user=other_user).get()
|
||||
assert response.json() == {
|
||||
"abilities": new_template_access.get_abilities(user),
|
||||
"id": str(new_template_access.id),
|
||||
"role": role,
|
||||
"user": str(other_user.id),
|
||||
"team": "",
|
||||
"role": role,
|
||||
"abilities": new_template_access.get_abilities(user),
|
||||
}
|
||||
|
||||
|
||||
def test_api_template_accesses_update_anonymous():
|
||||
"""Anonymous users should not be allowed to update a template access."""
|
||||
access = factories.TemplateAccessFactory()
|
||||
access = factories.UserTemplateAccessFactory()
|
||||
old_values = serializers.TemplateAccessSerializer(instance=access).data
|
||||
|
||||
new_values = {
|
||||
@@ -380,7 +434,7 @@ def test_api_template_accesses_update_authenticated_unrelated():
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
|
||||
access = factories.TemplateAccessFactory()
|
||||
access = factories.UserTemplateAccessFactory()
|
||||
old_values = serializers.TemplateAccessSerializer(instance=access).data
|
||||
|
||||
new_values = {
|
||||
@@ -402,15 +456,24 @@ def test_api_template_accesses_update_authenticated_unrelated():
|
||||
assert updated_values == old_values
|
||||
|
||||
|
||||
def test_api_template_accesses_update_authenticated_member():
|
||||
@pytest.mark.parametrize("via", VIA)
|
||||
def test_api_template_accesses_update_authenticated_member(via, mock_user_get_teams):
|
||||
"""Members of a template should not be allowed to update its accesses."""
|
||||
user = factories.UserFactory()
|
||||
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
|
||||
template = factories.TemplateFactory(users=[(user, "member")])
|
||||
access = factories.TemplateAccessFactory(template=template)
|
||||
template = factories.TemplateFactory()
|
||||
if via == USER:
|
||||
factories.UserTemplateAccessFactory(template=template, user=user, role="member")
|
||||
elif via == TEAM:
|
||||
mock_user_get_teams.return_value = ["lasuite", "unknown"]
|
||||
factories.TeamTemplateAccessFactory(
|
||||
template=template, team="lasuite", role="member"
|
||||
)
|
||||
|
||||
access = factories.UserTemplateAccessFactory(template=template)
|
||||
old_values = serializers.TemplateAccessSerializer(instance=access).data
|
||||
|
||||
new_values = {
|
||||
@@ -432,9 +495,12 @@ def test_api_template_accesses_update_authenticated_member():
|
||||
assert updated_values == old_values
|
||||
|
||||
|
||||
def test_api_template_accesses_update_administrator_except_owner():
|
||||
@pytest.mark.parametrize("via", VIA)
|
||||
def test_api_template_accesses_update_administrator_except_owner(
|
||||
via, mock_user_get_teams
|
||||
):
|
||||
"""
|
||||
A user who is an administrator in a template should be allowed to update a user
|
||||
A user who is a direct administrator in a template should be allowed to update a user
|
||||
access for this template, as long as they don't try to set the role to owner.
|
||||
"""
|
||||
user = factories.UserFactory()
|
||||
@@ -442,8 +508,18 @@ def test_api_template_accesses_update_administrator_except_owner():
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
|
||||
template = factories.TemplateFactory(users=[(user, "administrator")])
|
||||
access = factories.TemplateAccessFactory(
|
||||
template = factories.TemplateFactory()
|
||||
if via == USER:
|
||||
factories.UserTemplateAccessFactory(
|
||||
template=template, user=user, role="administrator"
|
||||
)
|
||||
elif via == TEAM:
|
||||
mock_user_get_teams.return_value = ["lasuite", "unknown"]
|
||||
factories.TeamTemplateAccessFactory(
|
||||
template=template, team="lasuite", role="administrator"
|
||||
)
|
||||
|
||||
access = factories.UserTemplateAccessFactory(
|
||||
template=template,
|
||||
role=random.choice(["administrator", "member"]),
|
||||
)
|
||||
@@ -478,7 +554,10 @@ def test_api_template_accesses_update_administrator_except_owner():
|
||||
assert updated_values == old_values
|
||||
|
||||
|
||||
def test_api_template_accesses_update_administrator_from_owner():
|
||||
@pytest.mark.parametrize("via", VIA)
|
||||
def test_api_template_accesses_update_administrator_from_owner(
|
||||
via, mock_user_get_teams
|
||||
):
|
||||
"""
|
||||
A user who is an administrator in a template, should not be allowed to update
|
||||
the user access of an "owner" for this template.
|
||||
@@ -488,9 +567,19 @@ def test_api_template_accesses_update_administrator_from_owner():
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
|
||||
template = factories.TemplateFactory(users=[(user, "administrator")])
|
||||
template = factories.TemplateFactory()
|
||||
if via == USER:
|
||||
factories.UserTemplateAccessFactory(
|
||||
template=template, user=user, role="administrator"
|
||||
)
|
||||
elif via == TEAM:
|
||||
mock_user_get_teams.return_value = ["lasuite", "unknown"]
|
||||
factories.TeamTemplateAccessFactory(
|
||||
template=template, team="lasuite", role="administrator"
|
||||
)
|
||||
|
||||
other_user = factories.UserFactory()
|
||||
access = factories.TemplateAccessFactory(
|
||||
access = factories.UserTemplateAccessFactory(
|
||||
template=template, user=other_user, role="owner"
|
||||
)
|
||||
old_values = serializers.TemplateAccessSerializer(instance=access).data
|
||||
@@ -514,7 +603,8 @@ def test_api_template_accesses_update_administrator_from_owner():
|
||||
assert updated_values == old_values
|
||||
|
||||
|
||||
def test_api_template_accesses_update_administrator_to_owner():
|
||||
@pytest.mark.parametrize("via", VIA)
|
||||
def test_api_template_accesses_update_administrator_to_owner(via, mock_user_get_teams):
|
||||
"""
|
||||
A user who is an administrator in a template, should not be allowed to update
|
||||
the user access of another user to grant template ownership.
|
||||
@@ -524,9 +614,19 @@ def test_api_template_accesses_update_administrator_to_owner():
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
|
||||
template = factories.TemplateFactory(users=[(user, "administrator")])
|
||||
template = factories.TemplateFactory()
|
||||
if via == USER:
|
||||
factories.UserTemplateAccessFactory(
|
||||
template=template, user=user, role="administrator"
|
||||
)
|
||||
elif via == TEAM:
|
||||
mock_user_get_teams.return_value = ["lasuite", "unknown"]
|
||||
factories.TeamTemplateAccessFactory(
|
||||
template=template, team="lasuite", role="administrator"
|
||||
)
|
||||
|
||||
other_user = factories.UserFactory()
|
||||
access = factories.TemplateAccessFactory(
|
||||
access = factories.UserTemplateAccessFactory(
|
||||
template=template,
|
||||
user=other_user,
|
||||
role=random.choice(["administrator", "member"]),
|
||||
@@ -557,7 +657,8 @@ def test_api_template_accesses_update_administrator_to_owner():
|
||||
assert updated_values == old_values
|
||||
|
||||
|
||||
def test_api_template_accesses_update_owner():
|
||||
@pytest.mark.parametrize("via", VIA)
|
||||
def test_api_template_accesses_update_owner(via, mock_user_get_teams):
|
||||
"""
|
||||
A user who is an owner in a template should be allowed to update
|
||||
a user access for this template whatever the role.
|
||||
@@ -567,9 +668,17 @@ def test_api_template_accesses_update_owner():
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
|
||||
template = factories.TemplateFactory(users=[(user, "owner")])
|
||||
template = factories.TemplateFactory()
|
||||
if via == USER:
|
||||
factories.UserTemplateAccessFactory(template=template, user=user, role="owner")
|
||||
elif via == TEAM:
|
||||
mock_user_get_teams.return_value = ["lasuite", "unknown"]
|
||||
factories.TeamTemplateAccessFactory(
|
||||
template=template, team="lasuite", role="owner"
|
||||
)
|
||||
|
||||
factories.UserFactory()
|
||||
access = factories.TemplateAccessFactory(
|
||||
access = factories.UserTemplateAccessFactory(
|
||||
template=template,
|
||||
)
|
||||
old_values = serializers.TemplateAccessSerializer(instance=access).data
|
||||
@@ -604,7 +713,8 @@ def test_api_template_accesses_update_owner():
|
||||
assert updated_values == old_values
|
||||
|
||||
|
||||
def test_api_template_accesses_update_owner_self():
|
||||
@pytest.mark.parametrize("via", VIA)
|
||||
def test_api_template_accesses_update_owner_self(via, mock_user_get_teams):
|
||||
"""
|
||||
A user who is owner of a template should be allowed to update
|
||||
their own user access provided there are other owners in the template.
|
||||
@@ -615,7 +725,16 @@ def test_api_template_accesses_update_owner_self():
|
||||
client.force_login(user)
|
||||
|
||||
template = factories.TemplateFactory()
|
||||
access = factories.TemplateAccessFactory(template=template, user=user, role="owner")
|
||||
if via == USER:
|
||||
access = factories.UserTemplateAccessFactory(
|
||||
template=template, user=user, role="owner"
|
||||
)
|
||||
elif via == TEAM:
|
||||
mock_user_get_teams.return_value = ["lasuite", "unknown"]
|
||||
access = factories.TeamTemplateAccessFactory(
|
||||
template=template, team="lasuite", role="owner"
|
||||
)
|
||||
|
||||
old_values = serializers.TemplateAccessSerializer(instance=access).data
|
||||
new_role = random.choice(["administrator", "member"])
|
||||
|
||||
@@ -630,7 +749,7 @@ def test_api_template_accesses_update_owner_self():
|
||||
assert access.role == "owner"
|
||||
|
||||
# Add another owner and it should now work
|
||||
factories.TemplateAccessFactory(template=template, role="owner")
|
||||
factories.UserTemplateAccessFactory(template=template, role="owner")
|
||||
|
||||
response = client.put(
|
||||
f"/api/v1.0/templates/{template.id!s}/accesses/{access.id!s}/",
|
||||
@@ -648,7 +767,7 @@ def test_api_template_accesses_update_owner_self():
|
||||
|
||||
def test_api_template_accesses_delete_anonymous():
|
||||
"""Anonymous users should not be allowed to destroy a template access."""
|
||||
access = factories.TemplateAccessFactory()
|
||||
access = factories.UserTemplateAccessFactory()
|
||||
|
||||
response = APIClient().delete(
|
||||
f"/api/v1.0/templates/{access.template.id!s}/accesses/{access.id!s}/",
|
||||
@@ -668,7 +787,7 @@ def test_api_template_accesses_delete_authenticated():
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
|
||||
access = factories.TemplateAccessFactory()
|
||||
access = factories.UserTemplateAccessFactory()
|
||||
|
||||
response = client.delete(
|
||||
f"/api/v1.0/templates/{access.template.id!s}/accesses/{access.id!s}/",
|
||||
@@ -678,7 +797,8 @@ def test_api_template_accesses_delete_authenticated():
|
||||
assert models.TemplateAccess.objects.count() == 1
|
||||
|
||||
|
||||
def test_api_template_accesses_delete_member():
|
||||
@pytest.mark.parametrize("via", VIA)
|
||||
def test_api_template_accesses_delete_member(via, mock_user_get_teams):
|
||||
"""
|
||||
Authenticated users should not be allowed to delete a template access for a
|
||||
template in which they are a simple member.
|
||||
@@ -688,8 +808,16 @@ def test_api_template_accesses_delete_member():
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
|
||||
template = factories.TemplateFactory(users=[(user, "member")])
|
||||
access = factories.TemplateAccessFactory(template=template)
|
||||
template = factories.TemplateFactory()
|
||||
if via == USER:
|
||||
factories.UserTemplateAccessFactory(template=template, user=user, role="member")
|
||||
elif via == TEAM:
|
||||
mock_user_get_teams.return_value = ["lasuite", "unknown"]
|
||||
factories.TeamTemplateAccessFactory(
|
||||
template=template, team="lasuite", role="member"
|
||||
)
|
||||
|
||||
access = factories.UserTemplateAccessFactory(template=template)
|
||||
|
||||
assert models.TemplateAccess.objects.count() == 2
|
||||
assert models.TemplateAccess.objects.filter(user=access.user).exists()
|
||||
@@ -702,7 +830,10 @@ def test_api_template_accesses_delete_member():
|
||||
assert models.TemplateAccess.objects.count() == 2
|
||||
|
||||
|
||||
def test_api_template_accesses_delete_administrators_except_owners():
|
||||
@pytest.mark.parametrize("via", VIA)
|
||||
def test_api_template_accesses_delete_administrators_except_owners(
|
||||
via, mock_user_get_teams
|
||||
):
|
||||
"""
|
||||
Users who are administrators in a template should be allowed to delete an access
|
||||
from the template provided it is not ownership.
|
||||
@@ -712,8 +843,18 @@ def test_api_template_accesses_delete_administrators_except_owners():
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
|
||||
template = factories.TemplateFactory(users=[(user, "administrator")])
|
||||
access = factories.TemplateAccessFactory(
|
||||
template = factories.TemplateFactory()
|
||||
if via == USER:
|
||||
factories.UserTemplateAccessFactory(
|
||||
template=template, user=user, role="administrator"
|
||||
)
|
||||
elif via == TEAM:
|
||||
mock_user_get_teams.return_value = ["lasuite", "unknown"]
|
||||
factories.TeamTemplateAccessFactory(
|
||||
template=template, team="lasuite", role="administrator"
|
||||
)
|
||||
|
||||
access = factories.UserTemplateAccessFactory(
|
||||
template=template, role=random.choice(["member", "administrator"])
|
||||
)
|
||||
|
||||
@@ -728,7 +869,8 @@ def test_api_template_accesses_delete_administrators_except_owners():
|
||||
assert models.TemplateAccess.objects.count() == 1
|
||||
|
||||
|
||||
def test_api_template_accesses_delete_administrators_owners():
|
||||
@pytest.mark.parametrize("via", VIA)
|
||||
def test_api_template_accesses_delete_administrator_on_owners(via, mock_user_get_teams):
|
||||
"""
|
||||
Users who are administrators in a template should not be allowed to delete an ownership
|
||||
access from the template.
|
||||
@@ -738,8 +880,18 @@ def test_api_template_accesses_delete_administrators_owners():
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
|
||||
template = factories.TemplateFactory(users=[(user, "administrator")])
|
||||
access = factories.TemplateAccessFactory(template=template, role="owner")
|
||||
template = factories.TemplateFactory()
|
||||
if via == USER:
|
||||
factories.UserTemplateAccessFactory(
|
||||
template=template, user=user, role="administrator"
|
||||
)
|
||||
elif via == TEAM:
|
||||
mock_user_get_teams.return_value = ["lasuite", "unknown"]
|
||||
factories.TeamTemplateAccessFactory(
|
||||
template=template, team="lasuite", role="administrator"
|
||||
)
|
||||
|
||||
access = factories.UserTemplateAccessFactory(template=template, role="owner")
|
||||
|
||||
assert models.TemplateAccess.objects.count() == 2
|
||||
assert models.TemplateAccess.objects.filter(user=access.user).exists()
|
||||
@@ -752,7 +904,8 @@ def test_api_template_accesses_delete_administrators_owners():
|
||||
assert models.TemplateAccess.objects.count() == 2
|
||||
|
||||
|
||||
def test_api_template_accesses_delete_owners():
|
||||
@pytest.mark.parametrize("via", VIA)
|
||||
def test_api_template_accesses_delete_owners(via, mock_user_get_teams):
|
||||
"""
|
||||
Users should be able to delete the template access of another user
|
||||
for a template of which they are owner.
|
||||
@@ -762,10 +915,16 @@ def test_api_template_accesses_delete_owners():
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
|
||||
template = factories.TemplateFactory(users=[(user, "owner")])
|
||||
access = factories.TemplateAccessFactory(
|
||||
template=template,
|
||||
)
|
||||
template = factories.TemplateFactory()
|
||||
if via == USER:
|
||||
factories.UserTemplateAccessFactory(template=template, user=user, role="owner")
|
||||
elif via == TEAM:
|
||||
mock_user_get_teams.return_value = ["lasuite", "unknown"]
|
||||
factories.TeamTemplateAccessFactory(
|
||||
template=template, team="lasuite", role="owner"
|
||||
)
|
||||
|
||||
access = factories.UserTemplateAccessFactory(template=template)
|
||||
|
||||
assert models.TemplateAccess.objects.count() == 2
|
||||
assert models.TemplateAccess.objects.filter(user=access.user).exists()
|
||||
@@ -778,7 +937,8 @@ def test_api_template_accesses_delete_owners():
|
||||
assert models.TemplateAccess.objects.count() == 1
|
||||
|
||||
|
||||
def test_api_template_accesses_delete_owners_last_owner():
|
||||
@pytest.mark.parametrize("via", VIA)
|
||||
def test_api_template_accesses_delete_owners_last_owner(via, mock_user_get_teams):
|
||||
"""
|
||||
It should not be possible to delete the last owner access from a template
|
||||
"""
|
||||
@@ -788,7 +948,15 @@ def test_api_template_accesses_delete_owners_last_owner():
|
||||
client.force_login(user)
|
||||
|
||||
template = factories.TemplateFactory()
|
||||
access = factories.TemplateAccessFactory(template=template, user=user, role="owner")
|
||||
if via == USER:
|
||||
access = factories.UserTemplateAccessFactory(
|
||||
template=template, user=user, role="owner"
|
||||
)
|
||||
elif via == TEAM:
|
||||
mock_user_get_teams.return_value = ["lasuite", "unknown"]
|
||||
access = factories.TeamTemplateAccessFactory(
|
||||
template=template, team="lasuite", role="owner"
|
||||
)
|
||||
|
||||
assert models.TemplateAccess.objects.count() == 1
|
||||
response = client.delete(
|
||||
|
||||
@@ -16,7 +16,7 @@ def test_models_template_accesses_str():
|
||||
The str representation should include user email, template title and role.
|
||||
"""
|
||||
user = factories.UserFactory(email="david.bowman@example.com")
|
||||
access = factories.TemplateAccessFactory(
|
||||
access = factories.UserTemplateAccessFactory(
|
||||
role="member",
|
||||
user=user,
|
||||
template__title="admins",
|
||||
@@ -24,15 +24,56 @@ def test_models_template_accesses_str():
|
||||
assert str(access) == "david.bowman@example.com is member in template admins"
|
||||
|
||||
|
||||
def test_models_template_accesses_unique():
|
||||
def test_models_template_accesses_unique_user():
|
||||
"""Template accesses should be unique for a given couple of user and template."""
|
||||
access = factories.TemplateAccessFactory()
|
||||
access = factories.UserTemplateAccessFactory()
|
||||
|
||||
with pytest.raises(
|
||||
ValidationError,
|
||||
match="Template/user relation with this User and Template already exists.",
|
||||
match="This user is already in this template.",
|
||||
):
|
||||
factories.TemplateAccessFactory(user=access.user, template=access.template)
|
||||
factories.UserTemplateAccessFactory(user=access.user, template=access.template)
|
||||
|
||||
|
||||
def test_models_template_accesses_several_empty_teams():
|
||||
"""A template can have several template accesses with an empty team."""
|
||||
access = factories.UserTemplateAccessFactory()
|
||||
factories.UserTemplateAccessFactory(template=access.template)
|
||||
|
||||
|
||||
def test_models_template_accesses_unique_team():
|
||||
"""Template accesses should be unique for a given couple of team and template."""
|
||||
access = factories.TeamTemplateAccessFactory()
|
||||
|
||||
with pytest.raises(
|
||||
ValidationError,
|
||||
match="This team is already in this template.",
|
||||
):
|
||||
factories.TeamTemplateAccessFactory(team=access.team, template=access.template)
|
||||
|
||||
|
||||
def test_models_template_accesses_several_null_users():
|
||||
"""A template can have several template accesses with a null user."""
|
||||
access = factories.TeamTemplateAccessFactory()
|
||||
factories.TeamTemplateAccessFactory(template=access.template)
|
||||
|
||||
|
||||
def test_models_template_accesses_user_and_team_set():
|
||||
"""User and team can't both be set on a template access."""
|
||||
with pytest.raises(
|
||||
ValidationError,
|
||||
match="Either user or team must be set, not both.",
|
||||
):
|
||||
factories.UserTemplateAccessFactory(team="my-team")
|
||||
|
||||
|
||||
def test_models_template_accesses_user_and_team_empty():
|
||||
"""User and team can't both be empty on a template access."""
|
||||
with pytest.raises(
|
||||
ValidationError,
|
||||
match="Either user or team must be set, not both.",
|
||||
):
|
||||
factories.UserTemplateAccessFactory(user=None)
|
||||
|
||||
|
||||
# get_abilities
|
||||
@@ -40,7 +81,7 @@ def test_models_template_accesses_unique():
|
||||
|
||||
def test_models_template_access_get_abilities_anonymous():
|
||||
"""Check abilities returned for an anonymous user."""
|
||||
access = factories.TemplateAccessFactory()
|
||||
access = factories.UserTemplateAccessFactory()
|
||||
abilities = access.get_abilities(AnonymousUser())
|
||||
assert abilities == {
|
||||
"destroy": False,
|
||||
@@ -52,7 +93,7 @@ def test_models_template_access_get_abilities_anonymous():
|
||||
|
||||
def test_models_template_access_get_abilities_authenticated():
|
||||
"""Check abilities returned for an authenticated user."""
|
||||
access = factories.TemplateAccessFactory()
|
||||
access = factories.UserTemplateAccessFactory()
|
||||
user = factories.UserFactory()
|
||||
abilities = access.get_abilities(user)
|
||||
assert abilities == {
|
||||
@@ -71,8 +112,8 @@ def test_models_template_access_get_abilities_for_owner_of_self_allowed():
|
||||
Check abilities of self access for the owner of a template when
|
||||
there is more than one owner left.
|
||||
"""
|
||||
access = factories.TemplateAccessFactory(role="owner")
|
||||
factories.TemplateAccessFactory(template=access.template, role="owner")
|
||||
access = factories.UserTemplateAccessFactory(role="owner")
|
||||
factories.UserTemplateAccessFactory(template=access.template, role="owner")
|
||||
abilities = access.get_abilities(access.user)
|
||||
assert abilities == {
|
||||
"destroy": True,
|
||||
@@ -86,7 +127,7 @@ def test_models_template_access_get_abilities_for_owner_of_self_last():
|
||||
"""
|
||||
Check abilities of self access for the owner of a template when there is only one owner left.
|
||||
"""
|
||||
access = factories.TemplateAccessFactory(role="owner")
|
||||
access = factories.UserTemplateAccessFactory(role="owner")
|
||||
abilities = access.get_abilities(access.user)
|
||||
assert abilities == {
|
||||
"destroy": False,
|
||||
@@ -98,9 +139,11 @@ def test_models_template_access_get_abilities_for_owner_of_self_last():
|
||||
|
||||
def test_models_template_access_get_abilities_for_owner_of_owner():
|
||||
"""Check abilities of owner access for the owner of a template."""
|
||||
access = factories.TemplateAccessFactory(role="owner")
|
||||
factories.TemplateAccessFactory(template=access.template) # another one
|
||||
user = factories.TemplateAccessFactory(template=access.template, role="owner").user
|
||||
access = factories.UserTemplateAccessFactory(role="owner")
|
||||
factories.UserTemplateAccessFactory(template=access.template) # another one
|
||||
user = factories.UserTemplateAccessFactory(
|
||||
template=access.template, role="owner"
|
||||
).user
|
||||
abilities = access.get_abilities(user)
|
||||
assert abilities == {
|
||||
"destroy": True,
|
||||
@@ -112,9 +155,11 @@ def test_models_template_access_get_abilities_for_owner_of_owner():
|
||||
|
||||
def test_models_template_access_get_abilities_for_owner_of_administrator():
|
||||
"""Check abilities of administrator access for the owner of a template."""
|
||||
access = factories.TemplateAccessFactory(role="administrator")
|
||||
factories.TemplateAccessFactory(template=access.template) # another one
|
||||
user = factories.TemplateAccessFactory(template=access.template, role="owner").user
|
||||
access = factories.UserTemplateAccessFactory(role="administrator")
|
||||
factories.UserTemplateAccessFactory(template=access.template) # another one
|
||||
user = factories.UserTemplateAccessFactory(
|
||||
template=access.template, role="owner"
|
||||
).user
|
||||
abilities = access.get_abilities(user)
|
||||
assert abilities == {
|
||||
"destroy": True,
|
||||
@@ -126,9 +171,11 @@ def test_models_template_access_get_abilities_for_owner_of_administrator():
|
||||
|
||||
def test_models_template_access_get_abilities_for_owner_of_member():
|
||||
"""Check abilities of member access for the owner of a template."""
|
||||
access = factories.TemplateAccessFactory(role="member")
|
||||
factories.TemplateAccessFactory(template=access.template) # another one
|
||||
user = factories.TemplateAccessFactory(template=access.template, role="owner").user
|
||||
access = factories.UserTemplateAccessFactory(role="member")
|
||||
factories.UserTemplateAccessFactory(template=access.template) # another one
|
||||
user = factories.UserTemplateAccessFactory(
|
||||
template=access.template, role="owner"
|
||||
).user
|
||||
abilities = access.get_abilities(user)
|
||||
assert abilities == {
|
||||
"destroy": True,
|
||||
@@ -143,9 +190,9 @@ def test_models_template_access_get_abilities_for_owner_of_member():
|
||||
|
||||
def test_models_template_access_get_abilities_for_administrator_of_owner():
|
||||
"""Check abilities of owner access for the administrator of a template."""
|
||||
access = factories.TemplateAccessFactory(role="owner")
|
||||
factories.TemplateAccessFactory(template=access.template) # another one
|
||||
user = factories.TemplateAccessFactory(
|
||||
access = factories.UserTemplateAccessFactory(role="owner")
|
||||
factories.UserTemplateAccessFactory(template=access.template) # another one
|
||||
user = factories.UserTemplateAccessFactory(
|
||||
template=access.template, role="administrator"
|
||||
).user
|
||||
abilities = access.get_abilities(user)
|
||||
@@ -159,9 +206,9 @@ def test_models_template_access_get_abilities_for_administrator_of_owner():
|
||||
|
||||
def test_models_template_access_get_abilities_for_administrator_of_administrator():
|
||||
"""Check abilities of administrator access for the administrator of a template."""
|
||||
access = factories.TemplateAccessFactory(role="administrator")
|
||||
factories.TemplateAccessFactory(template=access.template) # another one
|
||||
user = factories.TemplateAccessFactory(
|
||||
access = factories.UserTemplateAccessFactory(role="administrator")
|
||||
factories.UserTemplateAccessFactory(template=access.template) # another one
|
||||
user = factories.UserTemplateAccessFactory(
|
||||
template=access.template, role="administrator"
|
||||
).user
|
||||
abilities = access.get_abilities(user)
|
||||
@@ -175,9 +222,9 @@ def test_models_template_access_get_abilities_for_administrator_of_administrator
|
||||
|
||||
def test_models_template_access_get_abilities_for_administrator_of_member():
|
||||
"""Check abilities of member access for the administrator of a template."""
|
||||
access = factories.TemplateAccessFactory(role="member")
|
||||
factories.TemplateAccessFactory(template=access.template) # another one
|
||||
user = factories.TemplateAccessFactory(
|
||||
access = factories.UserTemplateAccessFactory(role="member")
|
||||
factories.UserTemplateAccessFactory(template=access.template) # another one
|
||||
user = factories.UserTemplateAccessFactory(
|
||||
template=access.template, role="administrator"
|
||||
).user
|
||||
abilities = access.get_abilities(user)
|
||||
@@ -194,9 +241,11 @@ def test_models_template_access_get_abilities_for_administrator_of_member():
|
||||
|
||||
def test_models_template_access_get_abilities_for_member_of_owner():
|
||||
"""Check abilities of owner access for the member of a template."""
|
||||
access = factories.TemplateAccessFactory(role="owner")
|
||||
factories.TemplateAccessFactory(template=access.template) # another one
|
||||
user = factories.TemplateAccessFactory(template=access.template, role="member").user
|
||||
access = factories.UserTemplateAccessFactory(role="owner")
|
||||
factories.UserTemplateAccessFactory(template=access.template) # another one
|
||||
user = factories.UserTemplateAccessFactory(
|
||||
template=access.template, role="member"
|
||||
).user
|
||||
abilities = access.get_abilities(user)
|
||||
assert abilities == {
|
||||
"destroy": False,
|
||||
@@ -208,9 +257,11 @@ def test_models_template_access_get_abilities_for_member_of_owner():
|
||||
|
||||
def test_models_template_access_get_abilities_for_member_of_administrator():
|
||||
"""Check abilities of administrator access for the member of a template."""
|
||||
access = factories.TemplateAccessFactory(role="administrator")
|
||||
factories.TemplateAccessFactory(template=access.template) # another one
|
||||
user = factories.TemplateAccessFactory(template=access.template, role="member").user
|
||||
access = factories.UserTemplateAccessFactory(role="administrator")
|
||||
factories.UserTemplateAccessFactory(template=access.template) # another one
|
||||
user = factories.UserTemplateAccessFactory(
|
||||
template=access.template, role="member"
|
||||
).user
|
||||
abilities = access.get_abilities(user)
|
||||
assert abilities == {
|
||||
"destroy": False,
|
||||
@@ -224,9 +275,11 @@ def test_models_template_access_get_abilities_for_member_of_member_user(
|
||||
django_assert_num_queries
|
||||
):
|
||||
"""Check abilities of member access for the member of a template."""
|
||||
access = factories.TemplateAccessFactory(role="member")
|
||||
factories.TemplateAccessFactory(template=access.template) # another one
|
||||
user = factories.TemplateAccessFactory(template=access.template, role="member").user
|
||||
access = factories.UserTemplateAccessFactory(role="member")
|
||||
factories.UserTemplateAccessFactory(template=access.template) # another one
|
||||
user = factories.UserTemplateAccessFactory(
|
||||
template=access.template, role="member"
|
||||
).user
|
||||
|
||||
with django_assert_num_queries(1):
|
||||
abilities = access.get_abilities(user)
|
||||
@@ -241,9 +294,11 @@ def test_models_template_access_get_abilities_for_member_of_member_user(
|
||||
|
||||
def test_models_template_access_get_abilities_preset_role(django_assert_num_queries):
|
||||
"""No query is done if the role is preset, e.g., with a query annotation."""
|
||||
access = factories.TemplateAccessFactory(role="member")
|
||||
user = factories.TemplateAccessFactory(template=access.template, role="member").user
|
||||
access.user_role = "member"
|
||||
access = factories.UserTemplateAccessFactory(role="member")
|
||||
user = factories.UserTemplateAccessFactory(
|
||||
template=access.template, role="member"
|
||||
).user
|
||||
access.user_roles = ["member"]
|
||||
|
||||
with django_assert_num_queries(0):
|
||||
abilities = access.get_abilities(user)
|
||||
|
||||
@@ -104,7 +104,7 @@ def test_models_templates_get_abilities_authenticated_not_public():
|
||||
def test_models_templates_get_abilities_owner():
|
||||
"""Check abilities returned for the owner of a template."""
|
||||
user = factories.UserFactory()
|
||||
access = factories.TemplateAccessFactory(role="owner", user=user)
|
||||
access = factories.UserTemplateAccessFactory(role="owner", user=user)
|
||||
abilities = access.template.get_abilities(access.user)
|
||||
assert abilities == {
|
||||
"destroy": True,
|
||||
@@ -117,7 +117,7 @@ def test_models_templates_get_abilities_owner():
|
||||
|
||||
def test_models_templates_get_abilities_administrator():
|
||||
"""Check abilities returned for the administrator of a template."""
|
||||
access = factories.TemplateAccessFactory(role="administrator")
|
||||
access = factories.UserTemplateAccessFactory(role="administrator")
|
||||
abilities = access.template.get_abilities(access.user)
|
||||
assert abilities == {
|
||||
"destroy": False,
|
||||
@@ -130,7 +130,7 @@ def test_models_templates_get_abilities_administrator():
|
||||
|
||||
def test_models_templates_get_abilities_member_user(django_assert_num_queries):
|
||||
"""Check abilities returned for the member of a template."""
|
||||
access = factories.TemplateAccessFactory(role="member")
|
||||
access = factories.UserTemplateAccessFactory(role="member")
|
||||
|
||||
with django_assert_num_queries(1):
|
||||
abilities = access.template.get_abilities(access.user)
|
||||
@@ -146,8 +146,8 @@ def test_models_templates_get_abilities_member_user(django_assert_num_queries):
|
||||
|
||||
def test_models_templates_get_abilities_preset_role(django_assert_num_queries):
|
||||
"""No query is done if the role is preset e.g. with query annotation."""
|
||||
access = factories.TemplateAccessFactory(role="member")
|
||||
access.template.user_role = "member"
|
||||
access = factories.UserTemplateAccessFactory(role="member")
|
||||
access.template.user_roles = ["member"]
|
||||
|
||||
with django_assert_num_queries(0):
|
||||
abilities = access.template.get_abilities(access.user)
|
||||
|
||||
Reference in New Issue
Block a user