✨(models/api) allow inviting external users to a document by their email
We want to be able to share a document with a person even if this person does not have an account in impress yet. This code is ported from https://github.com/numerique-gouv/people.
This commit is contained in:
committed by
Samuel Paccoud
parent
125284456f
commit
515b686795
@@ -66,7 +66,6 @@ class BaseAccessSerializer(serializers.ModelSerializer):
|
||||
|
||||
# Create
|
||||
else:
|
||||
teams = user.get_teams()
|
||||
try:
|
||||
resource_id = self.context["resource_id"]
|
||||
except KeyError as exc:
|
||||
@@ -74,6 +73,7 @@ class BaseAccessSerializer(serializers.ModelSerializer):
|
||||
"You must set a resource ID in kwargs to create a new access."
|
||||
) from exc
|
||||
|
||||
teams = user.get_teams()
|
||||
if not self.Meta.model.objects.filter( # pylint: disable=no-member
|
||||
Q(user=user) | Q(team__in=teams),
|
||||
role__in=[models.RoleChoices.OWNER, models.RoleChoices.ADMIN],
|
||||
@@ -172,3 +172,82 @@ class DocumentGenerationSerializer(serializers.Serializer):
|
||||
required=False,
|
||||
default="html",
|
||||
)
|
||||
|
||||
|
||||
class InvitationSerializer(serializers.ModelSerializer):
|
||||
"""Serialize invitations."""
|
||||
|
||||
abilities = serializers.SerializerMethodField(read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = models.Invitation
|
||||
fields = [
|
||||
"id",
|
||||
"abilities",
|
||||
"created_at",
|
||||
"email",
|
||||
"document",
|
||||
"role",
|
||||
"issuer",
|
||||
"is_expired",
|
||||
]
|
||||
read_only_fields = [
|
||||
"id",
|
||||
"abilities",
|
||||
"created_at",
|
||||
"document",
|
||||
"issuer",
|
||||
"is_expired",
|
||||
]
|
||||
|
||||
def get_abilities(self, invitation) -> dict:
|
||||
"""Return abilities of the logged-in user on the instance."""
|
||||
request = self.context.get("request")
|
||||
if request:
|
||||
return invitation.get_abilities(request.user)
|
||||
return {}
|
||||
|
||||
def validate(self, attrs):
|
||||
"""Validate and restrict invitation to new user based on email."""
|
||||
|
||||
request = self.context.get("request")
|
||||
user = getattr(request, "user", None)
|
||||
role = attrs.get("role")
|
||||
|
||||
try:
|
||||
document_id = self.context["resource_id"]
|
||||
except KeyError as exc:
|
||||
raise exceptions.ValidationError(
|
||||
"You must set a document ID in kwargs to create a new document invitation."
|
||||
) from exc
|
||||
|
||||
if not user and user.is_authenticated:
|
||||
raise exceptions.PermissionDenied(
|
||||
"Anonymous users are not allowed to create invitations."
|
||||
)
|
||||
|
||||
teams = user.get_teams()
|
||||
if not models.DocumentAccess.objects.filter(
|
||||
Q(user=user) | Q(team__in=teams),
|
||||
document=document_id,
|
||||
role__in=[models.RoleChoices.OWNER, models.RoleChoices.ADMIN],
|
||||
).exists():
|
||||
raise exceptions.PermissionDenied(
|
||||
"You are not allowed to manage invitations for this document."
|
||||
)
|
||||
|
||||
if (
|
||||
role == models.RoleChoices.OWNER
|
||||
and not models.DocumentAccess.objects.filter(
|
||||
Q(user=user) | Q(team__in=teams),
|
||||
document=document_id,
|
||||
role=models.RoleChoices.OWNER,
|
||||
).exists()
|
||||
):
|
||||
raise exceptions.PermissionDenied(
|
||||
"Only owners of a document can invite other users as owners."
|
||||
)
|
||||
|
||||
attrs["document_id"] = document_id
|
||||
attrs["issuer"] = user
|
||||
return attrs
|
||||
|
||||
@@ -479,3 +479,78 @@ class TemplateAccessViewSet(
|
||||
queryset = models.TemplateAccess.objects.select_related("user").all()
|
||||
resource_field_name = "template"
|
||||
serializer_class = serializers.TemplateAccessSerializer
|
||||
|
||||
|
||||
class InvitationViewset(
|
||||
mixins.CreateModelMixin,
|
||||
mixins.ListModelMixin,
|
||||
mixins.RetrieveModelMixin,
|
||||
mixins.DestroyModelMixin,
|
||||
viewsets.GenericViewSet,
|
||||
):
|
||||
"""API ViewSet for user invitations to document.
|
||||
|
||||
GET /api/v1.0/documents/<document_id>/invitations/:<invitation_id>/
|
||||
Return list of invitations related to that document or one
|
||||
document access if an id is provided.
|
||||
|
||||
POST /api/v1.0/documents/<document_id>/invitations/ with expected data:
|
||||
- email: str
|
||||
- role: str [owner|admin|member]
|
||||
Return newly created invitation (issuer and document are automatically set)
|
||||
|
||||
PUT / PATCH : Not permitted. Instead of updating your invitation,
|
||||
delete and create a new one.
|
||||
|
||||
DELETE /api/v1.0/documents/<document_id>/invitations/<invitation_id>/
|
||||
Delete targeted invitation
|
||||
"""
|
||||
|
||||
lookup_field = "id"
|
||||
pagination_class = Pagination
|
||||
permission_classes = [permissions.IsAuthenticated, permissions.AccessPermission]
|
||||
queryset = (
|
||||
models.Invitation.objects.all()
|
||||
.select_related("document")
|
||||
.order_by("-created_at")
|
||||
)
|
||||
serializer_class = serializers.InvitationSerializer
|
||||
|
||||
def get_serializer_context(self):
|
||||
"""Extra context provided to the serializer class."""
|
||||
context = super().get_serializer_context()
|
||||
context["resource_id"] = self.kwargs["resource_id"]
|
||||
return context
|
||||
|
||||
def get_queryset(self):
|
||||
"""Return the queryset according to the action."""
|
||||
queryset = super().get_queryset()
|
||||
queryset = queryset.filter(document=self.kwargs["resource_id"])
|
||||
|
||||
if self.action == "list":
|
||||
user = self.request.user
|
||||
teams = user.get_teams()
|
||||
|
||||
# Determine which role the logged-in user has in the document
|
||||
user_roles_query = (
|
||||
models.DocumentAccess.objects.filter(
|
||||
Q(user=user) | Q(team__in=teams),
|
||||
document=self.kwargs["resource_id"],
|
||||
)
|
||||
.values("document")
|
||||
.annotate(roles_array=ArrayAgg("role"))
|
||||
.values("roles_array")
|
||||
)
|
||||
|
||||
queryset = (
|
||||
# The logged-in user should be part of a document to see its accesses
|
||||
queryset.filter(
|
||||
Q(document__accesses__user=user)
|
||||
| Q(document__accesses__team__in=teams),
|
||||
)
|
||||
# Abilities are computed based on logged-in user's role and
|
||||
# the user role on each document access
|
||||
.annotate(user_roles=Subquery(user_roles_query))
|
||||
.distinct()
|
||||
)
|
||||
return queryset
|
||||
|
||||
Reference in New Issue
Block a user