class EventViewSet(ReadOnlyModelViewSet): """Event Read-Only Viewset""" queryset = Event.objects.all() serializer_class = EventSerializer ordering = ["-created"] search_fields = [ "event_uuid", "user", "action", "app", "context", "client_ip", ] filterset_class = EventsFilter @extend_schema( methods=["GET"], responses={200: EventTopPerUserSerializer(many=True)}, parameters=[ OpenApiParameter( "top_n", type=OpenApiTypes.INT, location=OpenApiParameter.QUERY, required=False, ) ], ) @action(detail=False, methods=["GET"], pagination_class=None) def top_per_user(self, request: Request): """Get the top_n events grouped by user count""" filtered_action = request.query_params.get("action", EventAction.LOGIN) top_n = int(request.query_params.get("top_n", "15")) return Response( get_objects_for_user( request.user, "authentik_events.view_event").filter( action=filtered_action).exclude( context__authorized_application=None).annotate( application=KeyTextTransform( "authorized_application", "context")).annotate( user_pk=KeyTextTransform("pk", "user")). values("application").annotate( counted_events=Count("application")).annotate( unique_users=Count("user_pk", distinct=True)).values( "unique_users", "application", "counted_events").order_by("-counted_events")[:top_n]) @extend_schema(responses={200: TypeCreateSerializer(many=True)}) @action(detail=False, pagination_class=None, filter_backends=[]) def actions(self, request: Request) -> Response: """Get all actions""" data = [] for value, name in EventAction.choices: data.append({ "name": name, "description": "", "component": value, "model_name": "" }) return Response(TypeCreateSerializer(data, many=True).data)
class SourceViewSet( mixins.RetrieveModelMixin, mixins.DestroyModelMixin, mixins.ListModelMixin, GenericViewSet, ): """Source Viewset""" queryset = Source.objects.none() serializer_class = SourceSerializer lookup_field = "slug" def get_queryset(self): return Source.objects.select_subclasses() @swagger_auto_schema(responses={200: TypeCreateSerializer(many=True)}) @action(detail=False, pagination_class=None, filter_backends=[]) def types(self, request: Request) -> Response: """Get all creatable source types""" data = [] for subclass in all_subclasses(self.queryset.model): subclass: Source component = "" if subclass._meta.abstract: component = subclass.__bases__[0]().component else: component = subclass().component # pyright: reportGeneralTypeIssues=false data.append( { "name": subclass._meta.verbose_name, "description": subclass.__doc__, "component": component, "model_name": subclass._meta.model_name, } ) return Response(TypeCreateSerializer(data, many=True).data) @swagger_auto_schema(responses={200: UserSettingSerializer(many=True)}) @action(detail=False, pagination_class=None, filter_backends=[]) def user_settings(self, request: Request) -> Response: """Get all sources the user can configure""" _all_sources: Iterable[Source] = Source.objects.filter( enabled=True ).select_subclasses() matching_sources: list[UserSettingSerializer] = [] for source in _all_sources: user_settings = source.ui_user_settings if not user_settings: continue policy_engine = PolicyEngine(source, request.user, request) policy_engine.build() if not policy_engine.passing: continue source_settings = source.ui_user_settings source_settings.initial_data["object_uid"] = source.slug if not source_settings.is_valid(): LOGGER.warning(source_settings.errors) matching_sources.append(source_settings.validated_data) return Response(matching_sources)
def actions(self, request: Request) -> Response: """Get all actions""" data = [] for value, name in EventAction.choices: data.append( {"name": name, "description": "", "component": value, "model_name": ""} ) return Response(TypeCreateSerializer(data, many=True).data)
class StageViewSet( mixins.RetrieveModelMixin, mixins.DestroyModelMixin, UsedByMixin, mixins.ListModelMixin, GenericViewSet, ): """Stage Viewset""" queryset = Stage.objects.all().select_related("flow_set") serializer_class = StageSerializer search_fields = ["name"] filterset_fields = ["name"] def get_queryset(self): # pragma: no cover return Stage.objects.select_subclasses() @extend_schema(responses={200: TypeCreateSerializer(many=True)}) @action(detail=False, pagination_class=None, filter_backends=[]) def types(self, request: Request) -> Response: """Get all creatable stage types""" data = [] for subclass in all_subclasses(self.queryset.model, False): subclass: Stage data.append({ "name": subclass._meta.verbose_name, "description": subclass.__doc__, "component": subclass().component, "model_name": subclass._meta.model_name, }) data = sorted(data, key=lambda x: x["name"]) return Response(TypeCreateSerializer(data, many=True).data) @extend_schema(responses={200: UserSettingSerializer(many=True)}) @action(detail=False, pagination_class=None, filter_backends=[]) def user_settings(self, request: Request) -> Response: """Get all stages the user can configure""" stages = [] for configurable_stage in all_subclasses(ConfigurableStage): stages += list(configurable_stage.objects.all().order_by("name")) matching_stages: list[dict] = [] for stage in stages: user_settings = stage.ui_user_settings() if not user_settings: continue user_settings.initial_data["object_uid"] = str(stage.pk) if hasattr(stage, "configure_flow") and stage.configure_flow: user_settings.initial_data["configure_url"] = reverse( "authentik_flows:configure", kwargs={"stage_uuid": stage.pk}, ) if not user_settings.is_valid(): LOGGER.warning(user_settings.errors) matching_stages.append(user_settings.initial_data) return Response(matching_stages)
def templates(self, request: Request) -> Response: """Get all available templates, including custom templates""" choices = [] for value, label in get_template_choices(): choices.append({ "name": value, "description": label, "component": "", "model_name": "", }) return Response(TypeCreateSerializer(choices, many=True).data)
def types(self, request: Request) -> Response: """Get all creatable policy types""" data = [] for subclass in all_subclasses(self.queryset.model): subclass: Policy data.append({ "name": subclass._meta.verbose_name, "description": subclass.__doc__, "component": subclass().component, "model_name": subclass._meta.model_name, }) return Response(TypeCreateSerializer(data, many=True).data)
def types(self, request: Request) -> Response: """Get all creatable property-mapping types""" data = [] for subclass in all_subclasses(self.queryset.model): subclass: PropertyMapping data.append({ "name": subclass._meta.verbose_name, "description": subclass.__doc__, # pyright: reportGeneralTypeIssues=false "component": subclass().component, "model_name": subclass._meta.model_name, }) return Response(TypeCreateSerializer(data, many=True).data)
def types(self, request: Request) -> Response: """Get all creatable service connection types""" data = [] for subclass in all_subclasses(self.queryset.model): subclass: OutpostServiceConnection # pyright: reportGeneralTypeIssues=false data.append({ "name": subclass._meta.verbose_name, "description": subclass.__doc__, "component": subclass().component, "model_name": subclass._meta.model_name, }) return Response(TypeCreateSerializer(data, many=True).data)
def types(self, request: Request) -> Response: """Get all creatable stage types""" data = [] for subclass in all_subclasses(self.queryset.model, False): subclass: Stage data.append({ "name": subclass._meta.verbose_name, "description": subclass.__doc__, "component": subclass().component, "model_name": subclass._meta.model_name, }) data = sorted(data, key=lambda x: x["name"]) return Response(TypeCreateSerializer(data, many=True).data)
class ProviderViewSet( mixins.RetrieveModelMixin, mixins.DestroyModelMixin, UsedByMixin, mixins.ListModelMixin, GenericViewSet, ): """Provider Viewset""" queryset = Provider.objects.none() serializer_class = ProviderSerializer filterset_fields = { "application": ["isnull"], } search_fields = [ "name", "application__name", ] def get_queryset(self): # pragma: no cover return Provider.objects.select_subclasses() @extend_schema(responses={200: TypeCreateSerializer(many=True)}) @action(detail=False, pagination_class=None, filter_backends=[]) def types(self, request: Request) -> Response: """Get all creatable provider types""" data = [] for subclass in all_subclasses(self.queryset.model): subclass: Provider data.append({ "name": subclass._meta.verbose_name, "description": subclass.__doc__, "component": subclass().component, "model_name": subclass._meta.model_name, }) data.append({ "name": _("SAML Provider from Metadata"), "description": _("Create a SAML Provider by importing its Metadata."), "component": "ak-provider-saml-import-form", "model_name": "", }) return Response(TypeCreateSerializer(data, many=True).data)
def types(self, request: Request) -> Response: """Get all creatable source types""" data = [] for subclass in all_subclasses(self.queryset.model): subclass: Source component = "" if subclass._meta.abstract: component = subclass.__bases__[0]().component else: component = subclass().component # pyright: reportGeneralTypeIssues=false data.append({ "name": subclass._meta.verbose_name, "description": subclass.__doc__, "component": component, "model_name": subclass._meta.model_name, }) return Response(TypeCreateSerializer(data, many=True).data)
def types(self, request: Request) -> Response: """Get all creatable provider types""" data = [] for subclass in all_subclasses(self.queryset.model): subclass: Provider data.append({ "name": subclass._meta.verbose_name, "description": subclass.__doc__, "component": subclass().component, }) data.append({ "name": _("SAML Provider from Metadata"), "description": _("Create a SAML Provider by importing its Metadata."), "component": "ak-provider-saml-import-form", }) return Response(TypeCreateSerializer(data, many=True).data)
class EmailStageViewSet(ModelViewSet): """EmailStage Viewset""" queryset = EmailStage.objects.all() serializer_class = EmailStageSerializer @extend_schema(responses={200: TypeCreateSerializer(many=True)}) @action(detail=False, pagination_class=None, filter_backends=[]) def templates(self, request: Request) -> Response: """Get all available templates, including custom templates""" choices = [] for value, label in get_template_choices(): choices.append({ "name": value, "description": label, "component": "", "model_name": "", }) return Response(TypeCreateSerializer(choices, many=True).data)
class ServiceConnectionViewSet( mixins.RetrieveModelMixin, mixins.DestroyModelMixin, UsedByMixin, mixins.ListModelMixin, GenericViewSet, ): """ServiceConnection Viewset""" queryset = OutpostServiceConnection.objects.select_subclasses() serializer_class = ServiceConnectionSerializer search_fields = ["name"] filterset_fields = ["name"] @extend_schema(responses={200: TypeCreateSerializer(many=True)}) @action(detail=False, pagination_class=None, filter_backends=[]) def types(self, request: Request) -> Response: """Get all creatable service connection types""" data = [] for subclass in all_subclasses(self.queryset.model): subclass: OutpostServiceConnection # pyright: reportGeneralTypeIssues=false data.append({ "name": subclass._meta.verbose_name, "description": subclass.__doc__, "component": subclass().component, "model_name": subclass._meta.model_name, }) return Response(TypeCreateSerializer(data, many=True).data) @extend_schema( responses={200: ServiceConnectionStateSerializer(many=False)}) @action(detail=True, pagination_class=None, filter_backends=[]) # pylint: disable=unused-argument, invalid-name def state(self, request: Request, pk: str) -> Response: """Get the service connection's state""" connection = self.get_object() return Response(asdict(connection.state))
class EmailStageViewSet(UsedByMixin, ModelViewSet): """EmailStage Viewset""" queryset = EmailStage.objects.all() serializer_class = EmailStageSerializer filterset_fields = [ "name", "use_global_settings", "host", "port", "username", "use_tls", "use_ssl", "timeout", "from_address", "token_expiry", "subject", "template", "activate_user_on_success", ] ordering = ["name"] @extend_schema(responses={200: TypeCreateSerializer(many=True)}) @action(detail=False, pagination_class=None, filter_backends=[]) def templates(self, request: Request) -> Response: """Get all available templates, including custom templates""" choices = [] for value, label in get_template_choices(): choices.append( { "name": value, "description": label, "component": "", "model_name": "", } ) return Response(TypeCreateSerializer(choices, many=True).data)
class PropertyMappingViewSet( mixins.RetrieveModelMixin, mixins.DestroyModelMixin, UsedByMixin, mixins.ListModelMixin, GenericViewSet, ): """PropertyMapping Viewset""" queryset = PropertyMapping.objects.none() serializer_class = PropertyMappingSerializer search_fields = [ "name", ] filterset_fields = {"managed": ["isnull"]} ordering = ["name"] def get_queryset(self): # pragma: no cover return PropertyMapping.objects.select_subclasses() @extend_schema(responses={200: TypeCreateSerializer(many=True)}) @action(detail=False, pagination_class=None, filter_backends=[]) def types(self, request: Request) -> Response: """Get all creatable property-mapping types""" data = [] for subclass in all_subclasses(self.queryset.model): subclass: PropertyMapping data.append({ "name": subclass._meta.verbose_name, "description": subclass.__doc__, # pyright: reportGeneralTypeIssues=false "component": subclass().component, "model_name": subclass._meta.model_name, }) return Response(TypeCreateSerializer(data, many=True).data) @permission_required("authentik_core.view_propertymapping") @extend_schema( request=PolicyTestSerializer(), responses={ 200: PropertyMappingTestResultSerializer, 400: OpenApiResponse(description="Invalid parameters"), }, parameters=[ OpenApiParameter( name="format_result", location=OpenApiParameter.QUERY, type=OpenApiTypes.BOOL, ) ], ) @action(detail=True, pagination_class=None, filter_backends=[], methods=["POST"]) # pylint: disable=unused-argument, invalid-name def test(self, request: Request, pk: str) -> Response: """Test Property Mapping""" mapping: PropertyMapping = self.get_object() test_params = PolicyTestSerializer(data=request.data) if not test_params.is_valid(): return Response(test_params.errors, status=400) format_result = str(request.GET.get("format_result", "false")).lower() == "true" # User permission check, only allow mapping testing for users that are readable users = get_objects_for_user( request.user, "authentik_core.view_user").filter( pk=test_params.validated_data["user"].pk) if not users.exists(): raise PermissionDenied() response_data = {"successful": True, "result": ""} try: result = mapping.evaluate( users.first(), self.request, **test_params.validated_data.get("context", {}), ) response_data["result"] = dumps( result, indent=(4 if format_result else None)) except Exception as exc: # pylint: disable=broad-except response_data["result"] = str(exc) response_data["successful"] = False response = PropertyMappingTestResultSerializer(response_data) return Response(response.data)
class PolicyViewSet( mixins.RetrieveModelMixin, mixins.DestroyModelMixin, mixins.ListModelMixin, GenericViewSet, ): """Policy Viewset""" queryset = Policy.objects.all() serializer_class = PolicySerializer filterset_fields = { "bindings": ["isnull"], "promptstage": ["isnull"], } search_fields = ["name"] def get_queryset(self): return Policy.objects.select_subclasses().prefetch_related( "bindings", "promptstage_set") @extend_schema(responses={200: TypeCreateSerializer(many=True)}) @action(detail=False, pagination_class=None, filter_backends=[]) def types(self, request: Request) -> Response: """Get all creatable policy types""" data = [] for subclass in all_subclasses(self.queryset.model): subclass: Policy data.append({ "name": subclass._meta.verbose_name, "description": subclass.__doc__, "component": subclass().component, "model_name": subclass._meta.model_name, }) return Response(TypeCreateSerializer(data, many=True).data) @permission_required(None, ["authentik_policies.view_policy_cache"]) @extend_schema(responses={200: CacheSerializer(many=False)}) @action(detail=False, pagination_class=None, filter_backends=[]) def cache_info(self, request: Request) -> Response: """Info about cached policies""" return Response(data={"count": len(cache.keys("policy_*"))}) @permission_required(None, ["authentik_policies.clear_policy_cache"]) @extend_schema( request=OpenApiTypes.NONE, responses={ 204: OpenApiResponse(description="Successfully cleared cache"), 400: OpenApiResponse(description="Bad request"), }, ) @action(detail=False, methods=["POST"]) def cache_clear(self, request: Request) -> Response: """Clear policy cache""" keys = cache.keys("policy_*") cache.delete_many(keys) LOGGER.debug("Cleared Policy cache", keys=len(keys)) # Also delete user application cache keys = cache.keys(user_app_cache_key("*")) cache.delete_many(keys) return Response(status=204) @permission_required("authentik_policies.view_policy") @extend_schema( request=PolicyTestSerializer(), responses={ 200: PolicyTestResultSerializer(), 400: OpenApiResponse(description="Invalid parameters"), }, ) @action(detail=True, pagination_class=None, filter_backends=[], methods=["POST"]) # pylint: disable=unused-argument, invalid-name def test(self, request: Request, pk: str) -> Response: """Test policy""" policy = self.get_object() test_params = PolicyTestSerializer(data=request.data) if not test_params.is_valid(): return Response(test_params.errors, status=400) # User permission check, only allow policy testing for users that are readable users = get_objects_for_user( request.user, "authentik_core.view_user").filter( pk=test_params.validated_data["user"].pk) if not users.exists(): raise PermissionDenied() p_request = PolicyRequest(users.first()) p_request.debug = True p_request.set_http_request(self.request) p_request.context = test_params.validated_data.get("context", {}) proc = PolicyProcess(PolicyBinding(policy=policy), p_request, None) result = proc.execute() response = PolicyTestResultSerializer(result) return Response(response.data)