(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:
Samuel Paccoud - DINUM
2024-03-03 08:49:27 +01:00
committed by Samuel Paccoud
parent a23118bee4
commit f581eb8abd
15 changed files with 922 additions and 223 deletions

View File

@@ -77,8 +77,3 @@ class TemplateAdmin(admin.ModelAdmin):
"""Template admin interface declaration.""" """Template admin interface declaration."""
inlines = (TemplateAccessInline,) inlines = (TemplateAccessInline,)
@admin.register(models.Team)
class TeamAdmin(admin.ModelAdmin):
"""Team admin interface declaration."""

View File

@@ -1,4 +1,5 @@
"""Client serializers for the publish core app.""" """Client serializers for the publish core app."""
from django.db.models import Q
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from rest_framework import exceptions, serializers from rest_framework import exceptions, serializers
@@ -31,7 +32,7 @@ class TemplateAccessSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = models.TemplateAccess model = models.TemplateAccess
fields = ["id", "user", "role", "abilities"] fields = ["id", "user", "team", "role", "abilities"]
read_only_fields = ["id", "abilities"] read_only_fields = ["id", "abilities"]
def update(self, instance, validated_data): def update(self, instance, validated_data):
@@ -68,6 +69,7 @@ class TemplateAccessSerializer(serializers.ModelSerializer):
# Create # Create
else: else:
teams = user.get_teams()
try: try:
template_id = self.context["template_id"] template_id = self.context["template_id"]
except KeyError as exc: except KeyError as exc:
@@ -76,8 +78,8 @@ class TemplateAccessSerializer(serializers.ModelSerializer):
) from exc ) from exc
if not models.TemplateAccess.objects.filter( if not models.TemplateAccess.objects.filter(
Q(user=user) | Q(team__in=teams),
template=template_id, template=template_id,
user=user,
role__in=[models.RoleChoices.OWNER, models.RoleChoices.ADMIN], role__in=[models.RoleChoices.OWNER, models.RoleChoices.ADMIN],
).exists(): ).exists():
raise exceptions.PermissionDenied( raise exceptions.PermissionDenied(
@@ -87,8 +89,8 @@ class TemplateAccessSerializer(serializers.ModelSerializer):
if ( if (
role == models.RoleChoices.OWNER role == models.RoleChoices.OWNER
and not models.TemplateAccess.objects.filter( and not models.TemplateAccess.objects.filter(
Q(user=user) | Q(team__in=teams),
template=template_id, template=template_id,
user=user,
role=models.RoleChoices.OWNER, role=models.RoleChoices.OWNER,
).exists() ).exists()
): ):

View File

@@ -1,6 +1,7 @@
"""API endpoints""" """API endpoints"""
from io import BytesIO from io import BytesIO
from django.contrib.postgres.aggregates import ArrayAgg
from django.db.models import ( from django.db.models import (
OuterRef, OuterRef,
Q, Q,
@@ -156,14 +157,22 @@ class TemplateViewSet(
if not self.request.user.is_authenticated: if not self.request.user.is_authenticated:
return models.Template.objects.filter(is_public=True) return models.Template.objects.filter(is_public=True)
user_role_query = models.TemplateAccess.objects.filter( user = self.request.user
user=self.request.user, template=OuterRef("pk") teams = user.get_teams()
).values("role")[:1]
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 ( return (
models.Template.objects.filter( 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() .distinct()
) )
@@ -263,19 +272,30 @@ class TemplateAccessViewSet(
queryset = queryset.filter(template=self.kwargs["template_id"]) queryset = queryset.filter(template=self.kwargs["template_id"])
if self.action == "list": 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 # Limit to template access instances related to a template THAT also has
# a template access # a template access
# instance for the logged-in user (we don't want to list only the template # instance for the logged-in user (we don't want to list only the template
# access instances pointing to the logged-in user) # 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 = (
queryset.filter( 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() .distinct()
) )
return queryset return queryset

View File

@@ -41,12 +41,12 @@ class TemplateFactory(factory.django.DjangoModelFactory):
if create and extracted: if create and extracted:
for item in extracted: for item in extracted:
if isinstance(item, models.User): if isinstance(item, models.User):
TemplateAccessFactory(template=self, user=item) UserTemplateAccessFactory(template=self, user=item)
else: 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.""" """Create fake template user accesses for testing."""
class Meta: class Meta:
@@ -55,3 +55,14 @@ class TemplateAccessFactory(factory.django.DjangoModelFactory):
template = factory.SubFactory(TemplateFactory) template = factory.SubFactory(TemplateFactory)
user = factory.SubFactory(UserFactory) user = factory.SubFactory(UserFactory)
role = factory.fuzzy.FuzzyChoice([r[0] for r in models.RoleChoices.choices]) 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])

View File

@@ -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.contrib.auth.models
import django.core.validators import django.core.validators
@@ -18,21 +18,6 @@ class Migration(migrations.Migration):
] ]
operations = [ 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( migrations.CreateModel(
name='Template', name='Template',
fields=[ 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')), ('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')), ('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')), ('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)), ('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')), ('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)), ('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( migrations.AddConstraint(
model_name='templateaccess', 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( migrations.AddConstraint(
model_name='templateaccess', 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.'),
), ),
] ]

View File

@@ -147,20 +147,12 @@ class User(AbstractBaseUser, BaseModel, auth_models.PermissionsMixin):
raise ValueError("User has no email address.") raise ValueError("User has no email address.")
mail.send_mail(subject, message, from_email, [self.email], **kwargs) mail.send_mail(subject, message, from_email, [self.email], **kwargs)
def get_teams(self):
class Team(BaseModel): """
"""Team used for role based access control when matched with templates in OIDC tokens.""" Get list of teams in which the user is, as a list of strings.
Must be cached if retrieved remotely.
name = models.CharField(max_length=100, unique=True) """
return []
class Meta:
db_table = "publish_role"
ordering = ("name",)
verbose_name = _("Team")
verbose_name_plural = _("Teams")
def __str__(self):
return self.name
class Template(BaseModel): class Template(BaseModel):
@@ -213,21 +205,25 @@ class Template(BaseModel):
Compute and return abilities for a given user on the template. Compute and return abilities for a given user on the template.
""" """
# Compute user role # Compute user role
role = None roles = []
if user.is_authenticated: if user.is_authenticated:
try: try:
role = self.user_role roles = self.user_roles or []
except AttributeError: except AttributeError:
teams = user.get_teams()
try: 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): except (TemplateAccess.DoesNotExist, IndexError):
role = None roles = []
is_owner_or_admin = bool(
is_owner_or_admin = role in [RoleChoices.OWNER, RoleChoices.ADMIN] set(roles).intersection({RoleChoices.OWNER, RoleChoices.ADMIN})
can_get = self.is_public or role is not None )
can_get = self.is_public or bool(roles)
return { return {
"destroy": role == RoleChoices.OWNER, "destroy": RoleChoices.OWNER in roles,
"generate_document": can_get, "generate_document": can_get,
"manage_accesses": is_owner_or_admin, "manage_accesses": is_owner_or_admin,
"update": is_owner_or_admin, "update": is_owner_or_admin,
@@ -250,13 +246,7 @@ class TemplateAccess(BaseModel):
null=True, null=True,
blank=True, blank=True,
) )
team = models.ForeignKey( team = models.CharField(max_length=100, blank=True)
Team,
on_delete=models.CASCADE,
related_name="accesses",
null=True,
blank=True,
)
role = models.CharField( role = models.CharField(
max_length=20, choices=RoleChoices.choices, default=RoleChoices.MEMBER max_length=20, choices=RoleChoices.choices, default=RoleChoices.MEMBER
) )
@@ -268,14 +258,22 @@ class TemplateAccess(BaseModel):
constraints = [ constraints = [
models.UniqueConstraint( models.UniqueConstraint(
fields=["user", "template"], fields=["user", "template"],
condition=models.Q(user__isnull=False), # Exclude null users
name="unique_template_user", name="unique_template_user",
violation_error_message=_("This user is already in this template."), violation_error_message=_("This user is already in this template."),
), ),
models.UniqueConstraint( models.UniqueConstraint(
fields=["team", "template"], fields=["team", "template"],
condition=models.Q(team__gt=""), # Exclude empty string teams
name="unique_template_team", name="unique_template_team",
violation_error_message=_("This team is already in this template."), 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): def __str__(self):
@@ -287,32 +285,34 @@ class TemplateAccess(BaseModel):
the current state of the object. the current state of the object.
""" """
is_template_owner_or_admin = False is_template_owner_or_admin = False
role = None
roles = []
if user.is_authenticated: if user.is_authenticated:
teams = user.get_teams()
try: try:
role = self.user_role roles = self.user_roles or []
except AttributeError: except AttributeError:
try: 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, template=self.template_id,
user=user, ).values_list("role", flat=True)
).values("role")[0]["role"]
except (self._meta.model.DoesNotExist, IndexError): except (self._meta.model.DoesNotExist, IndexError):
role = None roles = []
is_template_owner_or_admin = role in [RoleChoices.OWNER, RoleChoices.ADMIN]
is_template_owner_or_admin = bool(
set(roles).intersection({RoleChoices.OWNER, RoleChoices.ADMIN})
)
if self.role == RoleChoices.OWNER: if self.role == RoleChoices.OWNER:
can_delete = ( can_delete = (
role == RoleChoices.OWNER RoleChoices.OWNER in roles
and self.template.accesses.filter(role=RoleChoices.OWNER).count() > 1 and self.template.accesses.filter(role=RoleChoices.OWNER).count() > 1
) )
set_role_to = [RoleChoices.ADMIN, RoleChoices.MEMBER] if can_delete else [] set_role_to = [RoleChoices.ADMIN, RoleChoices.MEMBER] if can_delete else []
else: else:
can_delete = is_template_owner_or_admin can_delete = is_template_owner_or_admin
set_role_to = [] set_role_to = []
if role == RoleChoices.OWNER: if RoleChoices.OWNER in roles:
set_role_to.append(RoleChoices.OWNER) set_role_to.append(RoleChoices.OWNER)
if is_template_owner_or_admin: if is_template_owner_or_admin:
set_role_to.extend([RoleChoices.ADMIN, RoleChoices.MEMBER]) set_role_to.extend([RoleChoices.ADMIN, RoleChoices.MEMBER])
@@ -326,6 +326,6 @@ class TemplateAccess(BaseModel):
return { return {
"destroy": can_delete, "destroy": can_delete,
"update": bool(set_role_to), "update": bool(set_role_to),
"retrieve": bool(role), "retrieve": bool(roles),
"set_role_to": set_role_to, "set_role_to": set_role_to,
} }

View 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

View File

@@ -7,6 +7,7 @@ import pytest
from rest_framework.test import APIClient from rest_framework.test import APIClient
from core import factories, models from core import factories, models
from core.tests.conftest import TEAM, USER, VIA
pytestmark = pytest.mark.django_db pytestmark = pytest.mark.django_db
@@ -45,17 +46,27 @@ def test_api_templates_delete_authenticated_unrelated():
@pytest.mark.parametrize("role", ["member", "administrator"]) @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 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() user = factories.UserFactory()
client = APIClient() client = APIClient()
client.force_login(user) 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( response = client.delete(
f"/api/v1.0/templates/{template.id}/", 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 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 Authenticated users should be able to delete a template they own.
owner.
""" """
user = factories.UserFactory() user = factories.UserFactory()
client = APIClient() client = APIClient()
client.force_login(user) 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( response = client.delete(
f"/api/v1.0/templates/{template.id}/", f"/api/v1.0/templates/{template.id}/",

View File

@@ -5,6 +5,7 @@ import pytest
from rest_framework.test import APIClient from rest_framework.test import APIClient
from core import factories from core import factories
from core.tests.conftest import TEAM, USER, VIA
pytestmark = pytest.mark.django_db pytestmark = pytest.mark.django_db
@@ -89,14 +90,20 @@ def test_api_templates_generate_document_authenticated_not_public():
assert response.json() == {"detail": "Not found."} 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.""" """Users related to a template can generate pdf document."""
user = factories.UserFactory() user = factories.UserFactory()
client = APIClient() client = APIClient()
client.force_login(user) 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"} data = {"body": "# Test markdown body"}
response = client.post( response = client.post(

View File

@@ -28,10 +28,10 @@ def test_api_templates_list_anonymous():
assert expected_ids == results_id 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 Authenticated users should be able to list templates they are a direct
an owner/administrator/member of. owner/administrator/member of.
""" """
user = factories.UserFactory() user = factories.UserFactory()
@@ -40,7 +40,7 @@ def test_api_templates_list_authenticated():
related_templates = [ related_templates = [
access.template 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) public_templates = factories.TemplateFactory.create_batch(2, is_public=True)
factories.TemplateFactory.create_batch(2, is_public=False) factories.TemplateFactory.create_batch(2, is_public=False)
@@ -60,6 +60,43 @@ def test_api_templates_list_authenticated():
assert expected_ids == results_id 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) @mock.patch.object(PageNumberPagination, "get_page_size", return_value=2)
def test_api_templates_list_pagination( def test_api_templates_list_pagination(
_mock_page_size, _mock_page_size,
@@ -72,7 +109,7 @@ def test_api_templates_list_pagination(
template_ids = [ template_ids = [
str(access.template.id) 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 # Get page 1

View File

@@ -89,10 +89,10 @@ def test_api_templates_retrieve_authenticated_unrelated_not_public():
assert response.json() == {"detail": "Not found."} 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 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() user = factories.UserFactory()
@@ -100,8 +100,8 @@ def test_api_templates_retrieve_authenticated_related():
client.force_login(user) client.force_login(user)
template = factories.TemplateFactory() template = factories.TemplateFactory()
access1 = factories.TemplateAccessFactory(template=template, user=user) access1 = factories.UserTemplateAccessFactory(template=template, user=user)
access2 = factories.TemplateAccessFactory(template=template) access2 = factories.UserTemplateAccessFactory(template=template)
response = client.get( response = client.get(
f"/api/v1.0/templates/{template.id!s}/", f"/api/v1.0/templates/{template.id!s}/",
@@ -113,12 +113,14 @@ def test_api_templates_retrieve_authenticated_related():
{ {
"id": str(access1.id), "id": str(access1.id),
"user": str(user.id), "user": str(user.id),
"team": "",
"role": access1.role, "role": access1.role,
"abilities": access1.get_abilities(user), "abilities": access1.get_abilities(user),
}, },
{ {
"id": str(access2.id), "id": str(access2.id),
"user": str(access2.user.id), "user": str(access2.user.id),
"team": "",
"role": access2.role, "role": access2.role,
"abilities": access2.get_abilities(user), "abilities": access2.get_abilities(user),
}, },
@@ -130,3 +132,310 @@ def test_api_templates_retrieve_authenticated_related():
"title": template.title, "title": template.title,
"abilities": template.get_abilities(user), "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),
}

View File

@@ -8,6 +8,7 @@ from rest_framework.test import APIClient
from core import factories from core import factories
from core.api import serializers from core.api import serializers
from core.tests.conftest import TEAM, USER, VIA
pytestmark = pytest.mark.django_db pytestmark = pytest.mark.django_db
@@ -64,7 +65,8 @@ def test_api_templates_update_authenticated_unrelated():
assert template_values == old_template_values 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 Users who are members of a template but not administrators should
not be allowed to update it. not be allowed to update it.
@@ -74,7 +76,15 @@ def test_api_templates_update_authenticated_members():
client = APIClient() client = APIClient()
client.force_login(user) 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 old_template_values = serializers.TemplateSerializer(instance=template).data
new_template_values = serializers.TemplateSerializer( new_template_values = serializers.TemplateSerializer(
@@ -97,14 +107,25 @@ def test_api_templates_update_authenticated_members():
@pytest.mark.parametrize("role", ["administrator", "owner"]) @pytest.mark.parametrize("role", ["administrator", "owner"])
def test_api_templates_update_authenticated_administrators(role): @pytest.mark.parametrize("via", VIA)
"""Administrators of a template should be allowed to update it.""" 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() user = factories.UserFactory()
client = APIClient() client = APIClient()
client.force_login(user) 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 old_template_values = serializers.TemplateSerializer(instance=template).data
new_template_values = serializers.TemplateSerializer( new_template_values = serializers.TemplateSerializer(
@@ -126,7 +147,47 @@ def test_api_templates_update_authenticated_administrators(role):
assert value == new_template_values[key] 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 Being administrator or owner of a template should not grant authorization to update
another template. another template.
@@ -136,7 +197,19 @@ def test_api_templates_update_administrator_or_owner_of_another():
client = APIClient() client = APIClient()
client.force_login(user) 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]) is_public = random.choice([True, False])
template = factories.TemplateFactory(title="Old title", is_public=is_public) template = factories.TemplateFactory(title="Old title", is_public=is_public)
old_template_values = serializers.TemplateSerializer(instance=template).data old_template_values = serializers.TemplateSerializer(instance=template).data

View File

@@ -9,6 +9,7 @@ from rest_framework.test import APIClient
from core import factories, models from core import factories, models
from core.api import serializers from core.api import serializers
from core.tests.conftest import TEAM, USER, VIA
pytestmark = pytest.mark.django_db pytestmark = pytest.mark.django_db
@@ -16,7 +17,7 @@ pytestmark = pytest.mark.django_db
def test_api_template_accesses_list_anonymous(): def test_api_template_accesses_list_anonymous():
"""Anonymous users should not be allowed to list template accesses.""" """Anonymous users should not be allowed to list template accesses."""
template = factories.TemplateFactory() 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/") response = APIClient().get(f"/api/v1.0/templates/{template.id!s}/accesses/")
assert response.status_code == 401 assert response.status_code == 401
@@ -36,11 +37,11 @@ def test_api_template_accesses_list_authenticated_unrelated():
client.force_login(user) client.force_login(user)
template = factories.TemplateFactory() 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 # Accesses for other templates to which the user is related should not be listed either
other_access = factories.TemplateAccessFactory(user=user) other_access = factories.UserTemplateAccessFactory(user=user)
factories.TemplateAccessFactory(template=other_access.template) factories.UserTemplateAccessFactory(template=other_access.template)
response = client.get( response = client.get(
f"/api/v1.0/templates/{template.id!s}/accesses/", 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 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() user = factories.UserFactory()
@@ -65,16 +67,26 @@ def test_api_template_accesses_list_authenticated_related():
client.force_login(user) client.force_login(user)
template = factories.TemplateFactory() template = factories.TemplateFactory()
user_access = models.TemplateAccess.objects.create( if via == USER:
template=template, user=user user_access = models.TemplateAccess.objects.create(
) # random role template=template,
access1, access2 = factories.TemplateAccessFactory.create_batch( user=user,
2, template=template 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 # Accesses for other templates to which the user is related should not be listed either
other_access = factories.TemplateAccessFactory(user=user) other_access = factories.UserTemplateAccessFactory(user=user)
factories.TemplateAccessFactory(template=other_access.template) factories.UserTemplateAccessFactory(template=other_access.template)
response = client.get( response = client.get(
f"/api/v1.0/templates/{template.id!s}/accesses/", 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), "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, "role": user_access.role,
"abilities": user_access.get_abilities(user), "abilities": user_access.get_abilities(user),
}, },
{ {
"id": str(access1.id), "id": str(access1.id),
"user": str(access1.user.id), "user": None,
"team": access1.team,
"role": access1.role, "role": access1.role,
"abilities": access1.get_abilities(user), "abilities": access1.get_abilities(user),
}, },
{ {
"id": str(access2.id), "id": str(access2.id),
"user": str(access2.user.id), "user": str(access2.user.id),
"team": "",
"role": access2.role, "role": access2.role,
"abilities": access2.get_abilities(user), "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. Anonymous users should not be allowed to retrieve a template access.
""" """
access = factories.TemplateAccessFactory() access = factories.UserTemplateAccessFactory()
response = APIClient().get( response = APIClient().get(
f"/api/v1.0/templates/{access.template.id!s}/accesses/{access.id!s}/", 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) client.force_login(user)
template = factories.TemplateFactory() template = factories.TemplateFactory()
access = factories.TemplateAccessFactory(template=template) access = factories.UserTemplateAccessFactory(template=template)
response = client.get( response = client.get(
f"/api/v1.0/templates/{template.id!s}/accesses/{access.id!s}/", 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 # Accesses related to another template should be excluded even if the user is related to it
for access in [ for access in [
factories.TemplateAccessFactory(), factories.UserTemplateAccessFactory(),
factories.TemplateAccessFactory(user=user), factories.UserTemplateAccessFactory(user=user),
]: ]:
response = client.get( response = client.get(
f"/api/v1.0/templates/{template.id!s}/accesses/{access.id!s}/", 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."} 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 A user who is related to a template should be allowed to retrieve the
associated template user accesses. associated template user accesses.
@@ -168,8 +184,14 @@ def test_api_template_accesses_retrieve_authenticated_related():
client = APIClient() client = APIClient()
client.force_login(user) client.force_login(user)
template = factories.TemplateFactory(users=[user]) template = factories.TemplateFactory()
access = factories.TemplateAccessFactory(template=template) 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( response = client.get(
f"/api/v1.0/templates/{template.id!s}/accesses/{access.id!s}/", 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() == { assert response.json() == {
"id": str(access.id), "id": str(access.id),
"user": str(access.user.id), "user": str(access.user.id),
"team": "",
"role": access.role, "role": access.role,
"abilities": access.get_abilities(user), "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() 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.""" """Members of a template should not be allowed to create template accesses."""
user = factories.UserFactory() user = factories.UserFactory()
client = APIClient() client = APIClient()
client.force_login(user) 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() other_user = factories.UserFactory()
for role in [role[0] for role in models.RoleChoices.choices]: 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() 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 Administrators of a template should be able to create template accesses
except for the "owner" role. except for the "owner" role.
@@ -266,10 +301,18 @@ def test_api_template_accesses_create_authenticated_administrator():
client = APIClient() client = APIClient()
client.force_login(user) client.force_login(user)
template = factories.TemplateFactory(users=[(user, "administrator")]) template = factories.TemplateFactory()
other_user = factories.UserFactory() 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 # It should not be allowed to create an owner access
response = client.post( response = client.post(
@@ -306,12 +349,14 @@ def test_api_template_accesses_create_authenticated_administrator():
assert response.json() == { assert response.json() == {
"abilities": new_template_access.get_abilities(user), "abilities": new_template_access.get_abilities(user),
"id": str(new_template_access.id), "id": str(new_template_access.id),
"team": "",
"role": role, "role": role,
"user": str(other_user.id), "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. 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 = APIClient()
client.force_login(user) 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() other_user = factories.UserFactory()
role = random.choice([role[0] for role in models.RoleChoices.choices]) 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 assert models.TemplateAccess.objects.filter(user=other_user).count() == 1
new_template_access = models.TemplateAccess.objects.filter(user=other_user).get() new_template_access = models.TemplateAccess.objects.filter(user=other_user).get()
assert response.json() == { assert response.json() == {
"abilities": new_template_access.get_abilities(user),
"id": str(new_template_access.id), "id": str(new_template_access.id),
"role": role,
"user": str(other_user.id), "user": str(other_user.id),
"team": "",
"role": role,
"abilities": new_template_access.get_abilities(user),
} }
def test_api_template_accesses_update_anonymous(): def test_api_template_accesses_update_anonymous():
"""Anonymous users should not be allowed to update a template access.""" """Anonymous users should not be allowed to update a template access."""
access = factories.TemplateAccessFactory() access = factories.UserTemplateAccessFactory()
old_values = serializers.TemplateAccessSerializer(instance=access).data old_values = serializers.TemplateAccessSerializer(instance=access).data
new_values = { new_values = {
@@ -380,7 +434,7 @@ def test_api_template_accesses_update_authenticated_unrelated():
client = APIClient() client = APIClient()
client.force_login(user) client.force_login(user)
access = factories.TemplateAccessFactory() access = factories.UserTemplateAccessFactory()
old_values = serializers.TemplateAccessSerializer(instance=access).data old_values = serializers.TemplateAccessSerializer(instance=access).data
new_values = { new_values = {
@@ -402,15 +456,24 @@ def test_api_template_accesses_update_authenticated_unrelated():
assert updated_values == old_values 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.""" """Members of a template should not be allowed to update its accesses."""
user = factories.UserFactory() user = factories.UserFactory()
client = APIClient() client = APIClient()
client.force_login(user) client.force_login(user)
template = factories.TemplateFactory(users=[(user, "member")]) template = factories.TemplateFactory()
access = factories.TemplateAccessFactory(template=template) 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 old_values = serializers.TemplateAccessSerializer(instance=access).data
new_values = { new_values = {
@@ -432,9 +495,12 @@ def test_api_template_accesses_update_authenticated_member():
assert updated_values == old_values 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. access for this template, as long as they don't try to set the role to owner.
""" """
user = factories.UserFactory() user = factories.UserFactory()
@@ -442,8 +508,18 @@ def test_api_template_accesses_update_administrator_except_owner():
client = APIClient() client = APIClient()
client.force_login(user) client.force_login(user)
template = factories.TemplateFactory(users=[(user, "administrator")]) template = factories.TemplateFactory()
access = factories.TemplateAccessFactory( 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, template=template,
role=random.choice(["administrator", "member"]), role=random.choice(["administrator", "member"]),
) )
@@ -478,7 +554,10 @@ def test_api_template_accesses_update_administrator_except_owner():
assert updated_values == old_values 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 A user who is an administrator in a template, should not be allowed to update
the user access of an "owner" for this template. 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 = APIClient()
client.force_login(user) 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() other_user = factories.UserFactory()
access = factories.TemplateAccessFactory( access = factories.UserTemplateAccessFactory(
template=template, user=other_user, role="owner" template=template, user=other_user, role="owner"
) )
old_values = serializers.TemplateAccessSerializer(instance=access).data 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 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 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. 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 = APIClient()
client.force_login(user) 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() other_user = factories.UserFactory()
access = factories.TemplateAccessFactory( access = factories.UserTemplateAccessFactory(
template=template, template=template,
user=other_user, user=other_user,
role=random.choice(["administrator", "member"]), role=random.choice(["administrator", "member"]),
@@ -557,7 +657,8 @@ def test_api_template_accesses_update_administrator_to_owner():
assert updated_values == old_values 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 who is an owner in a template should be allowed to update
a user access for this template whatever the role. a user access for this template whatever the role.
@@ -567,9 +668,17 @@ def test_api_template_accesses_update_owner():
client = APIClient() client = APIClient()
client.force_login(user) 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() factories.UserFactory()
access = factories.TemplateAccessFactory( access = factories.UserTemplateAccessFactory(
template=template, template=template,
) )
old_values = serializers.TemplateAccessSerializer(instance=access).data old_values = serializers.TemplateAccessSerializer(instance=access).data
@@ -604,7 +713,8 @@ def test_api_template_accesses_update_owner():
assert updated_values == old_values 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 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. 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) client.force_login(user)
template = factories.TemplateFactory() 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 old_values = serializers.TemplateAccessSerializer(instance=access).data
new_role = random.choice(["administrator", "member"]) new_role = random.choice(["administrator", "member"])
@@ -630,7 +749,7 @@ def test_api_template_accesses_update_owner_self():
assert access.role == "owner" assert access.role == "owner"
# Add another owner and it should now work # Add another owner and it should now work
factories.TemplateAccessFactory(template=template, role="owner") factories.UserTemplateAccessFactory(template=template, role="owner")
response = client.put( response = client.put(
f"/api/v1.0/templates/{template.id!s}/accesses/{access.id!s}/", 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(): def test_api_template_accesses_delete_anonymous():
"""Anonymous users should not be allowed to destroy a template access.""" """Anonymous users should not be allowed to destroy a template access."""
access = factories.TemplateAccessFactory() access = factories.UserTemplateAccessFactory()
response = APIClient().delete( response = APIClient().delete(
f"/api/v1.0/templates/{access.template.id!s}/accesses/{access.id!s}/", 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 = APIClient()
client.force_login(user) client.force_login(user)
access = factories.TemplateAccessFactory() access = factories.UserTemplateAccessFactory()
response = client.delete( response = client.delete(
f"/api/v1.0/templates/{access.template.id!s}/accesses/{access.id!s}/", 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 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 Authenticated users should not be allowed to delete a template access for a
template in which they are a simple member. template in which they are a simple member.
@@ -688,8 +808,16 @@ def test_api_template_accesses_delete_member():
client = APIClient() client = APIClient()
client.force_login(user) client.force_login(user)
template = factories.TemplateFactory(users=[(user, "member")]) template = factories.TemplateFactory()
access = factories.TemplateAccessFactory(template=template) 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.count() == 2
assert models.TemplateAccess.objects.filter(user=access.user).exists() 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 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 Users who are administrators in a template should be allowed to delete an access
from the template provided it is not ownership. from the template provided it is not ownership.
@@ -712,8 +843,18 @@ def test_api_template_accesses_delete_administrators_except_owners():
client = APIClient() client = APIClient()
client.force_login(user) client.force_login(user)
template = factories.TemplateFactory(users=[(user, "administrator")]) template = factories.TemplateFactory()
access = factories.TemplateAccessFactory( 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"]) 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 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 Users who are administrators in a template should not be allowed to delete an ownership
access from the template. access from the template.
@@ -738,8 +880,18 @@ def test_api_template_accesses_delete_administrators_owners():
client = APIClient() client = APIClient()
client.force_login(user) client.force_login(user)
template = factories.TemplateFactory(users=[(user, "administrator")]) template = factories.TemplateFactory()
access = factories.TemplateAccessFactory(template=template, role="owner") 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.count() == 2
assert models.TemplateAccess.objects.filter(user=access.user).exists() 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 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 Users should be able to delete the template access of another user
for a template of which they are owner. for a template of which they are owner.
@@ -762,10 +915,16 @@ def test_api_template_accesses_delete_owners():
client = APIClient() client = APIClient()
client.force_login(user) client.force_login(user)
template = factories.TemplateFactory(users=[(user, "owner")]) template = factories.TemplateFactory()
access = factories.TemplateAccessFactory( if via == USER:
template=template, 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.count() == 2
assert models.TemplateAccess.objects.filter(user=access.user).exists() 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 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 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) client.force_login(user)
template = factories.TemplateFactory() 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 assert models.TemplateAccess.objects.count() == 1
response = client.delete( response = client.delete(

View File

@@ -16,7 +16,7 @@ def test_models_template_accesses_str():
The str representation should include user email, template title and role. The str representation should include user email, template title and role.
""" """
user = factories.UserFactory(email="david.bowman@example.com") user = factories.UserFactory(email="david.bowman@example.com")
access = factories.TemplateAccessFactory( access = factories.UserTemplateAccessFactory(
role="member", role="member",
user=user, user=user,
template__title="admins", 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" 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.""" """Template accesses should be unique for a given couple of user and template."""
access = factories.TemplateAccessFactory() access = factories.UserTemplateAccessFactory()
with pytest.raises( with pytest.raises(
ValidationError, 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 # get_abilities
@@ -40,7 +81,7 @@ def test_models_template_accesses_unique():
def test_models_template_access_get_abilities_anonymous(): def test_models_template_access_get_abilities_anonymous():
"""Check abilities returned for an anonymous user.""" """Check abilities returned for an anonymous user."""
access = factories.TemplateAccessFactory() access = factories.UserTemplateAccessFactory()
abilities = access.get_abilities(AnonymousUser()) abilities = access.get_abilities(AnonymousUser())
assert abilities == { assert abilities == {
"destroy": False, "destroy": False,
@@ -52,7 +93,7 @@ def test_models_template_access_get_abilities_anonymous():
def test_models_template_access_get_abilities_authenticated(): def test_models_template_access_get_abilities_authenticated():
"""Check abilities returned for an authenticated user.""" """Check abilities returned for an authenticated user."""
access = factories.TemplateAccessFactory() access = factories.UserTemplateAccessFactory()
user = factories.UserFactory() user = factories.UserFactory()
abilities = access.get_abilities(user) abilities = access.get_abilities(user)
assert abilities == { 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 Check abilities of self access for the owner of a template when
there is more than one owner left. there is more than one owner left.
""" """
access = factories.TemplateAccessFactory(role="owner") access = factories.UserTemplateAccessFactory(role="owner")
factories.TemplateAccessFactory(template=access.template, role="owner") factories.UserTemplateAccessFactory(template=access.template, role="owner")
abilities = access.get_abilities(access.user) abilities = access.get_abilities(access.user)
assert abilities == { assert abilities == {
"destroy": True, "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. 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) abilities = access.get_abilities(access.user)
assert abilities == { assert abilities == {
"destroy": False, "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(): def test_models_template_access_get_abilities_for_owner_of_owner():
"""Check abilities of owner access for the owner of a template.""" """Check abilities of owner access for the owner of a template."""
access = factories.TemplateAccessFactory(role="owner") access = factories.UserTemplateAccessFactory(role="owner")
factories.TemplateAccessFactory(template=access.template) # another one factories.UserTemplateAccessFactory(template=access.template) # another one
user = factories.TemplateAccessFactory(template=access.template, role="owner").user user = factories.UserTemplateAccessFactory(
template=access.template, role="owner"
).user
abilities = access.get_abilities(user) abilities = access.get_abilities(user)
assert abilities == { assert abilities == {
"destroy": True, "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(): def test_models_template_access_get_abilities_for_owner_of_administrator():
"""Check abilities of administrator access for the owner of a template.""" """Check abilities of administrator access for the owner of a template."""
access = factories.TemplateAccessFactory(role="administrator") access = factories.UserTemplateAccessFactory(role="administrator")
factories.TemplateAccessFactory(template=access.template) # another one factories.UserTemplateAccessFactory(template=access.template) # another one
user = factories.TemplateAccessFactory(template=access.template, role="owner").user user = factories.UserTemplateAccessFactory(
template=access.template, role="owner"
).user
abilities = access.get_abilities(user) abilities = access.get_abilities(user)
assert abilities == { assert abilities == {
"destroy": True, "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(): def test_models_template_access_get_abilities_for_owner_of_member():
"""Check abilities of member access for the owner of a template.""" """Check abilities of member access for the owner of a template."""
access = factories.TemplateAccessFactory(role="member") access = factories.UserTemplateAccessFactory(role="member")
factories.TemplateAccessFactory(template=access.template) # another one factories.UserTemplateAccessFactory(template=access.template) # another one
user = factories.TemplateAccessFactory(template=access.template, role="owner").user user = factories.UserTemplateAccessFactory(
template=access.template, role="owner"
).user
abilities = access.get_abilities(user) abilities = access.get_abilities(user)
assert abilities == { assert abilities == {
"destroy": True, "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(): def test_models_template_access_get_abilities_for_administrator_of_owner():
"""Check abilities of owner access for the administrator of a template.""" """Check abilities of owner access for the administrator of a template."""
access = factories.TemplateAccessFactory(role="owner") access = factories.UserTemplateAccessFactory(role="owner")
factories.TemplateAccessFactory(template=access.template) # another one factories.UserTemplateAccessFactory(template=access.template) # another one
user = factories.TemplateAccessFactory( user = factories.UserTemplateAccessFactory(
template=access.template, role="administrator" template=access.template, role="administrator"
).user ).user
abilities = access.get_abilities(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(): def test_models_template_access_get_abilities_for_administrator_of_administrator():
"""Check abilities of administrator access for the administrator of a template.""" """Check abilities of administrator access for the administrator of a template."""
access = factories.TemplateAccessFactory(role="administrator") access = factories.UserTemplateAccessFactory(role="administrator")
factories.TemplateAccessFactory(template=access.template) # another one factories.UserTemplateAccessFactory(template=access.template) # another one
user = factories.TemplateAccessFactory( user = factories.UserTemplateAccessFactory(
template=access.template, role="administrator" template=access.template, role="administrator"
).user ).user
abilities = access.get_abilities(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(): def test_models_template_access_get_abilities_for_administrator_of_member():
"""Check abilities of member access for the administrator of a template.""" """Check abilities of member access for the administrator of a template."""
access = factories.TemplateAccessFactory(role="member") access = factories.UserTemplateAccessFactory(role="member")
factories.TemplateAccessFactory(template=access.template) # another one factories.UserTemplateAccessFactory(template=access.template) # another one
user = factories.TemplateAccessFactory( user = factories.UserTemplateAccessFactory(
template=access.template, role="administrator" template=access.template, role="administrator"
).user ).user
abilities = access.get_abilities(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(): def test_models_template_access_get_abilities_for_member_of_owner():
"""Check abilities of owner access for the member of a template.""" """Check abilities of owner access for the member of a template."""
access = factories.TemplateAccessFactory(role="owner") access = factories.UserTemplateAccessFactory(role="owner")
factories.TemplateAccessFactory(template=access.template) # another one factories.UserTemplateAccessFactory(template=access.template) # another one
user = factories.TemplateAccessFactory(template=access.template, role="member").user user = factories.UserTemplateAccessFactory(
template=access.template, role="member"
).user
abilities = access.get_abilities(user) abilities = access.get_abilities(user)
assert abilities == { assert abilities == {
"destroy": False, "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(): def test_models_template_access_get_abilities_for_member_of_administrator():
"""Check abilities of administrator access for the member of a template.""" """Check abilities of administrator access for the member of a template."""
access = factories.TemplateAccessFactory(role="administrator") access = factories.UserTemplateAccessFactory(role="administrator")
factories.TemplateAccessFactory(template=access.template) # another one factories.UserTemplateAccessFactory(template=access.template) # another one
user = factories.TemplateAccessFactory(template=access.template, role="member").user user = factories.UserTemplateAccessFactory(
template=access.template, role="member"
).user
abilities = access.get_abilities(user) abilities = access.get_abilities(user)
assert abilities == { assert abilities == {
"destroy": False, "destroy": False,
@@ -224,9 +275,11 @@ def test_models_template_access_get_abilities_for_member_of_member_user(
django_assert_num_queries django_assert_num_queries
): ):
"""Check abilities of member access for the member of a template.""" """Check abilities of member access for the member of a template."""
access = factories.TemplateAccessFactory(role="member") access = factories.UserTemplateAccessFactory(role="member")
factories.TemplateAccessFactory(template=access.template) # another one factories.UserTemplateAccessFactory(template=access.template) # another one
user = factories.TemplateAccessFactory(template=access.template, role="member").user user = factories.UserTemplateAccessFactory(
template=access.template, role="member"
).user
with django_assert_num_queries(1): with django_assert_num_queries(1):
abilities = access.get_abilities(user) 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): 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.""" """No query is done if the role is preset, e.g., with a query annotation."""
access = factories.TemplateAccessFactory(role="member") access = factories.UserTemplateAccessFactory(role="member")
user = factories.TemplateAccessFactory(template=access.template, role="member").user user = factories.UserTemplateAccessFactory(
access.user_role = "member" template=access.template, role="member"
).user
access.user_roles = ["member"]
with django_assert_num_queries(0): with django_assert_num_queries(0):
abilities = access.get_abilities(user) abilities = access.get_abilities(user)

View File

@@ -104,7 +104,7 @@ def test_models_templates_get_abilities_authenticated_not_public():
def test_models_templates_get_abilities_owner(): def test_models_templates_get_abilities_owner():
"""Check abilities returned for the owner of a template.""" """Check abilities returned for the owner of a template."""
user = factories.UserFactory() user = factories.UserFactory()
access = factories.TemplateAccessFactory(role="owner", user=user) access = factories.UserTemplateAccessFactory(role="owner", user=user)
abilities = access.template.get_abilities(access.user) abilities = access.template.get_abilities(access.user)
assert abilities == { assert abilities == {
"destroy": True, "destroy": True,
@@ -117,7 +117,7 @@ def test_models_templates_get_abilities_owner():
def test_models_templates_get_abilities_administrator(): def test_models_templates_get_abilities_administrator():
"""Check abilities returned for the administrator of a template.""" """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) abilities = access.template.get_abilities(access.user)
assert abilities == { assert abilities == {
"destroy": False, "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): def test_models_templates_get_abilities_member_user(django_assert_num_queries):
"""Check abilities returned for the member of a template.""" """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): with django_assert_num_queries(1):
abilities = access.template.get_abilities(access.user) 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): 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.""" """No query is done if the role is preset e.g. with query annotation."""
access = factories.TemplateAccessFactory(role="member") access = factories.UserTemplateAccessFactory(role="member")
access.template.user_role = "member" access.template.user_roles = ["member"]
with django_assert_num_queries(0): with django_assert_num_queries(0):
abilities = access.template.get_abilities(access.user) abilities = access.template.get_abilities(access.user)