Compare commits

..

2 Commits

Author SHA1 Message Date
Teffen Ellis
c520808654 Fix order of heading override. 2025-11-28 15:48:38 +01:00
Teffen Ellis
e3cbc5f83f web: Update fonts to Patternfly 5 variants. 2025-11-28 14:53:42 +01:00
190 changed files with 210748 additions and 160685 deletions

View File

@@ -1,4 +1,3 @@
---
git:
filters:
- filter_type: file

View File

@@ -89,7 +89,7 @@ jobs:
git tag "version/${{ inputs.version }}" HEAD -m "version/${{ inputs.version }}"
git push --follow-tags
- name: Create Release
uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2.5.0
uses: softprops/action-gh-release@5be0e66d93ac7ed76da52eca8bb058f665c3a5fe # v2.4.2
with:
token: "${{ steps.app-token.outputs.token }}"
tag_name: "version/${{ inputs.version }}"

View File

@@ -0,0 +1,41 @@
---
# Rename transifex pull requests to have a correct naming
# Also enables auto squash-merge
name: Translation - Auto-rename Transifex PRs
on:
pull_request:
types: [opened, reopened]
permissions:
# Permission to rename PR
pull-requests: write
jobs:
rename_pr:
runs-on: ubuntu-latest
if: ${{ github.event.pull_request.user.login == 'transifex-integration[bot]'}}
steps:
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v5
- id: generate_token
uses: actions/create-github-app-token@7e473efe3cb98aa54f8d4bac15400b15fad77d94 # v2
with:
app-id: ${{ secrets.GH_APP_ID }}
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
- name: Get current title
id: title
env:
GH_TOKEN: ${{ steps.generate_token.outputs.token }}
run: |
title=$(gh pr view ${{ github.event.pull_request.number }} --json "title" -q ".title")
echo "title=${title}" >> "$GITHUB_OUTPUT"
- name: Rename
env:
GH_TOKEN: ${{ steps.generate_token.outputs.token }}
run: |
gh pr edit ${{ github.event.pull_request.number }} -t "translate: ${{ steps.title.outputs.title }}" --add-label dependencies
- uses: peter-evans/enable-pull-request-automerge@a660677d5469627102a1c1e11409dd063606628d # v3
with:
token: ${{ steps.generate_token.outputs.token }}
pull-request-number: ${{ github.event.pull_request.number }}
merge-method: squash

View File

@@ -76,7 +76,7 @@ RUN --mount=type=secret,id=GEOIPUPDATE_ACCOUNT_ID \
/bin/sh -c "GEOIPUPDATE_LICENSE_KEY_FILE=/run/secrets/GEOIPUPDATE_LICENSE_KEY /usr/bin/entry.sh || echo 'Failed to get GeoIP database, disabling'; exit 0"
# Stage 4: Download uv
FROM ghcr.io/astral-sh/uv:0.9.14@sha256:fef8e5fb8809f4b57069e919ffcd1529c92b432a2c8d8ad1768087b0b018d840 AS uv
FROM ghcr.io/astral-sh/uv:0.9.13@sha256:f07d1bf7b1fb4b983eed2b31320e25a2a76625bdf83d5ff0208fe105d4d8d2f5 AS uv
# Stage 5: Base python image
FROM ghcr.io/goauthentik/fips-python:3.13.9-slim-trixie-fips@sha256:700fc8c1e290bd14e5eaca50b1d8e8c748c820010559cbfb4c4f8dfbe2c4c9ff AS python-base

View File

@@ -27,16 +27,16 @@ except OSError:
ipc_key = None
def validate_auth(header: bytes, format="bearer") -> str | None:
def validate_auth(header: bytes) -> str | None:
"""Validate that the header is in a correct format,
returns type and credentials"""
auth_credentials = header.decode().strip()
if auth_credentials == "" or " " not in auth_credentials:
return None
auth_type, _, auth_credentials = auth_credentials.partition(" ")
if not compare_digest(auth_type.lower(), format):
if auth_type.lower() != "bearer":
LOGGER.debug("Unsupported authentication type, denying", type=auth_type.lower())
return None
raise AuthenticationFailed("Unsupported authentication type")
if auth_credentials == "": # nosec # noqa
raise AuthenticationFailed("Malformed header")
return auth_credentials

View File

@@ -24,7 +24,8 @@ class TestAPIAuth(TestCase):
def test_invalid_type(self):
"""Test invalid type"""
self.assertIsNone(bearer_auth(b"foo bar"))
with self.assertRaises(AuthenticationFailed):
bearer_auth(b"foo bar")
def test_invalid_empty(self):
"""Test invalid type"""
@@ -33,8 +34,9 @@ class TestAPIAuth(TestCase):
def test_invalid_no_token(self):
"""Test invalid with no token"""
auth = b64encode(b":abc").decode()
self.assertIsNone(bearer_auth(f"Basic :{auth}".encode()))
with self.assertRaises(AuthenticationFailed):
auth = b64encode(b":abc").decode()
self.assertIsNone(bearer_auth(f"Basic :{auth}".encode()))
def test_bearer_valid(self):
"""Test valid token"""

View File

@@ -5,7 +5,6 @@ from pathlib import Path
from django.conf import settings
from django.db import models
from django.dispatch import Signal
from django.http import HttpRequest
from drf_spectacular.utils import extend_schema
from rest_framework.fields import (
BooleanField,
@@ -64,8 +63,7 @@ class ConfigView(APIView):
permission_classes = [AllowAny]
@staticmethod
def get_capabilities(request: HttpRequest) -> list[Capabilities]:
def get_capabilities(self) -> list[Capabilities]:
"""Get all capabilities this server instance supports"""
caps = []
deb_test = settings.DEBUG or settings.TEST
@@ -78,19 +76,18 @@ class ConfigView(APIView):
for processor in get_context_processors():
if cap := processor.capability():
caps.append(cap)
if request.tenant.impersonation:
if self.request.tenant.impersonation:
caps.append(Capabilities.CAN_IMPERSONATE)
if settings.DEBUG: # pragma: no cover
caps.append(Capabilities.CAN_DEBUG)
if "authentik.enterprise" in settings.INSTALLED_APPS:
caps.append(Capabilities.IS_ENTERPRISE)
for _, result in capabilities.send(sender=ConfigView):
for _, result in capabilities.send(sender=self):
if result:
caps.append(result)
return caps
@staticmethod
def get_config(request: HttpRequest) -> ConfigSerializer:
def get_config(self) -> ConfigSerializer:
"""Get Config"""
return ConfigSerializer(
{
@@ -101,7 +98,7 @@ class ConfigView(APIView):
"send_pii": CONFIG.get("error_reporting.send_pii"),
"traces_sample_rate": float(CONFIG.get("error_reporting.sample_rate", 0.4)),
},
"capabilities": ConfigView.get_capabilities(request),
"capabilities": self.get_capabilities(),
"cache_timeout": CONFIG.get_int("cache.timeout"),
"cache_timeout_flows": CONFIG.get_int("cache.timeout_flows"),
"cache_timeout_policies": CONFIG.get_int("cache.timeout_policies"),
@@ -111,4 +108,4 @@ class ConfigView(APIView):
@extend_schema(responses={200: ConfigSerializer(many=False)})
def get(self, request: Request) -> Response:
"""Retrieve public configuration options"""
return Response(ConfigView.get_config(request).data)
return Response(self.get_config().data)

View File

@@ -44,8 +44,6 @@ from authentik.core.models import (
)
from authentik.endpoints.connectors.agent.models import (
AgentDeviceConnection,
AppleNonce,
DeviceAuthenticationToken,
)
from authentik.endpoints.connectors.agent.models import (
DeviceToken as EndpointDeviceToken,
@@ -152,8 +150,6 @@ def excluded_models() -> list[type[Model]]:
EndpointDeviceToken,
Device,
DeviceConnection,
DeviceAuthenticationToken,
AppleNonce,
AgentDeviceConnection,
DeviceFactSnapshot,
DeviceToken,

View File

@@ -14,7 +14,6 @@ from drf_spectacular.utils import (
extend_schema_field,
)
from guardian.shortcuts import get_objects_for_user
from rest_framework.authentication import SessionAuthentication
from rest_framework.decorators import action
from rest_framework.fields import CharField, IntegerField, SerializerMethodField
from rest_framework.request import Request
@@ -23,12 +22,10 @@ from rest_framework.serializers import ListSerializer, ValidationError
from rest_framework.validators import UniqueValidator
from rest_framework.viewsets import ModelViewSet
from authentik.api.authentication import TokenAuthentication
from authentik.api.validation import validate
from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.utils import JSONDictField, ModelSerializer, PassiveSerializer
from authentik.core.models import Group, User
from authentik.endpoints.connectors.agent.auth import AgentAuth
from authentik.rbac.api.roles import RoleSerializer
from authentik.rbac.decorators import permission_required
@@ -231,11 +228,6 @@ class GroupViewSet(UsedByMixin, ModelViewSet):
search_fields = ["name", "is_superuser"]
filterset_class = GroupFilter
ordering = ["name"]
authentication_classes = [
TokenAuthentication,
SessionAuthentication,
AgentAuth,
]
def get_ql_fields(self):
from djangoql.schema import BoolField, StrField

View File

@@ -109,8 +109,6 @@ class TokenSerializer(ManagedSerializer, ModelSerializer):
class TokenSetKeySerializer(PassiveSerializer):
"""Set token's key"""
key = CharField()

View File

@@ -31,7 +31,6 @@ from drf_spectacular.utils import (
inline_serializer,
)
from guardian.shortcuts import get_objects_for_user
from rest_framework.authentication import SessionAuthentication
from rest_framework.decorators import action
from rest_framework.exceptions import ValidationError
from rest_framework.fields import (
@@ -53,7 +52,6 @@ from rest_framework.validators import UniqueValidator
from rest_framework.viewsets import ModelViewSet
from structlog.stdlib import get_logger
from authentik.api.authentication import TokenAuthentication
from authentik.api.validation import validate
from authentik.blueprints.v1.importer import SERIALIZER_CONTEXT_BLUEPRINT
from authentik.brands.models import Brand
@@ -78,7 +76,6 @@ from authentik.core.models import (
User,
UserTypes,
)
from authentik.endpoints.connectors.agent.auth import AgentAuth
from authentik.events.models import Event, EventAction
from authentik.flows.exceptions import FlowNonApplicableException
from authentik.flows.models import FlowToken
@@ -436,11 +433,6 @@ class UserViewSet(UsedByMixin, ModelViewSet):
serializer_class = UserSerializer
filterset_class = UsersFilter
search_fields = ["email", "name", "uuid", "username"]
authentication_classes = [
TokenAuthentication,
SessionAuthentication,
AgentAuth,
]
def get_ql_fields(self):
from djangoql.schema import BoolField, StrField

View File

@@ -8,6 +8,7 @@ from django.http.response import HttpResponse
from django.shortcuts import redirect
from django.utils.translation import gettext as _
from django.views.generic.base import RedirectView, TemplateView
from rest_framework.request import Request
from authentik import authentik_build_hash
from authentik.admin.tasks import LOCAL_VERSION
@@ -46,7 +47,7 @@ class InterfaceView(TemplateView):
def get_context_data(self, **kwargs: Any) -> dict[str, Any]:
brand = CurrentBrandSerializer(self.request.brand)
kwargs["config_json"] = dumps(ConfigView.get_config(self.request).data)
kwargs["config_json"] = dumps(ConfigView(request=Request(self.request)).get_config().data)
kwargs["ui_theme"] = brand.data["ui_theme"]
kwargs["brand_json"] = dumps(brand.data)
kwargs["version_family"] = f"{LOCAL_VERSION.major}.{LOCAL_VERSION.minor}"

View File

@@ -1,5 +1,7 @@
"""authentik crypto app config"""
from datetime import UTC, datetime
from dramatiq.broker import get_broker
from authentik.blueprints.apps import ManagedAppConfig
@@ -45,7 +47,10 @@ class AuthentikCryptoConfig(ManagedAppConfig):
cert: CertificateKeyPair | None = CertificateKeyPair.objects.filter(
managed=MANAGED_KEY
).first()
if not cert:
now = datetime.now(tz=UTC)
if not cert or (
now < cert.certificate.not_valid_after_utc or now > cert.certificate.not_valid_after_utc
):
self._create_update_cert()
@ManagedAppConfig.reconcile_tenant

View File

@@ -2,24 +2,24 @@ from rest_framework.viewsets import ModelViewSet
from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.utils import ModelSerializer
from authentik.endpoints.models import DeviceAccessGroup
from authentik.endpoints.models import DeviceGroup
class DeviceAccessGroupSerializer(ModelSerializer):
class DeviceGroupSerializer(ModelSerializer):
class Meta:
model = DeviceAccessGroup
model = DeviceGroup
fields = [
"pbm_uuid",
"name",
]
class DeviceAccessGroupViewSet(UsedByMixin, ModelViewSet):
"""DeviceAccessGroup Viewset"""
class DeviceGroupViewSet(UsedByMixin, ModelViewSet):
"""DeviceGroup Viewset"""
queryset = DeviceAccessGroup.objects.all()
serializer_class = DeviceAccessGroupSerializer
queryset = DeviceGroup.objects.all()
serializer_class = DeviceGroupSerializer
search_fields = [
"pbm_uuid",
"name",

View File

@@ -4,15 +4,15 @@ from rest_framework.viewsets import GenericViewSet
from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.utils import ModelSerializer
from authentik.endpoints.api.device_access_group import DeviceAccessGroupSerializer
from authentik.endpoints.api.device_connections import DeviceConnectionSerializer
from authentik.endpoints.api.device_fact_snapshots import DeviceFactSnapshotSerializer
from authentik.endpoints.api.device_group import DeviceGroupSerializer
from authentik.endpoints.models import Device
class EndpointDeviceSerializer(ModelSerializer):
access_group_obj = DeviceAccessGroupSerializer(source="access_group", required=False)
group_obj = DeviceGroupSerializer(source="group")
facts = SerializerMethodField()
@@ -25,8 +25,8 @@ class EndpointDeviceSerializer(ModelSerializer):
"device_uuid",
"pbm_uuid",
"name",
"access_group",
"access_group_obj",
"group",
"group_obj",
"expiring",
"expires",
"facts",
@@ -58,7 +58,7 @@ class DeviceViewSet(
GenericViewSet,
):
queryset = Device.objects.all().select_related("access_group")
queryset = Device.objects.all().select_related("group")
serializer_class = EndpointDeviceSerializer
search_fields = [
"name",

View File

@@ -1,66 +0,0 @@
from rest_framework.fields import (
BooleanField,
CharField,
IntegerField,
SerializerMethodField,
)
from authentik.api.v3.config import ConfigSerializer, ConfigView
from authentik.core.api.utils import PassiveSerializer
from authentik.crypto.apps import MANAGED_KEY
from authentik.crypto.models import CertificateKeyPair
from authentik.endpoints.connectors.agent.models import AgentConnector
from authentik.endpoints.models import Device
from authentik.lib.utils.time import timedelta_from_string
from authentik.providers.oauth2.views.jwks import JWKSView
class AgentConfigSerializer(PassiveSerializer):
device_id = SerializerMethodField()
refresh_interval = SerializerMethodField()
authorization_flow = SerializerMethodField()
jwks = SerializerMethodField()
nss_uid_offset = IntegerField()
nss_gid_offset = IntegerField()
auth_terminate_session_on_expiry = BooleanField()
system_config = SerializerMethodField()
def get_device_id(self, instance: AgentConnector) -> str:
device: Device = self.context["device"]
return device.pk
def get_refresh_interval(self, instance: AgentConnector) -> int:
return int(timedelta_from_string(instance.refresh_interval).total_seconds())
def get_authorization_flow(self, instance: AgentConnector) -> str | None:
if not instance.authorization_flow:
return None
return instance.authorization_flow.slug
def get_jwks(self, instance: AgentConnector) -> dict:
kp = CertificateKeyPair.objects.filter(managed=MANAGED_KEY).first()
return {"keys": [JWKSView.get_jwk_for_key(kp, "sig")]}
def get_system_config(self, instance: AgentConnector) -> ConfigSerializer:
return ConfigView.get_config(self.context["request"]).data
class EnrollSerializer(PassiveSerializer):
device_serial = CharField(required=True)
device_name = CharField(required=True)
class AgentTokenResponseSerializer(PassiveSerializer):
token = CharField(required=True)
expires_in = IntegerField(required=0)
class AgentAuthenticationResponse(PassiveSerializer):
url = CharField()

View File

@@ -6,8 +6,11 @@ from drf_spectacular.utils import OpenApiResponse, extend_schema
from rest_framework.decorators import action
from rest_framework.exceptions import PermissionDenied, ValidationError
from rest_framework.fields import (
BooleanField,
CharField,
ChoiceField,
IntegerField,
SerializerMethodField,
)
from rest_framework.relations import PrimaryKeyRelatedField
from rest_framework.request import Request
@@ -17,11 +20,6 @@ from rest_framework.viewsets import ModelViewSet
from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.utils import PassiveSerializer
from authentik.endpoints.api.connectors import ConnectorSerializer
from authentik.endpoints.connectors.agent.api.agent import (
AgentConfigSerializer,
AgentTokenResponseSerializer,
EnrollSerializer,
)
from authentik.endpoints.connectors.agent.auth import (
AgentAuth,
AgentEnrollmentAuth,
@@ -34,24 +32,37 @@ from authentik.endpoints.connectors.agent.models import (
)
from authentik.endpoints.facts import DeviceFacts, OSFamily
from authentik.endpoints.models import Device
from authentik.lib.utils.reflection import ConditionalInheritance
from authentik.lib.utils.time import timedelta_from_string
class AgentConnectorSerializer(ConnectorSerializer):
class Meta(ConnectorSerializer.Meta):
model = AgentConnector
fields = ConnectorSerializer.Meta.fields + [
"snapshot_expiry",
"auth_session_duration",
"auth_terminate_session_on_expiry",
"refresh_interval",
"authorization_flow",
"nss_uid_offset",
"nss_gid_offset",
"challenge_key",
"jwt_federation_providers",
]
fields = "__all__"
class AgentConfigSerializer(PassiveSerializer):
nss_uid_offset = IntegerField()
nss_gid_offset = IntegerField()
authentication_flow = CharField()
auth_terminate_session_on_expiry = BooleanField()
refresh_interval = SerializerMethodField()
def get_refresh_interval(self, instance: AgentConnector) -> int:
return int(timedelta_from_string(instance.refresh_interval).total_seconds())
class EnrollSerializer(PassiveSerializer):
device_serial = CharField()
device_name = CharField()
class EnrollResponseSerializer(PassiveSerializer):
token = CharField()
class MDMConfigSerializer(PassiveSerializer):
@@ -77,13 +88,7 @@ class MDMConfigResponseSerializer(PassiveSerializer):
config = CharField(required=True)
class AgentConnectorViewSet(
ConditionalInheritance(
"authentik.enterprise.endpoints.connectors.agent.api.connectors.AgentConnectorViewSetMixin"
),
UsedByMixin,
ModelViewSet,
):
class AgentConnectorViewSet(UsedByMixin, ModelViewSet):
queryset = AgentConnector.objects.all()
serializer_class = AgentConnectorSerializer
@@ -91,6 +96,61 @@ class AgentConnectorViewSet(
ordering = ["name"]
filterset_fields = ["name", "enabled"]
@extend_schema(
request=EnrollSerializer(),
responses={200: EnrollResponseSerializer},
)
@action(
methods=["POST"],
detail=False,
authentication_classes=[AgentEnrollmentAuth],
)
def enroll(self, request: Request):
token: EnrollmentToken = request.auth
data = EnrollSerializer(data=request.data)
data.is_valid(raise_exception=True)
device, _ = Device.objects.get_or_create(
identifier=data.validated_data["device_serial"],
defaults={
"name": data.validated_data["device_name"],
"expiring": False,
"group": token.device_group,
},
)
connection, _ = AgentDeviceConnection.objects.update_or_create(
device=device,
connector=token.connector,
)
token = DeviceToken.objects.create(device=connection, expiring=False)
return Response(
{
"token": token.key,
}
)
@extend_schema(
responses=AgentConfigSerializer(),
request=OpenApiTypes.NONE,
)
@action(methods=["GET"], detail=False, authentication_classes=[AgentAuth])
def agent_config(self, request: Request):
token: DeviceToken = request.auth
connector: AgentConnector = token.device.connector.agentconnector
return Response(AgentConfigSerializer(connector).data)
@extend_schema(
request=DeviceFacts(),
responses={204: OpenApiResponse(description="Successfully checked in")},
)
@action(methods=["POST"], detail=False, authentication_classes=[AgentAuth])
def check_in(self, request: Request):
token: DeviceToken = request.auth
data = DeviceFacts(data=request.data)
data.is_valid(raise_exception=True)
connection: AgentDeviceConnection = token.device
connection.create_snapshot(data.validated_data)
return Response(status=204)
@extend_schema(
request=MDMConfigSerializer(),
responses=MDMConfigResponseSerializer(),
@@ -107,63 +167,3 @@ class AgentConnectorViewSet(
ctrl = connector.controller(connector)
payload = ctrl.generate_mdm_config(data.validated_data["platform"], request, token)
return Response({"config": payload})
@extend_schema(
request=EnrollSerializer(),
responses={200: AgentTokenResponseSerializer},
)
@action(
methods=["POST"],
detail=False,
authentication_classes=[AgentEnrollmentAuth],
)
def enroll(self, request: Request):
token: EnrollmentToken = request.auth
data = EnrollSerializer(data=request.data)
data.is_valid(raise_exception=True)
device, _ = Device.objects.get_or_create(
identifier=data.validated_data["device_serial"],
defaults={
"name": data.validated_data["device_name"],
"expiring": False,
"access_group": token.device_group,
},
)
connection, _ = AgentDeviceConnection.objects.update_or_create(
device=device,
connector=token.connector,
)
token = DeviceToken.objects.create(device=connection, expiring=False)
return Response(
{
"token": token.key,
"expires_in": 0,
}
)
@extend_schema(
request=OpenApiTypes.NONE,
responses=AgentConfigSerializer(),
)
@action(methods=["GET"], detail=False, authentication_classes=[AgentAuth])
def agent_config(self, request: Request):
token: DeviceToken = request.auth
connector: AgentConnector = token.device.connector.agentconnector
return Response(
AgentConfigSerializer(
connector, context={"request": request, "device": token.device.device}
).data
)
@extend_schema(
request=DeviceFacts(),
responses={204: OpenApiResponse(description="Successfully checked in")},
)
@action(methods=["POST"], detail=False, authentication_classes=[AgentAuth])
def check_in(self, request: Request):
token: DeviceToken = request.auth
data = DeviceFacts(data=request.data)
data.is_valid(raise_exception=True)
connection: AgentDeviceConnection = token.device
connection.create_snapshot(data.validated_data)
return Response(status=204)

View File

@@ -7,7 +7,7 @@ from rest_framework.viewsets import ModelViewSet
from authentik.core.api.tokens import TokenViewSerializer
from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.utils import ModelSerializer
from authentik.endpoints.api.device_access_group import DeviceAccessGroupSerializer
from authentik.endpoints.api.device_group import DeviceGroupSerializer
from authentik.endpoints.connectors.agent.models import EnrollmentToken
from authentik.events.models import Event, EventAction
from authentik.rbac.decorators import permission_required
@@ -15,9 +15,7 @@ from authentik.rbac.decorators import permission_required
class EnrollmentTokenSerializer(ModelSerializer):
device_group_obj = DeviceAccessGroupSerializer(
source="device_group", read_only=True, required=False
)
device_group_obj = DeviceGroupSerializer(source="device_group", read_only=True, required=False)
class Meta:
model = EnrollmentToken

View File

@@ -30,9 +30,7 @@ class AgentAuth(BaseAuthentication):
def authenticate(self, request: Request) -> tuple[User, Any] | None:
auth = get_authorization_header(request)
key = validate_auth(auth, format="bearer+agent")
if not key:
return None
key = validate_auth(auth)
device_token = DeviceToken.filter_not_expired(key=key).first()
if not device_token:
raise PermissionDenied()

View File

@@ -1,5 +1,4 @@
from plistlib import PlistFormat, dumps
from uuid import uuid4
from xml.etree.ElementTree import Element, SubElement, tostring # nosec
from django.http import HttpRequest
@@ -64,60 +63,18 @@ class AgentConnectorController(BaseController[AgentConnector]):
return payload
def _generate_mdm_config_macos(self, request: HttpRequest, token: EnrollmentToken) -> str:
token_uuid = str(token.pk).upper()
payload = dumps(
{
"PayloadContent": [
# Config for authentik Platform Agent (sysd)
{
"PayloadDisplayName": "authentik Platform",
"PayloadIdentifier": f"io.goauthentik.platform.{token_uuid}",
"PayloadIdentifier": f"io.goauthentik.platform.{str(token.pk).upper()}",
"PayloadType": "io.goauthentik.platform",
"PayloadUUID": str(uuid4()),
"PayloadUUID": str(token.pk).upper(),
"PayloadVersion": 1,
"RegistrationToken": token.key,
"URL": request.build_absolute_uri(reverse("authentik_core:root-redirect")),
},
# Config for MDM-associated domains (required for PSSO)
{
"PayloadDisplayName": "Associated Domains",
"PayloadIdentifier": f"com.apple.associated-domains.{token_uuid}",
"PayloadType": "com.apple.associated-domains",
"PayloadUUID": str(uuid4()),
"PayloadVersion": 1,
"Configuration": [
{
"ApplicationIdentifier": "232G855Y8N.io.goauthentik.platform.agent",
"AssociatedDomains": [f"authsrv:{request.get_host()}"],
"EnableDirectDownloads": False,
}
],
},
# Config for Platform SSO
{
"PayloadDisplayName": "Platform Single Sign-On",
"PayloadIdentifier": f"com.apple.extensiblesso.{token_uuid}",
"PayloadType": "com.apple.extensiblesso",
"PayloadUUID": str(uuid4()),
"PayloadVersion": 1,
"ExtensionIdentifier": "io.goauthentik.platform.psso",
"TeamIdentifier": "232G855Y8N",
"Type": "Redirect",
"URLs": [request.build_absolute_uri("")],
"PlatformSSO": {
"AccountDisplayName": "authentik",
"AllowDeviceIdentifiersInAttestation": True,
"AuthenticationMethod": "UserSecureEnclaveKey",
"EnableAuthorization": True,
"EnableCreateUserAtLogin": True,
"FileVaultPolicy": ["RequireAuthentication"],
"LoginPolicy": ["RequireAuthentication"],
"NewUserAuthorizationMode": "Standard",
"UnlockPolicy": ["RequireAuthentication"],
"UseSharedDeviceKeys": True,
"UserAuthorizationMode": "Standard",
},
},
}
],
"PayloadDisplayName": "authentik Platform",
"PayloadIdentifier": str(self.connector.pk).upper(),

View File

@@ -1,94 +0,0 @@
# Generated by Django 5.2.8 on 2025-11-27 00:16
import django.db.models.deletion
import uuid
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_endpoints", "0002_rename_devicegroup_deviceaccessgroup_and_more"),
("authentik_endpoints_connectors_agent", "0001_initial"),
(
"authentik_providers_oauth2",
"0031_remove_oauth2provider_backchannel_logout_uri_and_more",
),
]
operations = [
migrations.CreateModel(
name="DeviceAuthenticationToken",
fields=[
("expires", models.DateTimeField(default=None, null=True)),
("expiring", models.BooleanField(default=True)),
(
"identifier",
models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False),
),
("token", models.TextField()),
],
options={
"verbose_name": "Device authentication token",
"verbose_name_plural": "Device authentication tokens",
"abstract": False,
},
),
migrations.AlterModelOptions(
name="devicetoken",
options={"verbose_name": "Device Token", "verbose_name_plural": "Device Tokens"},
),
migrations.RenameField(
model_name="agentconnector",
old_name="authentication_flow",
new_name="authorization_flow",
),
migrations.AddField(
model_name="agentconnector",
name="jwt_federation_providers",
field=models.ManyToManyField(
blank=True, default=None, to="authentik_providers_oauth2.oauth2provider"
),
),
migrations.AddIndex(
model_name="devicetoken",
index=models.Index(fields=["key"], name="authentik_e_key_504bbc_idx"),
),
migrations.AddField(
model_name="deviceauthenticationtoken",
name="connector",
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="authentik_endpoints_connectors_agent.agentconnector",
),
),
migrations.AddField(
model_name="deviceauthenticationtoken",
name="device",
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, to="authentik_endpoints.device"
),
),
migrations.AddField(
model_name="deviceauthenticationtoken",
name="device_token",
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="authentik_endpoints_connectors_agent.devicetoken",
),
),
migrations.AddIndex(
model_name="deviceauthenticationtoken",
index=models.Index(fields=["expires"], name="authentik_e_expires_d52fb2_idx"),
),
migrations.AddIndex(
model_name="deviceauthenticationtoken",
index=models.Index(fields=["expiring"], name="authentik_e_expirin_e9b873_idx"),
),
migrations.AddIndex(
model_name="deviceauthenticationtoken",
index=models.Index(
fields=["expiring", "expires"], name="authentik_e_expirin_8c95fe_idx"
),
),
]

View File

@@ -1,70 +0,0 @@
# Generated by Django 5.2.8 on 2025-11-30 22:04
import authentik.lib.utils.time
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
(
"authentik_endpoints_connectors_agent",
"0002_deviceauthenticationtoken_alter_devicetoken_options_and_more",
),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.AddField(
model_name="agentconnector",
name="auth_session_duration",
field=models.TextField(
default="hours=8", validators=[authentik.lib.utils.time.timedelta_string_validator]
),
),
migrations.AddField(
model_name="deviceauthenticationtoken",
name="user",
field=models.ForeignKey(
default=None,
null=True,
on_delete=django.db.models.deletion.CASCADE,
to=settings.AUTH_USER_MODEL,
),
),
migrations.CreateModel(
name="AppleNonce",
fields=[
(
"id",
models.AutoField(
auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
),
),
("expires", models.DateTimeField(default=None, null=True)),
("expiring", models.BooleanField(default=True)),
("nonce", models.TextField()),
(
"device_token",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="authentik_endpoints_connectors_agent.devicetoken",
),
),
],
options={
"verbose_name": "Apple Nonce",
"verbose_name_plural": "Apple Nonces",
"abstract": False,
"indexes": [
models.Index(fields=["expires"], name="authentik_e_expires_e5d275_idx"),
models.Index(fields=["expiring"], name="authentik_e_expirin_0b4d8e_idx"),
models.Index(
fields=["expiring", "expires"], name="authentik_e_expirin_355561_idx"
),
],
},
),
]

View File

@@ -5,15 +5,9 @@ from django.db import models
from django.utils.translation import gettext_lazy as _
from rest_framework.serializers import Serializer
from authentik.core.models import ExpiringModel, User, default_token_key
from authentik.core.models import ExpiringModel, default_token_key
from authentik.crypto.models import CertificateKeyPair
from authentik.endpoints.models import (
Connector,
Device,
DeviceAccessGroup,
DeviceConnection,
DeviceUserBinding,
)
from authentik.endpoints.models import Connector, DeviceConnection, DeviceGroup, DeviceUserBinding
from authentik.flows.stage import StageView
from authentik.lib.generators import generate_key
from authentik.lib.models import SerializerModel
@@ -26,25 +20,17 @@ if TYPE_CHECKING:
class AgentConnector(Connector):
"""Configure authentication and add device compliance using the authentik Agent."""
nss_uid_offset = models.PositiveIntegerField(default=1000)
nss_gid_offset = models.PositiveIntegerField(default=1000)
authentication_flow = models.ForeignKey(
"authentik_flows.Flow", null=True, on_delete=models.SET_DEFAULT, default=None
)
auth_terminate_session_on_expiry = models.BooleanField(default=False)
refresh_interval = models.TextField(
default="minutes=30",
validators=[timedelta_string_validator],
)
auth_session_duration = models.TextField(
default="hours=8", validators=[timedelta_string_validator]
)
auth_terminate_session_on_expiry = models.BooleanField(default=False)
authorization_flow = models.ForeignKey(
"authentik_flows.Flow", null=True, on_delete=models.SET_DEFAULT, default=None
)
jwt_federation_providers = models.ManyToManyField(
"authentik_providers_oauth2.OAuth2Provider", blank=True, default=None
)
nss_uid_offset = models.PositiveIntegerField(default=1000)
nss_gid_offset = models.PositiveIntegerField(default=1000)
challenge_key = models.ForeignKey(CertificateKeyPair, on_delete=models.CASCADE, null=True)
@property
@@ -80,11 +66,11 @@ class AgentConnector(Connector):
class AgentDeviceConnection(DeviceConnection):
apple_key_exchange_key = models.TextField()
apple_encryption_key = models.TextField()
apple_enc_key_id = models.TextField()
apple_signing_key = models.TextField()
apple_encryption_key = models.TextField()
apple_key_exchange_key = models.TextField()
apple_sign_key_id = models.TextField()
apple_enc_key_id = models.TextField()
class AgentDeviceUserBinding(DeviceUserBinding):
@@ -94,30 +80,20 @@ class AgentDeviceUserBinding(DeviceUserBinding):
class DeviceToken(ExpiringModel):
"""Per-device token used for authentication."""
token_uuid = models.UUIDField(primary_key=True, default=uuid4)
device = models.ForeignKey(AgentDeviceConnection, on_delete=models.CASCADE)
key = models.TextField(default=generate_key)
class Meta:
verbose_name = _("Device Token")
verbose_name_plural = _("Device Tokens")
indexes = ExpiringModel.Meta.indexes + [
models.Index(fields=["key"]),
]
class EnrollmentToken(ExpiringModel, SerializerModel):
"""Token used during enrollment, a device will receive
a device token for further authentication"""
token_uuid = models.UUIDField(primary_key=True, editable=False, default=uuid4)
name = models.TextField()
key = models.TextField(default=default_token_key)
connector = models.ForeignKey(AgentConnector, on_delete=models.CASCADE)
device_group = models.ForeignKey(
DeviceAccessGroup, on_delete=models.SET_DEFAULT, default=None, null=True
DeviceGroup, on_delete=models.SET_DEFAULT, default=None, null=True
)
@property
@@ -137,29 +113,3 @@ class EnrollmentToken(ExpiringModel, SerializerModel):
permissions = [
("view_enrollment_token_key", _("View token's key")),
]
class DeviceAuthenticationToken(ExpiringModel):
identifier = models.UUIDField(default=uuid4, primary_key=True)
device = models.ForeignKey(Device, on_delete=models.CASCADE)
device_token = models.ForeignKey(DeviceToken, on_delete=models.CASCADE)
connector = models.ForeignKey(AgentConnector, on_delete=models.CASCADE)
user = models.ForeignKey(User, on_delete=models.CASCADE, null=True, default=None)
token = models.TextField()
def __str__(self):
return f"Device authentication token {self.identifier}"
class Meta(ExpiringModel.Meta):
verbose_name = _("Device authentication token")
verbose_name_plural = _("Device authentication tokens")
class AppleNonce(ExpiringModel):
nonce = models.TextField()
device_token = models.ForeignKey(DeviceToken, on_delete=models.CASCADE)
class Meta(ExpiringModel.Meta):
verbose_name = _("Apple Nonce")
verbose_name_plural = _("Apple Nonces")

View File

@@ -4,12 +4,11 @@ from django.urls import reverse
from django.utils.timezone import now
from rest_framework.test import APITestCase
from authentik.blueprints.tests import reconcile_app
from authentik.core.tests.utils import create_test_admin_user
from authentik.endpoints.connectors.agent.api.connectors import AgentDeviceConnection
from authentik.endpoints.connectors.agent.models import AgentConnector, DeviceToken, EnrollmentToken
from authentik.endpoints.facts import OSFamily
from authentik.endpoints.models import Device, DeviceAccessGroup
from authentik.endpoints.models import Device, DeviceGroup
from authentik.lib.generators import generate_id
CHECK_IN_DATA_VALID = {
@@ -36,7 +35,9 @@ CHECK_IN_DATA_VALID = {
class TestAgentAPI(APITestCase):
def setUp(self):
self.connector = AgentConnector.objects.create(name=generate_id())
self.connector = AgentConnector.objects.create(
name=generate_id(),
)
self.token = EnrollmentToken.objects.create(name=generate_id(), connector=self.connector)
self.device = Device.objects.create(
identifier=generate_id(),
@@ -59,7 +60,7 @@ class TestAgentAPI(APITestCase):
self.assertEqual(response.status_code, 200)
def test_enroll_group(self):
device_group = DeviceAccessGroup.objects.create(name=generate_id())
device_group = DeviceGroup.objects.create(name=generate_id())
self.token.device_group = device_group
self.token.save()
ident = generate_id()
@@ -71,7 +72,7 @@ class TestAgentAPI(APITestCase):
self.assertEqual(response.status_code, 200)
device = Device.objects.filter(identifier=ident).first()
self.assertIsNotNone(device)
self.assertEqual(device.access_group, device_group)
self.assertEqual(device.group, device_group)
def test_enroll_expired(self):
dev_id = generate_id()
@@ -86,11 +87,10 @@ class TestAgentAPI(APITestCase):
self.assertEqual(response.status_code, 403)
self.assertFalse(Device.objects.filter(identifier=dev_id).exists())
@reconcile_app("authentik_crypto")
def test_config(self):
response = self.client.get(
reverse("authentik_api:agentconnector-agent-config"),
HTTP_AUTHORIZATION=f"Bearer+agent {self.device_token.key}",
HTTP_AUTHORIZATION=f"Bearer {self.device_token.key}",
)
self.assertEqual(response.status_code, 200)
@@ -98,7 +98,7 @@ class TestAgentAPI(APITestCase):
response = self.client.post(
reverse("authentik_api:agentconnector-check-in"),
data=CHECK_IN_DATA_VALID,
HTTP_AUTHORIZATION=f"Bearer+agent {self.device_token.key}",
HTTP_AUTHORIZATION=f"Bearer {self.device_token.key}",
)
self.assertEqual(response.status_code, 204)
@@ -109,7 +109,7 @@ class TestAgentAPI(APITestCase):
response = self.client.post(
reverse("authentik_api:agentconnector-check-in"),
data=CHECK_IN_DATA_VALID,
HTTP_AUTHORIZATION=f"Bearer+agent {self.device_token.key}",
HTTP_AUTHORIZATION=f"Bearer {self.device_token.key}",
)
self.assertEqual(response.status_code, 403)
@@ -120,7 +120,7 @@ class TestAgentAPI(APITestCase):
response = self.client.post(
reverse("authentik_api:agentconnector-check-in"),
data=CHECK_IN_DATA_VALID,
HTTP_AUTHORIZATION=f"Bearer+agent {self.device_token.key}",
HTTP_AUTHORIZATION=f"Bearer {self.device_token.key}",
)
self.assertEqual(response.status_code, 403)

View File

@@ -67,9 +67,9 @@ class NetworkSerializer(Serializer):
class HardwareSerializer(Serializer):
model = CharField(required=False)
manufacturer = CharField(required=False)
serial = CharField()
model = CharField()
manufacturer = CharField()
serial = CharField(allow_blank=True)
cpu_name = CharField(required=False)
cpu_count = IntegerField(required=False)
@@ -91,18 +91,6 @@ class ProcessSerializer(Serializer):
user = CharField(required=False)
class DeviceUserSerializer(Serializer):
id = CharField(required=True)
username = CharField(required=False)
name = CharField(required=False)
home = CharField(required=False)
class DeviceGroupSerializer(Serializer):
id = CharField(required=True)
name = CharField(required=False)
class DeviceFacts(Serializer):
os = OperatingSystemSerializer(required=False, allow_null=True)
disks = ListField(child=DiskSerializer(), required=False, allow_null=True)
@@ -110,6 +98,4 @@ class DeviceFacts(Serializer):
hardware = HardwareSerializer(required=False, allow_null=True)
software = ListField(child=SoftwareSerializer(), required=False, allow_null=True)
processes = ListField(child=ProcessSerializer(), required=False, allow_null=True)
users = ListField(child=DeviceUserSerializer(), required=False, allow_null=True)
groups = ListField(child=DeviceGroupSerializer(), required=False, allow_null=True)
vendor = JSONDictField(required=False)

View File

@@ -1,61 +0,0 @@
# Generated by Django 5.2.8 on 2025-11-27 00:16
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_endpoints", "0001_initial"),
("authentik_endpoints_connectors_agent", "0001_initial"),
("authentik_policies", "0011_policybinding_failure_result_and_more"),
]
operations = [
migrations.RenameModel(
old_name="DeviceGroup",
new_name="DeviceAccessGroup",
),
migrations.AlterModelOptions(
name="device",
options={"verbose_name": "Device", "verbose_name_plural": "Devices"},
),
migrations.AlterModelOptions(
name="deviceaccessgroup",
options={
"verbose_name": "Device access group",
"verbose_name_plural": "Device access groups",
},
),
migrations.AlterModelOptions(
name="deviceconnection",
options={
"verbose_name": "Device connection",
"verbose_name_plural": "Device connections",
},
),
migrations.AlterModelOptions(
name="devicefactsnapshot",
options={
"verbose_name": "Device fact snapshot",
"verbose_name_plural": "Device fact snapshots",
},
),
migrations.AlterModelOptions(
name="deviceuserbinding",
options={
"verbose_name": "Device User binding",
"verbose_name_plural": "Device User bindings",
},
),
migrations.RenameField(
model_name="device",
old_name="group",
new_name="access_group",
),
migrations.AlterField(
model_name="device",
name="name",
field=models.TextField(unique=True),
),
]

View File

@@ -6,7 +6,6 @@ from django.core.cache import cache
from django.db import models
from django.db.models import OuterRef, Subquery
from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _
from model_utils.managers import InheritanceManager
from rest_framework.serializers import Serializer
from structlog.stdlib import get_logger
@@ -31,12 +30,10 @@ DEVICE_FACTS_CACHE_TIMEOUT = 3600
class Device(ExpiringModel, AttributesMixin, PolicyBindingModel):
device_uuid = models.UUIDField(default=uuid4, primary_key=True)
name = models.TextField(unique=True)
name = models.TextField()
identifier = models.TextField(unique=True)
connections = models.ManyToManyField("Connector", through="DeviceConnection")
access_group = models.ForeignKey(
"DeviceAccessGroup", null=True, on_delete=models.SET_DEFAULT, default=None
)
group = models.ForeignKey("DeviceGroup", null=True, on_delete=models.SET_DEFAULT, default=None)
@property
def cache_key_facts(self):
@@ -67,13 +64,6 @@ class Device(ExpiringModel, AttributesMixin, PolicyBindingModel):
last_updated = max(last_updated, snapshort_created)
return DeviceFactSnapshot(data=data, created=last_updated)
def __str__(self):
return f"Device {self.name} {self.identifier} ({self.pk})"
class Meta(ExpiringModel.Meta):
verbose_name = _("Device")
verbose_name_plural = _("Devices")
class DeviceUserBinding(PolicyBinding):
is_primary = models.BooleanField(default=False)
@@ -81,10 +71,6 @@ class DeviceUserBinding(PolicyBinding):
# by a connector and not manually
connector = models.ForeignKey("Connector", on_delete=models.CASCADE, null=True)
class Meta(PolicyBinding.Meta):
verbose_name = _("Device User binding")
verbose_name_plural = _("Device User bindings")
class DeviceConnection(SerializerModel):
device_connection_uuid = models.UUIDField(default=uuid4, primary_key=True)
@@ -110,10 +96,6 @@ class DeviceConnection(SerializerModel):
return DeviceConnectionSerializer
class Meta:
verbose_name = _("Device connection")
verbose_name_plural = _("Device connections")
class DeviceFactSnapshot(ExpiringModel, SerializerModel):
snapshot_id = models.UUIDField(primary_key=True, default=uuid4)
@@ -130,24 +112,19 @@ class DeviceFactSnapshot(ExpiringModel, SerializerModel):
return DeviceFactSnapshotSerializer
class Meta(ExpiringModel.Meta):
verbose_name = _("Device fact snapshot")
verbose_name_plural = _("Device fact snapshots")
class Connector(ScheduledModel, SerializerModel):
connector_uuid = models.UUIDField(default=uuid4, primary_key=True)
name = models.TextField()
enabled = models.BooleanField(default=True)
objects = InheritanceManager()
snapshot_expiry = models.TextField(
default="hours=24",
validators=[timedelta_string_validator],
)
objects = InheritanceManager()
@property
def stage(self) -> type[StageView] | None:
return None
@@ -175,20 +152,10 @@ class Connector(ScheduledModel, SerializerModel):
]
class DeviceAccessGroup(PolicyBindingModel):
class DeviceGroup(PolicyBindingModel):
name = models.TextField(unique=True)
@property
def serializer(self) -> type[Serializer]:
from authentik.endpoints.api.device_access_group import DeviceAccessGroupSerializer
return DeviceAccessGroupSerializer
class Meta:
verbose_name = _("Device access group")
verbose_name_plural = _("Device access groups")
class EndpointStage(Stage):

View File

@@ -1,5 +1,5 @@
from authentik.endpoints.api.connectors import ConnectorViewSet
from authentik.endpoints.api.device_access_group import DeviceAccessGroupViewSet
from authentik.endpoints.api.device_group import DeviceGroupViewSet
from authentik.endpoints.api.device_user_bindings import DeviceUserBindingViewSet
from authentik.endpoints.api.devices import DeviceViewSet
@@ -7,5 +7,5 @@ api_urlpatterns = [
("endpoints/connectors", ConnectorViewSet, "endpoint_connectors"),
("endpoints/devices", DeviceViewSet, "endpoint_device"),
("endpoints/device_bindings", DeviceUserBindingViewSet, "endpoint_device_bindings"),
("endpoints/device_access_groups", DeviceAccessGroupViewSet, "endpoint_device_access_groups"),
("endpoints/device_groups", DeviceGroupViewSet, "endpoint_device_groups"),
]

View File

@@ -1,111 +0,0 @@
from django.http import Http404, HttpResponseBadRequest
from django.urls import reverse
from django.utils.timezone import now
from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import OpenApiParameter, OpenApiResponse, extend_schema
from rest_framework.authentication import get_authorization_header
from rest_framework.decorators import action
from rest_framework.exceptions import ValidationError
from rest_framework.request import Request
from rest_framework.response import Response
from structlog.stdlib import get_logger
from authentik.api.authentication import validate_auth
from authentik.endpoints.connectors.agent.api.agent import (
AgentAuthenticationResponse,
AgentTokenResponseSerializer,
)
from authentik.endpoints.connectors.agent.auth import AgentAuth
from authentik.endpoints.connectors.agent.models import (
DeviceAuthenticationToken,
DeviceToken,
)
from authentik.endpoints.models import Device
from authentik.enterprise.endpoints.connectors.agent.auth import (
agent_auth_fed_validate,
agent_auth_issue_token,
check_device_policies,
)
from authentik.events.models import Event, EventAction
from authentik.flows.planner import PLAN_CONTEXT_DEVICE
from authentik.stages.password.stage import PLAN_CONTEXT_METHOD, PLAN_CONTEXT_METHOD_ARGS
LOGGER = get_logger()
class AgentConnectorViewSetMixin:
@extend_schema(
request=OpenApiTypes.NONE,
responses=AgentAuthenticationResponse(),
)
@action(methods=["POST"], detail=False, authentication_classes=[AgentAuth])
def auth_ia(self, request: Request) -> Response:
token: DeviceToken = request.auth
auth_token = DeviceAuthenticationToken.objects.create(
device=token.device.device,
device_token=token,
connector=token.device.connector.agentconnector,
)
return Response(
{
"url": request.build_absolute_uri(
reverse(
"authentik_enterprise_endpoints_connectors_agent:authenticate",
kwargs={"token_uuid": auth_token.identifier},
)
),
}
)
@extend_schema(
request=OpenApiTypes.NONE,
parameters=[OpenApiParameter("device", OpenApiTypes.STR, location="query", required=True)],
responses={
200: AgentTokenResponseSerializer(),
404: OpenApiResponse(description="Device not found"),
},
)
@action(
methods=["POST"],
detail=False,
pagination_class=None,
filter_backends=[],
permission_classes=[],
authentication_classes=[],
)
def auth_fed(self, request: Request) -> Response:
raw_token = validate_auth(get_authorization_header(request))
if not raw_token:
LOGGER.warning("Missing token")
return HttpResponseBadRequest()
device = Device.objects.filter(name=request.query_params.get("device")).first()
if not device:
LOGGER.warning("Couldn't find device")
raise Http404
federated_token, connector = agent_auth_fed_validate(raw_token, device)
LOGGER.info(
"successfully verified JWT with provider", provider=federated_token.provider.name
)
policy_result = check_device_policies(device, federated_token.user, request._request)
if not policy_result.passing:
raise ValidationError(
{"policy_result": "Policy denied access", "policy_messages": policy_result.messages}
)
token, exp = agent_auth_issue_token(device, connector, federated_token.user)
rel_exp = int((exp - now()).total_seconds())
Event.new(
EventAction.LOGIN,
**{
PLAN_CONTEXT_METHOD: "jwt",
PLAN_CONTEXT_METHOD_ARGS: {
"jwt": federated_token,
"provider": federated_token.provider,
},
PLAN_CONTEXT_DEVICE: device,
},
).from_http(request, user=federated_token.user)
return Response({"token": token, "expires_in": rel_exp})

View File

@@ -9,5 +9,4 @@ class AuthentikEnterpriseEndpointsConnectorAgentAppConfig(EnterpriseConfig):
default = True
mountpoints = {
"authentik.enterprise.endpoints.connectors.agent.urls_root": "",
"authentik.enterprise.endpoints.connectors.agent.urls": "endpoints/agent/",
}

View File

@@ -1,87 +0,0 @@
from django.http import Http404, HttpRequest
from django.utils.timezone import now
from jwt import PyJWTError, decode, encode
from rest_framework.exceptions import ValidationError
from structlog.stdlib import get_logger
from authentik.core.models import User
from authentik.crypto.apps import MANAGED_KEY
from authentik.crypto.models import CertificateKeyPair
from authentik.endpoints.connectors.agent.models import AgentConnector
from authentik.endpoints.models import Device
from authentik.lib.utils.time import timedelta_from_string
from authentik.policies.engine import PolicyEngine
from authentik.policies.models import PolicyBindingModel
from authentik.providers.oauth2.models import AccessToken, JWTAlgorithms, OAuth2Provider
LOGGER = get_logger()
PLATFORM_ISSUER = "goauthentik.io/platform"
def agent_auth_issue_token(device: Device, connector: AgentConnector, user: User, **kwargs):
kp = CertificateKeyPair.objects.filter(managed=MANAGED_KEY).first()
if not kp:
return None, None
exp = now() + timedelta_from_string(connector.auth_session_duration)
token = encode(
{
"iss": PLATFORM_ISSUER,
"aud": str(device.pk),
"iat": int(now().timestamp()),
"exp": int(exp.timestamp()),
"preferred_username": user.username,
**kwargs,
},
kp.private_key,
headers={
"kid": kp.kid,
},
algorithm=JWTAlgorithms.from_private_key(kp.private_key),
)
return token, exp
def agent_auth_fed_validate(
raw_token: str, device: Device
) -> tuple[AccessToken, AgentConnector | None]:
connectors_for_device = AgentConnector.objects.filter(device__in=[device])
connector = connectors_for_device.first()
providers = OAuth2Provider.objects.filter(agentconnector__in=connectors_for_device)
federated_token = AccessToken.objects.filter(token=raw_token, provider__in=providers).first()
if not federated_token:
LOGGER.warning("Couldn't lookup provider")
raise Http404
_key, _alg = federated_token.provider.jwt_key
try:
decode(
raw_token,
_key,
algorithms=[_alg],
options={
"verify_aud": False,
},
)
return federated_token, connector
except (PyJWTError, ValueError, TypeError, AttributeError) as exc:
LOGGER.warning("failed to verify JWT", exc=exc, provider=federated_token.provider.name)
raise ValidationError() from None
def check_device_policies(device: Device, user: User, request: HttpRequest):
"""Check policies bound to device group and device"""
if device.access_group:
result = check_pbm_policies(device.access_group, user, request)
if result.passing:
return result
return check_pbm_policies(device, user, request)
def check_pbm_policies(pbm: PolicyBindingModel, user: User, request: HttpRequest):
policy_engine = PolicyEngine(pbm, user, request)
policy_engine.use_cache = False
policy_engine.empty_result = False
policy_engine.mode = pbm.policy_engine_mode
policy_engine.build()
result = policy_engine.result
LOGGER.debug("PolicyAccessView user_has_access", user=user.username, result=result, pbm=pbm.pk)
return result

View File

@@ -1,121 +0,0 @@
from base64 import urlsafe_b64encode
from json import dumps
from secrets import token_bytes
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives.ciphers import Cipher
from cryptography.hazmat.primitives.ciphers.algorithms import AES
from cryptography.hazmat.primitives.ciphers.modes import GCM
from cryptography.hazmat.primitives.kdf.concatkdf import ConcatKDFHash
from django.http import HttpResponse
from jwcrypto.common import base64url_decode, base64url_encode
from authentik.endpoints.connectors.agent.models import AgentDeviceConnection
def length_prefixed(data: bytes) -> bytes:
length = len(data)
return length.to_bytes(4, "big") + data
def build_apu(public_key: ec.EllipticCurvePublicKey):
# X9.63 representation: 0x04 || X || Y
public_numbers = public_key.public_numbers()
x_bytes = public_numbers.x.to_bytes(32, "big")
y_bytes = public_numbers.y.to_bytes(32, "big")
x963 = bytes([0x04]) + x_bytes + y_bytes
result = length_prefixed(b"APPLE") + length_prefixed(x963)
return result
def encrypt_token_with_a256_gcm(body: dict, device_encryption_key: str, apv: bytes) -> str:
ephemeral_key = ec.generate_private_key(curve=ec.SECP256R1())
device_public_key = serialization.load_pem_public_key(
device_encryption_key.encode(), backend=default_backend()
)
shared_secret_z = ephemeral_key.exchange(ec.ECDH(), device_public_key)
apu = build_apu(ephemeral_key.public_key())
jwe_header = {
"enc": "A256GCM",
"kid": "ephemeralKey",
"epk": {
"x": base64url_encode(
ephemeral_key.public_key().public_numbers().x.to_bytes(32, "big")
),
"y": base64url_encode(
ephemeral_key.public_key().public_numbers().y.to_bytes(32, "big")
),
"kty": "EC",
"crv": "P-256",
},
"typ": "platformsso-login-response+jwt",
"alg": "ECDH-ES",
"apu": base64url_encode(apu),
"apv": base64url_encode(apv),
}
party_u_info = length_prefixed(apu)
party_v_info = length_prefixed(apv)
supp_pub_info = (256).to_bytes(4, "big")
other_info = length_prefixed(b"A256GCM") + party_u_info + party_v_info + supp_pub_info
ckdf = ConcatKDFHash(
algorithm=hashes.SHA256(),
length=32,
otherinfo=other_info,
)
derived_key = ckdf.derive(shared_secret_z)
nonce = token_bytes(96)
header_json = dumps(jwe_header, separators=(",", ":")).encode()
aad = urlsafe_b64encode(header_json).rstrip(b"=")
cipher = Cipher(AES(derived_key), GCM(nonce))
encryptor = cipher.encryptor()
encryptor.authenticate_additional_data(aad)
ciphertext = encryptor.update(dumps(body).encode()) + encryptor.finalize()
# base64url encoding
protected_b64 = urlsafe_b64encode(header_json).rstrip(b"=")
iv_b64 = urlsafe_b64encode(nonce).rstrip(b"=")
ciphertext_b64 = urlsafe_b64encode(ciphertext).rstrip(b"=")
tag_b64 = urlsafe_b64encode(encryptor.tag).rstrip(b"=")
jwe_compact = b".".join(
[
protected_b64,
b"",
iv_b64,
ciphertext_b64,
tag_b64,
]
)
return jwe_compact.decode()
class JWEResponse(HttpResponse):
def __init__(
self,
data: dict,
device: AgentDeviceConnection,
apv: str,
):
super().__init__(
content=encrypt_token_with_a256_gcm(
data, device.apple_encryption_key, base64url_decode(apv)
),
content_type="application/platformsso-login-response+jwt",
)

View File

@@ -1,55 +0,0 @@
from json import loads
from urllib.parse import quote
from django.test import TestCase
from django.urls import reverse
from authentik.blueprints.tests import reconcile_app
from authentik.core.tests.utils import create_test_user
from authentik.endpoints.connectors.agent.models import (
AgentConnector,
AgentDeviceConnection,
AppleNonce,
DeviceToken,
EnrollmentToken,
)
from authentik.endpoints.models import Device
from authentik.lib.generators import generate_id
class TestAppleViews(TestCase):
def setUp(self):
self.connector = AgentConnector.objects.create(name=generate_id())
self.token = EnrollmentToken.objects.create(name=generate_id(), connector=self.connector)
self.device = Device.objects.create(
name=generate_id(),
identifier=generate_id(),
)
self.connection = AgentDeviceConnection.objects.create(
device=self.device,
connector=self.connector,
)
self.user = create_test_user()
def test_apple_site_association(self):
res = self.client.get(reverse("authentik_enterprise_endpoints_connectors_agent_root:asa"))
self.assertEqual(res.status_code, 200)
@reconcile_app("authentik_crypto")
def test_apple_jwks(self):
res = self.client.get(reverse("authentik_enterprise_endpoints_connectors_agent:psso-jwks"))
self.assertEqual(res.status_code, 200)
def test_apple_nonce(self):
device_token = DeviceToken.objects.create(device=self.connection)
res = self.client.post(
reverse("authentik_enterprise_endpoints_connectors_agent:psso-nonce"),
data={"x-ak-device-token": quote(device_token.key)},
)
self.assertEqual(res.status_code, 200)
nonce = loads(res.content.decode()).get("Nonce")
self.assertIsNotNone(nonce)
db_nonce = AppleNonce.objects.filter(nonce=nonce).first()
self.assertIsNotNone(db_nonce)
self.assertFalse(db_nonce.is_expired)

View File

@@ -1,39 +0,0 @@
from base64 import urlsafe_b64decode
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import ec
from django.test import TestCase
from jwcrypto.jwe import JWE
from jwcrypto.jwk import JWK
from authentik.enterprise.endpoints.connectors.agent.http import (
base64url_decode,
encrypt_token_with_a256_gcm,
)
class TestAppleJWE(TestCase):
def test_encrypt(self):
data = {"foo": "bar"}
apv = (
"AAAABUFwcGxlAAAAQQTFgZOospN6KbkhXhx1lfa-AKYxjEfJhTJrkpdEY_srMmkPzS7VN0Bzt2AtNBEXE"
"aphDONiP2Mq6Oxytv5JKOxHAAAAJDgyOThERkY5LTVFMUUtNEUwMS04OEUwLUI3QkQzOUM4QjA3Qw"
)
key = ec.generate_private_key(curve=ec.SECP256R1())
pub = (
key.public_key()
.public_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PublicFormat.SubjectPublicKeyInfo,
)
.decode()
)
res = encrypt_token_with_a256_gcm(data, pub, base64url_decode(apv))
parsed = JWE()
parsed.deserialize(res, JWK.from_pyca(key))
payload = parsed.payload
self.assertEqual(payload, b'{"foo": "bar"}')
self.assertEqual(parsed.jose_header["apv"], apv)
self.assertEqual(parsed.jose_header["typ"], "platformsso-login-response+jwt")
self.assertIn(b"APPLE", urlsafe_b64decode(parsed.jose_header["apu"]))

View File

@@ -1,110 +0,0 @@
from json import loads
from unittest.mock import MagicMock, patch
from django.urls import reverse
from rest_framework.test import APITestCase
from authentik.blueprints.tests import reconcile_app
from authentik.core.tests.utils import create_test_user
from authentik.endpoints.connectors.agent.models import (
AgentConnector,
AgentDeviceConnection,
DeviceAuthenticationToken,
DeviceToken,
EnrollmentToken,
)
from authentik.endpoints.models import Device
from authentik.enterprise.license import LicenseKey
from authentik.enterprise.models import License
from authentik.enterprise.tests.test_license import expiry_valid
from authentik.lib.generators import generate_id
class TestAppleRegister(APITestCase):
def setUp(self):
self.connector = AgentConnector.objects.create(name=generate_id())
self.token = EnrollmentToken.objects.create(name=generate_id(), connector=self.connector)
self.device = Device.objects.create(
name=generate_id(),
identifier=generate_id(),
)
self.connection = AgentDeviceConnection.objects.create(
device=self.device,
connector=self.connector,
)
self.user = create_test_user()
self.device_token = DeviceToken.objects.create(device=self.connection)
@patch(
"authentik.enterprise.license.LicenseKey.validate",
MagicMock(
return_value=LicenseKey(
aud="",
exp=expiry_valid,
name=generate_id(),
internal_users=100,
external_users=100,
)
),
)
@reconcile_app("authentik_crypto")
def test_register_device(self):
License.objects.create(key=generate_id())
response = self.client.post(
reverse("authentik_api:psso-register-device"),
data={
"device_signing_key": generate_id(),
"device_encryption_key": generate_id(),
"sign_key_id": generate_id(),
"enc_key_id": generate_id(),
},
HTTP_AUTHORIZATION=f"Bearer+agent {self.device_token.key}",
)
self.assertEqual(response.status_code, 200)
self.assertJSONEqual(
response.content,
{
"client_id": str(self.connector.pk),
"audience": str(self.device.pk),
"issuer": "http://testserver/endpoints/agent/psso/token/",
"jwks_endpoint": "http://testserver/endpoints/agent/psso/jwks/",
"nonce_endpoint": "http://testserver/endpoints/agent/psso/nonce/",
"token_endpoint": "http://testserver/endpoints/agent/psso/token/",
},
)
@patch(
"authentik.enterprise.license.LicenseKey.validate",
MagicMock(
return_value=LicenseKey(
aud="",
exp=expiry_valid,
name=generate_id(),
internal_users=100,
external_users=100,
)
),
)
@reconcile_app("authentik_crypto")
def test_register_user(self):
License.objects.create(key=generate_id())
device_auth = DeviceAuthenticationToken.objects.create(
device=self.device,
device_token=self.device_token,
connector=self.connector,
user=self.user,
token=generate_id(),
)
response = self.client.post(
reverse("authentik_api:psso-register-user"),
data={
"user_auth": device_auth.token,
"user_secure_enclave_key": generate_id(),
"enclave_key_id": generate_id(),
},
HTTP_AUTHORIZATION=f"Bearer+agent {self.device_token.key}",
)
self.assertEqual(response.status_code, 200)
body = loads(response.content)
self.assertEqual(body["username"], self.user.username)

View File

@@ -1,108 +0,0 @@
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import ec
from django.test import TestCase
from django.urls import reverse
from jwt import encode
from authentik.blueprints.tests import reconcile_app
from authentik.core.tests.utils import create_test_cert, create_test_user
from authentik.crypto.builder import PrivateKeyAlg
from authentik.endpoints.connectors.agent.models import (
AgentConnector,
AgentDeviceConnection,
AgentDeviceUserBinding,
AppleNonce,
DeviceToken,
EnrollmentToken,
)
from authentik.endpoints.models import Device
from authentik.lib.generators import generate_id
from authentik.providers.oauth2.models import JWTAlgorithms
class TestAppleToken(TestCase):
def setUp(self):
self.apple_sign_key = create_test_cert(PrivateKeyAlg.ECDSA)
sign_key_pem = self.apple_sign_key.public_key.public_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PublicFormat.SubjectPublicKeyInfo,
).decode()
self.enc_key = ec.generate_private_key(curve=ec.SECP256R1())
self.enc_pub = (
self.enc_key.public_key()
.public_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PublicFormat.SubjectPublicKeyInfo,
)
.decode()
)
self.connector = AgentConnector.objects.create(name=generate_id())
self.token = EnrollmentToken.objects.create(name=generate_id(), connector=self.connector)
self.device = Device.objects.create(
name=generate_id(),
identifier=generate_id(),
)
self.connection = AgentDeviceConnection.objects.create(
device=self.device,
connector=self.connector,
apple_sign_key_id=self.apple_sign_key.kid,
apple_signing_key=sign_key_pem,
apple_encryption_key=self.enc_pub,
)
self.user = create_test_user()
AgentDeviceUserBinding.objects.create(
target=self.device,
user=self.user,
order=0,
apple_enclave_key_id=self.apple_sign_key.kid,
apple_secure_enclave_key=sign_key_pem,
)
self.device_token = DeviceToken.objects.create(device=self.connection)
@reconcile_app("authentik_crypto")
def test_token(self):
nonce = generate_id()
AppleNonce.objects.create(
device_token=self.device_token,
nonce=nonce,
)
embedded = encode(
{"iss": str(self.connector.pk), "aud": str(self.device.pk), "request_nonce": nonce},
self.apple_sign_key.private_key,
headers={
"kid": self.apple_sign_key.kid,
},
algorithm=JWTAlgorithms.from_private_key(self.apple_sign_key.private_key),
)
assertion = encode(
{
"iss": str(self.connector.pk),
"aud": "http://testserver/endpoints/agent/psso/token/",
"request_nonce": nonce,
"assertion": embedded,
"jwe_crypto": {
"apv": (
"AAAABUFwcGxlAAAAQQTFgZOospN6KbkhXhx1lfa-AKYxjEfJhTJrkpdEY_srMmkPzS7VN0Bzt2AtNBEXE"
"aphDONiP2Mq6Oxytv5JKOxHAAAAJDgyOThERkY5LTVFMUUtNEUwMS04OEUwLUI3QkQzOUM4QjA3Qw"
)
},
},
self.apple_sign_key.private_key,
headers={
"kid": self.apple_sign_key.kid,
},
algorithm=JWTAlgorithms.from_private_key(self.apple_sign_key.private_key),
)
res = self.client.post(
reverse("authentik_enterprise_endpoints_connectors_agent:psso-token"),
data={
"assertion": assertion,
"platform_sso_version": "1.0",
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
},
)
self.assertEqual(res.status_code, 200)

View File

@@ -1,111 +0,0 @@
from json import loads
from django.urls import reverse
from django.utils.timezone import now
from jwt import decode
from rest_framework.test import APITestCase
from authentik.blueprints.tests import reconcile_app
from authentik.core.models import Group
from authentik.core.tests.utils import create_test_user
from authentik.endpoints.connectors.agent.api.connectors import AgentDeviceConnection
from authentik.endpoints.connectors.agent.models import AgentConnector, EnrollmentToken
from authentik.endpoints.models import Device, DeviceAccessGroup
from authentik.lib.generators import generate_id
from authentik.policies.models import PolicyBinding
from authentik.providers.oauth2.models import AccessToken, OAuth2Provider
class TestConnectorAuthFed(APITestCase):
def setUp(self):
self.connector = AgentConnector.objects.create(name=generate_id())
self.token = EnrollmentToken.objects.create(name=generate_id(), connector=self.connector)
self.device = Device.objects.create(
name=generate_id(),
identifier=generate_id(),
)
self.connection = AgentDeviceConnection.objects.create(
device=self.device,
connector=self.connector,
)
self.user = create_test_user()
self.provider = OAuth2Provider.objects.create(name=generate_id())
self.raw_token = self.provider.encode({"foo": "bar"})
self.token = AccessToken.objects.create(
provider=self.provider, user=self.user, token=self.raw_token, auth_time=now()
)
self.connector.jwt_federation_providers.add(self.provider)
@reconcile_app("authentik_crypto")
def test_auth_fed(self):
response = self.client.post(
reverse("authentik_api:agentconnector-auth-fed") + f"?device={self.device.name}",
HTTP_AUTHORIZATION=f"Bearer {self.raw_token}",
)
self.assertEqual(response.status_code, 400)
@reconcile_app("authentik_crypto")
def test_auth_fed_policy_group(self):
device_group = DeviceAccessGroup.objects.create(name=generate_id())
self.device.access_group = device_group
self.device.save()
group = Group.objects.create(name=generate_id())
group.users.add(self.user)
PolicyBinding.objects.create(target=device_group, group=group, order=0)
response = self.client.post(
reverse("authentik_api:agentconnector-auth-fed") + f"?device={self.device.name}",
HTTP_AUTHORIZATION=f"Bearer {self.raw_token}",
)
self.assertEqual(response.status_code, 200)
res = loads(response.content)
token = decode(res["token"], options={"verify_signature": False})
self.assertEqual(token["iss"], "goauthentik.io/platform")
self.assertEqual(token["aud"], str(self.device.pk))
@reconcile_app("authentik_crypto")
def test_auth_fed_policy_group_deny(self):
device_group = DeviceAccessGroup.objects.create(name=generate_id())
self.device.access_group = device_group
self.device.save()
group = Group.objects.create(name=generate_id())
# group.users.add(self.user)
PolicyBinding.objects.create(target=device_group, group=group, order=0)
response = self.client.post(
reverse("authentik_api:agentconnector-auth-fed") + f"?device={self.device.name}",
HTTP_AUTHORIZATION=f"Bearer {self.raw_token}",
)
self.assertEqual(response.status_code, 400)
self.assertJSONEqual(
response.content,
{
"policy_result": "Policy denied access",
"policy_messages": [],
},
)
@reconcile_app("authentik_crypto")
def test_auth_fed_invalid(self):
# No token
response = self.client.post(
reverse("authentik_api:agentconnector-auth-fed") + f"?device={self.device.name}foo",
)
self.assertEqual(response.status_code, 400)
# No device
response = self.client.post(
reverse("authentik_api:agentconnector-auth-fed") + f"?device={self.device.name}foo",
HTTP_AUTHORIZATION=f"Bearer {self.raw_token}",
)
self.assertEqual(response.status_code, 404)
# invalid token
response = self.client.post(
reverse("authentik_api:agentconnector-auth-fed") + f"?device={self.device.name}",
HTTP_AUTHORIZATION=f"Bearer {self.raw_token}aa",
)
self.assertEqual(response.status_code, 404)

View File

@@ -1,128 +0,0 @@
from hashlib import sha256
from json import loads
from unittest.mock import MagicMock, patch
from urllib.parse import parse_qs, urlparse
from django.urls import reverse
from jwt import decode
from authentik.blueprints.tests import reconcile_app
from authentik.core.models import Group
from authentik.core.tests.utils import create_test_flow, create_test_user
from authentik.endpoints.connectors.agent.api.connectors import AgentDeviceConnection
from authentik.endpoints.connectors.agent.models import AgentConnector, DeviceToken, EnrollmentToken
from authentik.endpoints.models import Device, DeviceAccessGroup
from authentik.enterprise.endpoints.connectors.agent.views.auth_interactive import QS_AGENT_IA_TOKEN
from authentik.enterprise.license import LicenseKey
from authentik.enterprise.models import License
from authentik.enterprise.tests.test_license import expiry_valid
from authentik.flows.tests import FlowTestCase
from authentik.lib.generators import generate_id
from authentik.policies.models import PolicyBinding
class TestConnectorAuthIA(FlowTestCase):
def setUp(self):
self.connector = AgentConnector.objects.create(
name=generate_id(),
authorization_flow=create_test_flow(),
)
self.token = EnrollmentToken.objects.create(name=generate_id(), connector=self.connector)
self.device = Device.objects.create(
name=generate_id(),
identifier=generate_id(),
)
self.connection = AgentDeviceConnection.objects.create(
device=self.device,
connector=self.connector,
)
self.device_token = DeviceToken.objects.create(
device=self.connection,
key=generate_id(),
)
self.user = create_test_user()
@patch(
"authentik.enterprise.license.LicenseKey.validate",
MagicMock(
return_value=LicenseKey(
aud="",
exp=expiry_valid,
name=generate_id(),
internal_users=100,
external_users=100,
)
),
)
def test_auth_ia_initiate(self):
License.objects.create(key=generate_id())
response = self.client.post(
reverse("authentik_api:agentconnector-auth-ia"),
HTTP_AUTHORIZATION=f"Bearer+agent {self.device_token.key}",
)
self.assertEqual(response.status_code, 200)
@reconcile_app("authentik_crypto")
def test_auth_ia_fulfill(self):
self.client.force_login(self.user)
response = self.client.post(
reverse("authentik_api:agentconnector-auth-ia"),
HTTP_AUTHORIZATION=f"Bearer+agent {self.device_token.key}",
HTTP_X_AUTHENTIK_PLATFORM_AUTH_DTH=sha256(self.device_token.key.encode()).hexdigest(),
)
self.assertEqual(response.status_code, 200)
res = loads(response.content)
response = self.client.get(
res["url"],
HTTP_AUTHORIZATION=f"Bearer+agent {self.device_token.key}",
HTTP_X_AUTHENTIK_PLATFORM_AUTH_DTH=sha256(self.device_token.key.encode()).hexdigest(),
)
self.assertEqual(response.status_code, 200)
self.assertIn(b"Permission denied", response.content)
@patch(
"authentik.enterprise.license.LicenseKey.validate",
MagicMock(
return_value=LicenseKey(
aud="",
exp=expiry_valid,
name=generate_id(),
internal_users=100,
external_users=100,
)
),
)
@reconcile_app("authentik_crypto")
def test_auth_ia_fulfill_policy(self):
License.objects.create(key=generate_id())
device_group = DeviceAccessGroup.objects.create(name=generate_id())
self.device.access_group = device_group
self.device.save()
group = Group.objects.create(name=generate_id())
group.users.add(self.user)
PolicyBinding.objects.create(target=device_group, group=group, order=0)
self.client.force_login(self.user)
response = self.client.post(
reverse("authentik_api:agentconnector-auth-ia"),
HTTP_AUTHORIZATION=f"Bearer+agent {self.device_token.key}",
HTTP_X_AUTHENTIK_PLATFORM_AUTH_DTH=sha256(self.device_token.key.encode()).hexdigest(),
)
self.assertEqual(response.status_code, 200)
res = loads(response.content)
response = self.client.get(
res["url"],
HTTP_AUTHORIZATION=f"Bearer+agent {self.device_token.key}",
HTTP_X_AUTHENTIK_PLATFORM_AUTH_DTH=sha256(self.device_token.key.encode()).hexdigest(),
)
self.assertEqual(response.status_code, 302)
url = urlparse(response.url)
self.assertEqual(url.scheme, "goauthentik.io")
qs = parse_qs(url.query)
raw_token = qs[QS_AGENT_IA_TOKEN][0]
token = decode(raw_token.encode(), options={"verify_signature": False})
self.assertEqual(token["iss"], "goauthentik.io/platform")
self.assertEqual(token["aud"], str(self.device.pk))

View File

@@ -1,36 +0,0 @@
from django.urls import path
from authentik.enterprise.endpoints.connectors.agent.views.apple_jwks import AppleJWKSView
from authentik.enterprise.endpoints.connectors.agent.views.apple_nonce import NonceView
from authentik.enterprise.endpoints.connectors.agent.views.apple_register import (
RegisterDeviceView,
RegisterUserView,
)
from authentik.enterprise.endpoints.connectors.agent.views.apple_token import TokenView
from authentik.enterprise.endpoints.connectors.agent.views.auth_interactive import (
AgentInteractiveAuth,
)
urlpatterns = [
path(
"authenticate/<uuid:token_uuid>/",
AgentInteractiveAuth.as_view(),
name="authenticate",
),
path("psso/token/", TokenView.as_view(), name="psso-token"),
path("psso/jwks/", AppleJWKSView.as_view(), name="psso-jwks"),
path("psso/nonce/", NonceView.as_view(), name="psso-nonce"),
]
api_urlpatterns = [
path(
"endpoints/agents/psso/register/device/",
RegisterDeviceView.as_view(),
name="psso-register-device",
),
path(
"endpoints/agents/psso/register/user/",
RegisterUserView.as_view(),
name="psso-register-user",
),
]

View File

@@ -1,14 +0,0 @@
from django.http import Http404
from authentik.crypto.apps import MANAGED_KEY
from authentik.crypto.models import CertificateKeyPair
from authentik.providers.oauth2.views.jwks import JWKSView
class AppleJWKSView(JWKSView):
def get_keys(self):
kp = CertificateKeyPair.objects.filter(managed=MANAGED_KEY).first()
if not kp:
raise Http404
yield self.get_jwk_for_key(kp, "sig")

View File

@@ -1,32 +0,0 @@
from base64 import b64encode
from datetime import timedelta
from secrets import token_bytes
from urllib.parse import unquote
from django.http import HttpRequest, HttpResponseBadRequest, JsonResponse
from django.utils.decorators import method_decorator
from django.utils.timezone import now
from django.views import View
from django.views.decorators.csrf import csrf_exempt
from authentik.endpoints.connectors.agent.models import AppleNonce, DeviceToken
@method_decorator(csrf_exempt, name="dispatch")
class NonceView(View):
def post(self, request: HttpRequest, *args, **kwargs):
raw_token = unquote(self.request.POST.get("x-ak-device-token"))
device_token = DeviceToken.filter_not_expired(key=raw_token).first()
if not device_token:
return HttpResponseBadRequest()
nonce = AppleNonce.objects.create(
nonce=b64encode(token_bytes(32)).decode(),
expires=now() + timedelta(minutes=5),
device_token=device_token,
)
return JsonResponse(
{
"Nonce": nonce.nonce,
}
)

View File

@@ -1,131 +0,0 @@
from django.urls import reverse
from drf_spectacular.utils import extend_schema
from rest_framework.exceptions import ValidationError
from rest_framework.fields import CharField
from rest_framework.permissions import IsAuthenticated
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.views import APIView
from authentik.api.validation import validate
from authentik.core.api.users import UserSelfSerializer
from authentik.core.api.utils import PassiveSerializer
from authentik.endpoints.connectors.agent.auth import AgentAuth
from authentik.endpoints.connectors.agent.models import (
AgentDeviceConnection,
AgentDeviceUserBinding,
DeviceAuthenticationToken,
DeviceToken,
)
from authentik.enterprise.api import EnterpriseRequiredMixin
from authentik.lib.generators import generate_key
class RegisterDeviceView(APIView):
class AgentPSSODeviceRegistration(EnterpriseRequiredMixin, PassiveSerializer):
"""Register Apple device via Platform SSO"""
device_signing_key = CharField()
device_encryption_key = CharField()
sign_key_id = CharField()
enc_key_id = CharField()
class AgentPSSODeviceRegistrationResponse(PassiveSerializer):
"""authentik settings for Platform SSO tokens"""
client_id = CharField()
issuer = CharField()
token_endpoint = CharField()
jwks_endpoint = CharField()
audience = CharField()
nonce_endpoint = CharField()
permission_classes = [IsAuthenticated]
pagination_class = None
filter_backends = []
serializer_class = AgentPSSODeviceRegistration
authentication_classes = [AgentAuth]
@extend_schema(
responses={
200: AgentPSSODeviceRegistrationResponse(),
}
)
@validate(AgentPSSODeviceRegistration)
def post(self, request: Request, body: AgentPSSODeviceRegistration) -> Response:
device_token: DeviceToken = request.auth
conn: AgentDeviceConnection = device_token.device
conn.apple_signing_key = body.validated_data["device_signing_key"]
conn.apple_encryption_key = body.validated_data["device_encryption_key"]
conn.apple_sign_key_id = body.validated_data["sign_key_id"]
conn.apple_enc_key_id = body.validated_data["enc_key_id"]
conn.apple_key_exchange_key = generate_key()
conn.save()
return Response(
data={
"client_id": str(conn.connector.pk),
"issuer": self.request.build_absolute_uri(
reverse("authentik_enterprise_endpoints_connectors_agent:psso-token")
),
"audience": str(conn.device.pk),
"token_endpoint": request.build_absolute_uri(
reverse("authentik_enterprise_endpoints_connectors_agent:psso-token")
),
"jwks_endpoint": request.build_absolute_uri(
reverse("authentik_enterprise_endpoints_connectors_agent:psso-jwks")
),
"nonce_endpoint": request.build_absolute_uri(
reverse("authentik_enterprise_endpoints_connectors_agent:psso-nonce")
),
}
)
class RegisterUserView(APIView):
class AgentPSSOUserRegistration(EnterpriseRequiredMixin, PassiveSerializer):
"""Register Apple device user via Platform SSO"""
user_auth = CharField()
user_secure_enclave_key = CharField()
enclave_key_id = CharField()
permission_classes = [IsAuthenticated]
pagination_class = None
filter_backends = []
serializer_class = AgentPSSOUserRegistration
authentication_classes = [AgentAuth]
@extend_schema(
responses={
200: UserSelfSerializer(),
}
)
@validate(AgentPSSOUserRegistration)
def post(self, request: Request, body: AgentPSSOUserRegistration) -> Response:
device_token: DeviceToken = request.auth
conn: AgentDeviceConnection = device_token.device
user_token = DeviceAuthenticationToken.filter_not_expired(
device=conn.device,
token=body.validated_data["user_auth"],
device_token=device_token,
).first()
if not user_token:
raise ValidationError("Invalid user authentication")
AgentDeviceUserBinding.objects.update_or_create(
target=conn.device,
user=user_token.user,
connector=conn.connector,
create_defaults={
"is_primary": True,
"order": 0,
},
defaults={
"apple_secure_enclave_key": body.validated_data["user_secure_enclave_key"],
"apple_enclave_key_id": body.validated_data["enclave_key_id"],
},
)
return Response(
UserSelfSerializer(instance=user_token.user, context={"request": request}).data
)

View File

@@ -1,185 +0,0 @@
from typing import Any
from django.http import HttpRequest, HttpResponse
from django.urls import reverse
from django.utils.decorators import method_decorator
from django.utils.timezone import now
from django.views import View
from django.views.decorators.csrf import csrf_exempt
from jwt import PyJWTError, decode, encode, get_unverified_header
from rest_framework.exceptions import ValidationError
from structlog.stdlib import get_logger
from authentik.core.models import AuthenticatedSession, Session, User
from authentik.core.sessions import SessionStore
from authentik.crypto.apps import MANAGED_KEY
from authentik.crypto.models import CertificateKeyPair
from authentik.endpoints.connectors.agent.models import (
AgentConnector,
AgentDeviceConnection,
AgentDeviceUserBinding,
AppleNonce,
DeviceAuthenticationToken,
)
from authentik.enterprise.endpoints.connectors.agent.http import JWEResponse
from authentik.events.models import Event, EventAction
from authentik.events.signals import SESSION_LOGIN_EVENT
from authentik.lib.utils.time import timedelta_from_string
from authentik.providers.oauth2.constants import TOKEN_TYPE
from authentik.providers.oauth2.id_token import IDToken
from authentik.providers.oauth2.models import JWTAlgorithms
from authentik.root.middleware import SessionMiddleware
LOGGER = get_logger()
@method_decorator(csrf_exempt, name="dispatch")
class TokenView(View):
device_connection: AgentDeviceConnection
connector: AgentConnector
def post(self, request: HttpRequest) -> HttpResponse:
assertion = request.POST.get("assertion", request.POST.get("request"))
if not assertion:
return HttpResponse(status=400)
self.now = now()
try:
self.jwt_request = self.validate_request_token(assertion)
except PyJWTError as exc:
LOGGER.warning("failed to parse JWT", exc=exc)
raise ValidationError("Invalid request") from None
version = request.POST.get("platform_sso_version")
grant_type = request.POST.get("grant_type")
handler_func = (
f"handle_v{version}_{grant_type}".replace("-", "_")
.replace("+", "_")
.replace(":", "_")
.replace(".", "_")
)
handler = getattr(self, handler_func, None)
if not handler:
LOGGER.debug("Handler not found", handler=handler_func)
return HttpResponse(status=400)
LOGGER.debug("sending to handler", handler=handler_func)
return handler()
def validate_request_token(self, assertion: str) -> dict[str, Any]:
# Decode without validation to get header
header = get_unverified_header(assertion)
LOGGER.debug("token header", header=header)
expected_kid = header["kid"]
self.device_connection = (
AgentDeviceConnection.objects.filter(apple_sign_key_id=expected_kid)
.select_related("device")
.first()
)
self.connector = AgentConnector.objects.get(pk=self.device_connection.connector.pk)
LOGGER.debug("got device", device=self.device_connection.device)
expected_aud = self.request.build_absolute_uri(
reverse("authentik_enterprise_endpoints_connectors_agent:psso-token")
)
if not self.device_connection.apple_signing_key:
LOGGER.warning("Failed to issue token for device, no apple_signing_key")
raise ValidationError("Invalid request")
# Properly decode the JWT with the key from the device
decoded = decode(
assertion,
self.device_connection.apple_signing_key,
algorithms=["ES256"],
audience=expected_aud,
issuer=str(self.connector.pk),
)
self.remote_nonce = decoded.get("nonce")
# Check that the nonce hasn't been used before
nonce = AppleNonce.filter_not_expired(nonce=decoded["request_nonce"]).first()
if not nonce:
raise ValidationError("Invalid nonce")
self.nonce = nonce
nonce.delete()
return decoded
def validate_embedded_assertion(self, assertion: str) -> tuple[AgentDeviceUserBinding, dict]:
"""Decode an embedded assertion and validate it by looking up the matching device user"""
decode_unvalidated = get_unverified_header(assertion)
expected_kid = decode_unvalidated["kid"]
device_user = AgentDeviceUserBinding.objects.filter(
target=self.device_connection.device, apple_enclave_key_id=expected_kid
).first()
if not device_user:
LOGGER.warning("Could not find device user binding for user")
raise ValidationError("Invalid request")
decoded: dict[str, Any] = decode(
assertion,
device_user.apple_secure_enclave_key,
audience=str(self.device_connection.device.pk),
algorithms=["ES256"],
)
if decoded.get("nonce") != self.jwt_request.get("nonce"):
LOGGER.warning("Mis-matched nonce to outer assertion")
raise ValidationError("Invalid nonce")
return device_user, decoded
def create_auth_session(self, user: User):
event = Event.new(EventAction.LOGIN).from_http(self.request, user=user)
store = SessionStore()
store[SESSION_LOGIN_EVENT] = event
store.save()
session = Session.objects.filter(session_key=store.session_key).first()
session.expires = self.now + timedelta_from_string(self.connector.auth_session_duration)
AuthenticatedSession.objects.create(session=session, user=user)
session = SessionMiddleware.encode_session(store.session_key, user)
return session
def create_id_token(self, user: User, **kwargs):
issuer = self.request.build_absolute_uri(
reverse("authentik_enterprise_endpoints_connectors_agent:psso-token")
)
id_token = IDToken(
iss=issuer,
sub=user.username,
aud=str(self.connector.pk),
exp=int(
(self.now + timedelta_from_string(self.connector.auth_session_duration)).timestamp()
),
iat=int(now().timestamp()),
**kwargs,
)
kp = CertificateKeyPair.objects.filter(managed=MANAGED_KEY).first()
return encode(
id_token.to_dict(),
kp.private_key,
headers={
"kid": kp.kid,
},
algorithm=JWTAlgorithms.from_private_key(kp.private_key),
)
def handle_v1_0_urn_ietf_params_oauth_grant_type_jwt_bearer(self):
try:
user, inner = self.validate_embedded_assertion(self.jwt_request["assertion"])
except PyJWTError as exc:
LOGGER.warning("failed to validate inner assertion", exc=exc)
raise ValidationError("Invalid request") from None
id_token = self.create_id_token(user.user)
auth_token = DeviceAuthenticationToken.objects.create(
device=self.device_connection.device,
connector=self.connector,
user=user.user,
device_token=self.nonce.device_token,
)
return JWEResponse(
{
"refresh_token": auth_token.token,
"refresh_token_expires_in": int((auth_token.expires - now()).total_seconds()),
"id_token": id_token,
"token_type": TOKEN_TYPE,
"session_key": self.create_auth_session(user.user),
},
device=self.device_connection,
apv=self.jwt_request["jwe_crypto"]["apv"],
)

View File

@@ -1,111 +0,0 @@
from hashlib import sha256
from hmac import compare_digest
from django.http import Http404, HttpRequest, HttpResponse, HttpResponseBadRequest, QueryDict
from authentik.endpoints.connectors.agent.models import AgentConnector, DeviceAuthenticationToken
from authentik.endpoints.models import Device
from authentik.enterprise.endpoints.connectors.agent.auth import (
agent_auth_issue_token,
check_device_policies,
)
from authentik.enterprise.policy import EnterprisePolicyAccessView
from authentik.flows.exceptions import FlowNonApplicableException
from authentik.flows.models import in_memory_stage
from authentik.flows.planner import PLAN_CONTEXT_DEVICE, FlowPlanner
from authentik.flows.stage import StageView
from authentik.providers.oauth2.utils import HttpResponseRedirectScheme
PLAN_CONTEXT_DEVICE_AUTH_TOKEN = "goauthentik.io/endpoints/device_auth_token" # nosec
QS_AGENT_IA_TOKEN = "ak-auth-ia-token" # nosec
class AgentInteractiveAuth(EnterprisePolicyAccessView):
"""Agent device authentication"""
auth_token: DeviceAuthenticationToken
device: Device
connector: AgentConnector
def resolve_provider_application(self):
auth_token = (
DeviceAuthenticationToken.filter_not_expired(identifier=self.kwargs["token_uuid"])
.prefetch_related()
.first()
)
if not auth_token:
raise Http404
self.auth_token = auth_token
self.device = auth_token.device
self.connector = auth_token.connector.agentconnector
def user_has_access(self, user=None, pbm=None):
enterprise_result = self.check_license()
if not enterprise_result.passing:
return enterprise_result
return check_device_policies(self.device, user or self.request.user, self.request)
def modify_flow_context(self, flow, context):
return {
PLAN_CONTEXT_DEVICE: self.device,
**context,
}
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
device_token_hash = request.headers.get("X-Authentik-Platform-Auth-DTH")
if not device_token_hash:
return HttpResponseBadRequest("Invalid device token")
if not compare_digest(
device_token_hash, sha256(self.auth_token.device_token.key.encode()).hexdigest()
):
return HttpResponseBadRequest("Invalid device token")
planner = FlowPlanner(self.connector.authorization_flow)
planner.allow_empty_flows = True
try:
plan = planner.plan(
self.request,
{
PLAN_CONTEXT_DEVICE: self.device,
PLAN_CONTEXT_DEVICE_AUTH_TOKEN: self.auth_token,
},
)
except FlowNonApplicableException:
return self.handle_no_permission_authenticated()
plan.append_stage(in_memory_stage(AgentAuthFulfillmentStage))
return plan.to_redirect(
self.request,
self.connector.authorization_flow,
allowed_silent_types=[AgentAuthFulfillmentStage],
)
class AgentAuthFulfillmentStage(StageView):
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
device: Device = self.executor.plan.context.pop(PLAN_CONTEXT_DEVICE)
auth_token: DeviceAuthenticationToken = self.executor.plan.context.pop(
PLAN_CONTEXT_DEVICE_AUTH_TOKEN
)
token, exp = agent_auth_issue_token(
device,
auth_token.connector.agentconnector,
request.user,
jti=str(auth_token.identifier),
)
if not token or not exp:
return self.executor.stage_invalid("Failed to generate token")
auth_token.user = request.user
auth_token.token = token
auth_token.expires = exp
auth_token.expiring = True
auth_token.save()
qd = QueryDict(mutable=True)
qd[QS_AGENT_IA_TOKEN] = token
return HttpResponseRedirectScheme(
"goauthentik.io://platform/finished?" + qd.urlencode(),
allowed_schemes=["goauthentik.io"],
)

View File

@@ -4,7 +4,7 @@ from django.utils.translation import gettext_lazy as _
from authentik.core.models import User, UserTypes
from authentik.enterprise.license import LicenseKey
from authentik.policies.types import PolicyResult
from authentik.policies.types import PolicyRequest, PolicyResult
from authentik.policies.views import PolicyAccessView
@@ -21,6 +21,8 @@ class EnterprisePolicyAccessView(PolicyAccessView):
def user_has_access(self, user: User | None = None) -> PolicyResult:
user = user or self.request.user
request = PolicyRequest(user)
request.http_request = self.request
result = super().user_has_access(user)
enterprise_result = self.check_license()
if not enterprise_result.passing:

View File

@@ -14,12 +14,7 @@ from authentik.core.models import AuthenticatedSession, User
from authentik.core.signals import login_failed, password_changed
from authentik.events.models import Event, EventAction
from authentik.flows.models import Stage
from authentik.flows.planner import (
PLAN_CONTEXT_DEVICE,
PLAN_CONTEXT_OUTPOST,
PLAN_CONTEXT_SOURCE,
FlowPlan,
)
from authentik.flows.planner import PLAN_CONTEXT_OUTPOST, PLAN_CONTEXT_SOURCE, FlowPlan
from authentik.flows.views.executor import SESSION_KEY_PLAN
from authentik.stages.invitation.models import Invitation
from authentik.stages.invitation.signals import invitation_used
@@ -47,9 +42,6 @@ def on_user_logged_in(sender, request: HttpRequest, user: User, **_):
if PLAN_CONTEXT_OUTPOST in flow_plan.context:
# Save outpost context
kwargs[PLAN_CONTEXT_OUTPOST] = flow_plan.context[PLAN_CONTEXT_OUTPOST]
if PLAN_CONTEXT_DEVICE in flow_plan.context:
# Save device
kwargs[PLAN_CONTEXT_DEVICE] = flow_plan.context[PLAN_CONTEXT_DEVICE]
event = Event.new(EventAction.LOGIN, **kwargs).from_http(request, user=user)
request.session[SESSION_LOGIN_EVENT] = event
request.session.save()

View File

@@ -37,7 +37,6 @@ PLAN_CONTEXT_PENDING_USER = "pending_user"
PLAN_CONTEXT_SSO = "is_sso"
PLAN_CONTEXT_REDIRECT = "redirect"
PLAN_CONTEXT_APPLICATION = "application"
PLAN_CONTEXT_DEVICE = "device"
PLAN_CONTEXT_SOURCE = "source"
PLAN_CONTEXT_OUTPOST = "outpost"
PLAN_CONTEXT_POST = "goauthentik.io/http/post"

View File

@@ -1,8 +1,6 @@
"""authentik OAuth2 JWKS Views"""
from base64 import b64encode, urlsafe_b64encode
from collections.abc import Generator
from typing import Literal
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric.ec import (
@@ -66,7 +64,7 @@ class JWKSView(View):
"""Show RSA Key data for Provider"""
@staticmethod
def get_jwk_for_key(key: CertificateKeyPair, use: Literal["sig", "enc"]) -> dict | None:
def get_jwk_for_key(key: CertificateKeyPair, use: str) -> dict | None:
"""Convert a certificate-key pair into JWK"""
private_key = key.private_key
key_data = None
@@ -114,9 +112,10 @@ class JWKSView(View):
)
return key_data
def get_keys(self) -> Generator[dict | None]:
def get(self, request: HttpRequest, application_slug: str) -> HttpResponse:
"""Show JWK Key data for Provider"""
provider_ids = Application.objects.filter(
slug=self.kwargs["application_slug"],
slug=application_slug,
).values_list(
"provider_id",
flat=True,
@@ -130,15 +129,15 @@ class JWKSView(View):
if provider is None:
raise Http404()
if signing_key := provider.signing_key:
yield JWKSView.get_jwk_for_key(signing_key, "sig")
if encryption_key := provider.encryption_key:
yield JWKSView.get_jwk_for_key(encryption_key, "enc")
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
"""Show JWK Key data for Provider"""
response_data = {}
for jwk in self.get_keys():
if signing_key := provider.signing_key:
jwk = JWKSView.get_jwk_for_key(signing_key, "sig")
if jwk:
response_data.setdefault("keys", [])
response_data["keys"].append(jwk)
if encryption_key := provider.encryption_key:
jwk = JWKSView.get_jwk_for_key(encryption_key, "enc")
if jwk:
response_data.setdefault("keys", [])
response_data["keys"].append(jwk)

View File

@@ -557,8 +557,6 @@ class TokenView(View):
provider: OAuth2Provider | None = None
params: TokenParams | None = None
params_class = TokenParams
provider_class = OAuth2Provider
def dispatch(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse:
response = super().dispatch(request, *args, **kwargs)
@@ -578,14 +576,12 @@ class TokenView(View):
op="authentik.providers.oauth2.post.parse",
):
client_id, client_secret = extract_client_auth(request)
self.provider = self.provider_class.objects.filter(client_id=client_id).first()
self.provider = OAuth2Provider.objects.filter(client_id=client_id).first()
if not self.provider:
LOGGER.warning("OAuth2Provider does not exist", client_id=client_id)
raise TokenError("invalid_client")
CTX_AUTH_VIA.set("oauth_client_secret")
self.params = self.params_class.parse(
request, self.provider, client_id, client_secret
)
self.params = TokenParams.parse(request, self.provider, client_id, client_secret)
with start_span(
op="authentik.providers.oauth2.post.response",

View File

@@ -1,15 +1,12 @@
"""Group client"""
from itertools import batched
from typing import Any
from django.db import transaction
from orjson import dumps
from pydantic import ValidationError
from pydanticscim.group import GroupMember
from authentik.core.models import Group
from authentik.lib.merge import MERGE_LIST_UNIQUE
from authentik.lib.sync.mapper import PropertyMappingManager
from authentik.lib.sync.outgoing.base import Direction
from authentik.lib.sync.outgoing.exceptions import (
@@ -117,23 +114,10 @@ class SCIMGroupClient(SCIMClient[Group, SCIMProviderGroup, SCIMGroupSchema]):
self._patch_add_users(connection, users)
return connection
def diff(self, local_created: dict[str, Any], connection: SCIMProviderUser):
"""Check if a group is different than what we last wrote to the remote system.
Returns true if there is a difference in data."""
local_known = connection.attributes
local_updated = {}
MERGE_LIST_UNIQUE.merge(local_updated, local_known)
MERGE_LIST_UNIQUE.merge(local_updated, local_created)
return dumps(local_updated) != dumps(local_known)
def update(self, group: Group, connection: SCIMProviderGroup):
"""Update existing group"""
scim_group = self.to_schema(group, connection)
scim_group.id = connection.scim_id
payload = scim_group.model_dump(mode="json", exclude_unset=True)
if not self.diff(payload, connection):
self.logger.debug("Skipping group write as data has not changed")
return self.patch_compare_users(group)
try:
if self._config.patch.supported:
return self._update_patch(group, scim_group, connection)

View File

@@ -1,14 +1,10 @@
"""User client"""
from typing import Any
from django.db import transaction
from django.utils.http import urlencode
from orjson import dumps
from pydantic import ValidationError
from authentik.core.models import User
from authentik.lib.merge import MERGE_LIST_UNIQUE
from authentik.lib.sync.mapper import PropertyMappingManager
from authentik.lib.sync.outgoing.exceptions import ObjectExistsSyncException, StopSync
from authentik.policies.utils import delete_none_values
@@ -96,30 +92,17 @@ class SCIMUserClient(SCIMClient[User, SCIMProviderUser, SCIMUserSchema]):
provider=self.provider, user=user, scim_id=scim_id, attributes=response
)
def diff(self, local_created: dict[str, Any], connection: SCIMProviderUser):
"""Check if a user is different than what we last wrote to the remote system.
Returns true if there is a difference in data."""
local_known = connection.attributes
local_updated = {}
MERGE_LIST_UNIQUE.merge(local_updated, local_known)
MERGE_LIST_UNIQUE.merge(local_updated, local_created)
return dumps(local_updated) != dumps(local_known)
def update(self, user: User, connection: SCIMProviderUser):
"""Update existing user"""
scim_user = self.to_schema(user, connection)
scim_user.id = connection.scim_id
payload = scim_user.model_dump(
mode="json",
exclude_unset=True,
)
if not self.diff(payload, connection):
self.logger.debug("Skipping user write as data has not changed")
return
response = self._request(
"PUT",
f"/Users/{connection.scim_id}",
json=payload,
json=scim_user.model_dump(
mode="json",
exclude_unset=True,
),
)
connection.attributes = response
connection.save()

View File

@@ -9,7 +9,7 @@ from requests_mock import Mocker
from authentik.blueprints.tests import apply_blueprint
from authentik.core.models import Application, Group, User
from authentik.lib.generators import generate_id
from authentik.providers.scim.models import SCIMMapping, SCIMProvider, SCIMProviderGroup
from authentik.providers.scim.models import SCIMMapping, SCIMProvider
class SCIMGroupTests(TestCase):
@@ -106,7 +106,6 @@ class SCIMGroupTests(TestCase):
"displayName": group.name,
},
)
group.name = generate_id()
group.save()
self.assertEqual(mock.call_count, 4)
self.assertEqual(mock.request_history[0].method, "GET")
@@ -149,56 +148,3 @@ class SCIMGroupTests(TestCase):
self.assertEqual(mock.request_history[0].method, "GET")
self.assertEqual(mock.request_history[3].method, "DELETE")
self.assertEqual(mock.request_history[3].url, f"https://localhost/Groups/{scim_id}")
@Mocker()
def test_group_create_update_noop(self, mock: Mocker):
"""Test group creation and update"""
scim_id = generate_id()
mock.get(
"https://localhost/ServiceProviderConfig",
json={},
)
mock.post(
"https://localhost/Groups",
json={
"id": scim_id,
},
)
mock.put(
"https://localhost/Groups",
json={
"id": scim_id,
},
)
uid = generate_id()
group = Group.objects.create(
name=uid,
)
self.assertEqual(mock.call_count, 2)
self.assertEqual(mock.request_history[0].method, "GET")
self.assertEqual(mock.request_history[1].method, "POST")
body = loads(mock.request_history[1].body)
with open("schemas/scim-group.schema.json", encoding="utf-8") as schema:
validate(body, loads(schema.read()))
self.assertEqual(
body,
{
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:Group"],
"externalId": str(group.pk),
"displayName": group.name,
},
)
conn = SCIMProviderGroup.objects.filter(group=group).first()
conn.attributes = {
"id": scim_id,
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:Group"],
"externalId": str(group.pk),
"displayName": group.name,
}
conn.save()
group.save()
self.assertEqual(mock.call_count, 4)
self.assertEqual(mock.request_history[0].method, "GET")
self.assertEqual(mock.request_history[1].method, "POST")
self.assertEqual(mock.request_history[2].method, "GET")
self.assertEqual(mock.request_history[2].method, "GET")

View File

@@ -10,7 +10,7 @@ from authentik.blueprints.tests import apply_blueprint
from authentik.core.models import Application, Group, User
from authentik.lib.generators import generate_id
from authentik.lib.sync.outgoing.base import SAFE_METHODS
from authentik.providers.scim.models import SCIMMapping, SCIMProvider, SCIMProviderUser
from authentik.providers.scim.models import SCIMMapping, SCIMProvider
from authentik.providers.scim.tasks import scim_sync, scim_sync_objects
from authentik.tasks.models import Task
from authentik.tenants.models import Tenant
@@ -273,8 +273,6 @@ class SCIMUserTests(TestCase):
"userName": uid,
},
)
# Update user
user.name = "foo bar"
user.save()
self.assertEqual(mock.call_count, 4)
self.assertEqual(mock.request_history[0].method, "GET")
@@ -457,85 +455,3 @@ class SCIMUserTests(TestCase):
self.assertIsNotNone(log.attributes["url"])
self.assertIsNotNone(log.attributes["body"])
self.assertIsNotNone(log.attributes["method"])
@Mocker()
def test_user_create_update_noop(self, mock: Mocker):
"""Test user creation and update"""
scim_id = generate_id()
mock: Mocker
mock.get(
"https://localhost/ServiceProviderConfig",
json={},
)
mock.post(
"https://localhost/Users",
json={
"id": scim_id,
},
)
mock.put(
"https://localhost/Users",
json={
"id": scim_id,
},
)
uid = generate_id()
user = User.objects.create(
username=uid,
name=f"{uid} {uid}",
email=f"{uid}@goauthentik.io",
)
self.assertEqual(mock.call_count, 2)
self.assertEqual(mock.request_history[0].method, "GET")
self.assertEqual(mock.request_history[1].method, "POST")
body = loads(mock.request_history[1].body)
self.assertEqual(
body,
{
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
"active": True,
"emails": [
{
"primary": True,
"type": "other",
"value": f"{uid}@goauthentik.io",
}
],
"displayName": f"{uid} {uid}",
"externalId": user.uid,
"name": {
"familyName": uid,
"formatted": f"{uid} {uid}",
"givenName": uid,
},
"userName": uid,
},
)
conn = SCIMProviderUser.objects.filter(user=user).first()
conn.attributes = {
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
"active": True,
"emails": [
{
"primary": True,
"type": "other",
"value": f"{uid}@goauthentik.io",
}
],
"displayName": f"{uid} {uid}",
"externalId": user.uid,
"name": {
"familyName": uid,
"formatted": f"{uid} {uid}",
"givenName": uid,
},
"userName": uid,
"id": scim_id,
}
conn.save()
user.save()
self.assertEqual(mock.call_count, 3)
self.assertEqual(mock.request_history[0].method, "GET")
self.assertEqual(mock.request_history[1].method, "POST")
self.assertEqual(mock.request_history[2].method, "GET")
# No PUT request

View File

@@ -61,22 +61,6 @@ class SessionMiddleware(UpstreamSessionMiddleware):
pass
return session_key
@staticmethod
def encode_session(session_key: str, user: User):
payload = {
"sid": session_key,
"iss": "authentik",
"sub": "anonymous",
"authenticated": user.is_authenticated,
"acr": ACR_AUTHENTIK_SESSION,
}
if user.is_authenticated:
payload["sub"] = user.uid
value = encode(payload=payload, key=SIGNING_HASH)
if settings.TEST:
value = session_key
return value
def process_request(self, request: HttpRequest):
raw_session = request.COOKIES.get(settings.SESSION_COOKIE_NAME)
session_key = SessionMiddleware.decode_session_key(raw_session)
@@ -133,9 +117,21 @@ class SessionMiddleware(UpstreamSessionMiddleware):
"request completed. The user may have logged "
"out in a concurrent request, for example."
) from None
payload = {
"sid": request.session.session_key,
"iss": "authentik",
"sub": "anonymous",
"authenticated": request.user.is_authenticated,
"acr": ACR_AUTHENTIK_SESSION,
}
if request.user.is_authenticated:
payload["sub"] = request.user.uid
value = encode(payload=payload, key=SIGNING_HASH)
if settings.TEST:
value = request.session.session_key
response.set_cookie(
settings.SESSION_COOKIE_NAME,
SessionMiddleware.encode_session(request.session.session_key, request.user),
value,
max_age=max_age,
expires=expires,
domain=settings.SESSION_COOKIE_DOMAIN,

View File

@@ -13,7 +13,7 @@ from rest_framework.exceptions import ValidationError
from authentik.core.middleware import SESSION_KEY_IMPERSONATE_USER
from authentik.core.models import USER_ATTRIBUTE_SOURCES, User, UserSourceConnection, UserTypes
from authentik.core.sources.stage import PLAN_CONTEXT_SOURCES_CONNECTION
from authentik.events.utils import sanitize_dict, sanitize_item
from authentik.events.utils import sanitize_item
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER
from authentik.flows.stage import StageView
from authentik.flows.views.executor import FlowExecutorView
@@ -115,10 +115,7 @@ class UserWriteStageView(StageView):
continue
# For exact attributes match, update the dictionary in place
elif key == "attributes":
if isinstance(value, dict):
user.attributes.update(sanitize_dict(value))
else:
raise ValidationError("Attempt to overwrite complete attributes")
user.attributes.update(value)
# If using dot notation, use the correct helper to update the nested value
elif key.startswith("attributes.") or key.startswith("attributes_"):
UserWriteStageView.write_attribute(user, key, value)

View File

@@ -3,7 +3,6 @@
from unittest.mock import patch
from django.urls import reverse
from django.utils.timezone import now
from authentik.core.models import (
USER_ATTRIBUTE_SOURCES,
@@ -129,34 +128,6 @@ class TestUserWriteStage(FlowTestCase):
self.assertEqual(user_qs.first().attributes["foo"], "bar")
self.assertEqual(user_qs.first().attributes["some_custom_attribute"], "test")
def test_user_update_complex(self):
"""Test update of existing user"""
new_password = generate_key()
plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
plan.context[PLAN_CONTEXT_PENDING_USER] = User.objects.create(
username="unittest", email="test@goauthentik.io"
)
time = now()
plan.context[PLAN_CONTEXT_PROMPT] = {
"username": "test-user-new",
"password": new_password,
"attributes.foo": time,
}
session = self.client.session
session[SESSION_KEY_PLAN] = plan
session.save()
response = self.client.post(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug})
)
self.assertEqual(response.status_code, 200)
self.assertStageRedirects(response, reverse("authentik_core:root-redirect"))
user_qs = User.objects.filter(username=plan.context[PLAN_CONTEXT_PROMPT]["username"])
self.assertTrue(user_qs.exists())
self.assertTrue(user_qs.first().check_password(new_password))
self.assertEqual(user_qs.first().attributes["foo"], time.isoformat()[:-6] + "Z")
def test_user_update_source(self):
"""Test update of existing user with a source"""
new_password = generate_key()

View File

@@ -5278,58 +5278,50 @@
"authentik_crypto.view_certificatekeypair",
"authentik_endpoints.add_connector",
"authentik_endpoints.add_device",
"authentik_endpoints.add_deviceaccessgroup",
"authentik_endpoints.add_deviceconnection",
"authentik_endpoints.add_devicefactsnapshot",
"authentik_endpoints.add_devicegroup",
"authentik_endpoints.add_deviceuserbinding",
"authentik_endpoints.add_endpointstage",
"authentik_endpoints.change_connector",
"authentik_endpoints.change_device",
"authentik_endpoints.change_deviceaccessgroup",
"authentik_endpoints.change_deviceconnection",
"authentik_endpoints.change_devicefactsnapshot",
"authentik_endpoints.change_devicegroup",
"authentik_endpoints.change_deviceuserbinding",
"authentik_endpoints.change_endpointstage",
"authentik_endpoints.delete_connector",
"authentik_endpoints.delete_device",
"authentik_endpoints.delete_deviceaccessgroup",
"authentik_endpoints.delete_deviceconnection",
"authentik_endpoints.delete_devicefactsnapshot",
"authentik_endpoints.delete_devicegroup",
"authentik_endpoints.delete_deviceuserbinding",
"authentik_endpoints.delete_endpointstage",
"authentik_endpoints.view_connector",
"authentik_endpoints.view_device",
"authentik_endpoints.view_deviceaccessgroup",
"authentik_endpoints.view_deviceconnection",
"authentik_endpoints.view_devicefactsnapshot",
"authentik_endpoints.view_devicegroup",
"authentik_endpoints.view_deviceuserbinding",
"authentik_endpoints.view_endpointstage",
"authentik_endpoints_connectors_agent.add_agentconnector",
"authentik_endpoints_connectors_agent.add_agentdeviceconnection",
"authentik_endpoints_connectors_agent.add_agentdeviceuserbinding",
"authentik_endpoints_connectors_agent.add_applenonce",
"authentik_endpoints_connectors_agent.add_deviceauthenticationtoken",
"authentik_endpoints_connectors_agent.add_devicetoken",
"authentik_endpoints_connectors_agent.add_enrollmenttoken",
"authentik_endpoints_connectors_agent.change_agentconnector",
"authentik_endpoints_connectors_agent.change_agentdeviceconnection",
"authentik_endpoints_connectors_agent.change_agentdeviceuserbinding",
"authentik_endpoints_connectors_agent.change_applenonce",
"authentik_endpoints_connectors_agent.change_deviceauthenticationtoken",
"authentik_endpoints_connectors_agent.change_devicetoken",
"authentik_endpoints_connectors_agent.change_enrollmenttoken",
"authentik_endpoints_connectors_agent.delete_agentconnector",
"authentik_endpoints_connectors_agent.delete_agentdeviceconnection",
"authentik_endpoints_connectors_agent.delete_agentdeviceuserbinding",
"authentik_endpoints_connectors_agent.delete_applenonce",
"authentik_endpoints_connectors_agent.delete_deviceauthenticationtoken",
"authentik_endpoints_connectors_agent.delete_devicetoken",
"authentik_endpoints_connectors_agent.delete_enrollmenttoken",
"authentik_endpoints_connectors_agent.view_agentconnector",
"authentik_endpoints_connectors_agent.view_agentdeviceconnection",
"authentik_endpoints_connectors_agent.view_agentdeviceuserbinding",
"authentik_endpoints_connectors_agent.view_applenonce",
"authentik_endpoints_connectors_agent.view_deviceauthenticationtoken",
"authentik_endpoints_connectors_agent.view_devicetoken",
"authentik_endpoints_connectors_agent.view_enrollment_token_key",
"authentik_endpoints_connectors_agent.view_enrollmenttoken",
@@ -5981,25 +5973,6 @@
"minLength": 1,
"title": "Snapshot expiry"
},
"auth_session_duration": {
"type": "string",
"minLength": 1,
"title": "Auth session duration"
},
"auth_terminate_session_on_expiry": {
"type": "boolean",
"title": "Auth terminate session on expiry"
},
"refresh_interval": {
"type": "string",
"minLength": 1,
"title": "Refresh interval"
},
"authorization_flow": {
"type": "string",
"format": "uuid",
"title": "Authorization flow"
},
"nss_uid_offset": {
"type": "integer",
"minimum": 0,
@@ -6012,17 +5985,24 @@
"maximum": 2147483647,
"title": "Nss gid offset"
},
"auth_terminate_session_on_expiry": {
"type": "boolean",
"title": "Auth terminate session on expiry"
},
"refresh_interval": {
"type": "string",
"minLength": 1,
"title": "Refresh interval"
},
"authentication_flow": {
"type": "string",
"format": "uuid",
"title": "Authentication flow"
},
"challenge_key": {
"type": "string",
"format": "uuid",
"title": "Challenge key"
},
"jwt_federation_providers": {
"type": "array",
"items": {
"type": "integer"
},
"title": "Jwt federation providers"
}
},
"required": []
@@ -6287,7 +6267,9 @@
},
"slug": {
"type": "string",
"maxLength": 50,
"minLength": 1,
"pattern": "^[-a-zA-Z0-9_]+$",
"title": "Slug",
"description": "Visible in the URL."
},
@@ -10576,58 +10558,50 @@
"authentik_crypto.view_certificatekeypair",
"authentik_endpoints.add_connector",
"authentik_endpoints.add_device",
"authentik_endpoints.add_deviceaccessgroup",
"authentik_endpoints.add_deviceconnection",
"authentik_endpoints.add_devicefactsnapshot",
"authentik_endpoints.add_devicegroup",
"authentik_endpoints.add_deviceuserbinding",
"authentik_endpoints.add_endpointstage",
"authentik_endpoints.change_connector",
"authentik_endpoints.change_device",
"authentik_endpoints.change_deviceaccessgroup",
"authentik_endpoints.change_deviceconnection",
"authentik_endpoints.change_devicefactsnapshot",
"authentik_endpoints.change_devicegroup",
"authentik_endpoints.change_deviceuserbinding",
"authentik_endpoints.change_endpointstage",
"authentik_endpoints.delete_connector",
"authentik_endpoints.delete_device",
"authentik_endpoints.delete_deviceaccessgroup",
"authentik_endpoints.delete_deviceconnection",
"authentik_endpoints.delete_devicefactsnapshot",
"authentik_endpoints.delete_devicegroup",
"authentik_endpoints.delete_deviceuserbinding",
"authentik_endpoints.delete_endpointstage",
"authentik_endpoints.view_connector",
"authentik_endpoints.view_device",
"authentik_endpoints.view_deviceaccessgroup",
"authentik_endpoints.view_deviceconnection",
"authentik_endpoints.view_devicefactsnapshot",
"authentik_endpoints.view_devicegroup",
"authentik_endpoints.view_deviceuserbinding",
"authentik_endpoints.view_endpointstage",
"authentik_endpoints_connectors_agent.add_agentconnector",
"authentik_endpoints_connectors_agent.add_agentdeviceconnection",
"authentik_endpoints_connectors_agent.add_agentdeviceuserbinding",
"authentik_endpoints_connectors_agent.add_applenonce",
"authentik_endpoints_connectors_agent.add_deviceauthenticationtoken",
"authentik_endpoints_connectors_agent.add_devicetoken",
"authentik_endpoints_connectors_agent.add_enrollmenttoken",
"authentik_endpoints_connectors_agent.change_agentconnector",
"authentik_endpoints_connectors_agent.change_agentdeviceconnection",
"authentik_endpoints_connectors_agent.change_agentdeviceuserbinding",
"authentik_endpoints_connectors_agent.change_applenonce",
"authentik_endpoints_connectors_agent.change_deviceauthenticationtoken",
"authentik_endpoints_connectors_agent.change_devicetoken",
"authentik_endpoints_connectors_agent.change_enrollmenttoken",
"authentik_endpoints_connectors_agent.delete_agentconnector",
"authentik_endpoints_connectors_agent.delete_agentdeviceconnection",
"authentik_endpoints_connectors_agent.delete_agentdeviceuserbinding",
"authentik_endpoints_connectors_agent.delete_applenonce",
"authentik_endpoints_connectors_agent.delete_deviceauthenticationtoken",
"authentik_endpoints_connectors_agent.delete_devicetoken",
"authentik_endpoints_connectors_agent.delete_enrollmenttoken",
"authentik_endpoints_connectors_agent.view_agentconnector",
"authentik_endpoints_connectors_agent.view_agentdeviceconnection",
"authentik_endpoints_connectors_agent.view_agentdeviceuserbinding",
"authentik_endpoints_connectors_agent.view_applenonce",
"authentik_endpoints_connectors_agent.view_deviceauthenticationtoken",
"authentik_endpoints_connectors_agent.view_devicetoken",
"authentik_endpoints_connectors_agent.view_enrollment_token_key",
"authentik_endpoints_connectors_agent.view_enrollmenttoken",

4
go.mod
View File

@@ -9,7 +9,7 @@ require (
beryju.io/radius-eap v0.1.0
github.com/avast/retry-go/v4 v4.7.0
github.com/coreos/go-oidc/v3 v3.17.0
github.com/getsentry/sentry-go v0.40.0
github.com/getsentry/sentry-go v0.39.0
github.com/go-http-utils/etag v0.0.0-20161124023236-513ea8f21eb1
github.com/go-ldap/ldap/v3 v3.4.12
github.com/go-openapi/runtime v0.29.2
@@ -32,7 +32,7 @@ require (
github.com/spf13/cobra v1.10.1
github.com/stretchr/testify v1.11.1
github.com/wwt/guac v1.3.2
goauthentik.io/api/v3 v3.2025120.11
goauthentik.io/api/v3 v3.2025120.7
golang.org/x/exp v0.0.0-20230210204819-062eb4c674ab
golang.org/x/oauth2 v0.33.0
golang.org/x/sync v0.18.0

8
go.sum
View File

@@ -20,8 +20,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk=
github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/getsentry/sentry-go v0.40.0 h1:VTJMN9zbTvqDqPwheRVLcp0qcUcM+8eFivvGocAaSbo=
github.com/getsentry/sentry-go v0.40.0/go.mod h1:eRXCoh3uvmjQLY6qu63BjUZnaBu5L5WhMV1RwYO8W5s=
github.com/getsentry/sentry-go v0.39.0 h1:uhnexj8PNCyCve37GSqxXOeXHh4cJNLNNB4w70Jtgo0=
github.com/getsentry/sentry-go v0.39.0/go.mod h1:eRXCoh3uvmjQLY6qu63BjUZnaBu5L5WhMV1RwYO8W5s=
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 h1:BP4M0CvQ4S3TGls2FvczZtj5Re/2ZzkV9VwqPHH/3Bo=
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
@@ -214,8 +214,8 @@ go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
goauthentik.io/api/v3 v3.2025120.11 h1:qKKIj6FzWMG5x3suQyKJdD4ziIlqtp6Lq8SCjjQAMf0=
goauthentik.io/api/v3 v3.2025120.11/go.mod h1:82lqAz4jxzl6Cg0YDbhNtvvTG2rm6605ZhdJFnbbsl8=
goauthentik.io/api/v3 v3.2025120.7 h1:kcMcIm1l0ndbmEu4JlIHL2pscmdN2x4kAn0t8Zd+RNI=
goauthentik.io/api/v3 v3.2025120.7/go.mod h1:82lqAz4jxzl6Cg0YDbhNtvvTG2rm6605ZhdJFnbbsl8=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=

View File

@@ -31,7 +31,7 @@ RUN --mount=type=cache,sharing=locked,target=/go/pkg/mod \
go build -o /go/ldap ./cmd/ldap
# Stage 2: Run
FROM ghcr.io/goauthentik/fips-debian:trixie-slim-fips@sha256:c718f608885a76a8940700b0c9feab75b1c967e1c291adf52dcd3ad6f6a86a66
FROM ghcr.io/goauthentik/fips-debian:trixie-slim-fips@sha256:ac4c80b43351a3a10c0307d191fe6a6386359bc36e2aa3ccb1b23abd1f0a9e4f
ARG VERSION
ARG GIT_BUILD_HASH

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@@ -27,28 +27,27 @@
# Stefan Werner, 2024
# Jonas, 2025
# Niklas Kroese, 2025
# 97cce0ae0cad2a2cc552d3165d04643e_de3d740, 2025
# Dominic Wagner <mail@dominic-wagner.de>, 2025
# Till-Frederik Riechard, 2025
# Alexander Mnich, 2025
# Ben, 2025
# datenschmutz, 2025
# Ulrich Stark, 2025
# Benjamin Böhmke, 2025
# 97cce0ae0cad2a2cc552d3165d04643e_de3d740, 2025
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-12-01 15:07+0000\n"
"POT-Creation-Date: 2025-10-20 00:11+0000\n"
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
"Last-Translator: 97cce0ae0cad2a2cc552d3165d04643e_de3d740, 2025\n"
"Last-Translator: Benjamin Böhmke, 2025\n"
"Language-Team: German (https://app.transifex.com/authentik/teams/119923/de/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: de_DE\n"
"Language: de\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: authentik/admin/models.py
@@ -420,12 +419,6 @@ msgstr "Quellname"
msgid "Internal source name, used in URLs."
msgstr "Interner Quellname, genutzt für URLs"
#: authentik/core/models.py
msgid ""
"When enabled, this source will be displayed as a prominent button on the "
"login page, instead of a small icon."
msgstr ""
#: authentik/core/models.py
msgid "Flow to use when authenticating existing users."
msgstr "Flow der zur Authorisierung bereits ersteller Nutzer verwendet wird"
@@ -458,7 +451,7 @@ msgstr "Token"
msgid "Tokens"
msgstr "Tokens"
#: authentik/core/models.py authentik/endpoints/connectors/agent/models.py
#: authentik/core/models.py
msgid "View token's key"
msgstr "Schlüssel des Tokens anzeigen"
@@ -531,7 +524,6 @@ msgid "Remove temporary users created by SAML Sources."
msgstr "Temporäre Benutzer entfernen, die von SAML-Sources erstellt wurden."
#: authentik/core/templates/if/error.html
#: authentik/policies/templates/policies/denied.html
msgid "Go home"
msgstr "Zur Startseite"
@@ -563,26 +555,6 @@ msgstr "RSA"
msgid "ecdsa"
msgstr "ECDSA"
#: authentik/crypto/models.py
msgid "RSA"
msgstr ""
#: authentik/crypto/models.py
msgid "Elliptic Curve"
msgstr ""
#: authentik/crypto/models.py
msgid "DSA"
msgstr ""
#: authentik/crypto/models.py
msgid "Ed25519"
msgstr ""
#: authentik/crypto/models.py
msgid "Ed448"
msgstr ""
#: authentik/crypto/models.py
msgid "PEM-encoded Certificate data"
msgstr "PEM-verschlüsselte Zertifikatsdaten"
@@ -607,104 +579,6 @@ msgstr "Zertifikat-Schlüsselpaare"
msgid "Discover, import and update certificates from the filesystem."
msgstr "Zertifikate vom Dateisystem entdecken, importieren und aktualisieren."
#: authentik/endpoints/connectors/agent/api/connectors.py
msgid "Selected platform not supported"
msgstr ""
#: authentik/endpoints/connectors/agent/api/connectors.py
msgid "Token is expired"
msgstr ""
#: authentik/endpoints/connectors/agent/api/connectors.py
msgid "Invalid token for connector"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Agent Connector"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Agent Connectors"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
#: authentik/providers/oauth2/models.py
msgid "Device Token"
msgstr "Geräte-Token"
#: authentik/endpoints/connectors/agent/models.py
#: authentik/providers/oauth2/models.py
msgid "Device Tokens"
msgstr "Geräte-Token"
#: authentik/endpoints/connectors/agent/models.py
msgid "Enrollment Token"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Enrollment Tokens"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Device authentication token"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Device authentication tokens"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Apple Nonce"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Apple Nonces"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device"
msgstr "Gerät"
#: authentik/endpoints/models.py
msgid "Devices"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device User binding"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device User bindings"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device connection"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device connections"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device fact snapshot"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device fact snapshots"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device access group"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device access groups"
msgstr ""
#: authentik/endpoints/tasks.py
msgid "Sync endpoints."
msgstr ""
#: authentik/enterprise/api.py
msgid "Enterprise is required to create/update this object."
msgstr ""
@@ -1376,22 +1250,10 @@ msgstr "Flow-Token"
msgid "Flow Tokens"
msgstr "Flow-Tokens"
#: authentik/flows/templates/if/flow.html
msgid "Site footer"
msgstr ""
#: authentik/flows/views/executor.py
msgid "Invalid next URL"
msgstr "Ungültige nächste URL"
#: authentik/lib/sync/outgoing/models.py
msgid "Controls the number of objects synced in a single task"
msgstr ""
#: authentik/lib/sync/outgoing/models.py
msgid "Timeout for synchronization of a single page"
msgstr ""
#: authentik/lib/sync/outgoing/models.py
msgid ""
"When enabled, provider will not modify or create objects in the remote "
@@ -1795,10 +1657,6 @@ msgstr "Avatar des Benutzers"
msgid "Not you?"
msgstr "Nicht Sie?"
#: authentik/policies/templates/policies/denied.html
msgid "Error"
msgstr "Fehler"
#: authentik/policies/templates/policies/denied.html
msgid "Request has been denied."
msgstr "Anfrage wurde verweigert"
@@ -2182,6 +2040,14 @@ msgstr "OAuth2-Aktualisierungs-Token"
msgid "OAuth2 Refresh Tokens"
msgstr "OAuth2-Aktualisierungs-Token"
#: authentik/providers/oauth2/models.py
msgid "Device Token"
msgstr "Geräte-Token"
#: authentik/providers/oauth2/models.py
msgid "Device Tokens"
msgstr "Geräte-Token"
#: authentik/providers/oauth2/tasks.py
msgid "Send a back-channel logout request to the registered client"
msgstr "Eine Back-Channel-Logout-Anfrage an den registrierten Client senden."
@@ -2196,7 +2062,7 @@ msgstr ""
#: authentik/providers/saml/views/flows.py
#, python-brace-format
msgid "Redirecting to {app}..."
msgstr "Weiterleitung zu {app}..."
msgstr "Umleitung zu {app}..."
#: authentik/providers/oauth2/views/device_init.py
msgid "Invalid code"
@@ -2419,18 +2285,6 @@ msgstr "Der Import von Metadaten ist fehlgeschlagen: {messages}"
msgid "ACS URL"
msgstr "ACS URL"
#: authentik/providers/saml/models.py
msgid "Service Provider Binding"
msgstr "Service Provider zuordung"
#: authentik/providers/saml/models.py
msgid ""
"This determines how authentik sends the response back to the Service "
"Provider."
msgstr ""
"Damit wird festgelegt, wie Authentik die Antwort an den Dienstanbieter "
"zurücksendet."
#: authentik/providers/saml/models.py
msgid ""
"Value of the audience restriction field of the assertion. When left empty, "
@@ -2443,6 +2297,18 @@ msgstr ""
msgid "Also known as EntityID"
msgstr "Auch bekannt als EntityID"
#: authentik/providers/saml/models.py
msgid "Service Provider Binding"
msgstr "Service Provider zuordung"
#: authentik/providers/saml/models.py
msgid ""
"This determines how authentik sends the response back to the Service "
"Provider."
msgstr ""
"Damit wird festgelegt, wie Authentik die Antwort an den Dienstanbieter "
"zurücksendet."
#: authentik/providers/saml/models.py
msgid "SLS URL"
msgstr ""

Binary file not shown.

View File

@@ -8,14 +8,15 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-12-01 17:05+0000\n"
"POT-Creation-Date: 2025-11-25 00:09+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: en\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: authentik/admin/models.py
msgid "Version history"
@@ -347,12 +348,6 @@ msgstr ""
msgid "Internal source name, used in URLs."
msgstr ""
#: authentik/core/models.py
msgid ""
"When enabled, this source will be displayed as a prominent button on the "
"login page, instead of a small icon."
msgstr ""
#: authentik/core/models.py
msgid "Flow to use when authenticating existing users."
msgstr ""
@@ -545,16 +540,6 @@ msgstr ""
msgid "Agent Connectors"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
#: authentik/providers/oauth2/models.py
msgid "Device Token"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
#: authentik/providers/oauth2/models.py
msgid "Device Tokens"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Enrollment Token"
msgstr ""
@@ -563,62 +548,6 @@ msgstr ""
msgid "Enrollment Tokens"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Device authentication token"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Device authentication tokens"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Apple Nonce"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Apple Nonces"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device"
msgstr ""
#: authentik/endpoints/models.py
msgid "Devices"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device User binding"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device User bindings"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device connection"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device connections"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device fact snapshot"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device fact snapshots"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device access group"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device access groups"
msgstr ""
#: authentik/endpoints/tasks.py
msgid "Sync endpoints."
msgstr ""
@@ -1944,6 +1873,14 @@ msgstr ""
msgid "OAuth2 Refresh Tokens"
msgstr ""
#: authentik/providers/oauth2/models.py
msgid "Device Token"
msgstr ""
#: authentik/providers/oauth2/models.py
msgid "Device Tokens"
msgstr ""
#: authentik/providers/oauth2/tasks.py
msgid "Send a back-channel logout request to the registered client"
msgstr ""

Binary file not shown.

View File

@@ -16,14 +16,14 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-12-01 15:07+0000\n"
"POT-Creation-Date: 2025-10-20 00:11+0000\n"
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
"Last-Translator: Jens L. <jens@goauthentik.io>, 2025\n"
"Language-Team: Spanish (https://app.transifex.com/authentik/teams/119923/es/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: es_ES\n"
"Language: es\n"
"Plural-Forms: nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\n"
#: authentik/admin/models.py
@@ -394,12 +394,6 @@ msgstr "Nombre de visualización de la fuente."
msgid "Internal source name, used in URLs."
msgstr "Nombre de origen interno, utilizado en las URL."
#: authentik/core/models.py
msgid ""
"When enabled, this source will be displayed as a prominent button on the "
"login page, instead of a small icon."
msgstr ""
#: authentik/core/models.py
msgid "Flow to use when authenticating existing users."
msgstr "Flujo que se utilizará al autenticar a los usuarios existentes."
@@ -432,7 +426,7 @@ msgstr "Token"
msgid "Tokens"
msgstr "Tokens"
#: authentik/core/models.py authentik/endpoints/connectors/agent/models.py
#: authentik/core/models.py
msgid "View token's key"
msgstr "Ver llave del token"
@@ -504,7 +498,6 @@ msgid "Remove temporary users created by SAML Sources."
msgstr "Eliminar usuarios temporales creados por SAML Sources."
#: authentik/core/templates/if/error.html
#: authentik/policies/templates/policies/denied.html
msgid "Go home"
msgstr "Ir al inicio"
@@ -536,26 +529,6 @@ msgstr "rsa"
msgid "ecdsa"
msgstr "ecdsa"
#: authentik/crypto/models.py
msgid "RSA"
msgstr ""
#: authentik/crypto/models.py
msgid "Elliptic Curve"
msgstr ""
#: authentik/crypto/models.py
msgid "DSA"
msgstr ""
#: authentik/crypto/models.py
msgid "Ed25519"
msgstr ""
#: authentik/crypto/models.py
msgid "Ed448"
msgstr ""
#: authentik/crypto/models.py
msgid "PEM-encoded Certificate data"
msgstr "Datos de certificados codificados en PEM"
@@ -581,104 +554,6 @@ msgid "Discover, import and update certificates from the filesystem."
msgstr ""
"Descubra, importe y actualice certificados desde el sistema de archivos."
#: authentik/endpoints/connectors/agent/api/connectors.py
msgid "Selected platform not supported"
msgstr ""
#: authentik/endpoints/connectors/agent/api/connectors.py
msgid "Token is expired"
msgstr ""
#: authentik/endpoints/connectors/agent/api/connectors.py
msgid "Invalid token for connector"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Agent Connector"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Agent Connectors"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
#: authentik/providers/oauth2/models.py
msgid "Device Token"
msgstr "Token de Dispositivo"
#: authentik/endpoints/connectors/agent/models.py
#: authentik/providers/oauth2/models.py
msgid "Device Tokens"
msgstr "Tokens de Dispositivo"
#: authentik/endpoints/connectors/agent/models.py
msgid "Enrollment Token"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Enrollment Tokens"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Device authentication token"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Device authentication tokens"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Apple Nonce"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Apple Nonces"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device"
msgstr "Dispositivo"
#: authentik/endpoints/models.py
msgid "Devices"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device User binding"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device User bindings"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device connection"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device connections"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device fact snapshot"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device fact snapshots"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device access group"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device access groups"
msgstr ""
#: authentik/endpoints/tasks.py
msgid "Sync endpoints."
msgstr ""
#: authentik/enterprise/api.py
msgid "Enterprise is required to create/update this object."
msgstr "Se requiere de Enterprise para crear/actualizar este objeto."
@@ -1336,22 +1211,10 @@ msgstr "Token de flujo"
msgid "Flow Tokens"
msgstr "Tokens de flujo"
#: authentik/flows/templates/if/flow.html
msgid "Site footer"
msgstr ""
#: authentik/flows/views/executor.py
msgid "Invalid next URL"
msgstr "Siguiente URL invalida"
#: authentik/lib/sync/outgoing/models.py
msgid "Controls the number of objects synced in a single task"
msgstr ""
#: authentik/lib/sync/outgoing/models.py
msgid "Timeout for synchronization of a single page"
msgstr ""
#: authentik/lib/sync/outgoing/models.py
msgid ""
"When enabled, provider will not modify or create objects in the remote "
@@ -1752,10 +1615,6 @@ msgstr "Avatar del usuario"
msgid "Not you?"
msgstr "¿No eres tú?"
#: authentik/policies/templates/policies/denied.html
msgid "Error"
msgstr "Error"
#: authentik/policies/templates/policies/denied.html
msgid "Request has been denied."
msgstr "Se ha denegado la solicitud."
@@ -2136,6 +1995,14 @@ msgstr "Token de Actualización OAuth2"
msgid "OAuth2 Refresh Tokens"
msgstr "Tokens de Actualización OAuth2"
#: authentik/providers/oauth2/models.py
msgid "Device Token"
msgstr "Token de Dispositivo"
#: authentik/providers/oauth2/models.py
msgid "Device Tokens"
msgstr "Tokens de Dispositivo"
#: authentik/providers/oauth2/tasks.py
msgid "Send a back-channel logout request to the registered client"
msgstr ""
@@ -2378,17 +2245,6 @@ msgstr "No se pudieron importar los Metadatos: {messages}"
msgid "ACS URL"
msgstr "URL"
#: authentik/providers/saml/models.py
msgid "Service Provider Binding"
msgstr "Vinculación de Proveedor de Servicio"
#: authentik/providers/saml/models.py
msgid ""
"This determines how authentik sends the response back to the Service "
"Provider."
msgstr ""
"Esto determina cómo authentik envía la respuesta al Proveedor de Servicios."
#: authentik/providers/saml/models.py
msgid ""
"Value of the audience restriction field of the assertion. When left empty, "
@@ -2401,6 +2257,17 @@ msgstr ""
msgid "Also known as EntityID"
msgstr "También conocido como EntityID"
#: authentik/providers/saml/models.py
msgid "Service Provider Binding"
msgstr "Vinculación de Proveedor de Servicio"
#: authentik/providers/saml/models.py
msgid ""
"This determines how authentik sends the response back to the Service "
"Provider."
msgstr ""
"Esto determina cómo authentik envía la respuesta al Proveedor de Servicios."
#: authentik/providers/saml/models.py
msgid "SLS URL"
msgstr ""

Binary file not shown.

View File

@@ -15,14 +15,14 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-12-01 15:07+0000\n"
"POT-Creation-Date: 2025-11-13 03:19+0000\n"
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
"Last-Translator: Skyler Mäntysaari, 2025\n"
"Language-Team: Finnish (https://app.transifex.com/authentik/teams/119923/fi/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: fi_FI\n"
"Language: fi\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: authentik/admin/models.py
@@ -382,12 +382,6 @@ msgstr "Lähteen näytettävä nimi."
msgid "Internal source name, used in URLs."
msgstr "Lähteen sisäinen nimi, käytetään URLeissa."
#: authentik/core/models.py
msgid ""
"When enabled, this source will be displayed as a prominent button on the "
"login page, instead of a small icon."
msgstr ""
#: authentik/core/models.py
msgid "Flow to use when authenticating existing users."
msgstr "Prosessi, jota käytetään kun todennetaan olemassa olevia käyttäjiä."
@@ -420,7 +414,7 @@ msgstr "Tunniste"
msgid "Tokens"
msgstr "Tunnisteet"
#: authentik/core/models.py authentik/endpoints/connectors/agent/models.py
#: authentik/core/models.py
msgid "View token's key"
msgstr "Näytä tunnisteen avain"
@@ -568,104 +562,6 @@ msgstr "Sertifikaatti-avainparit"
msgid "Discover, import and update certificates from the filesystem."
msgstr "Havaitse, tuo ja päivitä sertifikaatteja levyjärjestelmästä."
#: authentik/endpoints/connectors/agent/api/connectors.py
msgid "Selected platform not supported"
msgstr ""
#: authentik/endpoints/connectors/agent/api/connectors.py
msgid "Token is expired"
msgstr ""
#: authentik/endpoints/connectors/agent/api/connectors.py
msgid "Invalid token for connector"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Agent Connector"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Agent Connectors"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
#: authentik/providers/oauth2/models.py
msgid "Device Token"
msgstr "Laitetunniste"
#: authentik/endpoints/connectors/agent/models.py
#: authentik/providers/oauth2/models.py
msgid "Device Tokens"
msgstr "Laitetunnisteet"
#: authentik/endpoints/connectors/agent/models.py
msgid "Enrollment Token"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Enrollment Tokens"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Device authentication token"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Device authentication tokens"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Apple Nonce"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Apple Nonces"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device"
msgstr "Laite"
#: authentik/endpoints/models.py
msgid "Devices"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device User binding"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device User bindings"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device connection"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device connections"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device fact snapshot"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device fact snapshots"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device access group"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device access groups"
msgstr ""
#: authentik/endpoints/tasks.py
msgid "Sync endpoints."
msgstr ""
#: authentik/enterprise/api.py
msgid "Enterprise is required to create/update this object."
msgstr "Tämän objektin luontiin/päivittämiseen tarvitaan Enterprise-versiota."
@@ -1319,10 +1215,6 @@ msgstr "Prosessin tunniste"
msgid "Flow Tokens"
msgstr "Prosessin tunnisteet"
#: authentik/flows/templates/if/flow.html
msgid "Site footer"
msgstr ""
#: authentik/flows/views/executor.py
msgid "Invalid next URL"
msgstr "Virheellinen seuraava URL"
@@ -2120,6 +2012,14 @@ msgstr "OAuth2-päivitystunnus"
msgid "OAuth2 Refresh Tokens"
msgstr "OAuth2-päivitystunnukset"
#: authentik/providers/oauth2/models.py
msgid "Device Token"
msgstr "Laitetunniste"
#: authentik/providers/oauth2/models.py
msgid "Device Tokens"
msgstr "Laitetunnisteet"
#: authentik/providers/oauth2/tasks.py
msgid "Send a back-channel logout request to the registered client"
msgstr ""

View File

@@ -9,24 +9,24 @@
# Kyllian Delaye-Maillot, 2023
# Manuel Viens, 2023
# Mordecai, 2023
# Charles Leclerc, 2025
# Tina, 2025
# nerdinator <florian.dupret@gmail.com>, 2025
# Marc Schmitt, 2025
# Charles Leclerc, 2025
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-12-01 15:07+0000\n"
"POT-Creation-Date: 2025-10-20 00:11+0000\n"
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
"Last-Translator: Charles Leclerc, 2025\n"
"Last-Translator: Marc Schmitt, 2025\n"
"Language-Team: French (https://app.transifex.com/authentik/teams/119923/fr/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: fr_FR\n"
"Language: fr\n"
"Plural-Forms: nplurals=3; plural=(n == 0 || n == 1) ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\n"
#: authentik/admin/models.py
@@ -397,12 +397,6 @@ msgstr "Nom d'affichage de la source."
msgid "Internal source name, used in URLs."
msgstr "Nom interne de la source, utilisé dans les URLs."
#: authentik/core/models.py
msgid ""
"When enabled, this source will be displayed as a prominent button on the "
"login page, instead of a small icon."
msgstr ""
#: authentik/core/models.py
msgid "Flow to use when authenticating existing users."
msgstr "Flux à utiliser pour authentifier les utilisateurs existants."
@@ -435,7 +429,7 @@ msgstr "Jeton"
msgid "Tokens"
msgstr "Jetons"
#: authentik/core/models.py authentik/endpoints/connectors/agent/models.py
#: authentik/core/models.py
msgid "View token's key"
msgstr "Voir la clé du jeton"
@@ -507,7 +501,6 @@ msgid "Remove temporary users created by SAML Sources."
msgstr "Supprime les utilisateurs temporaires créés par les sources SAML."
#: authentik/core/templates/if/error.html
#: authentik/policies/templates/policies/denied.html
msgid "Go home"
msgstr "Retourner à l'accueil"
@@ -539,26 +532,6 @@ msgstr "rsa"
msgid "ecdsa"
msgstr "ecdsa"
#: authentik/crypto/models.py
msgid "RSA"
msgstr ""
#: authentik/crypto/models.py
msgid "Elliptic Curve"
msgstr ""
#: authentik/crypto/models.py
msgid "DSA"
msgstr ""
#: authentik/crypto/models.py
msgid "Ed25519"
msgstr ""
#: authentik/crypto/models.py
msgid "Ed448"
msgstr ""
#: authentik/crypto/models.py
msgid "PEM-encoded Certificate data"
msgstr "Données du certificat au format PEM"
@@ -585,104 +558,6 @@ msgstr ""
"Découvre, importe et met à jour les certificats depuis le système de "
"fichiers."
#: authentik/endpoints/connectors/agent/api/connectors.py
msgid "Selected platform not supported"
msgstr ""
#: authentik/endpoints/connectors/agent/api/connectors.py
msgid "Token is expired"
msgstr ""
#: authentik/endpoints/connectors/agent/api/connectors.py
msgid "Invalid token for connector"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Agent Connector"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Agent Connectors"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
#: authentik/providers/oauth2/models.py
msgid "Device Token"
msgstr "Jeton d'équipement"
#: authentik/endpoints/connectors/agent/models.py
#: authentik/providers/oauth2/models.py
msgid "Device Tokens"
msgstr "Jetons d'équipement"
#: authentik/endpoints/connectors/agent/models.py
msgid "Enrollment Token"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Enrollment Tokens"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Device authentication token"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Device authentication tokens"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Apple Nonce"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Apple Nonces"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device"
msgstr "Équipement"
#: authentik/endpoints/models.py
msgid "Devices"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device User binding"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device User bindings"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device connection"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device connections"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device fact snapshot"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device fact snapshots"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device access group"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device access groups"
msgstr ""
#: authentik/endpoints/tasks.py
msgid "Sync endpoints."
msgstr ""
#: authentik/enterprise/api.py
msgid "Enterprise is required to create/update this object."
msgstr "Entreprise est requis pour créer/mettre à jour cet objet."
@@ -1350,22 +1225,10 @@ msgstr "Jeton du flux"
msgid "Flow Tokens"
msgstr "Jetons du flux"
#: authentik/flows/templates/if/flow.html
msgid "Site footer"
msgstr ""
#: authentik/flows/views/executor.py
msgid "Invalid next URL"
msgstr "URL suivante invalide"
#: authentik/lib/sync/outgoing/models.py
msgid "Controls the number of objects synced in a single task"
msgstr ""
#: authentik/lib/sync/outgoing/models.py
msgid "Timeout for synchronization of a single page"
msgstr ""
#: authentik/lib/sync/outgoing/models.py
msgid ""
"When enabled, provider will not modify or create objects in the remote "
@@ -1770,10 +1633,6 @@ msgstr "Avatar de l'utilisateu"
msgid "Not you?"
msgstr "Pas vous ?"
#: authentik/policies/templates/policies/denied.html
msgid "Error"
msgstr "Erreur"
#: authentik/policies/templates/policies/denied.html
msgid "Request has been denied."
msgstr "La requête a été refusée."
@@ -2160,6 +2019,14 @@ msgstr "Jeton de rafraîchissement OAuth2"
msgid "OAuth2 Refresh Tokens"
msgstr "Jetons de rafraîchissement OAuth2"
#: authentik/providers/oauth2/models.py
msgid "Device Token"
msgstr "Jeton d'équipement"
#: authentik/providers/oauth2/models.py
msgid "Device Tokens"
msgstr "Jetons d'équipement"
#: authentik/providers/oauth2/tasks.py
msgid "Send a back-channel logout request to the registered client"
msgstr "Envoyer une requête de déconnexion Back-Channel au client enregistré"
@@ -2399,18 +2266,6 @@ msgstr "Échec d'import des métadonnées : {messages}"
msgid "ACS URL"
msgstr "ACS URL"
#: authentik/providers/saml/models.py
msgid "Service Provider Binding"
msgstr "Liaison du fournisseur de services"
#: authentik/providers/saml/models.py
msgid ""
"This determines how authentik sends the response back to the Service "
"Provider."
msgstr ""
"Cela détermine la manière dont authentik renvoie la réponse au fournisseur "
"de services."
#: authentik/providers/saml/models.py
msgid ""
"Value of the audience restriction field of the assertion. When left empty, "
@@ -2423,6 +2278,18 @@ msgstr ""
msgid "Also known as EntityID"
msgstr "Aussi appelé EntityID"
#: authentik/providers/saml/models.py
msgid "Service Provider Binding"
msgstr "Liaison du fournisseur de services"
#: authentik/providers/saml/models.py
msgid ""
"This determines how authentik sends the response back to the Service "
"Provider."
msgstr ""
"Cela détermine la manière dont authentik renvoie la réponse au fournisseur "
"de services."
#: authentik/providers/saml/models.py
msgid "SLS URL"
msgstr "URL SLS"

Binary file not shown.

View File

@@ -11,23 +11,23 @@
# Nicola Mersi, 2024
# tmassimi, 2024
# Marc Schmitt, 2024
# Kowalski Dragon (kowalski7cc) <kowalski.7cc@gmail.com>, 2025
# Matteo Piccina <altermatte@gmail.com>, 2025
# albanobattistella <albanobattistella@gmail.com>, 2025
# Kowalski Dragon (kowalski7cc) <kowalski.7cc@gmail.com>, 2025
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-12-01 15:07+0000\n"
"POT-Creation-Date: 2025-10-20 00:11+0000\n"
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
"Last-Translator: Kowalski Dragon (kowalski7cc) <kowalski.7cc@gmail.com>, 2025\n"
"Last-Translator: albanobattistella <albanobattistella@gmail.com>, 2025\n"
"Language-Team: Italian (https://app.transifex.com/authentik/teams/119923/it/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: it_IT\n"
"Language: it\n"
"Plural-Forms: nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\n"
#: authentik/admin/models.py
@@ -389,12 +389,6 @@ msgstr "Nome visualizzato della sorgente."
msgid "Internal source name, used in URLs."
msgstr "Nome interno della sorgente, utilizzato negli URL."
#: authentik/core/models.py
msgid ""
"When enabled, this source will be displayed as a prominent button on the "
"login page, instead of a small icon."
msgstr ""
#: authentik/core/models.py
msgid "Flow to use when authenticating existing users."
msgstr "Flusso da usare per autenticare utenti esistenti."
@@ -427,7 +421,7 @@ msgstr "Token"
msgid "Tokens"
msgstr "Tokens"
#: authentik/core/models.py authentik/endpoints/connectors/agent/models.py
#: authentik/core/models.py
msgid "View token's key"
msgstr "Visualizza la chiave token"
@@ -499,7 +493,6 @@ msgid "Remove temporary users created by SAML Sources."
msgstr "Rimuovi gli utenti temporanei creati da SAML Sources."
#: authentik/core/templates/if/error.html
#: authentik/policies/templates/policies/denied.html
msgid "Go home"
msgstr "Vai alla pagina iniziale"
@@ -531,26 +524,6 @@ msgstr "rsa"
msgid "ecdsa"
msgstr "ecdsa"
#: authentik/crypto/models.py
msgid "RSA"
msgstr ""
#: authentik/crypto/models.py
msgid "Elliptic Curve"
msgstr ""
#: authentik/crypto/models.py
msgid "DSA"
msgstr ""
#: authentik/crypto/models.py
msgid "Ed25519"
msgstr ""
#: authentik/crypto/models.py
msgid "Ed448"
msgstr ""
#: authentik/crypto/models.py
msgid "PEM-encoded Certificate data"
msgstr "Dati del certificato in codifica PEM"
@@ -575,104 +548,6 @@ msgstr "Coppie certificato-chiave"
msgid "Discover, import and update certificates from the filesystem."
msgstr "Scopri, importa e aggiorna i certificati dal file system."
#: authentik/endpoints/connectors/agent/api/connectors.py
msgid "Selected platform not supported"
msgstr ""
#: authentik/endpoints/connectors/agent/api/connectors.py
msgid "Token is expired"
msgstr ""
#: authentik/endpoints/connectors/agent/api/connectors.py
msgid "Invalid token for connector"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Agent Connector"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Agent Connectors"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
#: authentik/providers/oauth2/models.py
msgid "Device Token"
msgstr "Token Dispositivo"
#: authentik/endpoints/connectors/agent/models.py
#: authentik/providers/oauth2/models.py
msgid "Device Tokens"
msgstr "Token Dispositivi"
#: authentik/endpoints/connectors/agent/models.py
msgid "Enrollment Token"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Enrollment Tokens"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Device authentication token"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Device authentication tokens"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Apple Nonce"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Apple Nonces"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device"
msgstr "Dispositivo"
#: authentik/endpoints/models.py
msgid "Devices"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device User binding"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device User bindings"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device connection"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device connections"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device fact snapshot"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device fact snapshots"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device access group"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device access groups"
msgstr ""
#: authentik/endpoints/tasks.py
msgid "Sync endpoints."
msgstr ""
#: authentik/enterprise/api.py
msgid "Enterprise is required to create/update this object."
msgstr "Versione Enterprise richiesta per creare/aggiornare questo oggetto"
@@ -1336,22 +1211,10 @@ msgstr "Token del flusso"
msgid "Flow Tokens"
msgstr "Tokens del flusso"
#: authentik/flows/templates/if/flow.html
msgid "Site footer"
msgstr ""
#: authentik/flows/views/executor.py
msgid "Invalid next URL"
msgstr "URL successivo non valido"
#: authentik/lib/sync/outgoing/models.py
msgid "Controls the number of objects synced in a single task"
msgstr ""
#: authentik/lib/sync/outgoing/models.py
msgid "Timeout for synchronization of a single page"
msgstr ""
#: authentik/lib/sync/outgoing/models.py
msgid ""
"When enabled, provider will not modify or create objects in the remote "
@@ -1750,10 +1613,6 @@ msgstr "Avatar utente"
msgid "Not you?"
msgstr "Non sei tu?"
#: authentik/policies/templates/policies/denied.html
msgid "Error"
msgstr "Errore"
#: authentik/policies/templates/policies/denied.html
msgid "Request has been denied."
msgstr "La richiesta è stata negata."
@@ -2135,6 +1994,14 @@ msgstr "Token di aggiornamento OAuth2"
msgid "OAuth2 Refresh Tokens"
msgstr "Tokens di aggiornamento OAuth2"
#: authentik/providers/oauth2/models.py
msgid "Device Token"
msgstr "Token Dispositivo"
#: authentik/providers/oauth2/models.py
msgid "Device Tokens"
msgstr "Token Dispositivi"
#: authentik/providers/oauth2/tasks.py
msgid "Send a back-channel logout request to the registered client"
msgstr ""
@@ -2371,18 +2238,6 @@ msgstr "Impossibile importare i metadati: {messages}"
msgid "ACS URL"
msgstr "URL ACS"
#: authentik/providers/saml/models.py
msgid "Service Provider Binding"
msgstr "Associazione fornitore di servizi"
#: authentik/providers/saml/models.py
msgid ""
"This determines how authentik sends the response back to the Service "
"Provider."
msgstr ""
"Ciò determina il modo in cui authentik invia la risposta al fornitore di "
"servizi."
#: authentik/providers/saml/models.py
msgid ""
"Value of the audience restriction field of the assertion. When left empty, "
@@ -2395,6 +2250,18 @@ msgstr ""
msgid "Also known as EntityID"
msgstr "Conosciuto anche come EntityID"
#: authentik/providers/saml/models.py
msgid "Service Provider Binding"
msgstr "Associazione fornitore di servizi"
#: authentik/providers/saml/models.py
msgid ""
"This determines how authentik sends the response back to the Service "
"Provider."
msgstr ""
"Ciò determina il modo in cui authentik invia la risposta al fornitore di "
"servizi."
#: authentik/providers/saml/models.py
msgid "SLS URL"
msgstr ""

Binary file not shown.

View File

@@ -19,14 +19,14 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-12-01 15:07+0000\n"
"POT-Creation-Date: 2025-11-10 00:11+0000\n"
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
"Last-Translator: Teffen Ellis, 2025\n"
"Language-Team: Japanese (https://app.transifex.com/authentik/teams/119923/ja/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: ja_JP\n"
"Language: ja\n"
"Plural-Forms: nplurals=1; plural=0;\n"
#: authentik/admin/models.py
@@ -363,12 +363,6 @@ msgstr "ソースの表示名。"
msgid "Internal source name, used in URLs."
msgstr "URLで使用される内部ソース名。"
#: authentik/core/models.py
msgid ""
"When enabled, this source will be displayed as a prominent button on the "
"login page, instead of a small icon."
msgstr ""
#: authentik/core/models.py
msgid "Flow to use when authenticating existing users."
msgstr "フローは既存のユーザーを認証するときに使用されます。"
@@ -397,7 +391,7 @@ msgstr "トークン"
msgid "Tokens"
msgstr "トークン"
#: authentik/core/models.py authentik/endpoints/connectors/agent/models.py
#: authentik/core/models.py
msgid "View token's key"
msgstr "トークンのキーを表示"
@@ -467,7 +461,6 @@ msgid "Remove temporary users created by SAML Sources."
msgstr "SAMLで作成された一時ユーザを削除。"
#: authentik/core/templates/if/error.html
#: authentik/policies/templates/policies/denied.html
msgid "Go home"
msgstr "ホームに戻る"
@@ -541,104 +534,6 @@ msgstr "証明書とキーのペア"
msgid "Discover, import and update certificates from the filesystem."
msgstr "証明書をファイルシステムから検出、インポート、更新する。"
#: authentik/endpoints/connectors/agent/api/connectors.py
msgid "Selected platform not supported"
msgstr ""
#: authentik/endpoints/connectors/agent/api/connectors.py
msgid "Token is expired"
msgstr ""
#: authentik/endpoints/connectors/agent/api/connectors.py
msgid "Invalid token for connector"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Agent Connector"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Agent Connectors"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
#: authentik/providers/oauth2/models.py
msgid "Device Token"
msgstr "デバイストークン"
#: authentik/endpoints/connectors/agent/models.py
#: authentik/providers/oauth2/models.py
msgid "Device Tokens"
msgstr "デバイストークン"
#: authentik/endpoints/connectors/agent/models.py
msgid "Enrollment Token"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Enrollment Tokens"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Device authentication token"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Device authentication tokens"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Apple Nonce"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Apple Nonces"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device"
msgstr ""
#: authentik/endpoints/models.py
msgid "Devices"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device User binding"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device User bindings"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device connection"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device connections"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device fact snapshot"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device fact snapshots"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device access group"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device access groups"
msgstr ""
#: authentik/endpoints/tasks.py
msgid "Sync endpoints."
msgstr ""
#: authentik/enterprise/api.py
msgid "Enterprise is required to create/update this object."
msgstr "このオブジェクトの作成/更新にはエンタープライズ契約が必要です。"
@@ -1248,10 +1143,6 @@ msgstr "フロートークン"
msgid "Flow Tokens"
msgstr "フロートークン"
#: authentik/flows/templates/if/flow.html
msgid "Site footer"
msgstr ""
#: authentik/flows/views/executor.py
msgid "Invalid next URL"
msgstr "無効なネクスト URL"
@@ -1631,10 +1522,6 @@ msgstr "アバター"
msgid "Not you?"
msgstr "あなたではありませんか?"
#: authentik/policies/templates/policies/denied.html
msgid "Error"
msgstr ""
#: authentik/policies/templates/policies/denied.html
msgid "Request has been denied."
msgstr "リクエストは拒否されました。"
@@ -1990,6 +1877,14 @@ msgstr "OAuth2 リフレッシュトークン"
msgid "OAuth2 Refresh Tokens"
msgstr "OAuth2 リフレッシュトークン"
#: authentik/providers/oauth2/models.py
msgid "Device Token"
msgstr "デバイストークン"
#: authentik/providers/oauth2/models.py
msgid "Device Tokens"
msgstr "デバイストークン"
#: authentik/providers/oauth2/tasks.py
msgid "Send a back-channel logout request to the registered client"
msgstr "登録されたクライアントにバックチャネルログアウトリクエストを送信"

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@@ -11,22 +11,22 @@
# Sjors Wortelboer, 2024
# Marc Schmitt, 2025
# Taeke <transifex.net@taeke.eu>, 2025
# Dany Sluijk, 2025
# Alex Kruidenberg <alexkruidenberg@hotmail.com>, 2025
# Dany Sluijk, 2025
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-12-01 15:07+0000\n"
"POT-Creation-Date: 2025-10-20 00:11+0000\n"
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
"Last-Translator: Alex Kruidenberg <alexkruidenberg@hotmail.com>, 2025\n"
"Last-Translator: Dany Sluijk, 2025\n"
"Language-Team: Dutch (https://app.transifex.com/authentik/teams/119923/nl/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: nl_NL\n"
"Language: nl\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: authentik/admin/models.py
@@ -388,12 +388,6 @@ msgstr "Weergavenaam van de bron."
msgid "Internal source name, used in URLs."
msgstr "Interne naam van de bron, gebruikt in URL's."
#: authentik/core/models.py
msgid ""
"When enabled, this source will be displayed as a prominent button on the "
"login page, instead of a small icon."
msgstr ""
#: authentik/core/models.py
msgid "Flow to use when authenticating existing users."
msgstr "Flow om te gebruiken bij het authenticeren van bestaande gebruikers."
@@ -426,7 +420,7 @@ msgstr "Token"
msgid "Tokens"
msgstr "Tokens"
#: authentik/core/models.py authentik/endpoints/connectors/agent/models.py
#: authentik/core/models.py
msgid "View token's key"
msgstr "Toon token sleutel"
@@ -498,7 +492,6 @@ msgid "Remove temporary users created by SAML Sources."
msgstr ""
#: authentik/core/templates/if/error.html
#: authentik/policies/templates/policies/denied.html
msgid "Go home"
msgstr "Ga naar startpagina"
@@ -530,26 +523,6 @@ msgstr "rsa"
msgid "ecdsa"
msgstr "ecdsa"
#: authentik/crypto/models.py
msgid "RSA"
msgstr ""
#: authentik/crypto/models.py
msgid "Elliptic Curve"
msgstr ""
#: authentik/crypto/models.py
msgid "DSA"
msgstr ""
#: authentik/crypto/models.py
msgid "Ed25519"
msgstr ""
#: authentik/crypto/models.py
msgid "Ed448"
msgstr ""
#: authentik/crypto/models.py
msgid "PEM-encoded Certificate data"
msgstr "PEM-gecodeerde certificaatgegevens"
@@ -574,104 +547,6 @@ msgstr "Certificaat-Sleutelparen"
msgid "Discover, import and update certificates from the filesystem."
msgstr ""
#: authentik/endpoints/connectors/agent/api/connectors.py
msgid "Selected platform not supported"
msgstr ""
#: authentik/endpoints/connectors/agent/api/connectors.py
msgid "Token is expired"
msgstr ""
#: authentik/endpoints/connectors/agent/api/connectors.py
msgid "Invalid token for connector"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Agent Connector"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Agent Connectors"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
#: authentik/providers/oauth2/models.py
msgid "Device Token"
msgstr "Apparaattoken"
#: authentik/endpoints/connectors/agent/models.py
#: authentik/providers/oauth2/models.py
msgid "Device Tokens"
msgstr "Apparaattokens"
#: authentik/endpoints/connectors/agent/models.py
msgid "Enrollment Token"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Enrollment Tokens"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Device authentication token"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Device authentication tokens"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Apple Nonce"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Apple Nonces"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device"
msgstr ""
#: authentik/endpoints/models.py
msgid "Devices"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device User binding"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device User bindings"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device connection"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device connections"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device fact snapshot"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device fact snapshots"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device access group"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device access groups"
msgstr ""
#: authentik/endpoints/tasks.py
msgid "Sync endpoints."
msgstr ""
#: authentik/enterprise/api.py
msgid "Enterprise is required to create/update this object."
msgstr "Enterprise is benodigd om dit object te aan te maken/bijwerken."
@@ -1302,22 +1177,10 @@ msgstr "Flowtoken"
msgid "Flow Tokens"
msgstr "Flowtokens"
#: authentik/flows/templates/if/flow.html
msgid "Site footer"
msgstr ""
#: authentik/flows/views/executor.py
msgid "Invalid next URL"
msgstr "Invalide volgend URL"
#: authentik/lib/sync/outgoing/models.py
msgid "Controls the number of objects synced in a single task"
msgstr ""
#: authentik/lib/sync/outgoing/models.py
msgid "Timeout for synchronization of a single page"
msgstr ""
#: authentik/lib/sync/outgoing/models.py
msgid ""
"When enabled, provider will not modify or create objects in the remote "
@@ -1710,10 +1573,6 @@ msgstr "Avatar van de gebruiker"
msgid "Not you?"
msgstr "Niet jij?"
#: authentik/policies/templates/policies/denied.html
msgid "Error"
msgstr "Fout"
#: authentik/policies/templates/policies/denied.html
msgid "Request has been denied."
msgstr "Verzoek is geweigerd."
@@ -2094,6 +1953,14 @@ msgstr "OAuth2 Verversingstoken"
msgid "OAuth2 Refresh Tokens"
msgstr "OAuth2 Verversingstokens"
#: authentik/providers/oauth2/models.py
msgid "Device Token"
msgstr "Apparaattoken"
#: authentik/providers/oauth2/models.py
msgid "Device Tokens"
msgstr "Apparaattokens"
#: authentik/providers/oauth2/tasks.py
msgid "Send a back-channel logout request to the registered client"
msgstr ""
@@ -2326,17 +2193,6 @@ msgstr ""
msgid "ACS URL"
msgstr "ACS-URL"
#: authentik/providers/saml/models.py
msgid "Service Provider Binding"
msgstr "Serviceproviderbinding"
#: authentik/providers/saml/models.py
msgid ""
"This determines how authentik sends the response back to the Service "
"Provider."
msgstr ""
"Dit bepaalt hoe authentik de reactie terugstuurt naar de serviceprovider."
#: authentik/providers/saml/models.py
msgid ""
"Value of the audience restriction field of the assertion. When left empty, "
@@ -2349,6 +2205,17 @@ msgstr ""
msgid "Also known as EntityID"
msgstr "Ook bekend als EntityID"
#: authentik/providers/saml/models.py
msgid "Service Provider Binding"
msgstr "Serviceproviderbinding"
#: authentik/providers/saml/models.py
msgid ""
"This determines how authentik sends the response back to the Service "
"Provider."
msgstr ""
"Dit bepaalt hoe authentik de reactie terugstuurt naar de serviceprovider."
#: authentik/providers/saml/models.py
msgid "SLS URL"
msgstr ""

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@@ -11,18 +11,18 @@
# Hacklab, 2025
# Victor Haddad, 2025
# Josenivaldo Benito Junior, 2025
# Rafael Mundel, 2025
# Hudson Oliveira, 2025
# Wagner Santos, 2025
# Rafael Mundel, 2025
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-12-01 15:07+0000\n"
"POT-Creation-Date: 2025-10-20 00:11+0000\n"
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
"Last-Translator: Rafael Mundel, 2025\n"
"Last-Translator: Wagner Santos, 2025\n"
"Language-Team: Portuguese (Brazil) (https://app.transifex.com/authentik/teams/119923/pt_BR/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -390,12 +390,6 @@ msgstr "Nome de exibição da fonte."
msgid "Internal source name, used in URLs."
msgstr "Nome da fonte interna, usado em URLs."
#: authentik/core/models.py
msgid ""
"When enabled, this source will be displayed as a prominent button on the "
"login page, instead of a small icon."
msgstr ""
#: authentik/core/models.py
msgid "Flow to use when authenticating existing users."
msgstr "Fluxo a ser usado ao autenticar usuários existentes."
@@ -428,7 +422,7 @@ msgstr "Token"
msgid "Tokens"
msgstr "Tokens"
#: authentik/core/models.py authentik/endpoints/connectors/agent/models.py
#: authentik/core/models.py
msgid "View token's key"
msgstr "Ver chaves do token"
@@ -500,7 +494,6 @@ msgid "Remove temporary users created by SAML Sources."
msgstr "Remover usuários temporários criados por Fontes SAML."
#: authentik/core/templates/if/error.html
#: authentik/policies/templates/policies/denied.html
msgid "Go home"
msgstr "Ir para casa"
@@ -532,26 +525,6 @@ msgstr "rsa"
msgid "ecdsa"
msgstr "ecdsa"
#: authentik/crypto/models.py
msgid "RSA"
msgstr ""
#: authentik/crypto/models.py
msgid "Elliptic Curve"
msgstr ""
#: authentik/crypto/models.py
msgid "DSA"
msgstr ""
#: authentik/crypto/models.py
msgid "Ed25519"
msgstr ""
#: authentik/crypto/models.py
msgid "Ed448"
msgstr ""
#: authentik/crypto/models.py
msgid "PEM-encoded Certificate data"
msgstr "Dados de certificado codificados por PEM"
@@ -576,104 +549,6 @@ msgstr "Pares de chave de certificado"
msgid "Discover, import and update certificates from the filesystem."
msgstr "Descobrir, importar e atualizar certificados do sistema de arquivos."
#: authentik/endpoints/connectors/agent/api/connectors.py
msgid "Selected platform not supported"
msgstr ""
#: authentik/endpoints/connectors/agent/api/connectors.py
msgid "Token is expired"
msgstr ""
#: authentik/endpoints/connectors/agent/api/connectors.py
msgid "Invalid token for connector"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Agent Connector"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Agent Connectors"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
#: authentik/providers/oauth2/models.py
msgid "Device Token"
msgstr "Token do dispositivo"
#: authentik/endpoints/connectors/agent/models.py
#: authentik/providers/oauth2/models.py
msgid "Device Tokens"
msgstr "Tokens de dispositivo"
#: authentik/endpoints/connectors/agent/models.py
msgid "Enrollment Token"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Enrollment Tokens"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Device authentication token"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Device authentication tokens"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Apple Nonce"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Apple Nonces"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device"
msgstr "Dispositivo"
#: authentik/endpoints/models.py
msgid "Devices"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device User binding"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device User bindings"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device connection"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device connections"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device fact snapshot"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device fact snapshots"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device access group"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device access groups"
msgstr ""
#: authentik/endpoints/tasks.py
msgid "Sync endpoints."
msgstr ""
#: authentik/enterprise/api.py
msgid "Enterprise is required to create/update this object."
msgstr "Enterprise é necessário para criar/atualizar esse objeto."
@@ -1326,22 +1201,10 @@ msgstr "Token de Fluxo"
msgid "Flow Tokens"
msgstr "Tokens de Fluxo"
#: authentik/flows/templates/if/flow.html
msgid "Site footer"
msgstr ""
#: authentik/flows/views/executor.py
msgid "Invalid next URL"
msgstr "URL de próximo passo inválida"
#: authentik/lib/sync/outgoing/models.py
msgid "Controls the number of objects synced in a single task"
msgstr ""
#: authentik/lib/sync/outgoing/models.py
msgid "Timeout for synchronization of a single page"
msgstr ""
#: authentik/lib/sync/outgoing/models.py
msgid ""
"When enabled, provider will not modify or create objects in the remote "
@@ -1736,10 +1599,6 @@ msgstr "Avatar do usuário"
msgid "Not you?"
msgstr "Não é você?"
#: authentik/policies/templates/policies/denied.html
msgid "Error"
msgstr "Erro"
#: authentik/policies/templates/policies/denied.html
msgid "Request has been denied."
msgstr "A solicitação foi negada."
@@ -2119,6 +1978,14 @@ msgstr "Token de Atualização OAuth2"
msgid "OAuth2 Refresh Tokens"
msgstr "Tokens de Atualização OAuth2"
#: authentik/providers/oauth2/models.py
msgid "Device Token"
msgstr "Token do dispositivo"
#: authentik/providers/oauth2/models.py
msgid "Device Tokens"
msgstr "Tokens de dispositivo"
#: authentik/providers/oauth2/tasks.py
msgid "Send a back-channel logout request to the registered client"
msgstr ""
@@ -2356,18 +2223,6 @@ msgstr "Falha ao importar Metadata: {messages}"
msgid "ACS URL"
msgstr "URL ACS"
#: authentik/providers/saml/models.py
msgid "Service Provider Binding"
msgstr "Vinculação do Provedor de Serviços"
#: authentik/providers/saml/models.py
msgid ""
"This determines how authentik sends the response back to the Service "
"Provider."
msgstr ""
"Isso determina como o authentik envia a resposta de volta ao provedor de "
"serviços."
#: authentik/providers/saml/models.py
msgid ""
"Value of the audience restriction field of the assertion. When left empty, "
@@ -2380,6 +2235,18 @@ msgstr ""
msgid "Also known as EntityID"
msgstr "Também conhecido como EntityID"
#: authentik/providers/saml/models.py
msgid "Service Provider Binding"
msgstr "Vinculação do Provedor de Serviços"
#: authentik/providers/saml/models.py
msgid ""
"This determines how authentik sends the response back to the Service "
"Provider."
msgstr ""
"Isso determina como o authentik envia a resposta de volta ao provedor de "
"serviços."
#: authentik/providers/saml/models.py
msgid "SLS URL"
msgstr "URLs SLS"

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@@ -18,14 +18,14 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-12-01 15:07+0000\n"
"POT-Creation-Date: 2025-10-20 00:11+0000\n"
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
"Last-Translator: Marc Schmitt, 2025\n"
"Language-Team: Russian (https://app.transifex.com/authentik/teams/119923/ru/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: ru_RU\n"
"Language: ru\n"
"Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n%100>=11 && n%100<=14)? 2 : 3);\n"
#: authentik/admin/models.py
@@ -388,12 +388,6 @@ msgstr "Отображаемое имя источника."
msgid "Internal source name, used in URLs."
msgstr "Внутреннее имя источника, используемое в URL-адресах."
#: authentik/core/models.py
msgid ""
"When enabled, this source will be displayed as a prominent button on the "
"login page, instead of a small icon."
msgstr ""
#: authentik/core/models.py
msgid "Flow to use when authenticating existing users."
msgstr "Поток, используемый при аутентификации существующих пользователей."
@@ -426,7 +420,7 @@ msgstr "Токен"
msgid "Tokens"
msgstr "Токены"
#: authentik/core/models.py authentik/endpoints/connectors/agent/models.py
#: authentik/core/models.py
msgid "View token's key"
msgstr "Просмотр ключа токена "
@@ -499,7 +493,6 @@ msgid "Remove temporary users created by SAML Sources."
msgstr ""
#: authentik/core/templates/if/error.html
#: authentik/policies/templates/policies/denied.html
msgid "Go home"
msgstr "Домой"
@@ -531,26 +524,6 @@ msgstr "rsa"
msgid "ecdsa"
msgstr "ecdsa"
#: authentik/crypto/models.py
msgid "RSA"
msgstr ""
#: authentik/crypto/models.py
msgid "Elliptic Curve"
msgstr ""
#: authentik/crypto/models.py
msgid "DSA"
msgstr ""
#: authentik/crypto/models.py
msgid "Ed25519"
msgstr ""
#: authentik/crypto/models.py
msgid "Ed448"
msgstr ""
#: authentik/crypto/models.py
msgid "PEM-encoded Certificate data"
msgstr "Данные сертификата, закодированные в формате PEM"
@@ -575,104 +548,6 @@ msgstr "Пары сертификат-ключ"
msgid "Discover, import and update certificates from the filesystem."
msgstr ""
#: authentik/endpoints/connectors/agent/api/connectors.py
msgid "Selected platform not supported"
msgstr ""
#: authentik/endpoints/connectors/agent/api/connectors.py
msgid "Token is expired"
msgstr ""
#: authentik/endpoints/connectors/agent/api/connectors.py
msgid "Invalid token for connector"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Agent Connector"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Agent Connectors"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
#: authentik/providers/oauth2/models.py
msgid "Device Token"
msgstr "Токен устройства"
#: authentik/endpoints/connectors/agent/models.py
#: authentik/providers/oauth2/models.py
msgid "Device Tokens"
msgstr "Токены устройства"
#: authentik/endpoints/connectors/agent/models.py
msgid "Enrollment Token"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Enrollment Tokens"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Device authentication token"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Device authentication tokens"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Apple Nonce"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Apple Nonces"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device"
msgstr ""
#: authentik/endpoints/models.py
msgid "Devices"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device User binding"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device User bindings"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device connection"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device connections"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device fact snapshot"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device fact snapshots"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device access group"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device access groups"
msgstr ""
#: authentik/endpoints/tasks.py
msgid "Sync endpoints."
msgstr ""
#: authentik/enterprise/api.py
msgid "Enterprise is required to create/update this object."
msgstr "Для создания/обновления этого объекта требуется Enterprise."
@@ -1301,22 +1176,10 @@ msgstr "Токен потока"
msgid "Flow Tokens"
msgstr "Токены потока"
#: authentik/flows/templates/if/flow.html
msgid "Site footer"
msgstr ""
#: authentik/flows/views/executor.py
msgid "Invalid next URL"
msgstr "Недопустимый следующий URL-адрес"
#: authentik/lib/sync/outgoing/models.py
msgid "Controls the number of objects synced in a single task"
msgstr ""
#: authentik/lib/sync/outgoing/models.py
msgid "Timeout for synchronization of a single page"
msgstr ""
#: authentik/lib/sync/outgoing/models.py
msgid ""
"When enabled, provider will not modify or create objects in the remote "
@@ -1711,10 +1574,6 @@ msgstr "Аватар пользователя"
msgid "Not you?"
msgstr "Не вы?"
#: authentik/policies/templates/policies/denied.html
msgid "Error"
msgstr ""
#: authentik/policies/templates/policies/denied.html
msgid "Request has been denied."
msgstr "В произведении запроса было отказано."
@@ -2093,6 +1952,14 @@ msgstr "OAuth2 Refresh токен"
msgid "OAuth2 Refresh Tokens"
msgstr "OAuth2 Refresh токены"
#: authentik/providers/oauth2/models.py
msgid "Device Token"
msgstr "Токен устройства"
#: authentik/providers/oauth2/models.py
msgid "Device Tokens"
msgstr "Токены устройства"
#: authentik/providers/oauth2/tasks.py
msgid "Send a back-channel logout request to the registered client"
msgstr ""
@@ -2326,17 +2193,6 @@ msgstr "Не удалось импортировать метаданные: {me
msgid "ACS URL"
msgstr "URL-адрес ACS"
#: authentik/providers/saml/models.py
msgid "Service Provider Binding"
msgstr "Привязка провайдера услуг"
#: authentik/providers/saml/models.py
msgid ""
"This determines how authentik sends the response back to the Service "
"Provider."
msgstr ""
"Это определяет, как authentik отправляет ответ обратно провайдеру услуг."
#: authentik/providers/saml/models.py
msgid ""
"Value of the audience restriction field of the assertion. When left empty, "
@@ -2349,6 +2205,17 @@ msgstr ""
msgid "Also known as EntityID"
msgstr "Также известен как EntityID"
#: authentik/providers/saml/models.py
msgid "Service Provider Binding"
msgstr "Привязка провайдера услуг"
#: authentik/providers/saml/models.py
msgid ""
"This determines how authentik sends the response back to the Service "
"Provider."
msgstr ""
"Это определяет, как authentik отправляет ответ обратно провайдеру услуг."
#: authentik/providers/saml/models.py
msgid "SLS URL"
msgstr ""

Binary file not shown.

View File

@@ -13,14 +13,14 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-12-01 15:07+0000\n"
"POT-Creation-Date: 2025-10-20 00:11+0000\n"
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
"Last-Translator: Jens L. <jens@goauthentik.io>, 2025\n"
"Language-Team: Turkish (https://app.transifex.com/authentik/teams/119923/tr/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: tr_TR\n"
"Language: tr\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
#: authentik/admin/models.py
@@ -381,12 +381,6 @@ msgstr "Kaynağın görünen Adı."
msgid "Internal source name, used in URLs."
msgstr "URL'lerde kullanılan iç kaynak adı."
#: authentik/core/models.py
msgid ""
"When enabled, this source will be displayed as a prominent button on the "
"login page, instead of a small icon."
msgstr ""
#: authentik/core/models.py
msgid "Flow to use when authenticating existing users."
msgstr "Mevcut kullanıcıların kimliğini doğrularken kullanılacak akış."
@@ -419,7 +413,7 @@ msgstr "Jeton"
msgid "Tokens"
msgstr "Jetonlar"
#: authentik/core/models.py authentik/endpoints/connectors/agent/models.py
#: authentik/core/models.py
msgid "View token's key"
msgstr "Jetonun anahtarını görüntüle"
@@ -491,7 +485,6 @@ msgid "Remove temporary users created by SAML Sources."
msgstr ""
#: authentik/core/templates/if/error.html
#: authentik/policies/templates/policies/denied.html
msgid "Go home"
msgstr "Başa dön"
@@ -523,26 +516,6 @@ msgstr "rsa"
msgid "ecdsa"
msgstr "ecdsa"
#: authentik/crypto/models.py
msgid "RSA"
msgstr ""
#: authentik/crypto/models.py
msgid "Elliptic Curve"
msgstr ""
#: authentik/crypto/models.py
msgid "DSA"
msgstr ""
#: authentik/crypto/models.py
msgid "Ed25519"
msgstr ""
#: authentik/crypto/models.py
msgid "Ed448"
msgstr ""
#: authentik/crypto/models.py
msgid "PEM-encoded Certificate data"
msgstr "PEM kodlu Sertifika verileri"
@@ -567,104 +540,6 @@ msgstr "Sertifika-Anahtar Çiftleri"
msgid "Discover, import and update certificates from the filesystem."
msgstr ""
#: authentik/endpoints/connectors/agent/api/connectors.py
msgid "Selected platform not supported"
msgstr ""
#: authentik/endpoints/connectors/agent/api/connectors.py
msgid "Token is expired"
msgstr ""
#: authentik/endpoints/connectors/agent/api/connectors.py
msgid "Invalid token for connector"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Agent Connector"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Agent Connectors"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
#: authentik/providers/oauth2/models.py
msgid "Device Token"
msgstr "Cihaz Jetonu"
#: authentik/endpoints/connectors/agent/models.py
#: authentik/providers/oauth2/models.py
msgid "Device Tokens"
msgstr "Cihaz Jetonları"
#: authentik/endpoints/connectors/agent/models.py
msgid "Enrollment Token"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Enrollment Tokens"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Device authentication token"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Device authentication tokens"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Apple Nonce"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Apple Nonces"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device"
msgstr "Cihaz"
#: authentik/endpoints/models.py
msgid "Devices"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device User binding"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device User bindings"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device connection"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device connections"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device fact snapshot"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device fact snapshots"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device access group"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device access groups"
msgstr ""
#: authentik/endpoints/tasks.py
msgid "Sync endpoints."
msgstr ""
#: authentik/enterprise/api.py
msgid "Enterprise is required to create/update this object."
msgstr "Bu nesneyi oluşturmak/güncellemek için Kurumsal Paket gereklidir."
@@ -1294,22 +1169,10 @@ msgstr "Akış Jetonu"
msgid "Flow Tokens"
msgstr "Akış Jetonları"
#: authentik/flows/templates/if/flow.html
msgid "Site footer"
msgstr ""
#: authentik/flows/views/executor.py
msgid "Invalid next URL"
msgstr "Sonraki URL geçersiz"
#: authentik/lib/sync/outgoing/models.py
msgid "Controls the number of objects synced in a single task"
msgstr ""
#: authentik/lib/sync/outgoing/models.py
msgid "Timeout for synchronization of a single page"
msgstr ""
#: authentik/lib/sync/outgoing/models.py
msgid ""
"When enabled, provider will not modify or create objects in the remote "
@@ -1699,10 +1562,6 @@ msgstr "Kullanıcının avatarı"
msgid "Not you?"
msgstr "Sen değil miydin?"
#: authentik/policies/templates/policies/denied.html
msgid "Error"
msgstr "Hata"
#: authentik/policies/templates/policies/denied.html
msgid "Request has been denied."
msgstr "İstek reddedildi."
@@ -2080,6 +1939,14 @@ msgstr "OAuth2 Yenileme Jetonu"
msgid "OAuth2 Refresh Tokens"
msgstr "OAuth2 Yenileme Jetonları"
#: authentik/providers/oauth2/models.py
msgid "Device Token"
msgstr "Cihaz Jetonu"
#: authentik/providers/oauth2/models.py
msgid "Device Tokens"
msgstr "Cihaz Jetonları"
#: authentik/providers/oauth2/tasks.py
msgid "Send a back-channel logout request to the registered client"
msgstr ""
@@ -2316,18 +2183,6 @@ msgstr "Meta Veriler içe aktarılamadı: {messages}"
msgid "ACS URL"
msgstr "ACS URL"
#: authentik/providers/saml/models.py
msgid "Service Provider Binding"
msgstr "Servis Sağlayıcı Bağlama"
#: authentik/providers/saml/models.py
msgid ""
"This determines how authentik sends the response back to the Service "
"Provider."
msgstr ""
"Bu, authentik'in yanıtı Servis Sağlayıcıya nasıl geri göndereceğini "
"belirler."
#: authentik/providers/saml/models.py
msgid ""
"Value of the audience restriction field of the assertion. When left empty, "
@@ -2340,6 +2195,18 @@ msgstr ""
msgid "Also known as EntityID"
msgstr "EntityID olarak da bilinir"
#: authentik/providers/saml/models.py
msgid "Service Provider Binding"
msgstr "Servis Sağlayıcı Bağlama"
#: authentik/providers/saml/models.py
msgid ""
"This determines how authentik sends the response back to the Service "
"Provider."
msgstr ""
"Bu, authentik'in yanıtı Servis Sağlayıcıya nasıl geri göndereceğini "
"belirler."
#: authentik/providers/saml/models.py
msgid "SLS URL"
msgstr ""

Some files were not shown because too many files have changed in this diff Show More