authentik.stages.invitation.api
Invitation Stage API Views
1"""Invitation Stage API Views""" 2 3from django.http import HttpRequest 4from django_filters.filters import BooleanFilter 5from django_filters.filterset import FilterSet 6from drf_spectacular.utils import extend_schema 7from guardian.shortcuts import get_anonymous_user 8from rest_framework.decorators import action 9from rest_framework.request import Request 10from rest_framework.response import Response 11from rest_framework.serializers import ( 12 CharField, 13 ListField, 14 PrimaryKeyRelatedField, 15 Serializer, 16) 17from rest_framework.viewsets import ModelViewSet 18from structlog.stdlib import get_logger 19 20from authentik.blueprints.v1.importer import SERIALIZER_CONTEXT_BLUEPRINT 21from authentik.core.api.groups import PartialUserSerializer 22from authentik.core.api.used_by import UsedByMixin 23from authentik.core.api.utils import JSONDictField, ModelSerializer 24from authentik.core.models import User 25from authentik.flows.api.flows import FlowSerializer 26from authentik.flows.api.stages import StageSerializer 27from authentik.lib.expression.evaluator import BaseEvaluator 28from authentik.stages.invitation.models import Invitation, InvitationStage 29 30LOGGER = get_logger() 31 32 33class InvitationStageSerializer(StageSerializer): 34 """InvitationStage Serializer""" 35 36 class Meta: 37 model = InvitationStage 38 fields = StageSerializer.Meta.fields + [ 39 "continue_flow_without_invitation", 40 ] 41 42 43class InvitationStageFilter(FilterSet): 44 """invitation filter""" 45 46 no_flows = BooleanFilter("flow", "isnull") 47 48 class Meta: 49 model = InvitationStage 50 fields = ["name", "no_flows", "continue_flow_without_invitation", "stage_uuid"] 51 52 53class InvitationStageViewSet(UsedByMixin, ModelViewSet): 54 """InvitationStage Viewset""" 55 56 queryset = InvitationStage.objects.all() 57 serializer_class = InvitationStageSerializer 58 filterset_class = InvitationStageFilter 59 ordering = ["name"] 60 search_fields = ["name"] 61 62 63class InvitationSerializer(ModelSerializer): 64 """Invitation Serializer""" 65 66 created_by = PartialUserSerializer(read_only=True) 67 fixed_data = JSONDictField(required=False) 68 flow_obj = FlowSerializer(read_only=True, required=False, source="flow") 69 70 def __init__(self, *args, **kwargs): 71 super().__init__(*args, **kwargs) 72 if SERIALIZER_CONTEXT_BLUEPRINT in self.context: 73 self.fields["created_by"] = PrimaryKeyRelatedField( 74 queryset=User.objects.all(), 75 required=False, 76 allow_null=True, 77 default=get_anonymous_user(), 78 ) 79 80 class Meta: 81 model = Invitation 82 fields = [ 83 "pk", 84 "name", 85 "expires", 86 "fixed_data", 87 "created_by", 88 "single_use", 89 "flow", 90 "flow_obj", 91 ] 92 93 94class InvitationSendEmailSerializer(Serializer): 95 """Serializer for sending invitation emails""" 96 97 email_addresses = ListField(required=True) 98 cc_addresses = ListField(required=False) 99 bcc_addresses = ListField(required=False) 100 template = CharField(required=False, default="invitation") 101 102 103class InvitationViewSet(UsedByMixin, ModelViewSet): 104 """Invitation Viewset""" 105 106 queryset = Invitation.objects.including_expired().all() 107 serializer_class = InvitationSerializer 108 ordering = ["-expires"] 109 search_fields = ["name", "created_by__username", "expires", "flow__slug"] 110 filterset_fields = ["name", "created_by__username", "expires", "flow__slug"] 111 112 def perform_create(self, serializer: InvitationSerializer): 113 kwargs = {} 114 if SERIALIZER_CONTEXT_BLUEPRINT not in serializer.context: 115 kwargs["created_by"] = self.request.user 116 serializer.save(**kwargs) 117 118 @extend_schema( 119 request=InvitationSendEmailSerializer, 120 responses={204: None}, 121 ) 122 @action( 123 detail=True, 124 methods=["post"], 125 serializer_class=InvitationSendEmailSerializer, 126 ) 127 def send_email(self, request: Request, pk: str) -> Response: 128 """Send invitation link via email to one or more addresses""" 129 invitation = self.get_object() 130 email_addresses = request.data.get("email_addresses", []) 131 cc_addresses = request.data.get("cc_addresses", []) 132 bcc_addresses = request.data.get("bcc_addresses", []) 133 template = request.data.get("template", "email/invitation.html") 134 135 if not email_addresses: 136 return Response({"error": "No email addresses provided"}, status=400) 137 138 # Build the invitation link 139 http_request: HttpRequest = request._request 140 protocol = "https" if http_request.is_secure() else "http" 141 host = http_request.get_host() 142 143 # Determine the flow slug 144 flow_slug = invitation.flow.slug if invitation.flow else None 145 if not flow_slug: 146 return Response({"error": "Invitation has no associated flow"}, status=400) 147 148 invitation_link = f"{protocol}://{host}/if/flow/{flow_slug}/?itoken={invitation.pk}" 149 150 # Prepare template context 151 context = { 152 "url": invitation_link, 153 "expires": invitation.expires, 154 "host": host, 155 } 156 157 # Prepare email content 158 subject = f"You have been invited to {host}" 159 160 # Queue emails for sending via async ak_send_email 161 evaluator = BaseEvaluator() 162 163 for email in email_addresses: 164 evaluator.expr_send_email( 165 address=email, 166 subject=subject, 167 template=template, 168 context=context, 169 stage=None, 170 cc=cc_addresses if cc_addresses else None, 171 bcc=bcc_addresses if bcc_addresses else None, 172 ) 173 174 return Response(status=204)
LOGGER =
<BoundLoggerLazyProxy(logger=None, wrapper_class=None, processors=None, context_class=None, initial_values={}, logger_factory_args=())>
34class InvitationStageSerializer(StageSerializer): 35 """InvitationStage Serializer""" 36 37 class Meta: 38 model = InvitationStage 39 fields = StageSerializer.Meta.fields + [ 40 "continue_flow_without_invitation", 41 ]
InvitationStage Serializer
Inherited Members
class
InvitationStageSerializer.Meta:
37 class Meta: 38 model = InvitationStage 39 fields = StageSerializer.Meta.fields + [ 40 "continue_flow_without_invitation", 41 ]
model =
<class 'authentik.stages.invitation.models.InvitationStage'>
class
InvitationStageFilter(django_filters.filterset.FilterSet):
44class InvitationStageFilter(FilterSet): 45 """invitation filter""" 46 47 no_flows = BooleanFilter("flow", "isnull") 48 49 class Meta: 50 model = InvitationStage 51 fields = ["name", "no_flows", "continue_flow_without_invitation", "stage_uuid"]
invitation filter
class
InvitationStageFilter.Meta:
49 class Meta: 50 model = InvitationStage 51 fields = ["name", "no_flows", "continue_flow_without_invitation", "stage_uuid"]
model =
<class 'authentik.stages.invitation.models.InvitationStage'>
class
InvitationStageViewSet(authentik.core.api.used_by.UsedByMixin, rest_framework.viewsets.ModelViewSet):
54class InvitationStageViewSet(UsedByMixin, ModelViewSet): 55 """InvitationStage Viewset""" 56 57 queryset = InvitationStage.objects.all() 58 serializer_class = InvitationStageSerializer 59 filterset_class = InvitationStageFilter 60 ordering = ["name"] 61 search_fields = ["name"]
InvitationStage Viewset
serializer_class =
<class 'InvitationStageSerializer'>
filterset_class =
<class 'InvitationStageFilter'>
Inherited Members
64class InvitationSerializer(ModelSerializer): 65 """Invitation Serializer""" 66 67 created_by = PartialUserSerializer(read_only=True) 68 fixed_data = JSONDictField(required=False) 69 flow_obj = FlowSerializer(read_only=True, required=False, source="flow") 70 71 def __init__(self, *args, **kwargs): 72 super().__init__(*args, **kwargs) 73 if SERIALIZER_CONTEXT_BLUEPRINT in self.context: 74 self.fields["created_by"] = PrimaryKeyRelatedField( 75 queryset=User.objects.all(), 76 required=False, 77 allow_null=True, 78 default=get_anonymous_user(), 79 ) 80 81 class Meta: 82 model = Invitation 83 fields = [ 84 "pk", 85 "name", 86 "expires", 87 "fixed_data", 88 "created_by", 89 "single_use", 90 "flow", 91 "flow_obj", 92 ]
Invitation Serializer
Inherited Members
class
InvitationSerializer.Meta:
81 class Meta: 82 model = Invitation 83 fields = [ 84 "pk", 85 "name", 86 "expires", 87 "fixed_data", 88 "created_by", 89 "single_use", 90 "flow", 91 "flow_obj", 92 ]
model =
<class 'authentik.stages.invitation.models.Invitation'>
class
InvitationSendEmailSerializer(rest_framework.serializers.Serializer):
95class InvitationSendEmailSerializer(Serializer): 96 """Serializer for sending invitation emails""" 97 98 email_addresses = ListField(required=True) 99 cc_addresses = ListField(required=False) 100 bcc_addresses = ListField(required=False) 101 template = CharField(required=False, default="invitation")
Serializer for sending invitation emails
class
InvitationViewSet(authentik.core.api.used_by.UsedByMixin, rest_framework.viewsets.ModelViewSet):
104class InvitationViewSet(UsedByMixin, ModelViewSet): 105 """Invitation Viewset""" 106 107 queryset = Invitation.objects.including_expired().all() 108 serializer_class = InvitationSerializer 109 ordering = ["-expires"] 110 search_fields = ["name", "created_by__username", "expires", "flow__slug"] 111 filterset_fields = ["name", "created_by__username", "expires", "flow__slug"] 112 113 def perform_create(self, serializer: InvitationSerializer): 114 kwargs = {} 115 if SERIALIZER_CONTEXT_BLUEPRINT not in serializer.context: 116 kwargs["created_by"] = self.request.user 117 serializer.save(**kwargs) 118 119 @extend_schema( 120 request=InvitationSendEmailSerializer, 121 responses={204: None}, 122 ) 123 @action( 124 detail=True, 125 methods=["post"], 126 serializer_class=InvitationSendEmailSerializer, 127 ) 128 def send_email(self, request: Request, pk: str) -> Response: 129 """Send invitation link via email to one or more addresses""" 130 invitation = self.get_object() 131 email_addresses = request.data.get("email_addresses", []) 132 cc_addresses = request.data.get("cc_addresses", []) 133 bcc_addresses = request.data.get("bcc_addresses", []) 134 template = request.data.get("template", "email/invitation.html") 135 136 if not email_addresses: 137 return Response({"error": "No email addresses provided"}, status=400) 138 139 # Build the invitation link 140 http_request: HttpRequest = request._request 141 protocol = "https" if http_request.is_secure() else "http" 142 host = http_request.get_host() 143 144 # Determine the flow slug 145 flow_slug = invitation.flow.slug if invitation.flow else None 146 if not flow_slug: 147 return Response({"error": "Invitation has no associated flow"}, status=400) 148 149 invitation_link = f"{protocol}://{host}/if/flow/{flow_slug}/?itoken={invitation.pk}" 150 151 # Prepare template context 152 context = { 153 "url": invitation_link, 154 "expires": invitation.expires, 155 "host": host, 156 } 157 158 # Prepare email content 159 subject = f"You have been invited to {host}" 160 161 # Queue emails for sending via async ak_send_email 162 evaluator = BaseEvaluator() 163 164 for email in email_addresses: 165 evaluator.expr_send_email( 166 address=email, 167 subject=subject, 168 template=template, 169 context=context, 170 stage=None, 171 cc=cc_addresses if cc_addresses else None, 172 bcc=bcc_addresses if bcc_addresses else None, 173 ) 174 175 return Response(status=204)
Invitation Viewset
serializer_class =
<class 'InvitationSerializer'>
@extend_schema(request=InvitationSendEmailSerializer, responses={204: None})
@action(detail=True, methods=['post'], serializer_class=InvitationSendEmailSerializer)
def
send_email( self, request: rest_framework.request.Request, pk: str) -> rest_framework.response.Response:
119 @extend_schema( 120 request=InvitationSendEmailSerializer, 121 responses={204: None}, 122 ) 123 @action( 124 detail=True, 125 methods=["post"], 126 serializer_class=InvitationSendEmailSerializer, 127 ) 128 def send_email(self, request: Request, pk: str) -> Response: 129 """Send invitation link via email to one or more addresses""" 130 invitation = self.get_object() 131 email_addresses = request.data.get("email_addresses", []) 132 cc_addresses = request.data.get("cc_addresses", []) 133 bcc_addresses = request.data.get("bcc_addresses", []) 134 template = request.data.get("template", "email/invitation.html") 135 136 if not email_addresses: 137 return Response({"error": "No email addresses provided"}, status=400) 138 139 # Build the invitation link 140 http_request: HttpRequest = request._request 141 protocol = "https" if http_request.is_secure() else "http" 142 host = http_request.get_host() 143 144 # Determine the flow slug 145 flow_slug = invitation.flow.slug if invitation.flow else None 146 if not flow_slug: 147 return Response({"error": "Invitation has no associated flow"}, status=400) 148 149 invitation_link = f"{protocol}://{host}/if/flow/{flow_slug}/?itoken={invitation.pk}" 150 151 # Prepare template context 152 context = { 153 "url": invitation_link, 154 "expires": invitation.expires, 155 "host": host, 156 } 157 158 # Prepare email content 159 subject = f"You have been invited to {host}" 160 161 # Queue emails for sending via async ak_send_email 162 evaluator = BaseEvaluator() 163 164 for email in email_addresses: 165 evaluator.expr_send_email( 166 address=email, 167 subject=subject, 168 template=template, 169 context=context, 170 stage=None, 171 cc=cc_addresses if cc_addresses else None, 172 bcc=bcc_addresses if bcc_addresses else None, 173 ) 174 175 return Response(status=204)
Send invitation link via email to one or more addresses