From f4e868210d65d6ef4e2e0b02afadf19ca6468eb0 Mon Sep 17 00:00:00 2001 From: "authentik-automation[bot]" <135050075+authentik-automation[bot]@users.noreply.github.com> Date: Tue, 12 May 2026 20:14:12 +0200 Subject: [PATCH] internal: Automated internal backport: GHSA-973w-j457-rp2m.sec.patch to authentik-main (#22305) Automated internal backport of patch GHSA-973w-j457-rp2m.sec.patch to authentik-main Signed-off-by: Jens Langhammer Co-authored-by: authentik-automation[bot] <135050075+authentik-automation[bot]@users.noreply.github.com> --- authentik/api/authentication.py | 33 +++++++++++-------- .../connectors/agent/api/connectors.py | 23 +++++++++---- authentik/endpoints/connectors/agent/auth.py | 15 +++++++-- .../connectors/agent/tests/test_agent_api.py | 14 ++++++++ .../connectors/agent/api/connectors.py | 9 ++++- schema.yml | 8 +++++ 6 files changed, 80 insertions(+), 22 deletions(-) diff --git a/authentik/api/authentication.py b/authentik/api/authentication.py index 0ee0509315..d1ea694103 100644 --- a/authentik/api/authentication.py +++ b/authentik/api/authentication.py @@ -42,11 +42,29 @@ def validate_auth(header: bytes, format="bearer") -> str | None: return auth_credentials -class IPCUser(AnonymousUser): +class VirtualUser(AnonymousUser): + is_active = True + + @property + def type(self): + return UserTypes.INTERNAL_SERVICE_ACCOUNT + + @property + def is_anonymous(self): + return False + + @property + def is_authenticated(self): + return True + + def all_roles(self): + return [] + + +class IPCUser(VirtualUser): """'Virtual' user for IPC communication between authentik core and the authentik router""" username = "authentik:system" - is_active = True is_superuser = True @property @@ -62,17 +80,6 @@ class IPCUser(AnonymousUser): def has_module_perms(self, module): return True - @property - def is_anonymous(self): - return False - - @property - def is_authenticated(self): - return True - - def all_roles(self): - return [] - class TokenAuthentication(BaseAuthentication): """Token-based authentication using HTTP Bearer authentication""" diff --git a/authentik/endpoints/connectors/agent/api/connectors.py b/authentik/endpoints/connectors/agent/api/connectors.py index 6155d77523..9c62448cad 100644 --- a/authentik/endpoints/connectors/agent/api/connectors.py +++ b/authentik/endpoints/connectors/agent/api/connectors.py @@ -7,7 +7,7 @@ from drf_spectacular.utils import OpenApiParameter, OpenApiResponse, extend_sche from rest_framework.decorators import action from rest_framework.exceptions import PermissionDenied, ValidationError from rest_framework.fields import ChoiceField -from rest_framework.permissions import IsAuthenticated +from rest_framework.permissions import AllowAny, IsAuthenticated from rest_framework.relations import PrimaryKeyRelatedField from rest_framework.request import Request from rest_framework.response import Response @@ -44,7 +44,6 @@ from authentik.stages.password.stage import PLAN_CONTEXT_METHOD, PLAN_CONTEXT_ME class AgentConnectorSerializer(ConnectorSerializer): - class Meta(ConnectorSerializer.Meta): model = AgentConnector fields = ConnectorSerializer.Meta.fields + [ @@ -63,7 +62,6 @@ class AgentConnectorSerializer(ConnectorSerializer): class MDMConfigSerializer(PassiveSerializer): - platform = ChoiceField(choices=OSFamily.choices) enrollment_token = PrimaryKeyRelatedField( queryset=EnrollmentToken.objects.including_expired().all() @@ -89,7 +87,6 @@ class AgentConnectorViewSet( UsedByMixin, ModelViewSet, ): - queryset = AgentConnector.objects.all() serializer_class = AgentConnectorSerializer search_fields = ["name"] @@ -121,6 +118,8 @@ class AgentConnectorViewSet( methods=["POST"], detail=False, authentication_classes=[AgentEnrollmentAuth], + # Permissions are handled via AgentEnrollmentAuth + permission_classes=[AllowAny], ) def enroll(self, request: Request): token: EnrollmentToken = request.auth @@ -151,7 +150,13 @@ class AgentConnectorViewSet( request=OpenApiTypes.NONE, responses=AgentConfigSerializer(), ) - @action(methods=["GET"], detail=False, authentication_classes=[AgentAuth]) + @action( + methods=["GET"], + detail=False, + authentication_classes=[AgentAuth], + # Permissions are handled via AgentAuth + permission_classes=[AllowAny], + ) def agent_config(self, request: Request): token: DeviceToken = request.auth connector: AgentConnector = token.device.connector.agentconnector @@ -165,7 +170,13 @@ class AgentConnectorViewSet( request=DeviceFacts(), responses={204: OpenApiResponse(description="Successfully checked in")}, ) - @action(methods=["POST"], detail=False, authentication_classes=[AgentAuth]) + @action( + methods=["POST"], + detail=False, + authentication_classes=[AgentAuth], + # Permissions are handled via AgentAuth + permission_classes=[AllowAny], + ) def check_in(self, request: Request): token: DeviceToken = request.auth data = DeviceFacts(data=request.data) diff --git a/authentik/endpoints/connectors/agent/auth.py b/authentik/endpoints/connectors/agent/auth.py index 97e4c248f5..6f89c552ea 100644 --- a/authentik/endpoints/connectors/agent/auth.py +++ b/authentik/endpoints/connectors/agent/auth.py @@ -1,5 +1,6 @@ from typing import Any +from django.db.models import Model from django.http import HttpRequest from django.utils.timezone import now from drf_spectacular.extensions import OpenApiAuthenticationExtension @@ -9,7 +10,7 @@ from rest_framework.exceptions import PermissionDenied from rest_framework.request import Request from structlog.stdlib import get_logger -from authentik.api.authentication import IPCUser, validate_auth +from authentik.api.authentication import VirtualUser, validate_auth from authentik.core.middleware import CTX_AUTH_VIA from authentik.core.models import User from authentik.crypto.apps import MANAGED_KEY @@ -25,9 +26,19 @@ LOGGER = get_logger() PLATFORM_ISSUER = "goauthentik.io/platform" -class DeviceUser(IPCUser): +class DeviceUser(VirtualUser): + username = "authentik:endpoints:device" + def has_perm(self, perm: str, obj: Model | None = None) -> bool: + print(perm) + if perm in [ + "authentik_core.view_user", + "authentik_core.view_group", + ]: + return True + return False + class AgentEnrollmentAuth(BaseAuthentication): diff --git a/authentik/endpoints/connectors/agent/tests/test_agent_api.py b/authentik/endpoints/connectors/agent/tests/test_agent_api.py index 3147505e44..17758729b8 100644 --- a/authentik/endpoints/connectors/agent/tests/test_agent_api.py +++ b/authentik/endpoints/connectors/agent/tests/test_agent_api.py @@ -223,3 +223,17 @@ class TestAgentAPI(APITestCase): data={"platform": OSFamily.macOS, "enrollment_token": self.token.pk}, ) self.assertEqual(res.status_code, 200) + + def test_users_list(self): + response = self.client.get( + reverse("authentik_api:user-list"), + HTTP_AUTHORIZATION=f"Bearer+agent {self.device_token.key}", + ) + self.assertEqual(response.status_code, 200) + + def test_other_api_forbidden(self): + response = self.client.get( + reverse("authentik_api:application-list"), + HTTP_AUTHORIZATION=f"Bearer+agent {self.device_token.key}", + ) + self.assertEqual(response.status_code, 403) diff --git a/authentik/enterprise/endpoints/connectors/agent/api/connectors.py b/authentik/enterprise/endpoints/connectors/agent/api/connectors.py index 330a2abb13..f4a2635dc0 100644 --- a/authentik/enterprise/endpoints/connectors/agent/api/connectors.py +++ b/authentik/enterprise/endpoints/connectors/agent/api/connectors.py @@ -2,6 +2,7 @@ from django.urls import reverse from drf_spectacular.types import OpenApiTypes from drf_spectacular.utils import extend_schema from rest_framework.decorators import action +from rest_framework.permissions import AllowAny from rest_framework.request import Request from rest_framework.response import Response from structlog.stdlib import get_logger @@ -25,7 +26,13 @@ class AgentConnectorViewSetMixin: request=OpenApiTypes.NONE, responses=AgentAuthenticationResponse(), ) - @action(methods=["POST"], detail=False, authentication_classes=[AgentAuth]) + @action( + methods=["POST"], + detail=False, + authentication_classes=[AgentAuth], + # Permissions are handled via AgentAuth + permission_classes=[AllowAny], + ) @enterprise_action def auth_ia(self, request: Request) -> Response: token: DeviceToken = request.auth diff --git a/schema.yml b/schema.yml index 38b5cb600e..1a3d8ff7d0 100644 --- a/schema.yml +++ b/schema.yml @@ -5386,6 +5386,8 @@ paths: using this object tags: - endpoints + security: + - {} responses: '200': content: @@ -5430,6 +5432,8 @@ paths: using this object tags: - endpoints + security: + - {} responses: '200': content: @@ -5453,6 +5457,8 @@ paths: application/json: schema: $ref: '#/components/schemas/DeviceFactsRequest' + security: + - {} responses: '204': description: Successfully checked in @@ -5473,6 +5479,8 @@ paths: schema: $ref: '#/components/schemas/EnrollRequest' required: true + security: + - {} responses: '200': content: