Compare commits

..

3 Commits

Author SHA1 Message Date
d660a392b9 punctuation fix 2024-08-09 15:57:35 -05:00
f530ce5e02 tweaks 2024-08-09 15:48:08 -05:00
d4012df59d add section about webhook cert config 2024-08-09 15:39:31 -05:00
316 changed files with 7567 additions and 27798 deletions

View File

@ -1,5 +1,5 @@
[bumpversion]
current_version = 2024.8.0
current_version = 2024.6.3
tag = True
commit = True
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(?:-(?P<rc_t>[a-zA-Z-]+)(?P<rc_n>[1-9]\\d*))?

View File

@ -29,9 +29,9 @@ outputs:
imageTags:
description: "Docker image tags"
value: ${{ steps.ev.outputs.imageTags }}
attestImageNames:
description: "Docker image names used for attestation"
value: ${{ steps.ev.outputs.attestImageNames }}
imageNames:
description: "Docker image names"
value: ${{ steps.ev.outputs.imageNames }}
imageMainTag:
description: "Docker image main tag"
value: ${{ steps.ev.outputs.imageMainTag }}

View File

@ -51,24 +51,15 @@ else:
]
image_main_tag = image_tags[0].split(":")[-1]
def get_attest_image_names(image_with_tags: list[str]):
"""Attestation only for GHCR"""
image_tags = []
for image_name in set(name.split(":")[0] for name in image_with_tags):
if not image_name.startswith("ghcr.io"):
continue
image_tags.append(image_name)
return ",".join(set(image_tags))
image_tags_rendered = ",".join(image_tags)
image_names_rendered = ",".join(set(name.split(":")[0] for name in image_tags))
with open(os.environ["GITHUB_OUTPUT"], "a+", encoding="utf-8") as _output:
print(f"shouldBuild={should_build}", file=_output)
print(f"sha={sha}", file=_output)
print(f"version={version}", file=_output)
print(f"prerelease={prerelease}", file=_output)
print(f"imageTags={','.join(image_tags)}", file=_output)
print(f"attestImageNames={get_attest_image_names(image_tags)}", file=_output)
print(f"imageTags={image_tags_rendered}", file=_output)
print(f"imageNames={image_names_rendered}", file=_output)
print(f"imageMainTag={image_main_tag}", file=_output)
print(f"imageMainName={image_tags[0]}", file=_output)

View File

@ -58,10 +58,6 @@ updates:
patterns:
- "@rollup/*"
- "rollup-*"
swc:
patterns:
- "@swc/*"
- "swc-*"
wdio:
patterns:
- "@wdio/*"

View File

@ -261,7 +261,7 @@ jobs:
id: attest
if: ${{ steps.ev.outputs.shouldBuild == 'true' }}
with:
subject-name: ${{ steps.ev.outputs.attestImageNames }}
subject-name: ${{ steps.ev.outputs.imageNames }}
subject-digest: ${{ steps.push.outputs.digest }}
push-to-registry: true
pr-comment:

View File

@ -31,7 +31,7 @@ jobs:
- name: golangci-lint
uses: golangci/golangci-lint-action@v6
with:
version: latest
version: v1.54.2
args: --timeout 5000s --verbose
skip-cache: true
test-unittest:
@ -115,7 +115,7 @@ jobs:
id: attest
if: ${{ steps.ev.outputs.shouldBuild == 'true' }}
with:
subject-name: ${{ steps.ev.outputs.attestImageNames }}
subject-name: ${{ steps.ev.outputs.imageNames }}
subject-digest: ${{ steps.push.outputs.digest }}
push-to-registry: true
build-binary:

View File

@ -92,4 +92,4 @@ jobs:
run: make gen-client-ts
- name: test
working-directory: web/
run: npm run test || exit 0
run: npm run test

View File

@ -51,14 +51,12 @@ jobs:
secrets: |
GEOIPUPDATE_ACCOUNT_ID=${{ secrets.GEOIPUPDATE_ACCOUNT_ID }}
GEOIPUPDATE_LICENSE_KEY=${{ secrets.GEOIPUPDATE_LICENSE_KEY }}
build-args: |
VERSION=${{ github.ref }}
tags: ${{ steps.ev.outputs.imageTags }}
platforms: linux/amd64,linux/arm64
- uses: actions/attest-build-provenance@v1
id: attest
with:
subject-name: ${{ steps.ev.outputs.attestImageNames }}
subject-name: ${{ steps.ev.outputs.imageNames }}
subject-digest: ${{ steps.push.outputs.digest }}
push-to-registry: true
build-outpost:
@ -113,8 +111,6 @@ jobs:
id: push
with:
push: true
build-args: |
VERSION=${{ github.ref }}
tags: ${{ steps.ev.outputs.imageTags }}
file: ${{ matrix.type }}.Dockerfile
platforms: linux/amd64,linux/arm64
@ -122,7 +118,7 @@ jobs:
- uses: actions/attest-build-provenance@v1
id: attest
with:
subject-name: ${{ steps.ev.outputs.attestImageNames }}
subject-name: ${{ steps.ev.outputs.imageNames }}
subject-digest: ${{ steps.push.outputs.digest }}
push-to-registry: true
build-outpost-binary:

View File

@ -1,7 +1,7 @@
# syntax=docker/dockerfile:1
# Stage 1: Build website
FROM --platform=${BUILDPLATFORM} docker.io/library/node:22 AS website-builder
FROM --platform=${BUILDPLATFORM} docker.io/library/node:22 as website-builder
ENV NODE_ENV=production
@ -20,7 +20,7 @@ COPY ./SECURITY.md /work/
RUN npm run build-bundled
# Stage 2: Build webui
FROM --platform=${BUILDPLATFORM} docker.io/library/node:22 AS web-builder
FROM --platform=${BUILDPLATFORM} docker.io/library/node:22 as web-builder
ARG GIT_BUILD_HASH
ENV GIT_BUILD_HASH=$GIT_BUILD_HASH
@ -43,7 +43,7 @@ COPY ./gen-ts-api /work/web/node_modules/@goauthentik/api
RUN npm run build
# Stage 3: Build go proxy
FROM --platform=${BUILDPLATFORM} mcr.microsoft.com/oss/go/microsoft/golang:1.23-fips-bookworm AS go-builder
FROM --platform=${BUILDPLATFORM} mcr.microsoft.com/oss/go/microsoft/golang:1.22-fips-bookworm AS go-builder
ARG TARGETOS
ARG TARGETARCH
@ -80,7 +80,7 @@ RUN --mount=type=cache,sharing=locked,target=/go/pkg/mod \
go build -o /go/authentik ./cmd/server
# Stage 4: MaxMind GeoIP
FROM --platform=${BUILDPLATFORM} ghcr.io/maxmind/geoipupdate:v7.0.1 AS geoip
FROM --platform=${BUILDPLATFORM} ghcr.io/maxmind/geoipupdate:v7.0.1 as geoip
ENV GEOIPUPDATE_EDITION_IDS="GeoLite2-City GeoLite2-ASN"
ENV GEOIPUPDATE_VERBOSE="1"
@ -96,9 +96,6 @@ RUN --mount=type=secret,id=GEOIPUPDATE_ACCOUNT_ID \
# Stage 5: Python dependencies
FROM ghcr.io/goauthentik/fips-python:3.12.5-slim-bookworm-fips-full AS python-deps
ARG TARGETARCH
ARG TARGETVARIANT
WORKDIR /ak-root/poetry
ENV VENV_PATH="/ak-root/venv" \
@ -126,15 +123,15 @@ RUN --mount=type=bind,target=./pyproject.toml,src=./pyproject.toml \
# Stage 6: Run
FROM ghcr.io/goauthentik/fips-python:3.12.5-slim-bookworm-fips-full AS final-image
ARG VERSION
ARG GIT_BUILD_HASH
ARG VERSION
ENV GIT_BUILD_HASH=$GIT_BUILD_HASH
LABEL org.opencontainers.image.url=https://goauthentik.io
LABEL org.opencontainers.image.description="goauthentik.io Main server image, see https://goauthentik.io for more info."
LABEL org.opencontainers.image.source=https://github.com/goauthentik/authentik
LABEL org.opencontainers.image.version=${VERSION}
LABEL org.opencontainers.image.revision=${GIT_BUILD_HASH}
LABEL org.opencontainers.image.url https://goauthentik.io
LABEL org.opencontainers.image.description goauthentik.io Main server image, see https://goauthentik.io for more info.
LABEL org.opencontainers.image.source https://github.com/goauthentik/authentik
LABEL org.opencontainers.image.version ${VERSION}
LABEL org.opencontainers.image.revision ${GIT_BUILD_HASH}
WORKDIR /

View File

@ -43,7 +43,7 @@ help: ## Show this help
sort
@echo ""
go-test:
test-go:
go test -timeout 0 -v -race -cover ./...
test-docker: ## Run all tests in a docker-compose
@ -205,14 +205,11 @@ gen: gen-build gen-client-ts
web-build: web-install ## Build the Authentik UI
cd web && npm run build
web: web-lint-fix web-lint web-check-compile web-test ## Automatically fix formatting issues in the Authentik UI source code, lint the code, and compile it
web: web-lint-fix web-lint web-check-compile ## Automatically fix formatting issues in the Authentik UI source code, lint the code, and compile it
web-install: ## Install the necessary libraries to build the Authentik UI
cd web && npm ci
web-test: ## Run tests for the Authentik UI
cd web && npm run test
web-watch: ## Build and watch the Authentik UI for changes, updating automatically
rm -rf web/dist/
mkdir web/dist/

View File

@ -15,9 +15,7 @@
## What is authentik?
authentik is an open-source Identity Provider that emphasizes flexibility and versatility, with support for a wide set of protocols.
Our [enterprise offer](https://goauthentik.io/pricing) can also be used as a self-hosted replacement for large-scale deployments of Okta/Auth0, Entra ID, Ping Identity, or other legacy IdPs for employees and B2B2C use.
authentik is an open-source Identity Provider that emphasizes flexibility and versatility. It can be seamlessly integrated into existing environments to support new protocols. authentik is also a great solution for implementing sign-up, recovery, and other similar features in your application, saving you the hassle of dealing with them.
## Installation

View File

@ -2,7 +2,7 @@
from os import environ
__version__ = "2024.8.0"
__version__ = "2024.6.3"
ENV_GIT_HASH_KEY = "GIT_BUILD_HASH"

View File

@ -12,7 +12,6 @@ from rest_framework.views import APIView
from authentik import __version__, get_build_hash
from authentik.admin.tasks import VERSION_CACHE_KEY, VERSION_NULL, update_latest_version
from authentik.core.api.utils import PassiveSerializer
from authentik.outposts.models import Outpost
class VersionSerializer(PassiveSerializer):
@ -23,7 +22,6 @@ class VersionSerializer(PassiveSerializer):
version_latest_valid = SerializerMethodField()
build_hash = SerializerMethodField()
outdated = SerializerMethodField()
outpost_outdated = SerializerMethodField()
def get_build_hash(self, _) -> str:
"""Get build hash, if version is not latest or released"""
@ -49,15 +47,6 @@ class VersionSerializer(PassiveSerializer):
"""Check if we're running the latest version"""
return parse(self.get_version_current(instance)) < parse(self.get_version_latest(instance))
def get_outpost_outdated(self, _) -> bool:
"""Check if any outpost is outdated/has a version mismatch"""
any_outdated = False
for outpost in Outpost.objects.all():
for state in outpost.state:
if state.version_outdated:
any_outdated = True
return any_outdated
class VersionView(APIView):
"""Get running and latest version."""

View File

@ -14,7 +14,6 @@ from rest_framework.request import Request
from rest_framework.response import Response
from authentik.core.api.utils import PassiveSerializer
from authentik.rbac.filters import ObjectFilter
class DeleteAction(Enum):
@ -54,7 +53,7 @@ class UsedByMixin:
@extend_schema(
responses={200: UsedBySerializer(many=True)},
)
@action(detail=True, pagination_class=None, filter_backends=[ObjectFilter])
@action(detail=True, pagination_class=None, filter_backends=[])
def used_by(self, request: Request, *args, **kwargs) -> Response:
"""Get a list of all objects that use this object"""
model: Model = self.get_object()

View File

@ -35,7 +35,6 @@ from authentik.crypto.builder import CertificateBuilder, PrivateKeyAlg
from authentik.crypto.models import CertificateKeyPair
from authentik.events.models import Event, EventAction
from authentik.rbac.decorators import permission_required
from authentik.rbac.filters import ObjectFilter
LOGGER = get_logger()
@ -266,7 +265,7 @@ class CertificateKeyPairViewSet(UsedByMixin, ModelViewSet):
],
responses={200: CertificateDataSerializer(many=False)},
)
@action(detail=True, pagination_class=None, filter_backends=[ObjectFilter])
@action(detail=True, pagination_class=None, filter_backends=[])
def view_certificate(self, request: Request, pk: str) -> Response:
"""Return certificate-key pairs certificate and log access"""
certificate: CertificateKeyPair = self.get_object()
@ -296,7 +295,7 @@ class CertificateKeyPairViewSet(UsedByMixin, ModelViewSet):
],
responses={200: CertificateDataSerializer(many=False)},
)
@action(detail=True, pagination_class=None, filter_backends=[ObjectFilter])
@action(detail=True, pagination_class=None, filter_backends=[])
def view_private_key(self, request: Request, pk: str) -> Response:
"""Return certificate-key pairs private key and log access"""
certificate: CertificateKeyPair = self.get_object()

View File

@ -214,46 +214,6 @@ class TestCrypto(APITestCase):
self.assertEqual(200, response.status_code)
self.assertIn("Content-Disposition", response)
def test_certificate_download_denied(self):
"""Test certificate export (download)"""
self.client.logout()
keypair = create_test_cert()
response = self.client.get(
reverse(
"authentik_api:certificatekeypair-view-certificate",
kwargs={"pk": keypair.pk},
)
)
self.assertEqual(403, response.status_code)
response = self.client.get(
reverse(
"authentik_api:certificatekeypair-view-certificate",
kwargs={"pk": keypair.pk},
),
data={"download": True},
)
self.assertEqual(403, response.status_code)
def test_private_key_download_denied(self):
"""Test private_key export (download)"""
self.client.logout()
keypair = create_test_cert()
response = self.client.get(
reverse(
"authentik_api:certificatekeypair-view-private-key",
kwargs={"pk": keypair.pk},
)
)
self.assertEqual(403, response.status_code)
response = self.client.get(
reverse(
"authentik_api:certificatekeypair-view-private-key",
kwargs={"pk": keypair.pk},
),
data={"download": True},
)
self.assertEqual(403, response.status_code)
def test_used_by(self):
"""Test used_by endpoint"""
self.client.force_login(create_test_admin_user())
@ -286,26 +246,6 @@ class TestCrypto(APITestCase):
],
)
def test_used_by_denied(self):
"""Test used_by endpoint"""
self.client.logout()
keypair = create_test_cert()
OAuth2Provider.objects.create(
name=generate_id(),
client_id="test",
client_secret=generate_key(),
authorization_flow=create_test_flow(),
redirect_uris="http://localhost",
signing_key=keypair,
)
response = self.client.get(
reverse(
"authentik_api:certificatekeypair-used-by",
kwargs={"pk": keypair.pk},
)
)
self.assertEqual(403, response.status_code)
def test_discovery(self):
"""Test certificate discovery"""
name = generate_id()

View File

@ -1,11 +1,12 @@
"""Enterprise API Views"""
from dataclasses import asdict
from datetime import timedelta
from django.utils.timezone import now
from django.utils.translation import gettext as _
from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import OpenApiParameter, extend_schema, inline_serializer
from drf_spectacular.utils import extend_schema, inline_serializer
from rest_framework.decorators import action
from rest_framework.exceptions import ValidationError
from rest_framework.fields import CharField, IntegerField
@ -86,7 +87,7 @@ class LicenseViewSet(UsedByMixin, ModelViewSet):
},
)
@action(detail=False, methods=["GET"])
def install_id(self, request: Request) -> Response:
def get_install_id(self, request: Request) -> Response:
"""Get install_id"""
return Response(
data={
@ -99,22 +100,12 @@ class LicenseViewSet(UsedByMixin, ModelViewSet):
responses={
200: LicenseSummarySerializer(),
},
parameters=[
OpenApiParameter(
name="cached",
location=OpenApiParameter.QUERY,
type=OpenApiTypes.BOOL,
default=True,
)
],
)
@action(detail=False, methods=["GET"], permission_classes=[IsAuthenticated])
def summary(self, request: Request) -> Response:
"""Get the total license status"""
summary = LicenseKey.cached_summary()
if request.query_params.get("cached", "true").lower() == "false":
summary = LicenseKey.get_total().summary()
response = LicenseSummarySerializer(instance=summary)
response = LicenseSummarySerializer(data=asdict(LicenseKey.cached_summary()))
response.is_valid(raise_exception=True)
return Response(response.data)
@permission_required(None, ["authentik_enterprise.view_license"])

View File

@ -25,4 +25,4 @@ class AuthentikEnterpriseConfig(EnterpriseConfig):
"""Actual enterprise check, cached"""
from authentik.enterprise.license import LicenseKey
return LicenseKey.cached_summary().status.is_valid
return LicenseKey.cached_summary().status

View File

@ -20,7 +20,6 @@ from rest_framework.fields import (
ChoiceField,
DateTimeField,
IntegerField,
ListField,
)
from authentik.core.api.utils import PassiveSerializer
@ -56,7 +55,6 @@ class LicenseFlags(Enum):
"""License flags"""
TRIAL = "trial"
NON_PRODUCTION = "non_production"
@dataclass
@ -67,7 +65,6 @@ class LicenseSummary:
external_users: int
status: LicenseUsageStatus
latest_valid: datetime
license_flags: list[LicenseFlags]
class LicenseSummarySerializer(PassiveSerializer):
@ -77,7 +74,6 @@ class LicenseSummarySerializer(PassiveSerializer):
external_users = IntegerField(required=True)
status = ChoiceField(choices=LicenseUsageStatus.choices)
latest_valid = DateTimeField()
license_flags = ListField(child=ChoiceField(choices=tuple(x.value for x in LicenseFlags)))
@dataclass
@ -90,7 +86,7 @@ class LicenseKey:
name: str
internal_users: int = 0
external_users: int = 0
license_flags: list[LicenseFlags] = field(default_factory=list)
flags: list[LicenseFlags] = field(default_factory=list)
@staticmethod
def validate(jwt: str, check_expiry=True) -> "LicenseKey":
@ -117,7 +113,7 @@ class LicenseKey:
our_cert.public_key(),
algorithms=["ES512"],
audience=get_license_aud(),
options={"verify_exp": check_expiry, "verify_signature": check_expiry},
options={"verify_exp": check_expiry},
),
)
except PyJWTError:
@ -134,8 +130,9 @@ class LicenseKey:
exp_ts = int(mktime(lic.expiry.timetuple()))
if total.exp == 0:
total.exp = exp_ts
total.exp = max(total.exp, exp_ts)
total.license_flags.extend(lic.status.license_flags)
if exp_ts <= total.exp:
total.exp = exp_ts
total.flags.extend(lic.status.flags)
return total
@staticmethod
@ -219,7 +216,6 @@ class LicenseKey:
internal_users=self.internal_users,
external_users=self.external_users,
status=status,
license_flags=self.license_flags,
)
@staticmethod

View File

@ -6,10 +6,7 @@ from authentik.core.api.providers import ProviderSerializer
from authentik.core.api.used_by import UsedByMixin
from authentik.enterprise.api import EnterpriseRequiredMixin
from authentik.enterprise.providers.google_workspace.models import GoogleWorkspaceProvider
from authentik.enterprise.providers.google_workspace.tasks import (
google_workspace_sync,
google_workspace_sync_objects,
)
from authentik.enterprise.providers.google_workspace.tasks import google_workspace_sync
from authentik.lib.sync.outgoing.api import OutgoingSyncProviderStatusMixin
@ -55,4 +52,3 @@ class GoogleWorkspaceProviderViewSet(OutgoingSyncProviderStatusMixin, UsedByMixi
search_fields = ["name"]
ordering = ["name"]
sync_single_task = google_workspace_sync
sync_objects_task = google_workspace_sync_objects

View File

@ -181,7 +181,7 @@ class GoogleWorkspaceProviderMapping(PropertyMapping):
@property
def component(self) -> str:
return "ak-property-mapping-provider-google-workspace-form"
return "ak-property-mapping-google-workspace-form"
@property
def serializer(self) -> type[Serializer]:

View File

@ -6,10 +6,7 @@ from authentik.core.api.providers import ProviderSerializer
from authentik.core.api.used_by import UsedByMixin
from authentik.enterprise.api import EnterpriseRequiredMixin
from authentik.enterprise.providers.microsoft_entra.models import MicrosoftEntraProvider
from authentik.enterprise.providers.microsoft_entra.tasks import (
microsoft_entra_sync,
microsoft_entra_sync_objects,
)
from authentik.enterprise.providers.microsoft_entra.tasks import microsoft_entra_sync
from authentik.lib.sync.outgoing.api import OutgoingSyncProviderStatusMixin
@ -53,4 +50,3 @@ class MicrosoftEntraProviderViewSet(OutgoingSyncProviderStatusMixin, UsedByMixin
search_fields = ["name"]
ordering = ["name"]
sync_single_task = microsoft_entra_sync
sync_objects_task = microsoft_entra_sync_objects

View File

@ -170,7 +170,7 @@ class MicrosoftEntraProviderMapping(PropertyMapping):
@property
def component(self) -> str:
return "ak-property-mapping-provider-microsoft-entra-form"
return "ak-property-mapping-microsoft-entra-form"
@property
def serializer(self) -> type[Serializer]:

View File

@ -1,20 +0,0 @@
# Generated by Django 5.0.8 on 2024-08-12 12:54
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("authentik_providers_rac", "0004_alter_connectiontoken_expires"),
]
operations = [
migrations.AlterModelOptions(
name="racpropertymapping",
options={
"verbose_name": "RAC Provider Property Mapping",
"verbose_name_plural": "RAC Provider Property Mappings",
},
),
]

View File

@ -125,7 +125,7 @@ class RACPropertyMapping(PropertyMapping):
@property
def component(self) -> str:
return "ak-property-mapping-provider-rac-form"
return "ak-property-mapping-rac-form"
@property
def serializer(self) -> type[Serializer]:
@ -136,8 +136,8 @@ class RACPropertyMapping(PropertyMapping):
return RACPropertyMappingSerializer
class Meta:
verbose_name = _("RAC Provider Property Mapping")
verbose_name_plural = _("RAC Provider Property Mappings")
verbose_name = _("RAC Property Mapping")
verbose_name_plural = _("RAC Property Mappings")
class ConnectionToken(ExpiringModel):

View File

@ -44,7 +44,7 @@ websocket_urlpatterns = [
api_urlpatterns = [
("providers/rac", RACProviderViewSet),
("propertymappings/provider/rac", RACPropertyMappingViewSet),
("propertymappings/rac", RACPropertyMappingViewSet),
("rac/endpoints", EndpointViewSet),
("rac/connection_tokens", ConnectionTokenViewSet),
]

View File

@ -37,7 +37,6 @@ from authentik.lib.utils.file import (
)
from authentik.lib.views import bad_request_message
from authentik.rbac.decorators import permission_required
from authentik.rbac.filters import ObjectFilter
LOGGER = get_logger()
@ -282,7 +281,7 @@ class FlowViewSet(UsedByMixin, ModelViewSet):
400: OpenApiResponse(description="Flow not applicable"),
},
)
@action(detail=True, pagination_class=None, filter_backends=[ObjectFilter])
@action(detail=True, pagination_class=None, filter_backends=[])
def execute(self, request: Request, slug: str):
"""Execute flow for current user"""
# Because we pre-plan the flow here, and not in the planner, we need to manually clear

View File

@ -1,19 +1,16 @@
from celery import Task
from collections.abc import Callable
from django.utils.text import slugify
from drf_spectacular.utils import OpenApiResponse, extend_schema
from guardian.shortcuts import get_objects_for_user
from rest_framework.decorators import action
from rest_framework.fields import BooleanField, CharField, ChoiceField
from rest_framework.fields import BooleanField
from rest_framework.request import Request
from rest_framework.response import Response
from authentik.core.api.utils import ModelSerializer, PassiveSerializer
from authentik.core.models import Group, User
from authentik.events.api.tasks import SystemTaskSerializer
from authentik.events.logs import LogEvent, LogEventSerializer
from authentik.lib.sync.outgoing.models import OutgoingSyncProvider
from authentik.lib.utils.reflection import class_to_path
from authentik.rbac.filters import ObjectFilter
class SyncStatusSerializer(PassiveSerializer):
@ -23,29 +20,10 @@ class SyncStatusSerializer(PassiveSerializer):
tasks = SystemTaskSerializer(many=True, read_only=True)
class SyncObjectSerializer(PassiveSerializer):
"""Sync object serializer"""
sync_object_model = ChoiceField(
choices=(
(class_to_path(User), "user"),
(class_to_path(Group), "group"),
)
)
sync_object_id = CharField()
class SyncObjectResultSerializer(PassiveSerializer):
"""Result of a single object sync"""
messages = LogEventSerializer(many=True, read_only=True)
class OutgoingSyncProviderStatusMixin:
"""Common API Endpoints for Outgoing sync providers"""
sync_single_task: type[Task] = None
sync_objects_task: type[Task] = None
sync_single_task: Callable = None
@extend_schema(
responses={
@ -58,7 +36,7 @@ class OutgoingSyncProviderStatusMixin:
detail=True,
pagination_class=None,
url_path="sync/status",
filter_backends=[ObjectFilter],
filter_backends=[],
)
def sync_status(self, request: Request, pk: int) -> Response:
"""Get provider's sync status"""
@ -77,30 +55,6 @@ class OutgoingSyncProviderStatusMixin:
}
return Response(SyncStatusSerializer(status).data)
@extend_schema(
request=SyncObjectSerializer,
responses={200: SyncObjectResultSerializer()},
)
@action(
methods=["POST"],
detail=True,
pagination_class=None,
url_path="sync/object",
filter_backends=[ObjectFilter],
)
def sync_object(self, request: Request, pk: int) -> Response:
"""Sync/Re-sync a single user/group object"""
provider: OutgoingSyncProvider = self.get_object()
params = SyncObjectSerializer(data=request.data)
params.is_valid(raise_exception=True)
res: list[LogEvent] = self.sync_objects_task.delay(
params.validated_data["sync_object_model"],
page=1,
provider_pk=provider.pk,
pk=params.validated_data["sync_object_id"],
).get()
return Response(SyncObjectResultSerializer(instance={"messages": res}).data)
class OutgoingSyncConnectionCreateMixin:
"""Mixin for connection objects that fetches remote data upon creation"""

View File

@ -105,7 +105,7 @@ class SyncTasks:
return
task.set_status(TaskStatus.SUCCESSFUL, *messages)
def sync_objects(self, object_type: str, page: int, provider_pk: int, **filter):
def sync_objects(self, object_type: str, page: int, provider_pk: int):
_object_type = path_to_class(object_type)
self.logger = get_logger().bind(
provider_type=class_to_path(self._provider_model),
@ -120,7 +120,7 @@ class SyncTasks:
client = provider.client_for_model(_object_type)
except TransientSyncException:
return messages
paginator = Paginator(provider.get_object_qs(_object_type).filter(**filter), PAGE_SIZE)
paginator = Paginator(provider.get_object_qs(_object_type), PAGE_SIZE)
if client.can_discover:
self.logger.debug("starting discover")
client.discover()

View File

@ -26,6 +26,7 @@ from authentik.outposts.apps import MANAGED_OUTPOST, MANAGED_OUTPOST_NAME
from authentik.outposts.models import (
Outpost,
OutpostConfig,
OutpostState,
OutpostType,
default_outpost_config,
)
@ -181,6 +182,7 @@ class OutpostViewSet(UsedByMixin, ModelViewSet):
outpost: Outpost = self.get_object()
states = []
for state in outpost.state:
state: OutpostState
states.append(
{
"uid": state.uid,

View File

@ -26,7 +26,6 @@ from authentik.outposts.models import (
KubernetesServiceConnection,
OutpostServiceConnection,
)
from authentik.rbac.filters import ObjectFilter
class ServiceConnectionSerializer(ModelSerializer, MetaNameSerializer):
@ -76,7 +75,7 @@ class ServiceConnectionViewSet(
filterset_fields = ["name"]
@extend_schema(responses={200: ServiceConnectionStateSerializer(many=False)})
@action(detail=True, pagination_class=None, filter_backends=[ObjectFilter])
@action(detail=True, pagination_class=None, filter_backends=[])
def state(self, request: Request, pk: str) -> Response:
"""Get the service connection's state"""
connection = self.get_object()

View File

@ -451,7 +451,7 @@ class OutpostState:
return False
if self.build_hash != get_build_hash():
return False
return parse(self.version) != OUR_VERSION
return parse(self.version) < OUR_VERSION
@staticmethod
def for_outpost(outpost: Outpost) -> list["OutpostState"]:

View File

@ -214,7 +214,7 @@ def outpost_post_save(model_class: str, model_pk: Any):
if not hasattr(instance, field_name):
continue
LOGGER.debug("triggering outpost update from field", field=field.name)
LOGGER.debug("triggering outpost update from from field", field=field.name)
# Because the Outpost Model has an M2M to Provider,
# we have to iterate over the entire QS
for reverse in getattr(instance, field_name).all():

View File

@ -36,7 +36,7 @@ def update_score(request: HttpRequest, identifier: str, amount: int):
if not created:
reputation.score = F("score") + amount
reputation.save()
LOGGER.info("Updated score", amount=amount, for_user=identifier, for_ip=remote_ip)
LOGGER.debug("Updated score", amount=amount, for_user=identifier, for_ip=remote_ip)
@receiver(login_failed)

View File

@ -2,25 +2,15 @@
from django.db.models import QuerySet
from django.db.models.query import Q
from django.shortcuts import get_object_or_404
from django_filters.filters import BooleanFilter
from django_filters.filterset import FilterSet
from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import OpenApiParameter, extend_schema
from rest_framework.decorators import action
from rest_framework.fields import BooleanField, CharField, ListField, SerializerMethodField
from rest_framework.fields import CharField, ListField, SerializerMethodField
from rest_framework.mixins import ListModelMixin
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.viewsets import GenericViewSet, ModelViewSet
from authentik.core.api.providers import ProviderSerializer
from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.utils import ModelSerializer, PassiveSerializer
from authentik.core.models import Application
from authentik.policies.api.exec import PolicyTestResultSerializer
from authentik.policies.engine import PolicyEngine
from authentik.policies.types import PolicyResult
from authentik.core.api.utils import ModelSerializer
from authentik.providers.ldap.models import LDAPProvider
@ -33,6 +23,7 @@ class LDAPProviderSerializer(ProviderSerializer):
model = LDAPProvider
fields = ProviderSerializer.Meta.fields + [
"base_dn",
"search_group",
"certificate",
"tls_server_name",
"uid_start_number",
@ -64,6 +55,8 @@ class LDAPProviderFilter(FilterSet):
"name": ["iexact"],
"authorization_flow__slug": ["iexact"],
"base_dn": ["iexact"],
"search_group__group_uuid": ["iexact"],
"search_group__name": ["iexact"],
"certificate__kp_uuid": ["iexact"],
"certificate__name": ["iexact"],
"tls_server_name": ["iexact"],
@ -102,6 +95,7 @@ class LDAPOutpostConfigSerializer(ModelSerializer):
"base_dn",
"bind_flow_slug",
"application_slug",
"search_group",
"certificate",
"tls_server_name",
"uid_start_number",
@ -122,33 +116,3 @@ class LDAPOutpostConfigViewSet(ListModelMixin, GenericViewSet):
ordering = ["name"]
search_fields = ["name"]
filterset_fields = ["name"]
class LDAPCheckAccessSerializer(PassiveSerializer):
has_search_permission = BooleanField(required=False)
access = PolicyTestResultSerializer()
@extend_schema(
request=None,
parameters=[OpenApiParameter("app_slug", OpenApiTypes.STR)],
responses={
200: LDAPCheckAccessSerializer(),
},
operation_id="outposts_ldap_access_check",
)
@action(detail=True)
def check_access(self, request: Request, pk) -> Response:
"""Check access to a single application by slug"""
provider = get_object_or_404(LDAPProvider, pk=pk)
application = get_object_or_404(Application, slug=request.query_params["app_slug"])
engine = PolicyEngine(application, request.user, request)
engine.use_cache = False
engine.build()
result = engine.result
access_response = PolicyResult(result.passing)
response = self.LDAPCheckAccessSerializer(
instance={
"has_search_permission": request.user.has_perm("search_full_directory", provider),
"access": access_response,
}
)
return Response(response.data)

View File

@ -1,54 +0,0 @@
# Generated by Django 5.0.7 on 2024-07-25 14:59
from django.apps.registry import Apps
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
from django.db import migrations
from django.contrib.auth.management import create_permissions
def migrate_search_group(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
from guardian.shortcuts import assign_perm
from authentik.core.models import User
from django.apps import apps as real_apps
db_alias = schema_editor.connection.alias
# Permissions are only created _after_ migrations are run
# - https://github.com/django/django/blob/43cdfa8b20e567a801b7d0a09ec67ddd062d5ea4/django/contrib/auth/apps.py#L19
# - https://stackoverflow.com/a/72029063/1870445
create_permissions(real_apps.get_app_config("authentik_providers_ldap"), using=db_alias)
LDAPProvider = apps.get_model("authentik_providers_ldap", "ldapprovider")
for provider in LDAPProvider.objects.using(db_alias).all():
for user_pk in (
provider.search_group.users.using(db_alias).all().values_list("pk", flat=True)
):
# We need the correct user model instance to assign the permission
assign_perm(
"search_full_directory", User.objects.using(db_alias).get(pk=user_pk), provider
)
class Migration(migrations.Migration):
dependencies = [
("authentik_providers_ldap", "0003_ldapprovider_mfa_support_and_more"),
]
operations = [
migrations.AlterModelOptions(
name="ldapprovider",
options={
"permissions": [("search_full_directory", "Search full LDAP directory")],
"verbose_name": "LDAP Provider",
"verbose_name_plural": "LDAP Providers",
},
),
migrations.RunPython(migrate_search_group),
migrations.RemoveField(
model_name="ldapprovider",
name="search_group",
),
]

View File

@ -7,7 +7,7 @@ from django.templatetags.static import static
from django.utils.translation import gettext_lazy as _
from rest_framework.serializers import Serializer
from authentik.core.models import BackchannelProvider
from authentik.core.models import BackchannelProvider, Group
from authentik.crypto.models import CertificateKeyPair
from authentik.outposts.models import OutpostModel
@ -27,6 +27,17 @@ class LDAPProvider(OutpostModel, BackchannelProvider):
help_text=_("DN under which objects are accessible."),
)
search_group = models.ForeignKey(
Group,
null=True,
default=None,
on_delete=models.SET_DEFAULT,
help_text=_(
"Users in this group can do search queries. "
"If not set, every user can execute search queries."
),
)
tls_server_name = models.TextField(
default="",
blank=True,
@ -102,6 +113,3 @@ class LDAPProvider(OutpostModel, BackchannelProvider):
class Meta:
verbose_name = _("LDAP Provider")
verbose_name_plural = _("LDAP Providers")
permissions = [
("search_full_directory", _("Search full LDAP directory")),
]

View File

@ -105,7 +105,7 @@ class ScopeMapping(PropertyMapping):
@property
def component(self) -> str:
return "ak-property-mapping-provider-scope-form"
return "ak-property-mapping-scope-form"
@property
def serializer(self) -> type[Serializer]:

View File

@ -62,7 +62,7 @@ urlpatterns = [
api_urlpatterns = [
("providers/oauth2", OAuth2ProviderViewSet),
("propertymappings/provider/scope", ScopeMappingViewSet),
("propertymappings/scope", ScopeMappingViewSet),
("oauth2/authorization_codes", AuthorizationCodeViewSet),
("oauth2/refresh_tokens", RefreshTokenViewSet),
("oauth2/access_tokens", AccessTokenViewSet),

View File

@ -433,21 +433,20 @@ class TokenParams:
app = Application.objects.filter(provider=self.provider).first()
if not app or not app.provider:
raise TokenError("invalid_grant")
with audit_ignore():
self.user, _ = User.objects.update_or_create(
# trim username to ensure the entire username is max 150 chars
# (22 chars being the length of the "template")
username=f"ak-{self.provider.name[:150-22]}-client_credentials",
defaults={
"attributes": {
USER_ATTRIBUTE_GENERATED: True,
},
"last_login": timezone.now(),
"name": f"Autogenerated user from application {app.name} (client credentials)",
"path": f"{USER_PATH_SYSTEM_PREFIX}/apps/{app.slug}",
"type": UserTypes.SERVICE_ACCOUNT,
self.user, _ = User.objects.update_or_create(
# trim username to ensure the entire username is max 150 chars
# (22 chars being the length of the "template")
username=f"ak-{self.provider.name[:150-22]}-client_credentials",
defaults={
"attributes": {
USER_ATTRIBUTE_GENERATED: True,
},
)
"last_login": timezone.now(),
"name": f"Autogenerated user from application {app.name} (client credentials)",
"path": f"{USER_PATH_SYSTEM_PREFIX}/apps/{app.slug}",
"type": UserTypes.SERVICE_ACCOUNT,
},
)
self.__check_policy_access(app, request)
Event.new(

View File

@ -154,7 +154,6 @@ class RadiusOutpostConfigViewSet(ListModelMixin, GenericViewSet):
responses={
200: RadiusCheckAccessSerializer(),
},
operation_id="outposts_radius_access_check",
)
@action(detail=True)
def check_access(self, request: Request, pk) -> Response:

View File

@ -1,20 +0,0 @@
# Generated by Django 5.0.8 on 2024-08-12 12:54
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("authentik_providers_radius", "0003_radiusproviderpropertymapping"),
]
operations = [
migrations.AlterModelOptions(
name="radiusproviderpropertymapping",
options={
"verbose_name": "Radius Provider Property Mapping",
"verbose_name_plural": "Radius Provider Property Mappings",
},
),
]

View File

@ -70,7 +70,7 @@ class RadiusProviderPropertyMapping(PropertyMapping):
@property
def component(self) -> str:
return "ak-property-mapping-provider-radius-form"
return "ak-property-mapping-radius-form"
@property
def serializer(self) -> type[Serializer]:
@ -81,8 +81,8 @@ class RadiusProviderPropertyMapping(PropertyMapping):
return RadiusProviderPropertyMappingSerializer
def __str__(self):
return f"Radius Provider Property Mapping {self.name}"
return f"Radius Property Mapping {self.name}"
class Meta:
verbose_name = _("Radius Provider Property Mapping")
verbose_name_plural = _("Radius Provider Property Mappings")
verbose_name = _("Radius Property Mapping")
verbose_name_plural = _("Radius Property Mappings")

View File

@ -7,7 +7,7 @@ from authentik.providers.radius.api.providers import (
)
api_urlpatterns = [
("propertymappings/provider/radius", RadiusProviderPropertyMappingViewSet),
("propertymappings/radius", RadiusProviderPropertyMappingViewSet),
("outposts/radius", RadiusOutpostConfigViewSet, "radiusprovideroutpost"),
("providers/radius", RadiusProviderViewSet),
]

View File

@ -133,17 +133,6 @@ class SAMLProviderSerializer(ProviderSerializer):
except Provider.application.RelatedObjectDoesNotExist:
return "-"
def validate(self, attrs: dict):
if attrs.get("signing_kp"):
if not attrs.get("sign_assertion") and not attrs.get("sign_response"):
raise ValidationError(
_(
"With a signing keypair selected, at least one of 'Sign assertion' "
"and 'Sign Response' must be selected."
)
)
return super().validate(attrs)
class Meta:
model = SAMLProvider
fields = ProviderSerializer.Meta.fields + [
@ -159,9 +148,6 @@ class SAMLProviderSerializer(ProviderSerializer):
"signature_algorithm",
"signing_kp",
"verification_kp",
"encryption_kp",
"sign_assertion",
"sign_response",
"sp_binding",
"default_relay_state",
"url_download_metadata",

View File

@ -1,20 +0,0 @@
# Generated by Django 5.0.8 on 2024-08-12 12:54
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("authentik_providers_saml", "0014_alter_samlprovider_digest_algorithm_and_more"),
]
operations = [
migrations.AlterModelOptions(
name="samlpropertymapping",
options={
"verbose_name": "SAML Provider Property Mapping",
"verbose_name_plural": "SAML Provider Property Mappings",
},
),
]

View File

@ -1,39 +0,0 @@
# Generated by Django 5.0.8 on 2024-08-15 14:52
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_crypto", "0004_alter_certificatekeypair_name"),
("authentik_providers_saml", "0015_alter_samlpropertymapping_options"),
]
operations = [
migrations.AddField(
model_name="samlprovider",
name="encryption_kp",
field=models.ForeignKey(
blank=True,
default=None,
help_text="When selected, incoming assertions are encrypted by the IdP using the public key of the encryption keypair. The assertion is decrypted by the SP using the the private key.",
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="+",
to="authentik_crypto.certificatekeypair",
verbose_name="Encryption Keypair",
),
),
migrations.AddField(
model_name="samlprovider",
name="sign_assertion",
field=models.BooleanField(default=True),
),
migrations.AddField(
model_name="samlprovider",
name="sign_response",
field=models.BooleanField(default=False),
),
]

View File

@ -144,28 +144,11 @@ class SAMLProvider(Provider):
on_delete=models.SET_NULL,
verbose_name=_("Signing Keypair"),
)
encryption_kp = models.ForeignKey(
CertificateKeyPair,
default=None,
null=True,
blank=True,
help_text=_(
"When selected, incoming assertions are encrypted by the IdP using the public "
"key of the encryption keypair. The assertion is decrypted by the SP using the "
"the private key."
),
on_delete=models.SET_NULL,
verbose_name=_("Encryption Keypair"),
related_name="+",
)
default_relay_state = models.TextField(
default="", blank=True, help_text=_("Default relay_state value for IDP-initiated logins")
)
sign_assertion = models.BooleanField(default=True)
sign_response = models.BooleanField(default=False)
@property
def launch_url(self) -> str | None:
"""Use IDP-Initiated SAML flow as launch URL"""
@ -208,7 +191,7 @@ class SAMLPropertyMapping(PropertyMapping):
@property
def component(self) -> str:
return "ak-property-mapping-provider-saml-form"
return "ak-property-mapping-saml-form"
@property
def serializer(self) -> type[Serializer]:
@ -221,8 +204,8 @@ class SAMLPropertyMapping(PropertyMapping):
return f"{self.name} ({name})"
class Meta:
verbose_name = _("SAML Provider Property Mapping")
verbose_name_plural = _("SAML Provider Property Mappings")
verbose_name = _("SAML Property Mapping")
verbose_name_plural = _("SAML Property Mappings")
class SAMLProviderImportModel(CreatableType, Provider):

View File

@ -18,11 +18,7 @@ from authentik.providers.saml.processors.authn_request_parser import AuthNReques
from authentik.providers.saml.utils import get_random_id
from authentik.providers.saml.utils.time import get_time_string
from authentik.sources.ldap.auth import LDAP_DISTINGUISHED_NAME
from authentik.sources.saml.exceptions import (
InvalidEncryption,
InvalidSignature,
UnsupportedNameIDFormat,
)
from authentik.sources.saml.exceptions import InvalidSignature, UnsupportedNameIDFormat
from authentik.sources.saml.processors.constants import (
DIGEST_ALGORITHM_TRANSLATION_MAP,
NS_MAP,
@ -260,17 +256,9 @@ class AssertionProcessor:
assertion,
xmlsec.constants.TransformExclC14N,
sign_algorithm_transform,
ns=xmlsec.constants.DSigNs,
ns="ds", # type: ignore
)
assertion.append(signature)
if self.provider.encryption_kp:
encryption = xmlsec.template.encrypted_data_create(
assertion,
xmlsec.constants.TransformAes128Cbc,
self._assertion_id,
ns=xmlsec.constants.DSigNs,
)
assertion.append(encryption)
assertion.append(self.get_assertion_subject())
assertion.append(self.get_assertion_conditions())
@ -298,86 +286,41 @@ class AssertionProcessor:
response.append(self.get_assertion())
return response
def _sign(self, element: Element):
"""Sign an XML element based on the providers' configured signing settings"""
digest_algorithm_transform = DIGEST_ALGORITHM_TRANSLATION_MAP.get(
self.provider.digest_algorithm, xmlsec.constants.TransformSha1
)
xmlsec.tree.add_ids(element, ["ID"])
signature_node = xmlsec.tree.find_node(element, xmlsec.constants.NodeSignature)
ref = xmlsec.template.add_reference(
signature_node,
digest_algorithm_transform,
uri="#" + self._assertion_id,
)
xmlsec.template.add_transform(ref, xmlsec.constants.TransformEnveloped)
xmlsec.template.add_transform(ref, xmlsec.constants.TransformExclC14N)
key_info = xmlsec.template.ensure_key_info(signature_node)
xmlsec.template.add_x509_data(key_info)
ctx = xmlsec.SignatureContext()
key = xmlsec.Key.from_memory(
self.provider.signing_kp.key_data,
xmlsec.constants.KeyDataFormatPem,
None,
)
key.load_cert_from_memory(
self.provider.signing_kp.certificate_data,
xmlsec.constants.KeyDataFormatCertPem,
)
ctx.key = key
try:
ctx.sign(signature_node)
except xmlsec.Error as exc:
raise InvalidSignature() from exc
def _encrypt(self, element: Element, parent: Element):
"""Encrypt SAMLResponse EncryptedAssertion Element"""
manager = xmlsec.KeysManager()
key = xmlsec.Key.from_memory(
self.provider.encryption_kp.key_data,
xmlsec.constants.KeyDataFormatPem,
)
key.load_cert_from_memory(
self.provider.encryption_kp.certificate_data,
xmlsec.constants.KeyDataFormatCertPem,
)
manager.add_key(key)
encryption_context = xmlsec.EncryptionContext(manager)
encryption_context.key = xmlsec.Key.generate(
xmlsec.constants.KeyDataAes, 128, xmlsec.constants.KeyDataTypeSession
)
container = SubElement(parent, f"{{{NS_SAML_ASSERTION}}}EncryptedAssertion")
enc_data = xmlsec.template.encrypted_data_create(
container, xmlsec.Transform.AES128, type=xmlsec.EncryptionType.ELEMENT, ns="xenc"
)
xmlsec.template.encrypted_data_ensure_cipher_value(enc_data)
key_info = xmlsec.template.encrypted_data_ensure_key_info(enc_data, ns="ds")
enc_key = xmlsec.template.add_encrypted_key(key_info, xmlsec.Transform.RSA_OAEP)
xmlsec.template.encrypted_data_ensure_cipher_value(enc_key)
try:
enc_data = encryption_context.encrypt_xml(enc_data, element)
except xmlsec.Error as exc:
raise InvalidEncryption() from exc
parent.remove(enc_data)
container.append(enc_data)
def build_response(self) -> str:
"""Build string XML Response and sign if signing is enabled."""
root_response = self.get_response()
if self.provider.signing_kp:
if self.provider.sign_assertion:
assertion = root_response.xpath("//saml:Assertion", namespaces=NS_MAP)[0]
self._sign(assertion)
if self.provider.sign_response:
response = root_response.xpath("//samlp:Response", namespaces=NS_MAP)[0]
self._sign(response)
if self.provider.encryption_kp:
digest_algorithm_transform = DIGEST_ALGORITHM_TRANSLATION_MAP.get(
self.provider.digest_algorithm, xmlsec.constants.TransformSha1
)
assertion = root_response.xpath("//saml:Assertion", namespaces=NS_MAP)[0]
self._encrypt(assertion, root_response)
xmlsec.tree.add_ids(assertion, ["ID"])
signature_node = xmlsec.tree.find_node(assertion, xmlsec.constants.NodeSignature)
ref = xmlsec.template.add_reference(
signature_node,
digest_algorithm_transform,
uri="#" + self._assertion_id,
)
xmlsec.template.add_transform(ref, xmlsec.constants.TransformEnveloped)
xmlsec.template.add_transform(ref, xmlsec.constants.TransformExclC14N)
key_info = xmlsec.template.ensure_key_info(signature_node)
xmlsec.template.add_x509_data(key_info)
ctx = xmlsec.SignatureContext()
key = xmlsec.Key.from_memory(
self.provider.signing_kp.key_data,
xmlsec.constants.KeyDataFormatPem,
None,
)
key.load_cert_from_memory(
self.provider.signing_kp.certificate_data,
xmlsec.constants.KeyDataFormatCertPem,
)
ctx.key = key
try:
ctx.sign(signature_node)
except xmlsec.Error as exc:
raise InvalidSignature() from exc
return etree.tostring(root_response).decode("utf-8") # nosec

View File

@ -126,7 +126,7 @@ class MetadataProcessor:
entity_descriptor,
xmlsec.constants.TransformExclC14N,
sign_algorithm_transform,
ns=xmlsec.constants.DSigNs,
ns="ds", # type: ignore
)
entity_descriptor.append(signature)

View File

@ -8,7 +8,7 @@ from rest_framework.test import APITestCase
from authentik.blueprints.tests import apply_blueprint
from authentik.core.models import Application
from authentik.core.tests.utils import create_test_admin_user, create_test_cert, create_test_flow
from authentik.core.tests.utils import create_test_admin_user, create_test_flow
from authentik.flows.models import FlowDesignation
from authentik.lib.generators import generate_id
from authentik.lib.tests.utils import load_fixture
@ -29,52 +29,12 @@ class TestSAMLProviderAPI(APITestCase):
name=generate_id(),
authorization_flow=create_test_flow(),
)
response = self.client.get(
reverse("authentik_api:samlprovider-detail", kwargs={"pk": provider.pk}),
)
self.assertEqual(200, response.status_code)
Application.objects.create(name=generate_id(), provider=provider, slug=generate_id())
response = self.client.get(
reverse("authentik_api:samlprovider-detail", kwargs={"pk": provider.pk}),
)
self.assertEqual(200, response.status_code)
def test_create_validate_signing_kp(self):
"""Test create"""
cert = create_test_cert()
response = self.client.post(
reverse("authentik_api:samlprovider-list"),
data={
"name": generate_id(),
"authorization_flow": create_test_flow().pk,
"acs_url": "http://localhost",
"signing_kp": cert.pk,
},
)
self.assertEqual(400, response.status_code)
self.assertJSONEqual(
response.content,
{
"non_field_errors": [
(
"With a signing keypair selected, at least one "
"of 'Sign assertion' and 'Sign Response' must be selected."
)
]
},
)
response = self.client.post(
reverse("authentik_api:samlprovider-list"),
data={
"name": generate_id(),
"authorization_flow": create_test_flow().pk,
"acs_url": "http://localhost",
"signing_kp": cert.pk,
"sign_assertion": True,
},
)
self.assertEqual(201, response.status_code)
def test_metadata(self):
"""Test metadata export (normal)"""
self.client.logout()

View File

@ -78,12 +78,12 @@ class TestAuthNRequest(TestCase):
@apply_blueprint("system/providers-saml.yaml")
def setUp(self):
self.cert = create_test_cert()
cert = create_test_cert()
self.provider: SAMLProvider = SAMLProvider.objects.create(
authorization_flow=create_test_flow(),
acs_url="http://testserver/source/saml/provider/acs/",
signing_kp=self.cert,
verification_kp=self.cert,
signing_kp=cert,
verification_kp=cert,
)
self.provider.property_mappings.set(SAMLPropertyMapping.objects.all())
self.provider.save()
@ -91,8 +91,8 @@ class TestAuthNRequest(TestCase):
slug="provider",
issuer="authentik",
pre_authentication_flow=create_test_flow(),
signing_kp=self.cert,
verification_kp=self.cert,
signing_kp=cert,
verification_kp=cert,
)
def test_signed_valid(self):
@ -112,34 +112,7 @@ class TestAuthNRequest(TestCase):
self.assertEqual(parsed_request.id, request_proc.request_id)
self.assertEqual(parsed_request.relay_state, "test_state")
def test_request_encrypt(self):
"""Test full SAML Request/Response flow, fully encrypted"""
self.provider.encryption_kp = self.cert
self.provider.save()
self.source.encryption_kp = self.cert
self.source.save()
http_request = get_request("/")
# First create an AuthNRequest
request_proc = RequestProcessor(self.source, http_request, "test_state")
request = request_proc.build_auth_n()
# To get an assertion we need a parsed request (parsed by provider)
parsed_request = AuthNRequestParser(self.provider).parse(
b64encode(request.encode()).decode(), "test_state"
)
# Now create a response and convert it to string (provider)
response_proc = AssertionProcessor(self.provider, http_request, parsed_request)
response = response_proc.build_response()
# Now parse the response (source)
http_request.POST = QueryDict(mutable=True)
http_request.POST["SAMLResponse"] = b64encode(response.encode()).decode()
response_parser = ResponseProcessor(self.source, http_request)
response_parser.parse()
def test_request_signed(self):
def test_request_full_signed(self):
"""Test full SAML Request/Response flow, fully signed"""
http_request = get_request("/")
@ -162,32 +135,6 @@ class TestAuthNRequest(TestCase):
response_parser = ResponseProcessor(self.source, http_request)
response_parser.parse()
def test_request_signed_both(self):
"""Test full SAML Request/Response flow, fully signed"""
self.provider.sign_assertion = True
self.provider.sign_response = True
self.provider.save()
http_request = get_request("/")
# First create an AuthNRequest
request_proc = RequestProcessor(self.source, http_request, "test_state")
request = request_proc.build_auth_n()
# To get an assertion we need a parsed request (parsed by provider)
parsed_request = AuthNRequestParser(self.provider).parse(
b64encode(request.encode()).decode(), "test_state"
)
# Now create a response and convert it to string (provider)
response_proc = AssertionProcessor(self.provider, http_request, parsed_request)
response = response_proc.build_response()
# Now parse the response (source)
http_request.POST = QueryDict(mutable=True)
http_request.POST["SAMLResponse"] = b64encode(response.encode()).decode()
response_parser = ResponseProcessor(self.source, http_request)
response_parser.parse()
def test_request_id_invalid(self):
"""Test generated AuthNRequest with invalid request ID"""
http_request = get_request("/")

View File

@ -54,11 +54,7 @@ class TestServiceProviderMetadataParser(TestCase):
request = self.factory.get("/")
metadata = lxml_from_string(MetadataProcessor(provider, request).build_entity_descriptor())
schema = etree.XMLSchema(
etree.parse(
source="schemas/saml-schema-metadata-2.0.xsd", parser=etree.XMLParser()
) # nosec
)
schema = etree.XMLSchema(etree.parse("schemas/saml-schema-metadata-2.0.xsd")) # nosec
self.assertTrue(schema.validate(metadata))
def test_schema_want_authn_requests_signed(self):

View File

@ -47,9 +47,7 @@ class TestSchema(TestCase):
metadata = lxml_from_string(request)
schema = etree.XMLSchema(
etree.parse("schemas/saml-schema-protocol-2.0.xsd", parser=etree.XMLParser()) # nosec
)
schema = etree.XMLSchema(etree.parse("schemas/saml-schema-protocol-2.0.xsd")) # nosec
self.assertTrue(schema.validate(metadata))
def test_response_schema(self):
@ -70,7 +68,5 @@ class TestSchema(TestCase):
metadata = lxml_from_string(response)
schema = etree.XMLSchema(
etree.parse("schemas/saml-schema-protocol-2.0.xsd", parser=etree.XMLParser()) # nosec
)
schema = etree.XMLSchema(etree.parse("schemas/saml-schema-protocol-2.0.xsd")) # nosec
self.assertTrue(schema.validate(metadata))

View File

@ -44,6 +44,6 @@ urlpatterns = [
]
api_urlpatterns = [
("propertymappings/provider/saml", SAMLPropertyMappingViewSet),
("propertymappings/saml", SAMLPropertyMappingViewSet),
("providers/saml", SAMLProviderViewSet),
]

View File

@ -6,7 +6,7 @@ from authentik.core.api.providers import ProviderSerializer
from authentik.core.api.used_by import UsedByMixin
from authentik.lib.sync.outgoing.api import OutgoingSyncProviderStatusMixin
from authentik.providers.scim.models import SCIMProvider
from authentik.providers.scim.tasks import scim_sync, scim_sync_objects
from authentik.providers.scim.tasks import scim_sync
class SCIMProviderSerializer(ProviderSerializer):
@ -42,4 +42,3 @@ class SCIMProviderViewSet(OutgoingSyncProviderStatusMixin, UsedByMixin, ModelVie
search_fields = ["name", "url"]
ordering = ["name", "url"]
sync_single_task = scim_sync
sync_objects_task = scim_sync_objects

View File

@ -1,7 +1,5 @@
"""Group client"""
from itertools import batched
from pydantic import ValidationError
from pydanticscim.group import GroupMember
from pydanticscim.responses import PatchOp, PatchOperation
@ -58,22 +56,17 @@ class SCIMGroupClient(SCIMClient[Group, SCIMProviderGroup, SCIMGroupSchema]):
if not scim_group.externalId:
scim_group.externalId = str(obj.pk)
if not self._config.patch.supported:
users = list(obj.users.order_by("id").values_list("id", flat=True))
connections = SCIMProviderUser.objects.filter(
provider=self.provider, user__pk__in=users
)
members = []
for user in connections:
members.append(
GroupMember(
value=user.scim_id,
)
users = list(obj.users.order_by("id").values_list("id", flat=True))
connections = SCIMProviderUser.objects.filter(provider=self.provider, user__pk__in=users)
members = []
for user in connections:
members.append(
GroupMember(
value=user.scim_id,
)
if members:
scim_group.members = members
else:
del scim_group.members
)
if members:
scim_group.members = members
return scim_group
def delete(self, obj: Group):
@ -100,19 +93,16 @@ class SCIMGroupClient(SCIMClient[Group, SCIMProviderGroup, SCIMGroupSchema]):
scim_id = response.get("id")
if not scim_id or scim_id == "":
raise StopSync("SCIM Response with missing or invalid `id`")
connection = SCIMProviderGroup.objects.create(
return SCIMProviderGroup.objects.create(
provider=self.provider, group=group, scim_id=scim_id
)
users = list(group.users.order_by("id").values_list("id", flat=True))
self._patch_add_users(group, users)
return connection
def update(self, group: Group, connection: SCIMProviderGroup):
"""Update existing group"""
scim_group = self.to_schema(group, connection)
scim_group.id = connection.scim_id
try:
self._request(
return self._request(
"PUT",
f"/Groups/{connection.scim_id}",
json=scim_group.model_dump(
@ -120,8 +110,6 @@ class SCIMGroupClient(SCIMClient[Group, SCIMProviderGroup, SCIMGroupSchema]):
exclude_unset=True,
),
)
users = list(group.users.order_by("id").values_list("id", flat=True))
return self._patch_add_users(group, users)
except NotFoundSyncException:
# Resource missing is handled by self.write, which will re-create the group
raise
@ -164,18 +152,14 @@ class SCIMGroupClient(SCIMClient[Group, SCIMProviderGroup, SCIMGroupSchema]):
group_id: str,
*ops: PatchOperation,
):
chunk_size = self._config.bulk.maxOperations
if chunk_size < 1:
chunk_size = len(ops)
for chunk in batched(ops, chunk_size):
req = PatchRequest(Operations=list(chunk))
self._request(
"PATCH",
f"/Groups/{group_id}",
json=req.model_dump(
mode="json",
),
)
req = PatchRequest(Operations=ops)
self._request(
"PATCH",
f"/Groups/{group_id}",
json=req.model_dump(
mode="json",
),
)
def _patch_add_users(self, group: Group, users_set: set[int]):
"""Add users in users_set to group"""
@ -196,14 +180,11 @@ class SCIMGroupClient(SCIMClient[Group, SCIMProviderGroup, SCIMGroupSchema]):
return
self._patch(
scim_group.scim_id,
*[
PatchOperation(
op=PatchOp.add,
path="members",
value=[{"value": x}],
)
for x in user_ids
],
PatchOperation(
op=PatchOp.add,
path="members",
value=[{"value": x} for x in user_ids],
),
)
def _patch_remove_users(self, group: Group, users_set: set[int]):
@ -225,12 +206,9 @@ class SCIMGroupClient(SCIMClient[Group, SCIMProviderGroup, SCIMGroupSchema]):
return
self._patch(
scim_group.scim_id,
*[
PatchOperation(
op=PatchOp.remove,
path="members",
value=[{"value": x}],
)
for x in user_ids
],
PatchOperation(
op=PatchOp.remove,
path="members",
value=[{"value": x} for x in user_ids],
),
)

View File

@ -1,11 +1,9 @@
"""Custom SCIM schemas"""
from pydantic import Field
from pydanticscim.group import Group as BaseGroup
from pydanticscim.responses import PatchRequest as BasePatchRequest
from pydanticscim.responses import SCIMError as BaseSCIMError
from pydanticscim.service_provider import Bulk as BaseBulk
from pydanticscim.service_provider import ChangePassword, Filter, Patch, Sort
from pydanticscim.service_provider import Bulk, ChangePassword, Filter, Patch, Sort
from pydanticscim.service_provider import (
ServiceProviderConfiguration as BaseServiceProviderConfiguration,
)
@ -31,16 +29,10 @@ class Group(BaseGroup):
meta: dict | None = None
class Bulk(BaseBulk):
maxOperations: int = Field()
class ServiceProviderConfiguration(BaseServiceProviderConfiguration):
"""ServiceProviderConfig with fallback"""
_is_fallback: bool | None = False
bulk: Bulk = Field(..., description="A complex type that specifies bulk configuration options.")
@property
def is_fallback(self) -> bool:
@ -53,7 +45,7 @@ class ServiceProviderConfiguration(BaseServiceProviderConfiguration):
"""Get default configuration, which doesn't support any optional features as fallback"""
return ServiceProviderConfiguration(
patch=Patch(supported=False),
bulk=Bulk(supported=False, maxOperations=0),
bulk=Bulk(supported=False),
filter=Filter(supported=False),
changePassword=ChangePassword(supported=False),
sort=Sort(supported=False),

View File

@ -1,20 +0,0 @@
# Generated by Django 5.0.8 on 2024-08-12 12:54
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("authentik_providers_scim", "0008_rename_scimgroup_scimprovidergroup_and_more"),
]
operations = [
migrations.AlterModelOptions(
name="scimmapping",
options={
"verbose_name": "SCIM Provider Mapping",
"verbose_name_plural": "SCIM Provider Mappings",
},
),
]

View File

@ -133,7 +133,7 @@ class SCIMMapping(PropertyMapping):
@property
def component(self) -> str:
return "ak-property-mapping-provider-scim-form"
return "ak-property-mapping-scim-form"
@property
def serializer(self) -> type[Serializer]:
@ -142,8 +142,8 @@ class SCIMMapping(PropertyMapping):
return SCIMMappingSerializer
def __str__(self):
return f"SCIM Provider Mapping {self.name}"
return f"SCIM Mapping {self.name}"
class Meta:
verbose_name = _("SCIM Provider Mapping")
verbose_name_plural = _("SCIM Provider Mappings")
verbose_name = _("SCIM Mapping")
verbose_name_plural = _("SCIM Mappings")

View File

@ -13,5 +13,5 @@ api_urlpatterns = [
("providers/scim", SCIMProviderViewSet),
("providers/scim_users", SCIMProviderUserViewSet),
("providers/scim_groups", SCIMProviderGroupViewSet),
("propertymappings/provider/scim", SCIMMappingViewSet),
("propertymappings/scim", SCIMMappingViewSet),
]

View File

@ -2,7 +2,7 @@
from uuid import uuid4
from django.contrib.auth.management import _get_all_permissions
from django.contrib.auth.models import Permission
from django.db import models
from django.db.transaction import atomic
from django.utils.translation import gettext_lazy as _
@ -10,26 +10,28 @@ from guardian.shortcuts import assign_perm
from rest_framework.serializers import BaseSerializer
from authentik.lib.models import SerializerModel
from authentik.lib.utils.reflection import get_apps
def get_permission_choices():
all_perms = []
for app in get_apps():
for model in app.get_models():
for perm, _desc in _get_all_permissions(model._meta):
all_perms.append((model, perm))
return sorted(
[
(
f"{model._meta.app_label}.{perm}",
f"{model._meta.app_label}.{perm}",
)
for model, perm in all_perms
]
def get_permissions():
return (
Permission.objects.all()
.select_related("content_type")
.filter(
content_type__app_label__startswith="authentik",
)
)
def get_permission_choices() -> list[tuple[str, str]]:
return [
(
f"{x.content_type.app_label}.{x.codename}",
f"{x.content_type.app_label}.{x.codename}",
)
for x in get_permissions()
]
class Role(SerializerModel):
"""RBAC role, which can have different permissions (both global and per-object) attached
to it."""

View File

@ -9,7 +9,6 @@ import orjson
from celery.schedules import crontab
from django.conf import ImproperlyConfigured
from sentry_sdk import set_tag
from xmlsec import enable_debug_trace
from authentik import __version__
from authentik.lib.config import CONFIG, redis_url
@ -521,7 +520,6 @@ if DEBUG:
"rest_framework.renderers.BrowsableAPIRenderer"
)
SHARED_APPS.insert(SHARED_APPS.index("django.contrib.staticfiles"), "daphne")
enable_debug_trace(True)
TENANT_APPS.append("authentik.core")

View File

@ -290,7 +290,7 @@ class LDAPSourcePropertyMapping(PropertyMapping):
@property
def component(self) -> str:
return "ak-property-mapping-source-ldap-form"
return "ak-property-mapping-ldap-source-form"
@property
def serializer(self) -> type[Serializer]:

View File

@ -268,7 +268,7 @@ class OAuthSourcePropertyMapping(PropertyMapping):
@property
def component(self) -> str:
return "ak-property-mapping-source-oauth-form"
return "ak-property-mapping-oauth-source-form"
@property
def serializer(self) -> type[Serializer]:

View File

@ -123,7 +123,7 @@ class PlexSourcePropertyMapping(PropertyMapping):
@property
def component(self) -> str:
return "ak-property-mapping-source-plex-form"
return "ak-property-mapping-plex-source-form"
@property
def serializer(self) -> type[Serializer]:

View File

@ -299,7 +299,7 @@ class SAMLSourcePropertyMapping(PropertyMapping):
@property
def component(self) -> str:
return "ak-property-mapping-source-saml-form"
return "ak-property-mapping-saml-source-form"
@property
def serializer(self) -> type[Serializer]:

View File

@ -6,14 +6,12 @@ NS_SAML_PROTOCOL = "urn:oasis:names:tc:SAML:2.0:protocol"
NS_SAML_ASSERTION = "urn:oasis:names:tc:SAML:2.0:assertion"
NS_SAML_METADATA = "urn:oasis:names:tc:SAML:2.0:metadata"
NS_SIGNATURE = "http://www.w3.org/2000/09/xmldsig#"
NS_ENC = "http://www.w3.org/2001/04/xmlenc#"
NS_MAP = {
"samlp": NS_SAML_PROTOCOL,
"saml": NS_SAML_ASSERTION,
"ds": NS_SIGNATURE,
"md": NS_SAML_METADATA,
"xenc": NS_ENC,
}
SAML_NAME_ID_FORMAT_EMAIL = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"

View File

@ -76,7 +76,7 @@ class RequestProcessor:
auth_n_request,
xmlsec.constants.TransformExclC14N,
sign_algorithm_transform,
ns=xmlsec.constants.DSigNs,
ns="ds", # type: ignore
)
auth_n_request.append(signature)

View File

@ -30,9 +30,7 @@ class TestMetadataProcessor(TestCase):
xml = MetadataProcessor(self.source, request).build_entity_descriptor()
metadata = lxml_from_string(xml)
schema = etree.XMLSchema(
etree.parse("schemas/saml-schema-metadata-2.0.xsd", parser=etree.XMLParser()) # nosec
)
schema = etree.XMLSchema(etree.parse("schemas/saml-schema-metadata-2.0.xsd")) # nosec
self.assertTrue(schema.validate(metadata))
def test_metadata_consistent(self):

View File

@ -85,7 +85,7 @@ class SCIMSourcePropertyMapping(PropertyMapping):
@property
def component(self) -> str:
return "ak-property-mapping-source-scim-form"
return "ak-property-mapping-scim-source-form"
@property
def serializer(self) -> type[Serializer]:

View File

@ -14,9 +14,7 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name="duodevice",
name="created",
field=models.DateTimeField(
auto_now_add=True, default=datetime.datetime(1, 1, 1, 0, 0, tzinfo=datetime.UTC)
),
field=models.DateTimeField(auto_now_add=True, default=datetime.datetime(1, 1, 1, 0, 0)),
preserve_default=False,
),
migrations.AddField(

View File

@ -14,9 +14,7 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name="smsdevice",
name="created",
field=models.DateTimeField(
auto_now_add=True, default=datetime.datetime(1, 1, 1, 0, 0, tzinfo=datetime.UTC)
),
field=models.DateTimeField(auto_now_add=True, default=datetime.datetime(1, 1, 1, 0, 0)),
preserve_default=False,
),
migrations.AddField(

View File

@ -14,9 +14,7 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name="staticdevice",
name="created",
field=models.DateTimeField(
auto_now_add=True, default=datetime.datetime(1, 1, 1, 0, 0, tzinfo=datetime.UTC)
),
field=models.DateTimeField(auto_now_add=True, default=datetime.datetime(1, 1, 1, 0, 0)),
preserve_default=False,
),
migrations.AddField(

View File

@ -14,9 +14,7 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name="totpdevice",
name="created",
field=models.DateTimeField(
auto_now_add=True, default=datetime.datetime(1, 1, 1, 0, 0, tzinfo=datetime.UTC)
),
field=models.DateTimeField(auto_now_add=True, default=datetime.datetime(1, 1, 1, 0, 0)),
preserve_default=False,
),
migrations.AddField(

File diff suppressed because one or more lines are too long

View File

@ -14,9 +14,7 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name="webauthndevice",
name="created",
field=models.DateTimeField(
auto_now_add=True, default=datetime.datetime(1, 1, 1, 0, 0, tzinfo=datetime.UTC)
),
field=models.DateTimeField(auto_now_add=True, default=datetime.datetime(1, 1, 1, 0, 0)),
preserve_default=False,
),
migrations.AddField(

File diff suppressed because it is too large Load Diff

View File

@ -31,7 +31,7 @@ services:
volumes:
- redis:/data
server:
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2024.8.0}
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2024.6.3}
restart: unless-stopped
command: server
environment:
@ -52,7 +52,7 @@ services:
- postgresql
- redis
worker:
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2024.8.0}
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2024.6.3}
restart: unless-stopped
command: worker
environment:

26
go.mod
View File

@ -1,8 +1,6 @@
module goauthentik.io
go 1.23
toolchain go1.23.0
go 1.22.2
require (
beryju.io/ldap v0.1.0
@ -11,25 +9,26 @@ require (
github.com/go-http-utils/etag v0.0.0-20161124023236-513ea8f21eb1
github.com/go-ldap/ldap/v3 v3.4.8
github.com/go-openapi/runtime v0.28.0
github.com/go-openapi/strfmt v0.23.0
github.com/golang-jwt/jwt/v5 v5.2.1
github.com/google/uuid v1.6.0
github.com/gorilla/handlers v1.5.2
github.com/gorilla/mux v1.8.1
github.com/gorilla/securecookie v1.1.2
github.com/gorilla/sessions v1.4.0
github.com/gorilla/sessions v1.3.0
github.com/gorilla/websocket v1.5.3
github.com/jellydator/ttlcache/v3 v3.2.1
github.com/jellydator/ttlcache/v3 v3.2.0
github.com/mitchellh/mapstructure v1.5.0
github.com/nmcclain/asn1-ber v0.0.0-20170104154839-2661553a0484
github.com/pires/go-proxyproto v0.7.0
github.com/prometheus/client_golang v1.20.2
github.com/prometheus/client_golang v1.19.1
github.com/redis/go-redis/v9 v9.6.1
github.com/sethvargo/go-envconfig v1.1.0
github.com/sirupsen/logrus v1.9.3
github.com/spf13/cobra v1.8.1
github.com/stretchr/testify v1.9.0
github.com/wwt/guac v1.3.2
goauthentik.io/api/v3 v3.2024064.1
goauthentik.io/api/v3 v3.2024063.6
golang.org/x/exp v0.0.0-20230210204819-062eb4c674ab
golang.org/x/oauth2 v0.22.0
golang.org/x/sync v0.8.0
@ -41,7 +40,7 @@ require (
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/felixge/httpsnoop v1.0.3 // indirect
@ -57,20 +56,17 @@ require (
github.com/go-openapi/jsonreference v0.21.0 // indirect
github.com/go-openapi/loads v0.22.0 // indirect
github.com/go-openapi/spec v0.21.0 // indirect
github.com/go-openapi/strfmt v0.23.0 // indirect
github.com/go-openapi/swag v0.23.0 // indirect
github.com/go-openapi/validate v0.24.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/klauspost/compress v1.17.9 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/oklog/ulid v1.3.1 // indirect
github.com/opentracing/opentracing-go v1.2.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.55.0 // indirect
github.com/prometheus/procfs v0.15.1 // indirect
github.com/prometheus/client_model v0.5.0 // indirect
github.com/prometheus/common v0.48.0 // indirect
github.com/prometheus/procfs v0.12.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
go.mongodb.org/mongo-driver v1.14.0 // indirect
go.opentelemetry.io/otel v1.24.0 // indirect
@ -79,6 +75,6 @@ require (
golang.org/x/crypto v0.25.0 // indirect
golang.org/x/sys v0.22.0 // indirect
golang.org/x/text v0.16.0 // indirect
google.golang.org/protobuf v1.34.2 // indirect
google.golang.org/protobuf v1.33.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

46
go.sum
View File

@ -48,8 +48,8 @@ github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdb
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
@ -175,8 +175,8 @@ github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+
github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA=
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
github.com/gorilla/sessions v1.4.0 h1:kpIYOp/oi6MG/p5PgxApU8srsSw9tuFbt46Lt7auzqQ=
github.com/gorilla/sessions v1.4.0/go.mod h1:FLWm50oby91+hl7p/wRxDth9bWSuk0qVL2emc7lT5ik=
github.com/gorilla/sessions v1.3.0 h1:XYlkq7KcpOB2ZhHBPv5WpjMIxrQosiZanfoy1HLZFzg=
github.com/gorilla/sessions v1.3.0/go.mod h1:ePLdVu+jbEgHH+KWw8I1z2wqd0BAdAQh/8LRvBeoNcQ=
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
@ -200,15 +200,13 @@ github.com/jcmturner/gokrb5/v8 v8.4.4 h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh6
github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs=
github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY=
github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
github.com/jellydator/ttlcache/v3 v3.2.1 h1:eS8ljnYY7BllYGkXw/TfczWZrXUu/CH7SIkC6ugn9Js=
github.com/jellydator/ttlcache/v3 v3.2.1/go.mod h1:bj2/e0l4jRnQdrnSTaGTsh4GSXvMjQcy41i7th0GVGw=
github.com/jellydator/ttlcache/v3 v3.2.0 h1:6lqVJ8X3ZaUwvzENqPAobDsXNExfUJd61u++uW8a3LE=
github.com/jellydator/ttlcache/v3 v3.2.0/go.mod h1:hi7MGFdMAwZna5n2tuvh63DvFLzVKySzCVW6+0gA2n4=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
@ -217,14 +215,10 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/nmcclain/asn1-ber v0.0.0-20170104154839-2661553a0484 h1:D9EvfGQvlkKaDr2CRKN++7HbSXbefUNDrPq60T+g24s=
github.com/nmcclain/asn1-ber v0.0.0-20170104154839-2661553a0484/go.mod h1:O1EljZ+oHprtxDDPHiMWVo/5dBT6PlvWX5PSwj80aBA=
github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=
@ -239,15 +233,15 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v1.20.2 h1:5ctymQzZlyOON1666svgwn3s6IKWgfbjsejTMiXIyjg=
github.com/prometheus/client_golang v1.20.2/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE=
github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc=
github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8=
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw=
github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI=
github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE=
github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc=
github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
github.com/redis/go-redis/v9 v9.6.1 h1:HHDteefn6ZkTtY5fGUE8tj8uy85AHk6zP7CpzIAM0y4=
github.com/redis/go-redis/v9 v9.6.1/go.mod h1:0C0c6ycQsdpVNQpxb1njEQIqkx5UcsM8FJCQLgE9+RA=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
@ -297,10 +291,10 @@ go.opentelemetry.io/otel/sdk v1.24.0 h1:YMPPDNymmQN3ZgczicBY3B6sf9n62Dlj9pWD3ucg
go.opentelemetry.io/otel/sdk v1.24.0/go.mod h1:KVrIYw6tEubO9E96HQpcmpTKDVn9gdv35HoYiQWGDFg=
go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI=
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
goauthentik.io/api/v3 v3.2024064.1 h1:vxquklgDGD+nGFhWRAsQ7ezQKg17MRq6bzEk25fbsb4=
goauthentik.io/api/v3 v3.2024064.1/go.mod h1:zz+mEZg8rY/7eEjkMGWJ2DnGqk+zqxuybGCGrR2O4Kw=
go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A=
go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4=
goauthentik.io/api/v3 v3.2024063.6 h1:TloJKYEhdxej4PRPjQiA//SlaSByxc5XCYT3QmjErN8=
goauthentik.io/api/v3 v3.2024063.6/go.mod h1:zz+mEZg8rY/7eEjkMGWJ2DnGqk+zqxuybGCGrR2O4Kw=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
@ -578,8 +572,8 @@ google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=

View File

@ -29,4 +29,4 @@ func UserAgent() string {
return fmt.Sprintf("authentik@%s", FullVersion())
}
const VERSION = "2024.8.0"
const VERSION = "2024.6.3"

View File

@ -77,17 +77,11 @@ func NewAPIController(akURL url.URL, token string) *APIController {
// Because we don't know the outpost UUID, we simply do a list and pick the first
// The service account this token belongs to should only have access to a single outpost
var outposts *api.PaginatedOutpostList
var err error
for {
outposts, _, err = apiClient.OutpostsApi.OutpostsInstancesList(context.Background()).Execute()
if err == nil {
break
}
outposts, _, err := apiClient.OutpostsApi.OutpostsInstancesList(context.Background()).Execute()
if err != nil {
log.WithError(err).Error("Failed to fetch outpost configuration, retrying in 3 seconds")
time.Sleep(time.Second * 3)
return NewAPIController(akURL, token)
}
if len(outposts.Results) < 1 {
panic("No outposts found with given token, ensure the given token corresponds to an authenitk Outpost")
@ -193,7 +187,7 @@ func (a *APIController) OnRefresh() error {
func (a *APIController) getWebsocketPingArgs() map[string]interface{} {
args := map[string]interface{}{
"version": constants.VERSION,
"buildHash": constants.BUILD(""),
"buildHash": constants.BUILD("tagged"),
"uuid": a.instanceUUID.String(),
"golangVersion": runtime.Version(),
"opensslEnabled": cryptobackend.OpensslEnabled,
@ -213,7 +207,7 @@ func (a *APIController) StartBackgroundTasks() error {
"outpost_type": a.Server.Type(),
"uuid": a.instanceUUID.String(),
"version": constants.VERSION,
"build": constants.BUILD(""),
"build": constants.BUILD("tagged"),
}).Set(1)
go func() {
a.logger.Debug("Starting WS Handler...")

View File

@ -145,7 +145,7 @@ func (ac *APIController) startWSHandler() {
"outpost_type": ac.Server.Type(),
"uuid": ac.instanceUUID.String(),
"version": constants.VERSION,
"build": constants.BUILD(""),
"build": constants.BUILD("tagged"),
}).SetToCurrentTime()
}
} else if wsMsg.Instruction == WebsocketInstructionProviderSpecific {
@ -207,7 +207,7 @@ func (ac *APIController) startIntervalUpdater() {
"outpost_type": ac.Server.Type(),
"uuid": ac.instanceUUID.String(),
"version": constants.VERSION,
"build": constants.BUILD(""),
"build": constants.BUILD("tagged"),
}).SetToCurrentTime()
}
ticker.Reset(getInterval())

View File

@ -120,6 +120,21 @@ func (fe *FlowExecutor) DelegateClientIP(a string) {
fe.api.GetConfig().AddDefaultHeader(HeaderAuthentikRemoteIP, fe.cip)
}
func (fe *FlowExecutor) CheckApplicationAccess(appSlug string) (bool, error) {
acsp := sentry.StartSpan(fe.Context, "authentik.outposts.flow_executor.check_access")
defer acsp.Finish()
p, _, err := fe.api.CoreApi.CoreApplicationsCheckAccessRetrieve(acsp.Context(), appSlug).Execute()
if err != nil {
return false, fmt.Errorf("failed to check access: %w", err)
}
if !p.Passing {
fe.log.Info("Access denied for user")
return false, nil
}
fe.log.Debug("User has access")
return true, nil
}
func (fe *FlowExecutor) getAnswer(stage StageComponent) string {
if v, o := fe.Answers[stage]; o {
return v

View File

@ -58,10 +58,8 @@ func (db *DirectBinder) Bind(username string, req *bind.Request) (ldap.LDAPResul
return ldap.LDAPResultInvalidCredentials, nil
}
access, _, err := fe.ApiClient().OutpostsApi.OutpostsLdapAccessCheck(
req.Context(), db.si.GetProviderID(),
).AppSlug(db.si.GetAppSlug()).Execute()
if !access.Access.Passing {
access, err := fe.CheckApplicationAccess(db.si.GetAppSlug())
if !access {
req.Log().Info("Access denied for user")
metrics.RequestsRejected.With(prometheus.Labels{
"outpost_name": db.si.GetOutpostName(),
@ -95,11 +93,12 @@ func (db *DirectBinder) Bind(username string, req *bind.Request) (ldap.LDAPResul
req.Log().WithError(err).Warning("failed to get user info")
return ldap.LDAPResultOperationsError, nil
}
cs := db.SearchAccessCheck(userInfo.User)
flags.UserPk = userInfo.User.Pk
flags.CanSearch = access.HasSearchPermission != nil
flags.CanSearch = cs != nil
db.si.SetFlags(req.BindDN, &flags)
if flags.CanSearch {
req.Log().Debug("Allowed access to search")
req.Log().WithField("group", cs).Info("Allowed access to search")
}
uisp.Finish()
return ldap.LDAPResultSuccess, nil

View File

@ -7,6 +7,7 @@ import (
goldap "github.com/go-ldap/ldap/v3"
log "github.com/sirupsen/logrus"
"goauthentik.io/api/v3"
"goauthentik.io/internal/outpost/flow"
"goauthentik.io/internal/outpost/ldap/server"
"goauthentik.io/internal/outpost/ldap/utils"
@ -46,6 +47,22 @@ func (db *DirectBinder) GetUsername(dn string) (string, error) {
return "", errors.New("failed to find cn")
}
// SearchAccessCheck Check if the current user is allowed to search
func (db *DirectBinder) SearchAccessCheck(user api.UserSelf) *string {
for _, group := range user.Groups {
for _, allowedGroup := range db.si.GetSearchAllowedGroups() {
if allowedGroup == nil {
continue
}
db.log.WithField("userGroup", group.Pk).WithField("allowedGroup", allowedGroup).Trace("Checking search access")
if group.Pk == allowedGroup.String() {
return &group.Name
}
}
}
return nil
}
func (db *DirectBinder) TimerFlowCacheExpiry(ctx context.Context) {
fe := flow.NewFlowExecutor(ctx, db.si.GetAuthenticationFlowSlug(), db.si.GetAPIClient().GetConfig(), log.Fields{})
fe.Params.Add("goauthentik.io/outpost/ldap", "true")

View File

@ -5,6 +5,7 @@ import (
"strings"
"sync"
"github.com/go-openapi/strfmt"
log "github.com/sirupsen/logrus"
"goauthentik.io/api/v3"
@ -30,13 +31,14 @@ type ProviderInstance struct {
s *LDAPServer
log *log.Entry
tlsServerName *string
cert *tls.Certificate
certUUID string
outpostName string
providerPk int32
boundUsersMutex *sync.RWMutex
boundUsers map[string]*flags.UserFlags
tlsServerName *string
cert *tls.Certificate
certUUID string
outpostName string
outpostPk int32
searchAllowedGroups []*strfmt.UUID
boundUsersMutex *sync.RWMutex
boundUsers map[string]*flags.UserFlags
uidStartNumber int32
gidStartNumber int32
@ -103,8 +105,8 @@ func (pi *ProviderInstance) GetInvalidationFlowSlug() string {
return pi.invalidationFlowSlug
}
func (pi *ProviderInstance) GetProviderID() int32 {
return pi.providerPk
func (pi *ProviderInstance) GetSearchAllowedGroups() []*strfmt.UUID {
return pi.searchAllowedGroups
}
func (pi *ProviderInstance) GetNeededObjects(scope int, baseDN string, filterOC string) (bool, bool) {

View File

@ -7,6 +7,7 @@ import (
"strings"
"sync"
"github.com/go-openapi/strfmt"
log "github.com/sirupsen/logrus"
"goauthentik.io/api/v3"
@ -22,7 +23,7 @@ import (
func (ls *LDAPServer) getCurrentProvider(pk int32) *ProviderInstance {
for _, p := range ls.providers {
if p.providerPk == pk {
if p.outpostPk == pk {
return p
}
}
@ -76,6 +77,7 @@ func (ls *LDAPServer) Refresh() error {
appSlug: provider.ApplicationSlug,
authenticationFlowSlug: provider.BindFlowSlug,
invalidationFlowSlug: invalidationFlow,
searchAllowedGroups: []*strfmt.UUID{(*strfmt.UUID)(provider.SearchGroup.Get())},
boundUsersMutex: usersMutex,
boundUsers: users,
s: ls,
@ -85,7 +87,7 @@ func (ls *LDAPServer) Refresh() error {
gidStartNumber: provider.GetGidStartNumber(),
mfaSupport: provider.GetMfaSupport(),
outpostName: ls.ac.Outpost.Name,
providerPk: provider.Pk,
outpostPk: provider.Pk,
}
if kp := provider.Certificate.Get(); kp != nil {
err := ls.cs.AddKeypair(*kp)

View File

@ -2,6 +2,7 @@ package server
import (
"beryju.io/ldap"
"github.com/go-openapi/strfmt"
"goauthentik.io/api/v3"
"goauthentik.io/internal/outpost/ldap/flags"
@ -14,7 +15,7 @@ type LDAPServerInstance interface {
GetAuthenticationFlowSlug() string
GetInvalidationFlowSlug() string
GetAppSlug() string
GetProviderID() int32
GetSearchAllowedGroups() []*strfmt.UUID
UserEntry(u api.User) *ldap.Entry

View File

@ -45,9 +45,7 @@ func (rs *RadiusServer) Handle_AccessRequest(w radius.ResponseWriter, r *RadiusR
_ = w.Write(r.Response(radius.CodeAccessReject))
return
}
access, _, err := fe.ApiClient().OutpostsApi.OutpostsRadiusAccessCheck(
r.Context(), r.pi.providerId,
).AppSlug(r.pi.appSlug).Execute()
access, _, err := fe.ApiClient().OutpostsApi.OutpostsRadiusCheckAccessRetrieve(r.Context(), r.pi.providerId).AppSlug(r.pi.appSlug).Execute()
if err != nil {
r.Log().WithField("username", username).WithError(err).Warning("failed to check access")
_ = w.Write(r.Response(radius.CodeAccessReject))

View File

@ -1,7 +1,7 @@
# syntax=docker/dockerfile:1
# Stage 1: Build
FROM --platform=${BUILDPLATFORM} mcr.microsoft.com/oss/go/microsoft/golang:1.23-fips-bookworm AS builder
FROM --platform=${BUILDPLATFORM} mcr.microsoft.com/oss/go/microsoft/golang:1.22-fips-bookworm AS builder
ARG TARGETOS
ARG TARGETARCH
@ -36,11 +36,11 @@ FROM ghcr.io/goauthentik/fips-debian:bookworm-slim-fips
ARG GIT_BUILD_HASH
ENV GIT_BUILD_HASH=$GIT_BUILD_HASH
LABEL org.opencontainers.image.url=https://goauthentik.io
LABEL org.opencontainers.image.description="goauthentik.io LDAP outpost, see https://goauthentik.io for more info."
LABEL org.opencontainers.image.source=https://github.com/goauthentik/authentik
LABEL org.opencontainers.image.version=${VERSION}
LABEL org.opencontainers.image.revision=${GIT_BUILD_HASH}
LABEL org.opencontainers.image.url https://goauthentik.io
LABEL org.opencontainers.image.description goauthentik.io LDAP outpost, see https://goauthentik.io for more info.
LABEL org.opencontainers.image.source https://github.com/goauthentik/authentik
LABEL org.opencontainers.image.version ${VERSION}
LABEL org.opencontainers.image.revision ${GIT_BUILD_HASH}
COPY --from=builder /go/ldap /

View File

@ -10,20 +10,15 @@ from redis.exceptions import RedisError
from authentik.lib.config import CONFIG, redis_url
CHECK_THRESHOLD = 30
def check_postgres():
attempt = 0
while True:
if attempt >= CHECK_THRESHOLD:
sysexit(1)
try:
conn = connect(
dbname=CONFIG.refresh("postgresql.name"),
user=CONFIG.refresh("postgresql.user"),
password=CONFIG.refresh("postgresql.password"),
host=CONFIG.refresh("postgresql.host"),
dbname=CONFIG.get("postgresql.name"),
user=CONFIG.get("postgresql.user"),
password=CONFIG.get("postgresql.password"),
host=CONFIG.get("postgresql.host"),
port=CONFIG.get_int("postgresql.port"),
sslmode=CONFIG.get("postgresql.sslmode"),
sslrootcert=CONFIG.get("postgresql.sslrootcert"),
@ -35,17 +30,12 @@ def check_postgres():
except OperationalError as exc:
sleep(1)
CONFIG.log("info", f"PostgreSQL connection failed, retrying... ({exc})")
finally:
attempt += 1
CONFIG.log("info", "PostgreSQL connection successful")
def check_redis():
url = CONFIG.get("cache.url") or redis_url(CONFIG.get("redis.db"))
attempt = 0
while True:
if attempt >= CHECK_THRESHOLD:
sysexit(1)
try:
redis = Redis.from_url(url)
redis.ping()
@ -53,8 +43,6 @@ def check_redis():
except RedisError as exc:
sleep(1)
CONFIG.log("info", f"Redis Connection failed, retrying... ({exc})")
finally:
attempt += 1
CONFIG.log("info", "Redis Connection successful")

View File

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-08-18 00:08+0000\n"
"POT-Creation-Date: 2024-06-16 00:08+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"
@ -75,12 +75,6 @@ msgid ""
"and `ba.b`"
msgstr ""
#: authentik/brands/models.py
msgid ""
"When set, external users will be redirected to this application after "
"authenticating."
msgstr ""
#: authentik/brands/models.py
msgid "Web Certificate used by the authentik Core webserver."
msgstr ""
@ -232,16 +226,6 @@ msgid ""
"exists."
msgstr ""
#: authentik/core/models.py
msgid ""
"Link to a group with identical name. Can have security implications when a "
"group name is used with another source."
msgstr ""
#: authentik/core/models.py
msgid "Use the group name, but deny enrollment when the name already exists."
msgstr ""
#: authentik/core/models.py
msgid "Source's display Name."
msgstr ""
@ -264,12 +248,6 @@ msgid ""
"new user enrolled."
msgstr ""
#: authentik/core/models.py
msgid ""
"How the source determines if an existing group should be used or a new group "
"created."
msgstr ""
#: authentik/core/models.py
msgid "Token"
msgstr ""
@ -369,7 +347,6 @@ msgid "Go home"
msgstr ""
#: authentik/core/templates/login/base_full.html
#: authentik/flows/templates/if/flow-sfe.html
msgid "Powered by authentik"
msgstr ""
@ -380,10 +357,6 @@ msgstr ""
msgid "You're about to sign into %(application)s."
msgstr ""
#: authentik/core/views/interface.py
msgid "Interface can only be accessed by internal users."
msgstr ""
#: authentik/crypto/api.py
msgid "Subject-alt name"
msgstr ""
@ -460,7 +433,7 @@ msgstr ""
#: authentik/enterprise/providers/google_workspace/models.py
#: authentik/enterprise/providers/microsoft_entra/models.py
#: authentik/providers/scim/models.py
#: authentik/providers/scim/models.py authentik/sources/ldap/models.py
msgid "Property mappings used for group creation/updating."
msgstr ""
@ -536,11 +509,11 @@ msgid "RAC Endpoints"
msgstr ""
#: authentik/enterprise/providers/rac/models.py
msgid "RAC Provider Property Mapping"
msgid "RAC Property Mapping"
msgstr ""
#: authentik/enterprise/providers/rac/models.py
msgid "RAC Provider Property Mappings"
msgid "RAC Property Mappings"
msgstr ""
#: authentik/enterprise/providers/rac/models.py
@ -1036,30 +1009,6 @@ msgstr ""
msgid "Expression Policies"
msgstr ""
#: authentik/policies/geoip/models.py
msgid "GeoIP: client IP not found in ASN database."
msgstr ""
#: authentik/policies/geoip/models.py
msgid "Client IP is not part of an allowed autonomous system."
msgstr ""
#: authentik/policies/geoip/models.py
msgid "GeoIP: client IP address not found in City database."
msgstr ""
#: authentik/policies/geoip/models.py
msgid "Client IP is not in an allowed country."
msgstr ""
#: authentik/policies/geoip/models.py
msgid "GeoIP Policy"
msgstr ""
#: authentik/policies/geoip/models.py
msgid "GeoIP Policies"
msgstr ""
#: authentik/policies/models.py
msgid "all, all policies must pass"
msgstr ""
@ -1212,6 +1161,12 @@ msgstr ""
msgid "DN under which objects are accessible."
msgstr ""
#: authentik/providers/ldap/models.py
msgid ""
"Users in this group can do search queries. If not set, every user can "
"execute search queries."
msgstr ""
#: authentik/providers/ldap/models.py
msgid ""
"The start for uidNumbers, this number is added to the user.pk to make sure "
@ -1244,10 +1199,6 @@ msgstr ""
msgid "LDAP Providers"
msgstr ""
#: authentik/providers/ldap/models.py
msgid "Search full LDAP directory"
msgstr ""
#: authentik/providers/oauth2/id_token.py
msgid "Based on the Hashed User ID"
msgstr ""
@ -1592,20 +1543,6 @@ msgstr ""
msgid "Radius Providers"
msgstr ""
#: authentik/providers/radius/models.py
msgid "Radius Provider Property Mapping"
msgstr ""
#: authentik/providers/radius/models.py
msgid "Radius Provider Property Mappings"
msgstr ""
#: authentik/providers/saml/api/providers.py
msgid ""
"With a signing keypair selected, at least one of 'Sign assertion' and 'Sign "
"Response' must be selected."
msgstr ""
#: authentik/providers/saml/api/providers.py
msgid "Invalid XML Syntax"
msgstr ""
@ -1737,17 +1674,6 @@ msgstr ""
msgid "Signing Keypair"
msgstr ""
#: authentik/providers/saml/models.py authentik/sources/saml/models.py
msgid ""
"When selected, incoming assertions are encrypted by the IdP using the public "
"key of the encryption keypair. The assertion is decrypted by the SP using "
"the the private key."
msgstr ""
#: authentik/providers/saml/models.py authentik/sources/saml/models.py
msgid "Encryption Keypair"
msgstr ""
#: authentik/providers/saml/models.py
msgid "Default relay_state value for IDP-initiated logins"
msgstr ""
@ -1761,11 +1687,11 @@ msgid "SAML Providers"
msgstr ""
#: authentik/providers/saml/models.py
msgid "SAML Provider Property Mapping"
msgid "SAML Property Mapping"
msgstr ""
#: authentik/providers/saml/models.py
msgid "SAML Provider Property Mappings"
msgid "SAML Property Mappings"
msgstr ""
#: authentik/providers/saml/models.py
@ -1793,11 +1719,11 @@ msgid "SCIM Providers"
msgstr ""
#: authentik/providers/scim/models.py
msgid "SCIM Provider Mapping"
msgid "SCIM Mapping"
msgstr ""
#: authentik/providers/scim/models.py
msgid "SCIM Provider Mappings"
msgid "SCIM Mappings"
msgstr ""
#: authentik/rbac/models.py
@ -1926,11 +1852,11 @@ msgid "LDAP Sources"
msgstr ""
#: authentik/sources/ldap/models.py
msgid "LDAP Source Property Mapping"
msgid "LDAP Property Mapping"
msgstr ""
#: authentik/sources/ldap/models.py
msgid "LDAP Source Property Mappings"
msgid "LDAP Property Mappings"
msgstr ""
#: authentik/sources/ldap/signals.py
@ -2098,14 +2024,6 @@ msgstr ""
msgid "Reddit OAuth Sources"
msgstr ""
#: authentik/sources/oauth/models.py
msgid "OAuth Source Property Mapping"
msgstr ""
#: authentik/sources/oauth/models.py
msgid "OAuth Source Property Mappings"
msgstr ""
#: authentik/sources/oauth/models.py
msgid "User OAuth Source Connection"
msgstr ""
@ -2114,14 +2032,6 @@ msgstr ""
msgid "User OAuth Source Connections"
msgstr ""
#: authentik/sources/oauth/models.py
msgid "Group OAuth Source Connection"
msgstr ""
#: authentik/sources/oauth/models.py
msgid "Group OAuth Source Connections"
msgstr ""
#: authentik/sources/oauth/views/callback.py
#, python-brace-format
msgid "Authentication failed: {reason}"
@ -2153,14 +2063,6 @@ msgstr ""
msgid "Plex Sources"
msgstr ""
#: authentik/sources/plex/models.py
msgid "Plex Source Property Mapping"
msgstr ""
#: authentik/sources/plex/models.py
msgid "Plex Source Property Mappings"
msgstr ""
#: authentik/sources/plex/models.py
msgid "User Plex Source Connection"
msgstr ""
@ -2169,14 +2071,6 @@ msgstr ""
msgid "User Plex Source Connections"
msgstr ""
#: authentik/sources/plex/models.py
msgid "Group Plex Source Connection"
msgstr ""
#: authentik/sources/plex/models.py
msgid "Group Plex Source Connections"
msgstr ""
#: authentik/sources/saml/models.py
msgid "Redirect Binding"
msgstr ""
@ -2251,14 +2145,6 @@ msgstr ""
msgid "SAML Sources"
msgstr ""
#: authentik/sources/saml/models.py
msgid "SAML Source Property Mapping"
msgstr ""
#: authentik/sources/saml/models.py
msgid "SAML Source Property Mappings"
msgstr ""
#: authentik/sources/saml/models.py
msgid "User SAML Source Connection"
msgstr ""
@ -2267,14 +2153,6 @@ msgstr ""
msgid "User SAML Source Connections"
msgstr ""
#: authentik/sources/saml/models.py
msgid "Group SAML Source Connection"
msgstr ""
#: authentik/sources/saml/models.py
msgid "Group SAML Source Connections"
msgstr ""
#: authentik/sources/scim/models.py
msgid "SCIM Source"
msgstr ""
@ -2283,14 +2161,6 @@ msgstr ""
msgid "SCIM Sources"
msgstr ""
#: authentik/sources/scim/models.py
msgid "SCIM Source Property Mapping"
msgstr ""
#: authentik/sources/scim/models.py
msgid "SCIM Source Property Mappings"
msgstr ""
#: authentik/stages/authenticator_duo/models.py
msgid "Duo Authenticator Setup Stage"
msgstr ""
@ -2855,12 +2725,6 @@ msgid ""
"out, use a reputation policy and a user_write stage."
msgstr ""
#: authentik/stages/password/models.py
msgid ""
"When enabled, provides a 'show password' button with the password input "
"field."
msgstr ""
#: authentik/stages/password/models.py
msgid "Password Stage"
msgstr ""

Binary file not shown.

View File

@ -19,7 +19,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-08-12 13:45+0000\n"
"POT-Creation-Date: 2024-06-05 00:07+0000\n"
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
"Last-Translator: Marc Schmitt, 2024\n"
"Language-Team: French (https://app.transifex.com/authentik/teams/119923/fr/)\n"
@ -93,14 +93,6 @@ msgstr ""
"Domain qui active cette marque. Peut être un super-ensemble, c'est-à-dire "
"`a.b` pour `aa.b` et `ba.b`"
#: authentik/brands/models.py
msgid ""
"When set, external users will be redirected to this application after "
"authenticating."
msgstr ""
"Si activé, les utilisateurs externes seront redirigés vers cette application"
" après s'être authentifiés."
#: authentik/brands/models.py
msgid "Web Certificate used by the authentik Core webserver."
msgstr "Certificate Web utilisé par le serveur web d'authentik core."
@ -272,19 +264,6 @@ msgstr ""
"Utiliser le nom d'utilisateur, mais refuser l'inscription si celui-ci existe"
" déjà."
#: authentik/core/models.py
msgid ""
"Link to a group with identical name. Can have security implications when a "
"group name is used with another source."
msgstr ""
"Lien vers un groupe ayant un nom identique. Peut poser des problèmes de "
"sécurité si ce nom est partagé avec une autre source."
#: authentik/core/models.py
msgid "Use the group name, but deny enrollment when the name already exists."
msgstr ""
"Utiliser le nom du groupe, mais refuser la création si celui-ci existe déjà."
#: authentik/core/models.py
msgid "Source's display Name."
msgstr "Nom d'affichage de la source."
@ -309,14 +288,6 @@ msgstr ""
"Comment la source détermine si un utilisateur existant doit être authentifié"
" ou un nouvelle utilisateur doit être inscrit."
#: authentik/core/models.py
msgid ""
"How the source determines if an existing group should be used or a new group"
" created."
msgstr ""
"Comment la source détermine si un groupe existant doit être utilisé ou un "
"nouveau groupe doit être créé."
#: authentik/core/models.py
msgid "Token"
msgstr "Jeton"
@ -427,7 +398,6 @@ msgid "Go home"
msgstr "Retourner à l'accueil"
#: authentik/core/templates/login/base_full.html
#: authentik/flows/templates/if/flow-sfe.html
msgid "Powered by authentik"
msgstr "Propulsé par authentik"
@ -438,10 +408,6 @@ msgstr "Propulsé par authentik"
msgid "You're about to sign into %(application)s."
msgstr "Vous êtes sur le point de vous connecter à %(application)s."
#: authentik/core/views/interface.py
msgid "Interface can only be accessed by internal users."
msgstr "L'interface est accessible uniquement aux utilisateurs internes."
#: authentik/crypto/api.py
msgid "Subject-alt name"
msgstr "Nom alternatif subject"
@ -502,25 +468,9 @@ msgstr "Entreprise est requis pour accéder à cette fonctionnalité."
msgid "Feature only accessible for internal users."
msgstr "Fonctionnalité accessible aux utilisateurs internes uniquement."
#: authentik/enterprise/providers/google_workspace/models.py
msgid "Google Workspace Provider User"
msgstr "Utilisateur du fournisseur Google Workspace"
#: authentik/enterprise/providers/google_workspace/models.py
msgid "Google Workspace Provider Users"
msgstr "Utilisateurs du fournisseur Google Workspace"
#: authentik/enterprise/providers/google_workspace/models.py
msgid "Google Workspace Provider Group"
msgstr "Groupe du fournisseur Google Workspace"
#: authentik/enterprise/providers/google_workspace/models.py
msgid "Google Workspace Provider Groups"
msgstr "Groupes du fournisseur Google Workspace"
#: authentik/enterprise/providers/google_workspace/models.py
#: authentik/enterprise/providers/microsoft_entra/models.py
#: authentik/providers/scim/models.py
#: authentik/providers/scim/models.py authentik/sources/ldap/models.py
msgid "Property mappings used for group creation/updating."
msgstr ""
"Mappages de propriétés utilisés lors de la création et de la mise à jour des"
@ -542,17 +492,21 @@ msgstr "Mappage de propriété Google Workspace"
msgid "Google Workspace Provider Mappings"
msgstr "Mappages de propriété Google Workspace"
#: authentik/enterprise/providers/microsoft_entra/models.py
msgid "Microsoft Entra Provider User"
msgstr "Utilisateur du fournisseur Microsoft Entra"
#: authentik/enterprise/providers/google_workspace/models.py
msgid "Google Workspace Provider User"
msgstr "Utilisateur du fournisseur Google Workspace"
#: authentik/enterprise/providers/microsoft_entra/models.py
msgid "Microsoft Entra Provider Group"
msgstr "Groupe du fournisseur Microsoft Entra"
#: authentik/enterprise/providers/google_workspace/models.py
msgid "Google Workspace Provider Users"
msgstr "Utilisateurs du fournisseur Google Workspace"
#: authentik/enterprise/providers/microsoft_entra/models.py
msgid "Microsoft Entra Provider Groups"
msgstr "Groupes du fournisseur Microsoft Entra"
#: authentik/enterprise/providers/google_workspace/models.py
msgid "Google Workspace Provider Group"
msgstr "Groupe du fournisseur Google Workspace"
#: authentik/enterprise/providers/google_workspace/models.py
msgid "Google Workspace Provider Groups"
msgstr "Groupes du fournisseur Google Workspace"
#: authentik/enterprise/providers/microsoft_entra/models.py
msgid "Microsoft Entra Provider"
@ -570,6 +524,18 @@ msgstr "Mappage de propriété Microsoft Entra"
msgid "Microsoft Entra Provider Mappings"
msgstr "Mappages de propriété Microsoft Entra"
#: authentik/enterprise/providers/microsoft_entra/models.py
msgid "Microsoft Entra Provider User"
msgstr "Utilisateur du fournisseur Microsoft Entra"
#: authentik/enterprise/providers/microsoft_entra/models.py
msgid "Microsoft Entra Provider Group"
msgstr "Groupe du fournisseur Microsoft Entra"
#: authentik/enterprise/providers/microsoft_entra/models.py
msgid "Microsoft Entra Provider Groups"
msgstr "Groupes du fournisseur Microsoft Entra"
#: authentik/enterprise/providers/rac/models.py
#: authentik/stages/user_login/models.py
msgid ""
@ -602,12 +568,12 @@ msgid "RAC Endpoints"
msgstr "Points de terminaison RAC"
#: authentik/enterprise/providers/rac/models.py
msgid "RAC Provider Property Mapping"
msgstr "Mappage de propriété fournisseur RAC"
msgid "RAC Property Mapping"
msgstr "Mappage de propriété RAC"
#: authentik/enterprise/providers/rac/models.py
msgid "RAC Provider Property Mappings"
msgstr "Mappages de propriété fournisseur RAC"
msgid "RAC Property Mappings"
msgstr "Mappages de propriété RAC"
#: authentik/enterprise/providers/rac/models.py
msgid "RAC Connection token"
@ -1153,32 +1119,6 @@ msgstr "Politique d'Expression"
msgid "Expression Policies"
msgstr "Politiques d'expression"
#: authentik/policies/geoip/models.py
msgid "GeoIP: client IP not found in ASN database."
msgstr ""
"GeoIP : l'IP du client n'a pas été trouvée dans la base de données ASN."
#: authentik/policies/geoip/models.py
msgid "Client IP is not part of an allowed autonomous system."
msgstr "L'IP du client ne fait pas partie d'un autonomous system autorisé."
#: authentik/policies/geoip/models.py
msgid "GeoIP: client IP address not found in City database."
msgstr ""
"GeoIP : l'IP du client n'a pas été trouvée dans la base de données Ville."
#: authentik/policies/geoip/models.py
msgid "Client IP is not in an allowed country."
msgstr "L'IP du client ne fait pas partie d'un pays autorisé."
#: authentik/policies/geoip/models.py
msgid "GeoIP Policy"
msgstr "Politique GeoIP"
#: authentik/policies/geoip/models.py
msgid "GeoIP Policies"
msgstr "Politiques GeoIP"
#: authentik/policies/models.py
msgid "all, all policies must pass"
msgstr "toutes, toutes les politiques doivent réussir"
@ -1788,14 +1728,6 @@ msgstr "Fournisseur Radius"
msgid "Radius Providers"
msgstr "Fournisseurs Radius"
#: authentik/providers/radius/models.py
msgid "Radius Provider Property Mapping"
msgstr "Mappage de propriété fournisseur Radius"
#: authentik/providers/radius/models.py
msgid "Radius Provider Property Mappings"
msgstr "Mappages de propriété fournisseur Radius"
#: authentik/providers/saml/api/providers.py
msgid "Invalid XML Syntax"
msgstr "Syntaxe XML Invalide"
@ -1957,12 +1889,12 @@ msgid "SAML Providers"
msgstr "Fournisseurs SAML"
#: authentik/providers/saml/models.py
msgid "SAML Provider Property Mapping"
msgstr "Mappage de propriété fournisseur SAML"
msgid "SAML Property Mapping"
msgstr "Mappages de propriétés SAML"
#: authentik/providers/saml/models.py
msgid "SAML Provider Property Mappings"
msgstr "Mappages de propriété fournisseur SAML"
msgid "SAML Property Mappings"
msgstr "Mappages de propriétés SAML"
#: authentik/providers/saml/models.py
msgid "SAML Provider from Metadata"
@ -1989,12 +1921,12 @@ msgid "SCIM Providers"
msgstr "Fournisseurs SCIM"
#: authentik/providers/scim/models.py
msgid "SCIM Provider Mapping"
msgstr "Mappage fournisseur SCIM"
msgid "SCIM Mapping"
msgstr "Mappage SCIM"
#: authentik/providers/scim/models.py
msgid "SCIM Provider Mappings"
msgstr "Mappages fournisseur SCIM"
msgid "SCIM Mappings"
msgstr "Mappages SCIM"
#: authentik/rbac/models.py
msgid "Role"
@ -2129,12 +2061,12 @@ msgid "LDAP Sources"
msgstr "Sources LDAP"
#: authentik/sources/ldap/models.py
msgid "LDAP Source Property Mapping"
msgstr "Mappage de propriété source LDAP"
msgid "LDAP Property Mapping"
msgstr "Mappage de propriété LDAP"
#: authentik/sources/ldap/models.py
msgid "LDAP Source Property Mappings"
msgstr "Mappages de propriété source LDAP"
msgid "LDAP Property Mappings"
msgstr "Mappages de propriété LDAP"
#: authentik/sources/ldap/signals.py
msgid "Password does not match Active Directory Complexity."
@ -2305,14 +2237,6 @@ msgstr "Source d'OAuth Reddit"
msgid "Reddit OAuth Sources"
msgstr "Sources d'OAuth Reddit"
#: authentik/sources/oauth/models.py
msgid "OAuth Source Property Mapping"
msgstr "Mappage de propriété source OAuth"
#: authentik/sources/oauth/models.py
msgid "OAuth Source Property Mappings"
msgstr "Mappages de propriété source OAuth"
#: authentik/sources/oauth/models.py
msgid "User OAuth Source Connection"
msgstr "Connexion de l'utilisateur à la source OAuth"
@ -2321,14 +2245,6 @@ msgstr "Connexion de l'utilisateur à la source OAuth"
msgid "User OAuth Source Connections"
msgstr "Connexion de l'utilisateur aux sources OAuth"
#: authentik/sources/oauth/models.py
msgid "Group OAuth Source Connection"
msgstr "Connexion du groupe à la source OAuth"
#: authentik/sources/oauth/models.py
msgid "Group OAuth Source Connections"
msgstr "Connexions du groupe à la source OAuth"
#: authentik/sources/oauth/views/callback.py
#, python-brace-format
msgid "Authentication failed: {reason}"
@ -2363,14 +2279,6 @@ msgstr "Source Plex"
msgid "Plex Sources"
msgstr "Sources Plex"
#: authentik/sources/plex/models.py
msgid "Plex Source Property Mapping"
msgstr "Mappage de propriété source Plex"
#: authentik/sources/plex/models.py
msgid "Plex Source Property Mappings"
msgstr "Mappages de propriété source Plex"
#: authentik/sources/plex/models.py
msgid "User Plex Source Connection"
msgstr "Connexion de l'utilisateur à la source Plex"
@ -2379,14 +2287,6 @@ msgstr "Connexion de l'utilisateur à la source Plex"
msgid "User Plex Source Connections"
msgstr "Connexion de l'utilisateur aux sources Plex"
#: authentik/sources/plex/models.py
msgid "Group Plex Source Connection"
msgstr "Connexion du groupe à la source Plex"
#: authentik/sources/plex/models.py
msgid "Group Plex Source Connections"
msgstr "Connexions du groupe à la source OAuth"
#: authentik/sources/saml/models.py
msgid "Redirect Binding"
msgstr "Liaison de Redirection"
@ -2466,20 +2366,6 @@ msgstr ""
"Paire de clés utilisées pour signer les réponses sortantes allant vers le "
"fournisseur d'identité."
#: authentik/sources/saml/models.py
msgid ""
"When selected, incoming assertions are encrypted by the IdP using the public"
" key of the encryption keypair. The assertion is decrypted by the SP using "
"the the private key."
msgstr ""
"Si activé, les assertions entrantes seront chiffrées par l'IdP avec la clé "
"publique de la paire de clé de chiffrement. L'assertion est déchiffrée par "
"le SP en utilisant la clé privée."
#: authentik/sources/saml/models.py
msgid "Encryption Keypair"
msgstr "Paire de clés de chiffrement"
#: authentik/sources/saml/models.py
msgid "SAML Source"
msgstr "Source SAML"
@ -2488,14 +2374,6 @@ msgstr "Source SAML"
msgid "SAML Sources"
msgstr "Sources SAML"
#: authentik/sources/saml/models.py
msgid "SAML Source Property Mapping"
msgstr "Mappage de propriété source SAML"
#: authentik/sources/saml/models.py
msgid "SAML Source Property Mappings"
msgstr "Mappages de propriété source SAML"
#: authentik/sources/saml/models.py
msgid "User SAML Source Connection"
msgstr "Connexion de l'utilisateur à la source SAML"
@ -2504,14 +2382,6 @@ msgstr "Connexion de l'utilisateur à la source SAML"
msgid "User SAML Source Connections"
msgstr "Connexion de l'utilisateur aux sources SAML"
#: authentik/sources/saml/models.py
msgid "Group SAML Source Connection"
msgstr "Connexion du groupe à la source SAML"
#: authentik/sources/saml/models.py
msgid "Group SAML Source Connections"
msgstr "Connexions du groupe à la source SAML"
#: authentik/sources/scim/models.py
msgid "SCIM Source"
msgstr "Source SCIM"
@ -2520,14 +2390,6 @@ msgstr "Source SCIM"
msgid "SCIM Sources"
msgstr "Sources SCIM"
#: authentik/sources/scim/models.py
msgid "SCIM Source Property Mapping"
msgstr "Mappage de propriété source SCIM"
#: authentik/sources/scim/models.py
msgid "SCIM Source Property Mappings"
msgstr "Mappages de propriété source SCIM"
#: authentik/stages/authenticator_duo/models.py
msgid "Duo Authenticator Setup Stage"
msgstr "Étape de configuration du Duo Authenticator"
@ -3155,14 +3017,6 @@ msgstr ""
"annulé. Pour verrouiller l'utilisateur, utilisez une politique de réputation"
" et une étape user_write."
#: authentik/stages/password/models.py
msgid ""
"When enabled, provides a 'show password' button with the password input "
"field."
msgstr ""
"Si activé, fourni un bouton « Montrer le mot de passe » avec le champ "
"d'entrée mot de passe."
#: authentik/stages/password/models.py
msgid "Password Stage"
msgstr "Étape de mot de passe"

View File

@ -9,16 +9,16 @@
# Nicholas Winterhalter, 2023
# Ренат Шарафутдинов, 2023
# Stepan Karavaev, 2024
# Anton Babenko, 2024
# Anton, 2024
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-08-15 00:09+0000\n"
"POT-Creation-Date: 2024-06-16 00:08+0000\n"
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
"Last-Translator: Anton Babenko, 2024\n"
"Last-Translator: Anton, 2024\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"
@ -89,14 +89,6 @@ msgstr ""
"Домен, активирующий данный бренд. Может быть суперсетом, т.е. `a.b` для "
"`aa.b` и `ba.b`."
#: authentik/brands/models.py
msgid ""
"When set, external users will be redirected to this application after "
"authenticating."
msgstr ""
"Если этот параметр установлен, внешние пользователи будут перенаправляться в"
" это приложение после аутентификации."
#: authentik/brands/models.py
msgid "Web Certificate used by the authentik Core webserver."
msgstr "Web Certificate используемый для authentik Core webserver."
@ -266,20 +258,6 @@ msgstr ""
"Использовать имя пользователя, но отказывать в регистрации, если имя "
"пользователя уже существует."
#: authentik/core/models.py
msgid ""
"Link to a group with identical name. Can have security implications when a "
"group name is used with another source."
msgstr ""
"Связать с группой с идентичным именем. Может иметь последствия для "
"безопасности, если имя группы используется в другом источнике."
#: authentik/core/models.py
msgid "Use the group name, but deny enrollment when the name already exists."
msgstr ""
"Использовать имя группы, но отказывать в регистрации, если имя уже "
"существует."
#: authentik/core/models.py
msgid "Source's display Name."
msgstr "Отображаемое имя источника."
@ -304,14 +282,6 @@ msgstr ""
"Как источник определяет, следует ли аутентифицировать существующего "
"пользователя или зачислить нового."
#: authentik/core/models.py
msgid ""
"How the source determines if an existing group should be used or a new group"
" created."
msgstr ""
"Как источник определяет, следует ли использовать существующую группу или "
"создать новую."
#: authentik/core/models.py
msgid "Token"
msgstr "Токен"
@ -423,7 +393,6 @@ msgid "Go home"
msgstr "Домой"
#: authentik/core/templates/login/base_full.html
#: authentik/flows/templates/if/flow-sfe.html
msgid "Powered by authentik"
msgstr "Основано на authentik"
@ -434,10 +403,6 @@ msgstr "Основано на authentik"
msgid "You're about to sign into %(application)s."
msgstr "Вы собираетесь войти в %(application)s."
#: authentik/core/views/interface.py
msgid "Interface can only be accessed by internal users."
msgstr "Доступ к интерфейсу могут иметь только внутренние пользователи."
#: authentik/crypto/api.py
msgid "Subject-alt name"
msgstr "Альтернативное имя субъекта"
@ -516,7 +481,7 @@ msgstr "Группы провайдера Google Workspace"
#: authentik/enterprise/providers/google_workspace/models.py
#: authentik/enterprise/providers/microsoft_entra/models.py
#: authentik/providers/scim/models.py
#: authentik/providers/scim/models.py authentik/sources/ldap/models.py
msgid "Property mappings used for group creation/updating."
msgstr "Сопоставления свойств, используемые для создания/обновления групп."
@ -596,12 +561,12 @@ msgid "RAC Endpoints"
msgstr "Точки подключения RAC"
#: authentik/enterprise/providers/rac/models.py
msgid "RAC Provider Property Mapping"
msgstr "Сопоставление свойства RAC провайдера"
msgid "RAC Property Mapping"
msgstr "Сопоставление свойств RAC"
#: authentik/enterprise/providers/rac/models.py
msgid "RAC Provider Property Mappings"
msgstr "Сопоставление свойств RAC провайдера"
msgid "RAC Property Mappings"
msgstr "Сопоставления свойств RAC"
#: authentik/enterprise/providers/rac/models.py
msgid "RAC Connection token"
@ -1142,30 +1107,6 @@ msgstr "Политика выражения"
msgid "Expression Policies"
msgstr "Политики выражения"
#: authentik/policies/geoip/models.py
msgid "GeoIP: client IP not found in ASN database."
msgstr "GeoIP: IP-адрес клиента не найден в базе данных ASN."
#: authentik/policies/geoip/models.py
msgid "Client IP is not part of an allowed autonomous system."
msgstr "IP-адрес клиента не входит в разрешенную автономную систему."
#: authentik/policies/geoip/models.py
msgid "GeoIP: client IP address not found in City database."
msgstr "GeoIP: IP-адрес клиента не найден в базе данных городов."
#: authentik/policies/geoip/models.py
msgid "Client IP is not in an allowed country."
msgstr "IP-адрес клиента находится не в разрешенной стране."
#: authentik/policies/geoip/models.py
msgid "GeoIP Policy"
msgstr "Политика GeoIP"
#: authentik/policies/geoip/models.py
msgid "GeoIP Policies"
msgstr "Политики GeoIP"
#: authentik/policies/models.py
msgid "all, all policies must pass"
msgstr "все, все политики должны пройти"
@ -1326,6 +1267,14 @@ msgstr "Не удалось получить приложение"
msgid "DN under which objects are accessible."
msgstr "DN, под которым доступны объекты."
#: authentik/providers/ldap/models.py
msgid ""
"Users in this group can do search queries. If not set, every user can "
"execute search queries."
msgstr ""
"Пользователи этой группы могут выполнять поисковые запросы. Если не задано, "
"каждый пользователь может выполнять поисковые запросы."
#: authentik/providers/ldap/models.py
msgid ""
"The start for uidNumbers, this number is added to the user.pk to make sure "
@ -1371,10 +1320,6 @@ msgstr "LDAP Провайдер"
msgid "LDAP Providers"
msgstr "LDAP Провайдеры"
#: authentik/providers/ldap/models.py
msgid "Search full LDAP directory"
msgstr "Поиск по всему каталогу LDAP"
#: authentik/providers/oauth2/id_token.py
msgid "Based on the Hashed User ID"
msgstr "На основе хэшированного идентификатора пользователя"
@ -1763,14 +1708,6 @@ msgstr "Radius Провайдер"
msgid "Radius Providers"
msgstr "Radius Провайдеры"
#: authentik/providers/radius/models.py
msgid "Radius Provider Property Mapping"
msgstr "Сопоставление свойства Radius провайдера"
#: authentik/providers/radius/models.py
msgid "Radius Provider Property Mappings"
msgstr "Сопоставление свойств Radius провайдера"
#: authentik/providers/saml/api/providers.py
msgid "Invalid XML Syntax"
msgstr "Некорректный синтаксис XML"
@ -1931,12 +1868,12 @@ msgid "SAML Providers"
msgstr "SAML Провайдеры"
#: authentik/providers/saml/models.py
msgid "SAML Provider Property Mapping"
msgstr "Сопоставление свойства SAML провайдера"
msgid "SAML Property Mapping"
msgstr "Сопоставление свойств SAML"
#: authentik/providers/saml/models.py
msgid "SAML Provider Property Mappings"
msgstr "Сопоставление свойств SAML провайдера"
msgid "SAML Property Mappings"
msgstr "Сопоставления свойств SAML"
#: authentik/providers/saml/models.py
msgid "SAML Provider from Metadata"
@ -1963,12 +1900,12 @@ msgid "SCIM Providers"
msgstr "SCIM Провайдеры"
#: authentik/providers/scim/models.py
msgid "SCIM Provider Mapping"
msgstr "Сопоставление свойства SCIM"
msgid "SCIM Mapping"
msgstr "Сопоставление SCIM"
#: authentik/providers/scim/models.py
msgid "SCIM Provider Mappings"
msgstr "Сопоставления свойств SCIM"
msgid "SCIM Mappings"
msgstr "Сопоставления SCIM"
#: authentik/rbac/models.py
msgid "Role"
@ -2105,12 +2042,12 @@ msgid "LDAP Sources"
msgstr "Источники LDAP"
#: authentik/sources/ldap/models.py
msgid "LDAP Source Property Mapping"
msgstr "Сопоставление свойства LDAP источника"
msgid "LDAP Property Mapping"
msgstr "Сопоставление свойств LDAP"
#: authentik/sources/ldap/models.py
msgid "LDAP Source Property Mappings"
msgstr "Сопоставление свойств LDAP источника"
msgid "LDAP Property Mappings"
msgstr "Сопоставления свойств LDAP"
#: authentik/sources/ldap/signals.py
msgid "Password does not match Active Directory Complexity."
@ -2282,14 +2219,6 @@ msgstr "Источник Reddit OAuth"
msgid "Reddit OAuth Sources"
msgstr "Источники Reddit OAuth"
#: authentik/sources/oauth/models.py
msgid "OAuth Source Property Mapping"
msgstr "Сопоставление свойства OAuth источника"
#: authentik/sources/oauth/models.py
msgid "OAuth Source Property Mappings"
msgstr "Сопоставление свойств OAuth источника"
#: authentik/sources/oauth/models.py
msgid "User OAuth Source Connection"
msgstr "Пользовательское подключение к источнику OAuth"
@ -2298,14 +2227,6 @@ msgstr "Пользовательское подключение к источн
msgid "User OAuth Source Connections"
msgstr "Пользовательские подключения к источнику OAuth"
#: authentik/sources/oauth/models.py
msgid "Group OAuth Source Connection"
msgstr "Групповое подключение к источнику OAuth"
#: authentik/sources/oauth/models.py
msgid "Group OAuth Source Connections"
msgstr "Групповые подключения к источнику OAuth"
#: authentik/sources/oauth/views/callback.py
#, python-brace-format
msgid "Authentication failed: {reason}"
@ -2340,14 +2261,6 @@ msgstr "Источник Plex"
msgid "Plex Sources"
msgstr "Источники Plex"
#: authentik/sources/plex/models.py
msgid "Plex Source Property Mapping"
msgstr "Сопоставление свойства Plex источника"
#: authentik/sources/plex/models.py
msgid "Plex Source Property Mappings"
msgstr "Сопоставление свойств Plex источника"
#: authentik/sources/plex/models.py
msgid "User Plex Source Connection"
msgstr "Пользовательское подключение к источнику Plex"
@ -2356,14 +2269,6 @@ msgstr "Пользовательское подключение к источн
msgid "User Plex Source Connections"
msgstr "Пользовательские подключения к источнику Plex"
#: authentik/sources/plex/models.py
msgid "Group Plex Source Connection"
msgstr "Групповое подключение к источнику Plex"
#: authentik/sources/plex/models.py
msgid "Group Plex Source Connections"
msgstr "Групповые подключения к источнику Plex"
#: authentik/sources/saml/models.py
msgid "Redirect Binding"
msgstr "Привязка переадресации"
@ -2446,21 +2351,6 @@ msgstr ""
"Пара ключей, используемая для подписи исходящих ответов, направляемых "
"провайдеру идентификационных данных."
#: authentik/sources/saml/models.py
msgid ""
"When selected, incoming assertions are encrypted by the IdP using the public"
" key of the encryption keypair. The assertion is decrypted by the SP using "
"the the private key."
msgstr ""
"При выборе этого варианта, входящие утверждения шифруются поставщиком "
"идентификации (IdP) с использованием открытого ключа из пары ключей "
"шифрования. Утверждение расшифровывается поставщиком услуг (SP) с "
"использованием закрытого ключа."
#: authentik/sources/saml/models.py
msgid "Encryption Keypair"
msgstr "Пара ключей шифрования"
#: authentik/sources/saml/models.py
msgid "SAML Source"
msgstr "Источник SAML"
@ -2469,14 +2359,6 @@ msgstr "Источник SAML"
msgid "SAML Sources"
msgstr "Источники SAML"
#: authentik/sources/saml/models.py
msgid "SAML Source Property Mapping"
msgstr "Сопоставление свойства SAML источника"
#: authentik/sources/saml/models.py
msgid "SAML Source Property Mappings"
msgstr "Сопоставление свойств SAML источника"
#: authentik/sources/saml/models.py
msgid "User SAML Source Connection"
msgstr "Пользовательское подключение к источнику SAML"
@ -2485,14 +2367,6 @@ msgstr "Пользовательское подключение к источн
msgid "User SAML Source Connections"
msgstr "Пользовательские подключения к источнику SAML"
#: authentik/sources/saml/models.py
msgid "Group SAML Source Connection"
msgstr "Групповое подключение к источнику SAML"
#: authentik/sources/saml/models.py
msgid "Group SAML Source Connections"
msgstr "Групповые подключения к источнику SAML"
#: authentik/sources/scim/models.py
msgid "SCIM Source"
msgstr "Источник SCIM"
@ -2501,14 +2375,6 @@ msgstr "Источник SCIM"
msgid "SCIM Sources"
msgstr "Источники SCIM"
#: authentik/sources/scim/models.py
msgid "SCIM Source Property Mapping"
msgstr "Сопоставление свойства SCIM источника"
#: authentik/sources/scim/models.py
msgid "SCIM Source Property Mappings"
msgstr "Сопоставление свойств SCIM источника"
#: authentik/stages/authenticator_duo/models.py
msgid "Duo Authenticator Setup Stage"
msgstr "Этап настройки аутентификатора Duo"
@ -3142,14 +3008,6 @@ msgstr ""
"Количество попыток пользователя до отмены потока. Чтобы заблокировать "
"пользователя, используйте политику репутации и этап user_write."
#: authentik/stages/password/models.py
msgid ""
"When enabled, provides a 'show password' button with the password input "
"field."
msgstr ""
"Если эта функция включена, в поле ввода пароля отображается кнопка "
"\"показать пароль\"."
#: authentik/stages/password/models.py
msgid "Password Stage"
msgstr "Этап пароля"

View File

@ -15,7 +15,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-08-18 00:08+0000\n"
"POT-Creation-Date: 2024-06-05 00:07+0000\n"
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
"Last-Translator: deluxghost, 2024\n"
"Language-Team: Chinese Simplified (https://app.transifex.com/authentik/teams/119923/zh-Hans/)\n"
@ -84,12 +84,6 @@ msgid ""
"and `ba.b`"
msgstr "激活此品牌的域。可以是超集,即 `a.b` 可以同时表示 `aa.b` 和 `ba.b`"
#: authentik/brands/models.py
msgid ""
"When set, external users will be redirected to this application after "
"authenticating."
msgstr "设置时,外部用户在验证身份后会被重定向到此应用程序。"
#: authentik/brands/models.py
msgid "Web Certificate used by the authentik Core webserver."
msgstr "authentik 核心 Web 服务器使用的 Web 证书。"
@ -242,16 +236,6 @@ msgid ""
"exists."
msgstr "使用用户的用户名,但在用户名已存在时拒绝注册。"
#: authentik/core/models.py
msgid ""
"Link to a group with identical name. Can have security implications when a "
"group name is used with another source."
msgstr "链接到名称相同的组。当其他源使用相同组名时,可能会有安全隐患。"
#: authentik/core/models.py
msgid "Use the group name, but deny enrollment when the name already exists."
msgstr "使用组的名称,但在名称已存在时拒绝注册。"
#: authentik/core/models.py
msgid "Source's display Name."
msgstr "源的显示名称。"
@ -274,12 +258,6 @@ msgid ""
"new user enrolled."
msgstr "源怎样确定应该验证已有用户的身份还是注册新用户。"
#: authentik/core/models.py
msgid ""
"How the source determines if an existing group should be used or a new group"
" created."
msgstr "源怎样确定应该使用已有组的身份还是创建新组。"
#: authentik/core/models.py
msgid "Token"
msgstr "令牌"
@ -387,7 +365,6 @@ msgid "Go home"
msgstr "前往首页"
#: authentik/core/templates/login/base_full.html
#: authentik/flows/templates/if/flow-sfe.html
msgid "Powered by authentik"
msgstr "由 authentik 强力驱动"
@ -398,10 +375,6 @@ msgstr "由 authentik 强力驱动"
msgid "You're about to sign into %(application)s."
msgstr "您即将登录 %(application)s。"
#: authentik/core/views/interface.py
msgid "Interface can only be accessed by internal users."
msgstr "仅内部用户能访问此接口。"
#: authentik/crypto/api.py
msgid "Subject-alt name"
msgstr "替代名称"
@ -460,25 +433,9 @@ msgstr "访问此功能需要企业版。"
msgid "Feature only accessible for internal users."
msgstr "仅内部用户能访问此功能。"
#: authentik/enterprise/providers/google_workspace/models.py
msgid "Google Workspace Provider User"
msgstr "Google Workspace 提供程序用户"
#: authentik/enterprise/providers/google_workspace/models.py
msgid "Google Workspace Provider Users"
msgstr "Google Workspace 提供程序用户"
#: authentik/enterprise/providers/google_workspace/models.py
msgid "Google Workspace Provider Group"
msgstr "Google Workspace 提供程序组"
#: authentik/enterprise/providers/google_workspace/models.py
msgid "Google Workspace Provider Groups"
msgstr "Google Workspace 提供程序组"
#: authentik/enterprise/providers/google_workspace/models.py
#: authentik/enterprise/providers/microsoft_entra/models.py
#: authentik/providers/scim/models.py
#: authentik/providers/scim/models.py authentik/sources/ldap/models.py
msgid "Property mappings used for group creation/updating."
msgstr "用于创建/更新组的属性映射。"
@ -498,17 +455,21 @@ msgstr "Google Workspace 提供程序映射"
msgid "Google Workspace Provider Mappings"
msgstr "Google Workspace 提供程序映射"
#: authentik/enterprise/providers/microsoft_entra/models.py
msgid "Microsoft Entra Provider User"
msgstr "Microsoft Entra 提供程序用户"
#: authentik/enterprise/providers/google_workspace/models.py
msgid "Google Workspace Provider User"
msgstr "Google Workspace 提供程序用户"
#: authentik/enterprise/providers/microsoft_entra/models.py
msgid "Microsoft Entra Provider Group"
msgstr "Microsoft Entra 提供程序"
#: authentik/enterprise/providers/google_workspace/models.py
msgid "Google Workspace Provider Users"
msgstr "Google Workspace 提供程序用户"
#: authentik/enterprise/providers/microsoft_entra/models.py
msgid "Microsoft Entra Provider Groups"
msgstr "Microsoft Entra 提供程序组"
#: authentik/enterprise/providers/google_workspace/models.py
msgid "Google Workspace Provider Group"
msgstr "Google Workspace 提供程序组"
#: authentik/enterprise/providers/google_workspace/models.py
msgid "Google Workspace Provider Groups"
msgstr "Google Workspace 提供程序组"
#: authentik/enterprise/providers/microsoft_entra/models.py
msgid "Microsoft Entra Provider"
@ -526,6 +487,18 @@ msgstr "Microsoft Entra 提供程序映射"
msgid "Microsoft Entra Provider Mappings"
msgstr "Microsoft Entra 提供程序映射"
#: authentik/enterprise/providers/microsoft_entra/models.py
msgid "Microsoft Entra Provider User"
msgstr "Microsoft Entra 提供程序用户"
#: authentik/enterprise/providers/microsoft_entra/models.py
msgid "Microsoft Entra Provider Group"
msgstr "Microsoft Entra 提供程序组"
#: authentik/enterprise/providers/microsoft_entra/models.py
msgid "Microsoft Entra Provider Groups"
msgstr "Microsoft Entra 提供程序组"
#: authentik/enterprise/providers/rac/models.py
#: authentik/stages/user_login/models.py
msgid ""
@ -554,12 +527,12 @@ msgid "RAC Endpoints"
msgstr "RAC 端点"
#: authentik/enterprise/providers/rac/models.py
msgid "RAC Provider Property Mapping"
msgstr "RAC 提供程序属性映射"
msgid "RAC Property Mapping"
msgstr "RAC 属性映射"
#: authentik/enterprise/providers/rac/models.py
msgid "RAC Provider Property Mappings"
msgstr "RAC 提供程序属性映射"
msgid "RAC Property Mappings"
msgstr "RAC 属性映射"
#: authentik/enterprise/providers/rac/models.py
msgid "RAC Connection token"
@ -1058,30 +1031,6 @@ msgstr "表达式策略"
msgid "Expression Policies"
msgstr "表达式策略"
#: authentik/policies/geoip/models.py
msgid "GeoIP: client IP not found in ASN database."
msgstr "GeoIP无法在 ASN 数据库中找到客户端 IP。"
#: authentik/policies/geoip/models.py
msgid "Client IP is not part of an allowed autonomous system."
msgstr "客户端 IP 不属于受允许的自治系统AS。"
#: authentik/policies/geoip/models.py
msgid "GeoIP: client IP address not found in City database."
msgstr "GeoIP无法在城市数据库中找到客户端 IP。"
#: authentik/policies/geoip/models.py
msgid "Client IP is not in an allowed country."
msgstr "客户端 IP 不在受允许的地区。"
#: authentik/policies/geoip/models.py
msgid "GeoIP Policy"
msgstr "GeoIP 策略"
#: authentik/policies/geoip/models.py
msgid "GeoIP Policies"
msgstr "GeoIP 策略"
#: authentik/policies/models.py
msgid "all, all policies must pass"
msgstr "All必须通过所有策略"
@ -1237,6 +1186,12 @@ msgstr "解析应用程序失败"
msgid "DN under which objects are accessible."
msgstr "可访问对象的 DN。"
#: authentik/providers/ldap/models.py
msgid ""
"Users in this group can do search queries. If not set, every user can "
"execute search queries."
msgstr "该组中的用户可以执行搜索查询。如果未设置,则每个用户都可以执行搜索查询。"
#: authentik/providers/ldap/models.py
msgid ""
"The start for uidNumbers, this number is added to the user.pk to make sure "
@ -1275,10 +1230,6 @@ msgstr "LDAP 提供程序"
msgid "LDAP Providers"
msgstr "LDAP 提供程序"
#: authentik/providers/ldap/models.py
msgid "Search full LDAP directory"
msgstr "搜索完整 LDAP 目录"
#: authentik/providers/oauth2/id_token.py
msgid "Based on the Hashed User ID"
msgstr "基于经过哈希处理的用户 ID"
@ -1625,20 +1576,6 @@ msgstr "Radius 提供程序"
msgid "Radius Providers"
msgstr "Radius 提供程序"
#: authentik/providers/radius/models.py
msgid "Radius Provider Property Mapping"
msgstr "Radius 提供程序属性映射"
#: authentik/providers/radius/models.py
msgid "Radius Provider Property Mappings"
msgstr "Radius 提供程序属性映射"
#: authentik/providers/saml/api/providers.py
msgid ""
"With a signing keypair selected, at least one of 'Sign assertion' and 'Sign "
"Response' must be selected."
msgstr "选择签名密钥对后,必须至少选择“签名断言”和“签名响应”之一。"
#: authentik/providers/saml/api/providers.py
msgid "Invalid XML Syntax"
msgstr "无效 XML 语法"
@ -1770,17 +1707,6 @@ msgstr "密钥对,用于签署发送给服务提供程序的传出响应。"
msgid "Signing Keypair"
msgstr "签名密钥对"
#: authentik/providers/saml/models.py authentik/sources/saml/models.py
msgid ""
"When selected, incoming assertions are encrypted by the IdP using the public"
" key of the encryption keypair. The assertion is decrypted by the SP using "
"the the private key."
msgstr "选择此选项后,传入的断言将由 IdP 使用加密密钥对的公钥进行加密。 SP 会使用私钥解密该断言。"
#: authentik/providers/saml/models.py authentik/sources/saml/models.py
msgid "Encryption Keypair"
msgstr "加密密钥对"
#: authentik/providers/saml/models.py
msgid "Default relay_state value for IDP-initiated logins"
msgstr "用于 IDP 发起登录的默认 relay_state 值"
@ -1794,12 +1720,12 @@ msgid "SAML Providers"
msgstr "SAML 提供程序"
#: authentik/providers/saml/models.py
msgid "SAML Provider Property Mapping"
msgstr "SAML 提供程序属性映射"
msgid "SAML Property Mapping"
msgstr "SAML 属性映射"
#: authentik/providers/saml/models.py
msgid "SAML Provider Property Mappings"
msgstr "SAML 提供程序属性映射"
msgid "SAML Property Mappings"
msgstr "SAML 属性映射"
#: authentik/providers/saml/models.py
msgid "SAML Provider from Metadata"
@ -1826,12 +1752,12 @@ msgid "SCIM Providers"
msgstr "SCIM 提供程序"
#: authentik/providers/scim/models.py
msgid "SCIM Provider Mapping"
msgstr "SCIM 提供程序映射"
msgid "SCIM Mapping"
msgstr "SCIM 映射"
#: authentik/providers/scim/models.py
msgid "SCIM Provider Mappings"
msgstr "SCIM 提供程序映射"
msgid "SCIM Mappings"
msgstr "SCIM 映射"
#: authentik/rbac/models.py
msgid "Role"
@ -1959,12 +1885,12 @@ msgid "LDAP Sources"
msgstr "LDAP 源"
#: authentik/sources/ldap/models.py
msgid "LDAP Source Property Mapping"
msgstr "LDAP 属性映射"
msgid "LDAP Property Mapping"
msgstr "LDAP 属性映射"
#: authentik/sources/ldap/models.py
msgid "LDAP Source Property Mappings"
msgstr "LDAP 属性映射"
msgid "LDAP Property Mappings"
msgstr "LDAP 属性映射"
#: authentik/sources/ldap/signals.py
msgid "Password does not match Active Directory Complexity."
@ -2132,14 +2058,6 @@ msgstr "Reddit OAuth 源"
msgid "Reddit OAuth Sources"
msgstr "Reddit OAuth 源"
#: authentik/sources/oauth/models.py
msgid "OAuth Source Property Mapping"
msgstr "OAuth 源属性映射"
#: authentik/sources/oauth/models.py
msgid "OAuth Source Property Mappings"
msgstr "OAuth 源属性映射"
#: authentik/sources/oauth/models.py
msgid "User OAuth Source Connection"
msgstr "用户 OAuth 源连接"
@ -2148,14 +2066,6 @@ msgstr "用户 OAuth 源连接"
msgid "User OAuth Source Connections"
msgstr "用户 OAuth 源连接"
#: authentik/sources/oauth/models.py
msgid "Group OAuth Source Connection"
msgstr "组 OAuth 源连接"
#: authentik/sources/oauth/models.py
msgid "Group OAuth Source Connections"
msgstr "组 OAuth 源连接"
#: authentik/sources/oauth/views/callback.py
#, python-brace-format
msgid "Authentication failed: {reason}"
@ -2187,14 +2097,6 @@ msgstr "Plex 源"
msgid "Plex Sources"
msgstr "Plex 源"
#: authentik/sources/plex/models.py
msgid "Plex Source Property Mapping"
msgstr "Plex 源属性映射"
#: authentik/sources/plex/models.py
msgid "Plex Source Property Mappings"
msgstr "Plex 源属性映射"
#: authentik/sources/plex/models.py
msgid "User Plex Source Connection"
msgstr "用户 Plex 源连接"
@ -2203,14 +2105,6 @@ msgstr "用户 Plex 源连接"
msgid "User Plex Source Connections"
msgstr "用户 Plex 源连接"
#: authentik/sources/plex/models.py
msgid "Group Plex Source Connection"
msgstr "组 Plex 源连接"
#: authentik/sources/plex/models.py
msgid "Group Plex Source Connections"
msgstr "组 Plex 源连接"
#: authentik/sources/saml/models.py
msgid "Redirect Binding"
msgstr "重定向绑定"
@ -2289,14 +2183,6 @@ msgstr "SAML 源"
msgid "SAML Sources"
msgstr "SAML 源"
#: authentik/sources/saml/models.py
msgid "SAML Source Property Mapping"
msgstr "SAML 源属性映射"
#: authentik/sources/saml/models.py
msgid "SAML Source Property Mappings"
msgstr "SAML 源属性映射"
#: authentik/sources/saml/models.py
msgid "User SAML Source Connection"
msgstr "用户 SAML 源连接"
@ -2305,14 +2191,6 @@ msgstr "用户 SAML 源连接"
msgid "User SAML Source Connections"
msgstr "用户 SAML 源连接"
#: authentik/sources/saml/models.py
msgid "Group SAML Source Connection"
msgstr "组 SAML 源连接"
#: authentik/sources/saml/models.py
msgid "Group SAML Source Connections"
msgstr "组 SAML 源连接"
#: authentik/sources/scim/models.py
msgid "SCIM Source"
msgstr "SCIM 源"
@ -2321,14 +2199,6 @@ msgstr "SCIM 源"
msgid "SCIM Sources"
msgstr "SCIM 源"
#: authentik/sources/scim/models.py
msgid "SCIM Source Property Mapping"
msgstr "SCIM 源属性映射"
#: authentik/sources/scim/models.py
msgid "SCIM Source Property Mappings"
msgstr "SCIM 源属性映射"
#: authentik/stages/authenticator_duo/models.py
msgid "Duo Authenticator Setup Stage"
msgstr "Duo 身份验证器设置阶段"
@ -2911,12 +2781,6 @@ msgid ""
"out, use a reputation policy and a user_write stage."
msgstr "在取消流程之前,用户可以尝试多少次。要锁定用户,请使用信誉策略和 user_write 阶段。"
#: authentik/stages/password/models.py
msgid ""
"When enabled, provides a 'show password' button with the password input "
"field."
msgstr "启用时,在密码输入字段中提供“显示密码”按钮。"
#: authentik/stages/password/models.py
msgid "Password Stage"
msgstr "密码阶段"

View File

@ -14,7 +14,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-08-18 00:08+0000\n"
"POT-Creation-Date: 2024-06-05 00:07+0000\n"
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
"Last-Translator: deluxghost, 2024\n"
"Language-Team: Chinese (China) (https://app.transifex.com/authentik/teams/119923/zh_CN/)\n"
@ -83,12 +83,6 @@ msgid ""
"and `ba.b`"
msgstr "激活此品牌的域。可以是超集,即 `a.b` 可以同时表示 `aa.b` 和 `ba.b`"
#: authentik/brands/models.py
msgid ""
"When set, external users will be redirected to this application after "
"authenticating."
msgstr "设置时,外部用户在验证身份后会被重定向到此应用程序。"
#: authentik/brands/models.py
msgid "Web Certificate used by the authentik Core webserver."
msgstr "authentik 核心 Web 服务器使用的 Web 证书。"
@ -241,16 +235,6 @@ msgid ""
"exists."
msgstr "使用用户的用户名,但在用户名已存在时拒绝注册。"
#: authentik/core/models.py
msgid ""
"Link to a group with identical name. Can have security implications when a "
"group name is used with another source."
msgstr "链接到名称相同的组。当其他源使用相同组名时,可能会有安全隐患。"
#: authentik/core/models.py
msgid "Use the group name, but deny enrollment when the name already exists."
msgstr "使用组的名称,但在名称已存在时拒绝注册。"
#: authentik/core/models.py
msgid "Source's display Name."
msgstr "源的显示名称。"
@ -273,12 +257,6 @@ msgid ""
"new user enrolled."
msgstr "源怎样确定应该验证已有用户的身份还是注册新用户。"
#: authentik/core/models.py
msgid ""
"How the source determines if an existing group should be used or a new group"
" created."
msgstr "源怎样确定应该使用已有组的身份还是创建新组。"
#: authentik/core/models.py
msgid "Token"
msgstr "令牌"
@ -386,7 +364,6 @@ msgid "Go home"
msgstr "前往首页"
#: authentik/core/templates/login/base_full.html
#: authentik/flows/templates/if/flow-sfe.html
msgid "Powered by authentik"
msgstr "由 authentik 强力驱动"
@ -397,10 +374,6 @@ msgstr "由 authentik 强力驱动"
msgid "You're about to sign into %(application)s."
msgstr "您即将登录 %(application)s。"
#: authentik/core/views/interface.py
msgid "Interface can only be accessed by internal users."
msgstr "仅内部用户能访问此接口。"
#: authentik/crypto/api.py
msgid "Subject-alt name"
msgstr "替代名称"
@ -459,25 +432,9 @@ msgstr "访问此功能需要企业版。"
msgid "Feature only accessible for internal users."
msgstr "仅内部用户能访问此功能。"
#: authentik/enterprise/providers/google_workspace/models.py
msgid "Google Workspace Provider User"
msgstr "Google Workspace 提供程序用户"
#: authentik/enterprise/providers/google_workspace/models.py
msgid "Google Workspace Provider Users"
msgstr "Google Workspace 提供程序用户"
#: authentik/enterprise/providers/google_workspace/models.py
msgid "Google Workspace Provider Group"
msgstr "Google Workspace 提供程序组"
#: authentik/enterprise/providers/google_workspace/models.py
msgid "Google Workspace Provider Groups"
msgstr "Google Workspace 提供程序组"
#: authentik/enterprise/providers/google_workspace/models.py
#: authentik/enterprise/providers/microsoft_entra/models.py
#: authentik/providers/scim/models.py
#: authentik/providers/scim/models.py authentik/sources/ldap/models.py
msgid "Property mappings used for group creation/updating."
msgstr "用于创建/更新组的属性映射。"
@ -497,17 +454,21 @@ msgstr "Google Workspace 提供程序映射"
msgid "Google Workspace Provider Mappings"
msgstr "Google Workspace 提供程序映射"
#: authentik/enterprise/providers/microsoft_entra/models.py
msgid "Microsoft Entra Provider User"
msgstr "Microsoft Entra 提供程序用户"
#: authentik/enterprise/providers/google_workspace/models.py
msgid "Google Workspace Provider User"
msgstr "Google Workspace 提供程序用户"
#: authentik/enterprise/providers/microsoft_entra/models.py
msgid "Microsoft Entra Provider Group"
msgstr "Microsoft Entra 提供程序"
#: authentik/enterprise/providers/google_workspace/models.py
msgid "Google Workspace Provider Users"
msgstr "Google Workspace 提供程序用户"
#: authentik/enterprise/providers/microsoft_entra/models.py
msgid "Microsoft Entra Provider Groups"
msgstr "Microsoft Entra 提供程序组"
#: authentik/enterprise/providers/google_workspace/models.py
msgid "Google Workspace Provider Group"
msgstr "Google Workspace 提供程序组"
#: authentik/enterprise/providers/google_workspace/models.py
msgid "Google Workspace Provider Groups"
msgstr "Google Workspace 提供程序组"
#: authentik/enterprise/providers/microsoft_entra/models.py
msgid "Microsoft Entra Provider"
@ -525,6 +486,18 @@ msgstr "Microsoft Entra 提供程序映射"
msgid "Microsoft Entra Provider Mappings"
msgstr "Microsoft Entra 提供程序映射"
#: authentik/enterprise/providers/microsoft_entra/models.py
msgid "Microsoft Entra Provider User"
msgstr "Microsoft Entra 提供程序用户"
#: authentik/enterprise/providers/microsoft_entra/models.py
msgid "Microsoft Entra Provider Group"
msgstr "Microsoft Entra 提供程序组"
#: authentik/enterprise/providers/microsoft_entra/models.py
msgid "Microsoft Entra Provider Groups"
msgstr "Microsoft Entra 提供程序组"
#: authentik/enterprise/providers/rac/models.py
#: authentik/stages/user_login/models.py
msgid ""
@ -553,12 +526,12 @@ msgid "RAC Endpoints"
msgstr "RAC 端点"
#: authentik/enterprise/providers/rac/models.py
msgid "RAC Provider Property Mapping"
msgstr "RAC 提供程序属性映射"
msgid "RAC Property Mapping"
msgstr "RAC 属性映射"
#: authentik/enterprise/providers/rac/models.py
msgid "RAC Provider Property Mappings"
msgstr "RAC 提供程序属性映射"
msgid "RAC Property Mappings"
msgstr "RAC 属性映射"
#: authentik/enterprise/providers/rac/models.py
msgid "RAC Connection token"
@ -1057,30 +1030,6 @@ msgstr "表达式策略"
msgid "Expression Policies"
msgstr "表达式策略"
#: authentik/policies/geoip/models.py
msgid "GeoIP: client IP not found in ASN database."
msgstr "GeoIP无法在 ASN 数据库中找到客户端 IP。"
#: authentik/policies/geoip/models.py
msgid "Client IP is not part of an allowed autonomous system."
msgstr "客户端 IP 不属于受允许的自治系统AS。"
#: authentik/policies/geoip/models.py
msgid "GeoIP: client IP address not found in City database."
msgstr "GeoIP无法在城市数据库中找到客户端 IP。"
#: authentik/policies/geoip/models.py
msgid "Client IP is not in an allowed country."
msgstr "客户端 IP 不在受允许的地区。"
#: authentik/policies/geoip/models.py
msgid "GeoIP Policy"
msgstr "GeoIP 策略"
#: authentik/policies/geoip/models.py
msgid "GeoIP Policies"
msgstr "GeoIP 策略"
#: authentik/policies/models.py
msgid "all, all policies must pass"
msgstr "All必须通过所有策略"
@ -1236,6 +1185,12 @@ msgstr "解析应用程序失败"
msgid "DN under which objects are accessible."
msgstr "可访问对象的 DN。"
#: authentik/providers/ldap/models.py
msgid ""
"Users in this group can do search queries. If not set, every user can "
"execute search queries."
msgstr "该组中的用户可以执行搜索查询。如果未设置,则每个用户都可以执行搜索查询。"
#: authentik/providers/ldap/models.py
msgid ""
"The start for uidNumbers, this number is added to the user.pk to make sure "
@ -1274,10 +1229,6 @@ msgstr "LDAP 提供程序"
msgid "LDAP Providers"
msgstr "LDAP 提供程序"
#: authentik/providers/ldap/models.py
msgid "Search full LDAP directory"
msgstr "搜索完整 LDAP 目录"
#: authentik/providers/oauth2/id_token.py
msgid "Based on the Hashed User ID"
msgstr "基于经过哈希处理的用户 ID"
@ -1624,20 +1575,6 @@ msgstr "Radius 提供程序"
msgid "Radius Providers"
msgstr "Radius 提供程序"
#: authentik/providers/radius/models.py
msgid "Radius Provider Property Mapping"
msgstr "Radius 提供程序属性映射"
#: authentik/providers/radius/models.py
msgid "Radius Provider Property Mappings"
msgstr "Radius 提供程序属性映射"
#: authentik/providers/saml/api/providers.py
msgid ""
"With a signing keypair selected, at least one of 'Sign assertion' and 'Sign "
"Response' must be selected."
msgstr "选择签名密钥对后,必须至少选择“签名断言”和“签名响应”之一。"
#: authentik/providers/saml/api/providers.py
msgid "Invalid XML Syntax"
msgstr "无效 XML 语法"
@ -1769,17 +1706,6 @@ msgstr "密钥对,用于签署发送给服务提供程序的传出响应。"
msgid "Signing Keypair"
msgstr "签名密钥对"
#: authentik/providers/saml/models.py authentik/sources/saml/models.py
msgid ""
"When selected, incoming assertions are encrypted by the IdP using the public"
" key of the encryption keypair. The assertion is decrypted by the SP using "
"the the private key."
msgstr "选择此选项后,传入的断言将由 IdP 使用加密密钥对的公钥进行加密。 SP 会使用私钥解密该断言。"
#: authentik/providers/saml/models.py authentik/sources/saml/models.py
msgid "Encryption Keypair"
msgstr "加密密钥对"
#: authentik/providers/saml/models.py
msgid "Default relay_state value for IDP-initiated logins"
msgstr "用于 IDP 发起登录的默认 relay_state 值"
@ -1793,12 +1719,12 @@ msgid "SAML Providers"
msgstr "SAML 提供程序"
#: authentik/providers/saml/models.py
msgid "SAML Provider Property Mapping"
msgstr "SAML 提供程序属性映射"
msgid "SAML Property Mapping"
msgstr "SAML 属性映射"
#: authentik/providers/saml/models.py
msgid "SAML Provider Property Mappings"
msgstr "SAML 提供程序属性映射"
msgid "SAML Property Mappings"
msgstr "SAML 属性映射"
#: authentik/providers/saml/models.py
msgid "SAML Provider from Metadata"
@ -1825,12 +1751,12 @@ msgid "SCIM Providers"
msgstr "SCIM 提供程序"
#: authentik/providers/scim/models.py
msgid "SCIM Provider Mapping"
msgstr "SCIM 提供程序映射"
msgid "SCIM Mapping"
msgstr "SCIM 映射"
#: authentik/providers/scim/models.py
msgid "SCIM Provider Mappings"
msgstr "SCIM 提供程序映射"
msgid "SCIM Mappings"
msgstr "SCIM 映射"
#: authentik/rbac/models.py
msgid "Role"
@ -1958,12 +1884,12 @@ msgid "LDAP Sources"
msgstr "LDAP 源"
#: authentik/sources/ldap/models.py
msgid "LDAP Source Property Mapping"
msgstr "LDAP 属性映射"
msgid "LDAP Property Mapping"
msgstr "LDAP 属性映射"
#: authentik/sources/ldap/models.py
msgid "LDAP Source Property Mappings"
msgstr "LDAP 属性映射"
msgid "LDAP Property Mappings"
msgstr "LDAP 属性映射"
#: authentik/sources/ldap/signals.py
msgid "Password does not match Active Directory Complexity."
@ -2131,14 +2057,6 @@ msgstr "Reddit OAuth 源"
msgid "Reddit OAuth Sources"
msgstr "Reddit OAuth 源"
#: authentik/sources/oauth/models.py
msgid "OAuth Source Property Mapping"
msgstr "OAuth 源属性映射"
#: authentik/sources/oauth/models.py
msgid "OAuth Source Property Mappings"
msgstr "OAuth 源属性映射"
#: authentik/sources/oauth/models.py
msgid "User OAuth Source Connection"
msgstr "用户 OAuth 源连接"
@ -2147,14 +2065,6 @@ msgstr "用户 OAuth 源连接"
msgid "User OAuth Source Connections"
msgstr "用户 OAuth 源连接"
#: authentik/sources/oauth/models.py
msgid "Group OAuth Source Connection"
msgstr "组 OAuth 源连接"
#: authentik/sources/oauth/models.py
msgid "Group OAuth Source Connections"
msgstr "组 OAuth 源连接"
#: authentik/sources/oauth/views/callback.py
#, python-brace-format
msgid "Authentication failed: {reason}"
@ -2186,14 +2096,6 @@ msgstr "Plex 源"
msgid "Plex Sources"
msgstr "Plex 源"
#: authentik/sources/plex/models.py
msgid "Plex Source Property Mapping"
msgstr "Plex 源属性映射"
#: authentik/sources/plex/models.py
msgid "Plex Source Property Mappings"
msgstr "Plex 源属性映射"
#: authentik/sources/plex/models.py
msgid "User Plex Source Connection"
msgstr "用户 Plex 源连接"
@ -2202,14 +2104,6 @@ msgstr "用户 Plex 源连接"
msgid "User Plex Source Connections"
msgstr "用户 Plex 源连接"
#: authentik/sources/plex/models.py
msgid "Group Plex Source Connection"
msgstr "组 Plex 源连接"
#: authentik/sources/plex/models.py
msgid "Group Plex Source Connections"
msgstr "组 Plex 源连接"
#: authentik/sources/saml/models.py
msgid "Redirect Binding"
msgstr "重定向绑定"
@ -2288,14 +2182,6 @@ msgstr "SAML 源"
msgid "SAML Sources"
msgstr "SAML 源"
#: authentik/sources/saml/models.py
msgid "SAML Source Property Mapping"
msgstr "SAML 源属性映射"
#: authentik/sources/saml/models.py
msgid "SAML Source Property Mappings"
msgstr "SAML 源属性映射"
#: authentik/sources/saml/models.py
msgid "User SAML Source Connection"
msgstr "用户 SAML 源连接"
@ -2304,14 +2190,6 @@ msgstr "用户 SAML 源连接"
msgid "User SAML Source Connections"
msgstr "用户 SAML 源连接"
#: authentik/sources/saml/models.py
msgid "Group SAML Source Connection"
msgstr "组 SAML 源连接"
#: authentik/sources/saml/models.py
msgid "Group SAML Source Connections"
msgstr "组 SAML 源连接"
#: authentik/sources/scim/models.py
msgid "SCIM Source"
msgstr "SCIM 源"
@ -2320,14 +2198,6 @@ msgstr "SCIM 源"
msgid "SCIM Sources"
msgstr "SCIM 源"
#: authentik/sources/scim/models.py
msgid "SCIM Source Property Mapping"
msgstr "SCIM 源属性映射"
#: authentik/sources/scim/models.py
msgid "SCIM Source Property Mappings"
msgstr "SCIM 源属性映射"
#: authentik/stages/authenticator_duo/models.py
msgid "Duo Authenticator Setup Stage"
msgstr "Duo 身份验证器设置阶段"
@ -2910,12 +2780,6 @@ msgid ""
"out, use a reputation policy and a user_write stage."
msgstr "在取消流程之前,用户可以尝试多少次。要锁定用户,请使用信誉策略和 user_write 阶段。"
#: authentik/stages/password/models.py
msgid ""
"When enabled, provides a 'show password' button with the password input "
"field."
msgstr "启用时,在密码输入字段中提供“显示密码”按钮。"
#: authentik/stages/password/models.py
msgid "Password Stage"
msgstr "密码阶段"

View File

@ -1,5 +1,5 @@
{
"name": "@goauthentik/authentik",
"version": "2024.8.0",
"version": "2024.6.3",
"private": true
}

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