Compare commits
177 Commits
npm-worksp
...
version/20
Author | SHA1 | Date | |
---|---|---|---|
b6b47b669e | |||
8422568c42 | |||
9973064f50 | |||
4fea65f5cc | |||
784446a47d | |||
516bc65fc4 | |||
efb59adeff | |||
43a2ad66f0 | |||
ec0c59f1fc | |||
8f80072321 | |||
33f95c837b | |||
43637b8a75 | |||
7a4518be26 | |||
b94fb53821 | |||
2be5c9633b | |||
e729e42595 | |||
01d591b84e | |||
dd08e1bf66 | |||
150705f221 | |||
6b39f6495e | |||
639c57245b | |||
730600aea4 | |||
e15ce5a3f0 | |||
1fc91b004b | |||
644705e6fe | |||
ff8ef523db | |||
1051dd19ea | |||
04cb4fd267 | |||
da9508f839 | |||
841a286a25 | |||
63c48d7b99 | |||
5994fd2c61 | |||
5f745e682e | |||
6f1b16e7f9 | |||
57bce19e7a | |||
850c5d5a45 | |||
8b7d11f94c | |||
45737909f6 | |||
4c5fe84f92 | |||
5faa224c81 | |||
736da3abef | |||
52d90f8d3b | |||
7b812de977 | |||
a4bd2cc263 | |||
14038ba8d2 | |||
eaff59b6b0 | |||
cb702ca07a | |||
cb0bfb0dad | |||
bf46d5c916 | |||
59e686c8b9 | |||
9e736f2838 | |||
c2dd3d9c1b | |||
42302d3187 | |||
20ccabf3ec | |||
8f939fa577 | |||
2519bcef89 | |||
3e3615a859 | |||
79e82c8dc9 | |||
ccd4432e1f | |||
b3137f5307 | |||
2591ed9840 | |||
b3e89ef570 | |||
45b48c5cd6 | |||
1eefd834fc | |||
4cc6ed97c5 | |||
bb55d9b3de | |||
3972afb865 | |||
04a013cc1b | |||
fb396f7737 | |||
cf120ff3ff | |||
3e4923d52e | |||
01793088f0 | |||
e2bf2ec2cc | |||
4dfbe28709 | |||
b2021a7191 | |||
81e5fb0c18 | |||
a2a2d940a8 | |||
c034930219 | |||
da3dc51d87 | |||
d217a39513 | |||
7729a9317c | |||
be5f5dd3f0 | |||
bed8d5da4b | |||
4f70f84e80 | |||
97b8551866 | |||
9a0b67e700 | |||
97e4c89cec | |||
65aedde8f7 | |||
17450f23bf | |||
ab3ad6b7fd | |||
45bc3cbd41 | |||
9c1bcac6af | |||
0a133265c5 | |||
57f25a97c9 | |||
8f32242787 | |||
c4bb19051d | |||
10f4fae711 | |||
2d9eab3f60 | |||
fa66195619 | |||
134eb126b6 | |||
f5a6136a58 | |||
1a82dfcd61 | |||
61fc1dc1fb | |||
1f921cc18e | |||
2f94ee3f1f | |||
154fba12e0 | |||
0d18c1d797 | |||
e905dd52d8 | |||
245126a1c3 | |||
15d84d30ba | |||
c6333f9e19 | |||
56565b0895 | |||
cbbc7c1825 | |||
908aaa5afa | |||
937342eab1 | |||
82823a7449 | |||
ad50f14a3e | |||
e0cf6128df | |||
bfbe8b8038 | |||
36ba8bc4e7 | |||
dd5edf7fd9 | |||
da1b252f3b | |||
a8e543972a | |||
6e03045d1f | |||
f4b39e7465 | |||
e7cd5880b5 | |||
d8c6a2417d | |||
a1fe471a59 | |||
054dfda73f | |||
2e5e8f5c58 | |||
c28b65a3f2 | |||
afc9847e36 | |||
620c95dfa1 | |||
15c7a0a9be | |||
5fed8ca575 | |||
f471ddfb29 | |||
1b1f06c9f7 | |||
67c31a8ac3 | |||
638180d246 | |||
a3be1bbb57 | |||
fbd0ba2865 | |||
182ad912cb | |||
e850b2ba1a | |||
b4ce7f9ab0 | |||
5f0bd6f5ea | |||
f5944ccb95 | |||
9bd3bad605 | |||
dff60ee9fb | |||
4e932e47c9 | |||
e57a98aeb5 | |||
807ea2a52a | |||
0775bc0f1e | |||
35a4d9cc71 | |||
ed9008a7d4 | |||
a377ce6b45 | |||
dac24ba62d | |||
826acbde2a | |||
b7d97da2bc | |||
cc6fcd831d | |||
e5e3a5df80 | |||
74268500b0 | |||
614740a4ff | |||
f48496b2cf | |||
35da3d65d2 | |||
fb53fe2b3e | |||
dda2338258 | |||
f582e66c67 | |||
f595375f2d | |||
fd8317de7f | |||
2f1eab5aed | |||
70460bfb30 | |||
0be9c60a71 | |||
abaf8d9544 | |||
73a3f29001 | |||
159bf4012e | |||
9b3c1b5cff | |||
19aa268e4e |
@ -1,5 +1,5 @@
|
|||||||
[bumpversion]
|
[bumpversion]
|
||||||
current_version = 2025.4.1
|
current_version = 2025.6.2
|
||||||
tag = True
|
tag = True
|
||||||
commit = 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*))?
|
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(?:-(?P<rc_t>[a-zA-Z-]+)(?P<rc_n>[1-9]\\d*))?
|
||||||
@ -21,6 +21,8 @@ optional_value = final
|
|||||||
|
|
||||||
[bumpversion:file:package.json]
|
[bumpversion:file:package.json]
|
||||||
|
|
||||||
|
[bumpversion:file:package-lock.json]
|
||||||
|
|
||||||
[bumpversion:file:docker-compose.yml]
|
[bumpversion:file:docker-compose.yml]
|
||||||
|
|
||||||
[bumpversion:file:schema.yml]
|
[bumpversion:file:schema.yml]
|
||||||
@ -31,6 +33,4 @@ optional_value = final
|
|||||||
|
|
||||||
[bumpversion:file:internal/constants/constants.go]
|
[bumpversion:file:internal/constants/constants.go]
|
||||||
|
|
||||||
[bumpversion:file:web/src/common/constants.ts]
|
|
||||||
|
|
||||||
[bumpversion:file:lifecycle/aws/template.yaml]
|
[bumpversion:file:lifecycle/aws/template.yaml]
|
||||||
|
2
.github/actions/setup/action.yml
vendored
2
.github/actions/setup/action.yml
vendored
@ -36,7 +36,7 @@ runs:
|
|||||||
with:
|
with:
|
||||||
go-version-file: "go.mod"
|
go-version-file: "go.mod"
|
||||||
- name: Setup docker cache
|
- name: Setup docker cache
|
||||||
uses: ScribeMD/docker-cache@0.5.0
|
uses: AndreKurait/docker-cache@0fe76702a40db986d9663c24954fc14c6a6031b7
|
||||||
with:
|
with:
|
||||||
key: docker-images-${{ runner.os }}-${{ hashFiles('.github/actions/setup/docker-compose.yml', 'Makefile') }}-${{ inputs.postgresql_version }}
|
key: docker-images-${{ runner.os }}-${{ hashFiles('.github/actions/setup/docker-compose.yml', 'Makefile') }}-${{ inputs.postgresql_version }}
|
||||||
- name: Setup dependencies
|
- name: Setup dependencies
|
||||||
|
14
.github/dependabot.yml
vendored
14
.github/dependabot.yml
vendored
@ -23,7 +23,13 @@ updates:
|
|||||||
- package-ecosystem: npm
|
- package-ecosystem: npm
|
||||||
directories:
|
directories:
|
||||||
- "/web"
|
- "/web"
|
||||||
- "/web/sfe"
|
- "/web/packages/sfe"
|
||||||
|
- "/web/packages/core"
|
||||||
|
- "/web/packages/esbuild-plugin-live-reload"
|
||||||
|
- "/packages/prettier-config"
|
||||||
|
- "/packages/tsconfig"
|
||||||
|
- "/packages/docusaurus-config"
|
||||||
|
- "/packages/eslint-config"
|
||||||
schedule:
|
schedule:
|
||||||
interval: daily
|
interval: daily
|
||||||
time: "04:00"
|
time: "04:00"
|
||||||
@ -68,6 +74,9 @@ updates:
|
|||||||
wdio:
|
wdio:
|
||||||
patterns:
|
patterns:
|
||||||
- "@wdio/*"
|
- "@wdio/*"
|
||||||
|
goauthentik:
|
||||||
|
patterns:
|
||||||
|
- "@goauthentik/*"
|
||||||
- package-ecosystem: npm
|
- package-ecosystem: npm
|
||||||
directory: "/website"
|
directory: "/website"
|
||||||
schedule:
|
schedule:
|
||||||
@ -88,6 +97,9 @@ updates:
|
|||||||
- "swc-*"
|
- "swc-*"
|
||||||
- "lightningcss*"
|
- "lightningcss*"
|
||||||
- "@rspack/binding*"
|
- "@rspack/binding*"
|
||||||
|
goauthentik:
|
||||||
|
patterns:
|
||||||
|
- "@goauthentik/*"
|
||||||
- package-ecosystem: npm
|
- package-ecosystem: npm
|
||||||
directory: "/lifecycle/aws"
|
directory: "/lifecycle/aws"
|
||||||
schedule:
|
schedule:
|
||||||
|
2
.github/workflows/ci-main.yml
vendored
2
.github/workflows/ci-main.yml
vendored
@ -62,6 +62,7 @@ jobs:
|
|||||||
psql:
|
psql:
|
||||||
- 15-alpine
|
- 15-alpine
|
||||||
- 16-alpine
|
- 16-alpine
|
||||||
|
- 17-alpine
|
||||||
run_id: [1, 2, 3, 4, 5]
|
run_id: [1, 2, 3, 4, 5]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
@ -116,6 +117,7 @@ jobs:
|
|||||||
psql:
|
psql:
|
||||||
- 15-alpine
|
- 15-alpine
|
||||||
- 16-alpine
|
- 16-alpine
|
||||||
|
- 17-alpine
|
||||||
run_id: [1, 2, 3, 4, 5]
|
run_id: [1, 2, 3, 4, 5]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
16
.github/workflows/packages-npm-publish.yml
vendored
16
.github/workflows/packages-npm-publish.yml
vendored
@ -7,6 +7,7 @@ on:
|
|||||||
- packages/eslint-config/**
|
- packages/eslint-config/**
|
||||||
- packages/prettier-config/**
|
- packages/prettier-config/**
|
||||||
- packages/tsconfig/**
|
- packages/tsconfig/**
|
||||||
|
- web/packages/esbuild-plugin-live-reload/**
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
jobs:
|
jobs:
|
||||||
publish:
|
publish:
|
||||||
@ -16,27 +17,28 @@ jobs:
|
|||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
package:
|
package:
|
||||||
- docusaurus-config
|
- packages/docusaurus-config
|
||||||
- eslint-config
|
- packages/eslint-config
|
||||||
- prettier-config
|
- packages/prettier-config
|
||||||
- tsconfig
|
- packages/tsconfig
|
||||||
|
- web/packages/esbuild-plugin-live-reload
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 2
|
fetch-depth: 2
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version-file: packages/${{ matrix.package }}/package.json
|
node-version-file: ${{ matrix.package }}/package.json
|
||||||
registry-url: "https://registry.npmjs.org"
|
registry-url: "https://registry.npmjs.org"
|
||||||
- name: Get changed files
|
- name: Get changed files
|
||||||
id: changed-files
|
id: changed-files
|
||||||
uses: tj-actions/changed-files@ed68ef82c095e0d48ec87eccea555d944a631a4c
|
uses: tj-actions/changed-files@ed68ef82c095e0d48ec87eccea555d944a631a4c
|
||||||
with:
|
with:
|
||||||
files: |
|
files: |
|
||||||
packages/${{ matrix.package }}/package.json
|
${{ matrix.package }}/package.json
|
||||||
- name: Publish package
|
- name: Publish package
|
||||||
if: steps.changed-files.outputs.any_changed == 'true'
|
if: steps.changed-files.outputs.any_changed == 'true'
|
||||||
working-directory: packages/${{ matrix.package}}
|
working-directory: ${{ matrix.package }}
|
||||||
run: |
|
run: |
|
||||||
npm ci
|
npm ci
|
||||||
npm run build
|
npm run build
|
||||||
|
3
.github/workflows/translation-rename.yml
vendored
3
.github/workflows/translation-rename.yml
vendored
@ -15,6 +15,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: ${{ github.event.pull_request.user.login == 'transifex-integration[bot]'}}
|
if: ${{ github.event.pull_request.user.login == 'transifex-integration[bot]'}}
|
||||||
steps:
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
- id: generate_token
|
- id: generate_token
|
||||||
uses: tibdex/github-app-token@v2
|
uses: tibdex/github-app-token@v2
|
||||||
with:
|
with:
|
||||||
@ -31,7 +32,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ steps.generate_token.outputs.token }}
|
GH_TOKEN: ${{ steps.generate_token.outputs.token }}
|
||||||
run: |
|
run: |
|
||||||
gh pr edit -t "translate: ${{ steps.title.outputs.title }}" --add-label dependencies
|
gh pr edit ${{ github.event.pull_request.number }} -t "translate: ${{ steps.title.outputs.title }}" --add-label dependencies
|
||||||
- uses: peter-evans/enable-pull-request-automerge@v3
|
- uses: peter-evans/enable-pull-request-automerge@v3
|
||||||
with:
|
with:
|
||||||
token: ${{ steps.generate_token.outputs.token }}
|
token: ${{ steps.generate_token.outputs.token }}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
# syntax=docker/dockerfile:1
|
# syntax=docker/dockerfile:1
|
||||||
|
|
||||||
# Stage 1: Build website
|
# Stage 1: Build website
|
||||||
FROM --platform=${BUILDPLATFORM} docker.io/library/node:22 AS website-builder
|
FROM --platform=${BUILDPLATFORM} docker.io/library/node:24 AS website-builder
|
||||||
|
|
||||||
ENV NODE_ENV=production
|
ENV NODE_ENV=production
|
||||||
|
|
||||||
@ -20,7 +20,7 @@ COPY ./SECURITY.md /work/
|
|||||||
RUN npm run build-bundled
|
RUN npm run build-bundled
|
||||||
|
|
||||||
# Stage 2: Build webui
|
# Stage 2: Build webui
|
||||||
FROM --platform=${BUILDPLATFORM} docker.io/library/node:22 AS web-builder
|
FROM --platform=${BUILDPLATFORM} docker.io/library/node:24 AS web-builder
|
||||||
|
|
||||||
ARG GIT_BUILD_HASH
|
ARG GIT_BUILD_HASH
|
||||||
ENV GIT_BUILD_HASH=$GIT_BUILD_HASH
|
ENV GIT_BUILD_HASH=$GIT_BUILD_HASH
|
||||||
@ -94,7 +94,7 @@ RUN --mount=type=secret,id=GEOIPUPDATE_ACCOUNT_ID \
|
|||||||
/bin/sh -c "GEOIPUPDATE_LICENSE_KEY_FILE=/run/secrets/GEOIPUPDATE_LICENSE_KEY /usr/bin/entry.sh || echo 'Failed to get GeoIP database, disabling'; exit 0"
|
/bin/sh -c "GEOIPUPDATE_LICENSE_KEY_FILE=/run/secrets/GEOIPUPDATE_LICENSE_KEY /usr/bin/entry.sh || echo 'Failed to get GeoIP database, disabling'; exit 0"
|
||||||
|
|
||||||
# Stage 5: Download uv
|
# Stage 5: Download uv
|
||||||
FROM ghcr.io/astral-sh/uv:0.7.5 AS uv
|
FROM ghcr.io/astral-sh/uv:0.7.8 AS uv
|
||||||
# Stage 6: Base python image
|
# Stage 6: Base python image
|
||||||
FROM ghcr.io/goauthentik/fips-python:3.13.3-slim-bookworm-fips AS python-base
|
FROM ghcr.io/goauthentik/fips-python:3.13.3-slim-bookworm-fips AS python-base
|
||||||
|
|
||||||
|
2
Makefile
2
Makefile
@ -1,6 +1,6 @@
|
|||||||
.PHONY: gen dev-reset all clean test web website
|
.PHONY: gen dev-reset all clean test web website
|
||||||
|
|
||||||
SHELL := /bin/bash
|
SHELL := /usr/bin/env bash
|
||||||
.SHELLFLAGS += ${SHELLFLAGS} -e -o pipefail
|
.SHELLFLAGS += ${SHELLFLAGS} -e -o pipefail
|
||||||
PWD = $(shell pwd)
|
PWD = $(shell pwd)
|
||||||
UID = $(shell id -u)
|
UID = $(shell id -u)
|
||||||
|
@ -20,8 +20,8 @@ Even if the issue is not a CVE, we still greatly appreciate your help in hardeni
|
|||||||
|
|
||||||
| Version | Supported |
|
| Version | Supported |
|
||||||
| --------- | --------- |
|
| --------- | --------- |
|
||||||
| 2025.2.x | ✅ |
|
|
||||||
| 2025.4.x | ✅ |
|
| 2025.4.x | ✅ |
|
||||||
|
| 2025.6.x | ✅ |
|
||||||
|
|
||||||
## Reporting a Vulnerability
|
## Reporting a Vulnerability
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
from os import environ
|
from os import environ
|
||||||
|
|
||||||
__version__ = "2025.4.1"
|
__version__ = "2025.6.2"
|
||||||
ENV_GIT_HASH_KEY = "GIT_BUILD_HASH"
|
ENV_GIT_HASH_KEY = "GIT_BUILD_HASH"
|
||||||
|
|
||||||
|
|
||||||
|
@ -148,3 +148,14 @@ class TestBrands(APITestCase):
|
|||||||
"default_locale": "",
|
"default_locale": "",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_custom_css(self):
|
||||||
|
"""Test custom_css"""
|
||||||
|
brand = create_test_brand()
|
||||||
|
brand.branding_custom_css = """* {
|
||||||
|
font-family: "Foo bar";
|
||||||
|
}"""
|
||||||
|
brand.save()
|
||||||
|
res = self.client.get(reverse("authentik_core:if-user"))
|
||||||
|
self.assertEqual(res.status_code, 200)
|
||||||
|
self.assertIn(brand.branding_custom_css, res.content.decode())
|
||||||
|
@ -5,6 +5,8 @@ from typing import Any
|
|||||||
from django.db.models import F, Q
|
from django.db.models import F, Q
|
||||||
from django.db.models import Value as V
|
from django.db.models import Value as V
|
||||||
from django.http.request import HttpRequest
|
from django.http.request import HttpRequest
|
||||||
|
from django.utils.html import _json_script_escapes
|
||||||
|
from django.utils.safestring import mark_safe
|
||||||
|
|
||||||
from authentik import get_full_version
|
from authentik import get_full_version
|
||||||
from authentik.brands.models import Brand
|
from authentik.brands.models import Brand
|
||||||
@ -32,8 +34,13 @@ def context_processor(request: HttpRequest) -> dict[str, Any]:
|
|||||||
"""Context Processor that injects brand object into every template"""
|
"""Context Processor that injects brand object into every template"""
|
||||||
brand = getattr(request, "brand", DEFAULT_BRAND)
|
brand = getattr(request, "brand", DEFAULT_BRAND)
|
||||||
tenant = getattr(request, "tenant", Tenant())
|
tenant = getattr(request, "tenant", Tenant())
|
||||||
|
# similarly to `json_script` we escape everything HTML-related, however django
|
||||||
|
# only directly exposes this as a function that also wraps it in a <script> tag
|
||||||
|
# which we dont want for CSS
|
||||||
|
brand_css = mark_safe(str(brand.branding_custom_css).translate(_json_script_escapes)) # nosec
|
||||||
return {
|
return {
|
||||||
"brand": brand,
|
"brand": brand,
|
||||||
|
"brand_css": brand_css,
|
||||||
"footer_links": tenant.footer_links,
|
"footer_links": tenant.footer_links,
|
||||||
"html_meta": {**get_http_meta()},
|
"html_meta": {**get_http_meta()},
|
||||||
"version": get_full_version(),
|
"version": get_full_version(),
|
||||||
|
@ -84,6 +84,7 @@ from authentik.flows.views.executor import QS_KEY_TOKEN
|
|||||||
from authentik.lib.avatars import get_avatar
|
from authentik.lib.avatars import get_avatar
|
||||||
from authentik.rbac.decorators import permission_required
|
from authentik.rbac.decorators import permission_required
|
||||||
from authentik.rbac.models import get_permission_choices
|
from authentik.rbac.models import get_permission_choices
|
||||||
|
from authentik.stages.email.flow import pickle_flow_token_for_email
|
||||||
from authentik.stages.email.models import EmailStage
|
from authentik.stages.email.models import EmailStage
|
||||||
from authentik.stages.email.tasks import send_mails
|
from authentik.stages.email.tasks import send_mails
|
||||||
from authentik.stages.email.utils import TemplateEmailMessage
|
from authentik.stages.email.utils import TemplateEmailMessage
|
||||||
@ -451,7 +452,7 @@ class UserViewSet(UsedByMixin, ModelViewSet):
|
|||||||
def list(self, request, *args, **kwargs):
|
def list(self, request, *args, **kwargs):
|
||||||
return super().list(request, *args, **kwargs)
|
return super().list(request, *args, **kwargs)
|
||||||
|
|
||||||
def _create_recovery_link(self) -> tuple[str, Token]:
|
def _create_recovery_link(self, for_email=False) -> tuple[str, Token]:
|
||||||
"""Create a recovery link (when the current brand has a recovery flow set),
|
"""Create a recovery link (when the current brand has a recovery flow set),
|
||||||
that can either be shown to an admin or sent to the user directly"""
|
that can either be shown to an admin or sent to the user directly"""
|
||||||
brand: Brand = self.request._request.brand
|
brand: Brand = self.request._request.brand
|
||||||
@ -473,12 +474,16 @@ class UserViewSet(UsedByMixin, ModelViewSet):
|
|||||||
raise ValidationError(
|
raise ValidationError(
|
||||||
{"non_field_errors": "Recovery flow not applicable to user"}
|
{"non_field_errors": "Recovery flow not applicable to user"}
|
||||||
) from None
|
) from None
|
||||||
|
_plan = FlowToken.pickle(plan)
|
||||||
|
if for_email:
|
||||||
|
_plan = pickle_flow_token_for_email(plan)
|
||||||
token, __ = FlowToken.objects.update_or_create(
|
token, __ = FlowToken.objects.update_or_create(
|
||||||
identifier=f"{user.uid}-password-reset",
|
identifier=f"{user.uid}-password-reset",
|
||||||
defaults={
|
defaults={
|
||||||
"user": user,
|
"user": user,
|
||||||
"flow": flow,
|
"flow": flow,
|
||||||
"_plan": FlowToken.pickle(plan),
|
"_plan": _plan,
|
||||||
|
"revoke_on_execution": not for_email,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
querystring = urlencode({QS_KEY_TOKEN: token.key})
|
querystring = urlencode({QS_KEY_TOKEN: token.key})
|
||||||
@ -648,7 +653,7 @@ class UserViewSet(UsedByMixin, ModelViewSet):
|
|||||||
if for_user.email == "":
|
if for_user.email == "":
|
||||||
LOGGER.debug("User doesn't have an email address")
|
LOGGER.debug("User doesn't have an email address")
|
||||||
raise ValidationError({"non_field_errors": "User does not have an email address set."})
|
raise ValidationError({"non_field_errors": "User does not have an email address set."})
|
||||||
link, token = self._create_recovery_link()
|
link, token = self._create_recovery_link(for_email=True)
|
||||||
# Lookup the email stage to assure the current user can access it
|
# Lookup the email stage to assure the current user can access it
|
||||||
stages = get_objects_for_user(
|
stages = get_objects_for_user(
|
||||||
request.user, "authentik_stages_email.view_emailstage"
|
request.user, "authentik_stages_email.view_emailstage"
|
||||||
|
@ -79,6 +79,7 @@ def _migrate_session(
|
|||||||
AuthenticatedSession.objects.using(db_alias).create(
|
AuthenticatedSession.objects.using(db_alias).create(
|
||||||
session=session,
|
session=session,
|
||||||
user=old_auth_session.user,
|
user=old_auth_session.user,
|
||||||
|
uuid=old_auth_session.uuid,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,10 +1,81 @@
|
|||||||
# Generated by Django 5.1.9 on 2025-05-14 11:15
|
# Generated by Django 5.1.9 on 2025-05-14 11:15
|
||||||
|
|
||||||
from django.apps.registry import Apps
|
from django.apps.registry import Apps, apps as global_apps
|
||||||
from django.db import migrations
|
from django.db import migrations
|
||||||
|
from django.contrib.contenttypes.management import create_contenttypes
|
||||||
|
from django.contrib.auth.management import create_permissions
|
||||||
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
|
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
|
||||||
|
|
||||||
|
|
||||||
|
def migrate_authenticated_session_permissions(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
|
||||||
|
"""Migrate permissions from OldAuthenticatedSession to AuthenticatedSession"""
|
||||||
|
db_alias = schema_editor.connection.alias
|
||||||
|
|
||||||
|
# `apps` here is just an instance of `django.db.migrations.state.AppConfigStub`, we need the
|
||||||
|
# real config for creating permissions and content types
|
||||||
|
authentik_core_config = global_apps.get_app_config("authentik_core")
|
||||||
|
# These are only ran by django after all migrations, but we need them right now.
|
||||||
|
# `global_apps` is needed,
|
||||||
|
create_permissions(authentik_core_config, using=db_alias, verbosity=1)
|
||||||
|
create_contenttypes(authentik_core_config, using=db_alias, verbosity=1)
|
||||||
|
|
||||||
|
# But from now on, this is just a regular migration, so use `apps`
|
||||||
|
Permission = apps.get_model("auth", "Permission")
|
||||||
|
ContentType = apps.get_model("contenttypes", "ContentType")
|
||||||
|
|
||||||
|
try:
|
||||||
|
old_ct = ContentType.objects.using(db_alias).get(
|
||||||
|
app_label="authentik_core", model="oldauthenticatedsession"
|
||||||
|
)
|
||||||
|
new_ct = ContentType.objects.using(db_alias).get(
|
||||||
|
app_label="authentik_core", model="authenticatedsession"
|
||||||
|
)
|
||||||
|
except ContentType.DoesNotExist:
|
||||||
|
# This should exist at this point, but if not, let's cut our losses
|
||||||
|
return
|
||||||
|
|
||||||
|
# Get all permissions for the old content type
|
||||||
|
old_perms = Permission.objects.using(db_alias).filter(content_type=old_ct)
|
||||||
|
|
||||||
|
# Create equivalent permissions for the new content type
|
||||||
|
for old_perm in old_perms:
|
||||||
|
new_perm = (
|
||||||
|
Permission.objects.using(db_alias)
|
||||||
|
.filter(
|
||||||
|
content_type=new_ct,
|
||||||
|
codename=old_perm.codename,
|
||||||
|
)
|
||||||
|
.first()
|
||||||
|
)
|
||||||
|
if not new_perm:
|
||||||
|
# This should exist at this point, but if not, let's cut our losses
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Global user permissions
|
||||||
|
User = apps.get_model("authentik_core", "User")
|
||||||
|
User.user_permissions.through.objects.using(db_alias).filter(
|
||||||
|
permission=old_perm
|
||||||
|
).all().update(permission=new_perm)
|
||||||
|
|
||||||
|
# Global role permissions
|
||||||
|
DjangoGroup = apps.get_model("auth", "Group")
|
||||||
|
DjangoGroup.permissions.through.objects.using(db_alias).filter(
|
||||||
|
permission=old_perm
|
||||||
|
).all().update(permission=new_perm)
|
||||||
|
|
||||||
|
# Object user permissions
|
||||||
|
UserObjectPermission = apps.get_model("guardian", "UserObjectPermission")
|
||||||
|
UserObjectPermission.objects.using(db_alias).filter(permission=old_perm).all().update(
|
||||||
|
permission=new_perm, content_type=new_ct
|
||||||
|
)
|
||||||
|
|
||||||
|
# Object role permissions
|
||||||
|
GroupObjectPermission = apps.get_model("guardian", "GroupObjectPermission")
|
||||||
|
GroupObjectPermission.objects.using(db_alias).filter(permission=old_perm).all().update(
|
||||||
|
permission=new_perm, content_type=new_ct
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def remove_old_authenticated_session_content_type(
|
def remove_old_authenticated_session_content_type(
|
||||||
apps: Apps, schema_editor: BaseDatabaseSchemaEditor
|
apps: Apps, schema_editor: BaseDatabaseSchemaEditor
|
||||||
):
|
):
|
||||||
@ -21,7 +92,12 @@ class Migration(migrations.Migration):
|
|||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
|
migrations.RunPython(
|
||||||
|
code=migrate_authenticated_session_permissions,
|
||||||
|
reverse_code=migrations.RunPython.noop,
|
||||||
|
),
|
||||||
migrations.RunPython(
|
migrations.RunPython(
|
||||||
code=remove_old_authenticated_session_content_type,
|
code=remove_old_authenticated_session_content_type,
|
||||||
|
reverse_code=migrations.RunPython.noop,
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
{% block head_before %}
|
{% block head_before %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
<link rel="stylesheet" type="text/css" href="{% static 'dist/authentik.css' %}">
|
<link rel="stylesheet" type="text/css" href="{% static 'dist/authentik.css' %}">
|
||||||
<style>{{ brand.branding_custom_css }}</style>
|
<style>{{ brand_css }}</style>
|
||||||
<script src="{% versioned_script 'dist/poly-%v.js' %}" type="module"></script>
|
<script src="{% versioned_script 'dist/poly-%v.js' %}" type="module"></script>
|
||||||
<script src="{% versioned_script 'dist/standalone/loading/index-%v.js' %}" type="module"></script>
|
<script src="{% versioned_script 'dist/standalone/loading/index-%v.js' %}" type="module"></script>
|
||||||
{% block head %}
|
{% block head %}
|
||||||
|
@ -3,7 +3,14 @@ from urllib.parse import unquote_plus
|
|||||||
|
|
||||||
from cryptography.exceptions import InvalidSignature
|
from cryptography.exceptions import InvalidSignature
|
||||||
from cryptography.hazmat.primitives import hashes
|
from cryptography.hazmat.primitives import hashes
|
||||||
from cryptography.x509 import Certificate, NameOID, ObjectIdentifier, load_pem_x509_certificate
|
from cryptography.x509 import (
|
||||||
|
Certificate,
|
||||||
|
NameOID,
|
||||||
|
ObjectIdentifier,
|
||||||
|
UnsupportedGeneralNameType,
|
||||||
|
load_pem_x509_certificate,
|
||||||
|
)
|
||||||
|
from cryptography.x509.verification import PolicyBuilder, Store, VerificationError
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from authentik.brands.models import Brand
|
from authentik.brands.models import Brand
|
||||||
@ -102,16 +109,22 @@ class MTLSStageView(ChallengeStageView):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
def validate_cert(self, authorities: list[CertificateKeyPair], certs: list[Certificate]):
|
def validate_cert(self, authorities: list[CertificateKeyPair], certs: list[Certificate]):
|
||||||
|
authorities_cert = [x.certificate for x in authorities]
|
||||||
for _cert in certs:
|
for _cert in certs:
|
||||||
for ca in authorities:
|
try:
|
||||||
try:
|
PolicyBuilder().store(Store(authorities_cert)).build_client_verifier().verify(
|
||||||
_cert.verify_directly_issued_by(ca.certificate)
|
_cert, []
|
||||||
return _cert
|
)
|
||||||
except (InvalidSignature, TypeError, ValueError) as exc:
|
return _cert
|
||||||
self.logger.warning(
|
except (
|
||||||
"Discarding cert not issued by authority", cert=_cert, authority=ca, exc=exc
|
InvalidSignature,
|
||||||
)
|
TypeError,
|
||||||
continue
|
ValueError,
|
||||||
|
VerificationError,
|
||||||
|
UnsupportedGeneralNameType,
|
||||||
|
) as exc:
|
||||||
|
self.logger.warning("Discarding invalid certificate", cert=_cert, exc=exc)
|
||||||
|
continue
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def check_if_user(self, cert: Certificate):
|
def check_if_user(self, cert: Certificate):
|
||||||
@ -141,7 +154,9 @@ class MTLSStageView(ChallengeStageView):
|
|||||||
"subject": cert.subject.rfc4514_string(),
|
"subject": cert.subject.rfc4514_string(),
|
||||||
"issuer": cert.issuer.rfc4514_string(),
|
"issuer": cert.issuer.rfc4514_string(),
|
||||||
"fingerprint_sha256": hexlify(cert.fingerprint(hashes.SHA256()), ":").decode("utf-8"),
|
"fingerprint_sha256": hexlify(cert.fingerprint(hashes.SHA256()), ":").decode("utf-8"),
|
||||||
"fingerprint_sha1": hexlify(cert.fingerprint(hashes.SHA256()), ":").decode("utf-8"),
|
"fingerprint_sha1": hexlify(cert.fingerprint(hashes.SHA1()), ":").decode( # nosec
|
||||||
|
"utf-8"
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
def auth_user(self, user: User, cert: Certificate):
|
def auth_user(self, user: User, cert: Certificate):
|
||||||
|
@ -1,30 +1,31 @@
|
|||||||
-----BEGIN CERTIFICATE-----
|
-----BEGIN CERTIFICATE-----
|
||||||
MIIFOzCCAyOgAwIBAgIUbnIMy+Ewi5RvK7OBDxWMCk7wi08wDQYJKoZIhvcNAQEL
|
MIIFWTCCA0GgAwIBAgIUDEnKCSmIXG/akySGes7bhOGrN/8wDQYJKoZIhvcNAQEL
|
||||||
BQAwRjEaMBgGA1UEAwwRYXV0aGVudGlrIFRlc3QgQ0ExEjAQBgNVBAoMCWF1dGhl
|
BQAwRjEaMBgGA1UEAwwRYXV0aGVudGlrIFRlc3QgQ0ExEjAQBgNVBAoMCWF1dGhl
|
||||||
bnRpazEUMBIGA1UECwwLU2VsZi1zaWduZWQwHhcNMjUwNDI3MTgzMTE3WhcNMjYw
|
bnRpazEUMBIGA1UECwwLU2VsZi1zaWduZWQwHhcNMjUwNTE5MTIzODQ2WhcNMjYw
|
||||||
NDIzMTgzMTE3WjARMQ8wDQYDVQQDDAZjbGllbnQwggIiMA0GCSqGSIb3DQEBAQUA
|
NTE1MTIzODQ2WjARMQ8wDQYDVQQDDAZjbGllbnQwggIiMA0GCSqGSIb3DQEBAQUA
|
||||||
A4ICDwAwggIKAoICAQCdV+GEa7+7ito1i/z637OZW+0azv1kuF2aDwSzv+FJd+4L
|
A4ICDwAwggIKAoICAQCkPkS1V6l0gj0ulxMznkxkgrw4p9Tjd8teSsGZt02A2Eo6
|
||||||
6hCroRbVYTUFS3I3YwanOOZfau64xH0+pFM5Js8aREG68eqKBayx8vT27hyAOFhd
|
7D8FbJ7pp3d5fYW/TWuEKVBLWTID6rijW5EGcdgTM5Jxf/QR+aZTEK6umQxUd4yO
|
||||||
giEVmSQJfla4ogvPie1rJ0HVOL7CiR72HDPQvz+9k1iDX3xQ/4sdAb3XurN13e+M
|
mOtp+xVS3KlcsSej2dFpeE5h5VkZizHpvh5xkoAP8W5VtQLOVF0hIeumHnJmaeLj
|
||||||
Gtavhjiyqxmoo/H4WRd8BhD/BZQFWtaxWODDY8aKk5R7omw6Xf7aRv1BlHdE4Ucy
|
+mhK9PBFpO7k9SFrYYhd/uLrYbIdANihbIO2Q74rNEJHewhFNM7oNSjjEWzRd/7S
|
||||||
Wozvpsj2Kz0l61rRUhiMlE0D9dpijgaRYFB+M7R2casH3CdhGQbBHTRiqBkZa6iq
|
qNdQij9JGrVG7u8YJJscEQHqyHMYFVCEMjxmsge5BO6Vx5OWmUE3wXPzb5TbyTS4
|
||||||
SDkTiTwNJQQJov8yPTsR+9P8OOuV6QN+DGm/FXJJFaPnsHw/HDy7EAbA1PcdbSyK
|
+yg88g9rYTUXrzz+poCyKpaur45qBsdw35lJ8nq69VJj2xJLGQDwoTgGSXRuPciC
|
||||||
XvJ8nVjdNhCEGbLGVSwAQLO+78hChVIN5YH+QSrP84YBSxKZYArnf4z2e9drqAN3
|
3OilQI+Ma+j8qQGJxJ8WJxISlf1cuhp+V4ZUd1lawlM5hAXyXmHRlH4pun4y+g7O
|
||||||
KmC26TkaUzkXnndnxOXBEIOSmyCdD4Dutg1XPE/bs8rA6rVGIR3pKXbCr29Z8hZn
|
O34+fE3pK25JjVCicMT/rC2A/sb95j/fHTzzJpbB70U0I50maTcIsOkyw6aiF//E
|
||||||
Cn9jbxwDwTX865ljR1Oc3dnIeCWa9AS/uHaSMdGlbGbDrt4Bj/nyyfu8xc034K/0
|
0ShTDz14x22SCMolUc6hxTDZvBB6yrcJHd7d9CCnFH2Sgo13QrtNJ/atXgm13HGh
|
||||||
uPh3hF3FLWNAomRVZCvtuh/v7IEIQEgUbvQMWBhZJ8hu3HdtV8V9TIAryVKzEzGy
|
wBzRwK38XUGl/J4pJaxAupTVCPriStUM3m0EYHNelRRUE91pbyeGT0rvOuv00uLw
|
||||||
Q72UHuQyK0njRDTmA/T+jn7P8GWOuf9eNdzd0gH0gcEuhCZFxPPRvUAeDuC7DQID
|
Rj7K7hJZR8avTKWmKrVBVpq+gSojGW1DwBS0NiDNkZs0d/IjB1wkzczEgdZjXwID
|
||||||
AQABo1YwVDAdBgNVHSUEFjAUBggrBgEFBQcDAgYIKwYBBQUHAwEwFAYDVR0RAQH/
|
AQABo3QwcjAfBgNVHSMEGDAWgBTa+Ns6QzqlNvnTGszkouQQtZnVJDAdBgNVHSUE
|
||||||
BAowCIIGY2xpZW50MB0GA1UdDgQWBBQ5KZwTD8+4CqLnbM/cBSXg8XeLXTANBgkq
|
FjAUBggrBgEFBQcDAgYIKwYBBQUHAwEwEQYDVR0RBAowCIIGY2xpZW50MB0GA1Ud
|
||||||
hkiG9w0BAQsFAAOCAgEABDkb3iyEOl1xKq1wxyRzf2L8qfPXAQw71FxMbgHArM+a
|
DgQWBBT1xg5sXkypRBwvCxBuyfoanaiZ5jANBgkqhkiG9w0BAQsFAAOCAgEAvUAz
|
||||||
e44wJGO3mZgPH0trOaJ+tuN6erB5YbZfsoX+xFacwskj9pKyb4QJLr/ENmJZRgyL
|
YwIjxY/0KHZDU8owdILVqKChzfLcy9OHNPyEI3TSOI8X6gNtBO+HE6r8aWGcC9vw
|
||||||
wp5P6PB6IUJhvryvy/GxrG938YGmFtYQ+ikeJw5PWhB6218C1aZ9hsi94wZ1Zzrc
|
zzeIsNQ3UEjvRWi2r+vUVbiPTbFdZboNDSZv6ZmGHxwd85VsjXRGoXV6koCT/9zi
|
||||||
Ry0q0D4QvIEZ0X2HL1Harc7gerE3VqhgQ7EWyImM+lCRtNDduwDQnZauwhr2r6cW
|
9/lCM1DwqwYSwBphMJdRVFRUMluSYk1oHflGeA18xgGuts4eFivJwhabGm1AdVVQ
|
||||||
XG4VTe1RCNsDA0xinXQE2Xf9voCd0Zf6wOOXJseQtrXpf+tG4N13cy5heF5ihed1
|
/CYvqCuTxd/DCzWZBdyxYpDru64i/kyeJCt1pThKEFDWmpumFdBI4CxJ0OhxVSGp
|
||||||
hDxSeki0KjTM+18kVVfVm4fzxf1Zg0gm54UlzWceIWh9EtnWMUV08H0D1M9YNmW8
|
dOXzK+Y6ULepxCvi6/OpSog52jQ6PnNd1ghiYtq7yO1T4GQz65M1vtHHVvQ3gfBE
|
||||||
hWTupk7M+jAw8Y+suHOe6/RLi0+fb9NSJpIpq4GqJ5UF2kerXHX0SvuAavoXyB0j
|
AuKYQp6io7ypitRx+LpjsBQenyP4FFGfrq7pm90nLluOBOArfSdF0N+CP2wo/YFV
|
||||||
CQrUXkRScEKOO2KAbVExSG56Ff7Ee8cRUAQ6rLC5pQRACq/R0sa6RcUsFPXul3Yv
|
9BGf89OtvRi3BXCm2NXkE/Sc4We26tY8x7xNLOmNs8YOT0O3r/EQ690W9GIwRMx0
|
||||||
vbO2rTuArAUPkNVFknwkndheN4lOslRd1If02HunZETmsnal6p+nmuMWt2pQ2fDA
|
m0r/RXWn5V3o4Jib9r8eH9NzaDstD8g9dECcGfM4fHoM/DAGFaRrNcjMsS1APP3L
|
||||||
vIguG54FyQ1T1IbF/QhfTEY62CQAebcgutnqqJHt9qe7Jr6ev57hMrJDEjotSzkY
|
jp7+BfBSXtrz9V6rVJ3CBLXlLK0AuSm7bqd1MJsGA9uMLpsVZIUA+KawcmPGdPU+
|
||||||
OhOVrcYqgLldr1nBqNVlIK/4VrDaWH8H5dNJ72gA9aMNVH4/bSTJhuO7cJkLnHw=
|
NxdpBCtzyurQSUyaTLtVqSeP35gMAwaNzUDph8Uh+vHz+kRwgXS19OQvTaud5LJu
|
||||||
|
nQe4JNS+u5e2VDEBWUxt8NTpu6eShDN0iIEHtxA=
|
||||||
-----END CERTIFICATE-----
|
-----END CERTIFICATE-----
|
||||||
|
@ -5,7 +5,12 @@ from django.urls import reverse
|
|||||||
from guardian.shortcuts import assign_perm
|
from guardian.shortcuts import assign_perm
|
||||||
|
|
||||||
from authentik.core.models import User
|
from authentik.core.models import User
|
||||||
from authentik.core.tests.utils import create_test_brand, create_test_flow, create_test_user
|
from authentik.core.tests.utils import (
|
||||||
|
create_test_brand,
|
||||||
|
create_test_cert,
|
||||||
|
create_test_flow,
|
||||||
|
create_test_user,
|
||||||
|
)
|
||||||
from authentik.crypto.models import CertificateKeyPair
|
from authentik.crypto.models import CertificateKeyPair
|
||||||
from authentik.enterprise.stages.mtls.models import (
|
from authentik.enterprise.stages.mtls.models import (
|
||||||
CertAttributes,
|
CertAttributes,
|
||||||
@ -127,6 +132,18 @@ class MTLSStageTests(FlowTestCase):
|
|||||||
self.assertEqual(res.status_code, 200)
|
self.assertEqual(res.status_code, 200)
|
||||||
self.assertStageResponse(res, self.flow, component="ak-stage-access-denied")
|
self.assertStageResponse(res, self.flow, component="ak-stage-access-denied")
|
||||||
|
|
||||||
|
def test_invalid_cert(self):
|
||||||
|
"""Test invalid certificate"""
|
||||||
|
cert = create_test_cert()
|
||||||
|
with self.assertFlowFinishes() as plan:
|
||||||
|
res = self.client.get(
|
||||||
|
reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}),
|
||||||
|
headers={"X-Forwarded-TLS-Client-Cert": quote_plus(cert.certificate_data)},
|
||||||
|
)
|
||||||
|
self.assertEqual(res.status_code, 200)
|
||||||
|
self.assertStageResponse(res, self.flow, component="ak-stage-access-denied")
|
||||||
|
self.assertNotIn(PLAN_CONTEXT_PENDING_USER, plan().context)
|
||||||
|
|
||||||
def test_auth_no_user(self):
|
def test_auth_no_user(self):
|
||||||
"""Test auth with no user"""
|
"""Test auth with no user"""
|
||||||
User.objects.filter(username="client").delete()
|
User.objects.filter(username="client").delete()
|
||||||
@ -200,14 +217,12 @@ class MTLSStageTests(FlowTestCase):
|
|||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
plan().context[PLAN_CONTEXT_CERTIFICATE],
|
plan().context[PLAN_CONTEXT_CERTIFICATE],
|
||||||
{
|
{
|
||||||
"fingerprint_sha1": (
|
"fingerprint_sha1": "52:39:ca:1e:3a:1f:78:3a:9f:26:3b:c2:84:99:48:68:99:99:81:8a",
|
||||||
"08:d4:a4:79:25:ca:c3:51:28:88:bb:30:c2:96:c3:44:5a:eb:18:07:84:ca:b4:75:27:74:61:19:8a:6a:af:fc"
|
|
||||||
),
|
|
||||||
"fingerprint_sha256": (
|
"fingerprint_sha256": (
|
||||||
"08:d4:a4:79:25:ca:c3:51:28:88:bb:30:c2:96:c3:44:5a:eb:18:07:84:ca:b4:75:27:74:61:19:8a:6a:af:fc"
|
"c1:07:8b:7c:e9:02:57:87:1e:92:e5:81:83:21:bc:92:c7:47:65:e3:97:fb:05:97:6f:36:9e:b5:31:77:98:b7"
|
||||||
),
|
),
|
||||||
"issuer": "OU=Self-signed,O=authentik,CN=authentik Test CA",
|
"issuer": "OU=Self-signed,O=authentik,CN=authentik Test CA",
|
||||||
"serial_number": "630532384467334865093173111400266136879266564943",
|
"serial_number": "70153443448884702681996102271549704759327537151",
|
||||||
"subject": "CN=client",
|
"subject": "CN=client",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 5.1.9 on 2025-05-27 12:52
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("authentik_flows", "0027_auto_20231028_1424"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="flowtoken",
|
||||||
|
name="revoke_on_execution",
|
||||||
|
field=models.BooleanField(default=True),
|
||||||
|
),
|
||||||
|
]
|
@ -303,9 +303,10 @@ class FlowToken(Token):
|
|||||||
|
|
||||||
flow = models.ForeignKey(Flow, on_delete=models.CASCADE)
|
flow = models.ForeignKey(Flow, on_delete=models.CASCADE)
|
||||||
_plan = models.TextField()
|
_plan = models.TextField()
|
||||||
|
revoke_on_execution = models.BooleanField(default=True)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def pickle(plan) -> str:
|
def pickle(plan: "FlowPlan") -> str:
|
||||||
"""Pickle into string"""
|
"""Pickle into string"""
|
||||||
data = dumps(plan)
|
data = dumps(plan)
|
||||||
return b64encode(data).decode()
|
return b64encode(data).decode()
|
||||||
|
@ -99,9 +99,10 @@ class ChallengeStageView(StageView):
|
|||||||
self.logger.debug("Got StageInvalidException", exc=exc)
|
self.logger.debug("Got StageInvalidException", exc=exc)
|
||||||
return self.executor.stage_invalid()
|
return self.executor.stage_invalid()
|
||||||
if not challenge.is_valid():
|
if not challenge.is_valid():
|
||||||
self.logger.warning(
|
self.logger.error(
|
||||||
"f(ch): Invalid challenge",
|
"f(ch): Invalid challenge",
|
||||||
errors=challenge.errors,
|
errors=challenge.errors,
|
||||||
|
challenge=challenge.data,
|
||||||
)
|
)
|
||||||
return HttpChallengeResponse(challenge)
|
return HttpChallengeResponse(challenge)
|
||||||
|
|
||||||
|
@ -146,7 +146,8 @@ class FlowExecutorView(APIView):
|
|||||||
except (AttributeError, EOFError, ImportError, IndexError) as exc:
|
except (AttributeError, EOFError, ImportError, IndexError) as exc:
|
||||||
LOGGER.warning("f(exec): Failed to restore token plan", exc=exc)
|
LOGGER.warning("f(exec): Failed to restore token plan", exc=exc)
|
||||||
finally:
|
finally:
|
||||||
token.delete()
|
if token.revoke_on_execution:
|
||||||
|
token.delete()
|
||||||
if not isinstance(plan, FlowPlan):
|
if not isinstance(plan, FlowPlan):
|
||||||
return None
|
return None
|
||||||
plan.context[PLAN_CONTEXT_IS_RESTORED] = token
|
plan.context[PLAN_CONTEXT_IS_RESTORED] = token
|
||||||
|
@ -81,7 +81,6 @@ debugger: false
|
|||||||
|
|
||||||
log_level: info
|
log_level: info
|
||||||
|
|
||||||
session_storage: cache
|
|
||||||
sessions:
|
sessions:
|
||||||
unauthenticated_age: days=1
|
unauthenticated_age: days=1
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
from collections.abc import Callable
|
from collections.abc import Callable
|
||||||
from dataclasses import asdict
|
from dataclasses import asdict
|
||||||
|
|
||||||
|
from celery import group
|
||||||
from celery.exceptions import Retry
|
from celery.exceptions import Retry
|
||||||
from celery.result import allow_join_result
|
from celery.result import allow_join_result
|
||||||
from django.core.paginator import Paginator
|
from django.core.paginator import Paginator
|
||||||
@ -82,21 +83,41 @@ class SyncTasks:
|
|||||||
self.logger.debug("Failed to acquire sync lock, skipping", provider=provider.name)
|
self.logger.debug("Failed to acquire sync lock, skipping", provider=provider.name)
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
for page in users_paginator.page_range:
|
messages.append(_("Syncing users"))
|
||||||
messages.append(_("Syncing page {page} of users".format(page=page)))
|
user_results = (
|
||||||
for msg in sync_objects.apply_async(
|
group(
|
||||||
args=(class_to_path(User), page, provider_pk),
|
[
|
||||||
time_limit=PAGE_TIMEOUT,
|
sync_objects.signature(
|
||||||
soft_time_limit=PAGE_TIMEOUT,
|
args=(class_to_path(User), page, provider_pk),
|
||||||
).get():
|
time_limit=PAGE_TIMEOUT,
|
||||||
|
soft_time_limit=PAGE_TIMEOUT,
|
||||||
|
)
|
||||||
|
for page in users_paginator.page_range
|
||||||
|
]
|
||||||
|
)
|
||||||
|
.apply_async()
|
||||||
|
.get()
|
||||||
|
)
|
||||||
|
for result in user_results:
|
||||||
|
for msg in result:
|
||||||
messages.append(LogEvent(**msg))
|
messages.append(LogEvent(**msg))
|
||||||
for page in groups_paginator.page_range:
|
messages.append(_("Syncing groups"))
|
||||||
messages.append(_("Syncing page {page} of groups".format(page=page)))
|
group_results = (
|
||||||
for msg in sync_objects.apply_async(
|
group(
|
||||||
args=(class_to_path(Group), page, provider_pk),
|
[
|
||||||
time_limit=PAGE_TIMEOUT,
|
sync_objects.signature(
|
||||||
soft_time_limit=PAGE_TIMEOUT,
|
args=(class_to_path(Group), page, provider_pk),
|
||||||
).get():
|
time_limit=PAGE_TIMEOUT,
|
||||||
|
soft_time_limit=PAGE_TIMEOUT,
|
||||||
|
)
|
||||||
|
for page in groups_paginator.page_range
|
||||||
|
]
|
||||||
|
)
|
||||||
|
.apply_async()
|
||||||
|
.get()
|
||||||
|
)
|
||||||
|
for result in group_results:
|
||||||
|
for msg in result:
|
||||||
messages.append(LogEvent(**msg))
|
messages.append(LogEvent(**msg))
|
||||||
except TransientSyncException as exc:
|
except TransientSyncException as exc:
|
||||||
self.logger.warning("transient sync exception", exc=exc)
|
self.logger.warning("transient sync exception", exc=exc)
|
||||||
@ -109,7 +130,7 @@ class SyncTasks:
|
|||||||
def sync_objects(
|
def sync_objects(
|
||||||
self, object_type: str, page: int, provider_pk: int, override_dry_run=False, **filter
|
self, object_type: str, page: int, provider_pk: int, override_dry_run=False, **filter
|
||||||
):
|
):
|
||||||
_object_type = path_to_class(object_type)
|
_object_type: type[Model] = path_to_class(object_type)
|
||||||
self.logger = get_logger().bind(
|
self.logger = get_logger().bind(
|
||||||
provider_type=class_to_path(self._provider_model),
|
provider_type=class_to_path(self._provider_model),
|
||||||
provider_pk=provider_pk,
|
provider_pk=provider_pk,
|
||||||
@ -132,6 +153,19 @@ class SyncTasks:
|
|||||||
self.logger.debug("starting discover")
|
self.logger.debug("starting discover")
|
||||||
client.discover()
|
client.discover()
|
||||||
self.logger.debug("starting sync for page", page=page)
|
self.logger.debug("starting sync for page", page=page)
|
||||||
|
messages.append(
|
||||||
|
asdict(
|
||||||
|
LogEvent(
|
||||||
|
_(
|
||||||
|
"Syncing page {page} of {object_type}".format(
|
||||||
|
page=page, object_type=_object_type._meta.verbose_name_plural
|
||||||
|
)
|
||||||
|
),
|
||||||
|
log_level="info",
|
||||||
|
logger=f"{provider._meta.verbose_name}@{object_type}",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
for obj in paginator.page(page).object_list:
|
for obj in paginator.page(page).object_list:
|
||||||
obj: Model
|
obj: Model
|
||||||
try:
|
try:
|
||||||
|
@ -38,6 +38,7 @@ class TestOutpostWS(TransactionTestCase):
|
|||||||
)
|
)
|
||||||
connected, _ = await communicator.connect()
|
connected, _ = await communicator.connect()
|
||||||
self.assertFalse(connected)
|
self.assertFalse(connected)
|
||||||
|
await communicator.disconnect()
|
||||||
|
|
||||||
async def test_auth_valid(self):
|
async def test_auth_valid(self):
|
||||||
"""Test auth with token"""
|
"""Test auth with token"""
|
||||||
@ -48,6 +49,7 @@ class TestOutpostWS(TransactionTestCase):
|
|||||||
)
|
)
|
||||||
connected, _ = await communicator.connect()
|
connected, _ = await communicator.connect()
|
||||||
self.assertTrue(connected)
|
self.assertTrue(connected)
|
||||||
|
await communicator.disconnect()
|
||||||
|
|
||||||
async def test_send(self):
|
async def test_send(self):
|
||||||
"""Test sending of Hello"""
|
"""Test sending of Hello"""
|
||||||
|
@ -7,10 +7,8 @@ from django.db import migrations
|
|||||||
|
|
||||||
|
|
||||||
def migrate_search_group(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
|
def migrate_search_group(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
|
||||||
from authentik.core.models import User
|
|
||||||
from django.apps import apps as real_apps
|
from django.apps import apps as real_apps
|
||||||
from django.contrib.auth.management import create_permissions
|
from django.contrib.auth.management import create_permissions
|
||||||
from guardian.shortcuts import UserObjectPermission
|
|
||||||
|
|
||||||
db_alias = schema_editor.connection.alias
|
db_alias = schema_editor.connection.alias
|
||||||
|
|
||||||
|
@ -50,3 +50,4 @@ AMR_PASSWORD = "pwd" # nosec
|
|||||||
AMR_MFA = "mfa"
|
AMR_MFA = "mfa"
|
||||||
AMR_OTP = "otp"
|
AMR_OTP = "otp"
|
||||||
AMR_WEBAUTHN = "user"
|
AMR_WEBAUTHN = "user"
|
||||||
|
AMR_SMART_CARD = "sc"
|
||||||
|
@ -16,6 +16,7 @@ from authentik.providers.oauth2.constants import (
|
|||||||
ACR_AUTHENTIK_DEFAULT,
|
ACR_AUTHENTIK_DEFAULT,
|
||||||
AMR_MFA,
|
AMR_MFA,
|
||||||
AMR_PASSWORD,
|
AMR_PASSWORD,
|
||||||
|
AMR_SMART_CARD,
|
||||||
AMR_WEBAUTHN,
|
AMR_WEBAUTHN,
|
||||||
)
|
)
|
||||||
from authentik.stages.password.stage import PLAN_CONTEXT_METHOD, PLAN_CONTEXT_METHOD_ARGS
|
from authentik.stages.password.stage import PLAN_CONTEXT_METHOD, PLAN_CONTEXT_METHOD_ARGS
|
||||||
@ -139,9 +140,10 @@ class IDToken:
|
|||||||
amr.append(AMR_PASSWORD)
|
amr.append(AMR_PASSWORD)
|
||||||
if method == "auth_webauthn_pwl":
|
if method == "auth_webauthn_pwl":
|
||||||
amr.append(AMR_WEBAUTHN)
|
amr.append(AMR_WEBAUTHN)
|
||||||
|
if "certificate" in method_args:
|
||||||
|
amr.append(AMR_SMART_CARD)
|
||||||
if "mfa_devices" in method_args:
|
if "mfa_devices" in method_args:
|
||||||
if len(amr) > 0:
|
amr.append(AMR_MFA)
|
||||||
amr.append(AMR_MFA)
|
|
||||||
if amr:
|
if amr:
|
||||||
id_token.amr = amr
|
id_token.amr = amr
|
||||||
|
|
||||||
|
@ -47,6 +47,8 @@ class IngressReconciler(KubernetesObjectReconciler[V1Ingress]):
|
|||||||
def reconcile(self, current: V1Ingress, reference: V1Ingress):
|
def reconcile(self, current: V1Ingress, reference: V1Ingress):
|
||||||
super().reconcile(current, reference)
|
super().reconcile(current, reference)
|
||||||
self._check_annotations(current, reference)
|
self._check_annotations(current, reference)
|
||||||
|
if current.spec.ingress_class_name != reference.spec.ingress_class_name:
|
||||||
|
raise NeedsUpdate()
|
||||||
# Create a list of all expected host and tls hosts
|
# Create a list of all expected host and tls hosts
|
||||||
expected_hosts = []
|
expected_hosts = []
|
||||||
expected_hosts_tls = []
|
expected_hosts_tls = []
|
||||||
|
@ -166,7 +166,6 @@ class ConnectionToken(ExpiringModel):
|
|||||||
always_merger.merge(settings, default_settings)
|
always_merger.merge(settings, default_settings)
|
||||||
always_merger.merge(settings, self.endpoint.provider.settings)
|
always_merger.merge(settings, self.endpoint.provider.settings)
|
||||||
always_merger.merge(settings, self.endpoint.settings)
|
always_merger.merge(settings, self.endpoint.settings)
|
||||||
always_merger.merge(settings, self.settings)
|
|
||||||
|
|
||||||
def mapping_evaluator(mappings: QuerySet):
|
def mapping_evaluator(mappings: QuerySet):
|
||||||
for mapping in mappings:
|
for mapping in mappings:
|
||||||
@ -191,6 +190,7 @@ class ConnectionToken(ExpiringModel):
|
|||||||
mapping_evaluator(
|
mapping_evaluator(
|
||||||
RACPropertyMapping.objects.filter(endpoint__in=[self.endpoint]).order_by("name")
|
RACPropertyMapping.objects.filter(endpoint__in=[self.endpoint]).order_by("name")
|
||||||
)
|
)
|
||||||
|
always_merger.merge(settings, self.settings)
|
||||||
|
|
||||||
settings["drive-path"] = f"/tmp/connection/{self.token}" # nosec
|
settings["drive-path"] = f"/tmp/connection/{self.token}" # nosec
|
||||||
settings["create-drive-path"] = "true"
|
settings["create-drive-path"] = "true"
|
||||||
|
@ -90,23 +90,6 @@ class TestModels(TransactionTestCase):
|
|||||||
"resize-method": "display-update",
|
"resize-method": "display-update",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
# Set settings in token
|
|
||||||
token.settings = {
|
|
||||||
"level": "token",
|
|
||||||
}
|
|
||||||
token.save()
|
|
||||||
self.assertEqual(
|
|
||||||
token.get_settings(),
|
|
||||||
{
|
|
||||||
"hostname": self.endpoint.host.split(":")[0],
|
|
||||||
"port": "1324",
|
|
||||||
"client-name": f"authentik - {self.user}",
|
|
||||||
"drive-path": path,
|
|
||||||
"create-drive-path": "true",
|
|
||||||
"level": "token",
|
|
||||||
"resize-method": "display-update",
|
|
||||||
},
|
|
||||||
)
|
|
||||||
# Set settings in property mapping (provider)
|
# Set settings in property mapping (provider)
|
||||||
mapping = RACPropertyMapping.objects.create(
|
mapping = RACPropertyMapping.objects.create(
|
||||||
name=generate_id(),
|
name=generate_id(),
|
||||||
@ -151,3 +134,22 @@ class TestModels(TransactionTestCase):
|
|||||||
"resize-method": "display-update",
|
"resize-method": "display-update",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
# Set settings in token
|
||||||
|
token.settings = {
|
||||||
|
"level": "token",
|
||||||
|
}
|
||||||
|
token.save()
|
||||||
|
self.assertEqual(
|
||||||
|
token.get_settings(),
|
||||||
|
{
|
||||||
|
"hostname": self.endpoint.host.split(":")[0],
|
||||||
|
"port": "1324",
|
||||||
|
"client-name": f"authentik - {self.user}",
|
||||||
|
"drive-path": path,
|
||||||
|
"create-drive-path": "true",
|
||||||
|
"foo": "true",
|
||||||
|
"bar": "6",
|
||||||
|
"resize-method": "display-update",
|
||||||
|
"level": "token",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
@ -20,6 +20,9 @@ from authentik.lib.utils.time import timedelta_from_string
|
|||||||
from authentik.policies.engine import PolicyEngine
|
from authentik.policies.engine import PolicyEngine
|
||||||
from authentik.policies.views import PolicyAccessView
|
from authentik.policies.views import PolicyAccessView
|
||||||
from authentik.providers.rac.models import ConnectionToken, Endpoint, RACProvider
|
from authentik.providers.rac.models import ConnectionToken, Endpoint, RACProvider
|
||||||
|
from authentik.stages.prompt.stage import PLAN_CONTEXT_PROMPT
|
||||||
|
|
||||||
|
PLAN_CONNECTION_SETTINGS = "connection_settings"
|
||||||
|
|
||||||
|
|
||||||
class RACStartView(PolicyAccessView):
|
class RACStartView(PolicyAccessView):
|
||||||
@ -109,10 +112,15 @@ class RACFinalStage(RedirectStage):
|
|||||||
return super().dispatch(request, *args, **kwargs)
|
return super().dispatch(request, *args, **kwargs)
|
||||||
|
|
||||||
def get_challenge(self, *args, **kwargs) -> RedirectChallenge:
|
def get_challenge(self, *args, **kwargs) -> RedirectChallenge:
|
||||||
|
settings = self.executor.plan.context.get(PLAN_CONNECTION_SETTINGS)
|
||||||
|
if not settings:
|
||||||
|
settings = self.executor.plan.context.get(PLAN_CONTEXT_PROMPT, {}).get(
|
||||||
|
PLAN_CONNECTION_SETTINGS
|
||||||
|
)
|
||||||
token = ConnectionToken.objects.create(
|
token = ConnectionToken.objects.create(
|
||||||
provider=self.provider,
|
provider=self.provider,
|
||||||
endpoint=self.endpoint,
|
endpoint=self.endpoint,
|
||||||
settings=self.executor.plan.context.get("connection_settings", {}),
|
settings=settings or {},
|
||||||
session=self.request.session["authenticatedsession"],
|
session=self.request.session["authenticatedsession"],
|
||||||
expires=now() + timedelta_from_string(self.provider.connection_expiry),
|
expires=now() + timedelta_from_string(self.provider.connection_expiry),
|
||||||
expiring=True,
|
expiring=True,
|
||||||
|
@ -47,15 +47,16 @@ class SCIMGroupClient(SCIMClient[Group, SCIMProviderGroup, SCIMGroupSchema]):
|
|||||||
|
|
||||||
def to_schema(self, obj: Group, connection: SCIMProviderGroup) -> SCIMGroupSchema:
|
def to_schema(self, obj: Group, connection: SCIMProviderGroup) -> SCIMGroupSchema:
|
||||||
"""Convert authentik user into SCIM"""
|
"""Convert authentik user into SCIM"""
|
||||||
raw_scim_group = super().to_schema(
|
raw_scim_group = super().to_schema(obj, connection)
|
||||||
obj,
|
|
||||||
connection,
|
|
||||||
schemas=(SCIM_GROUP_SCHEMA,),
|
|
||||||
)
|
|
||||||
try:
|
try:
|
||||||
scim_group = SCIMGroupSchema.model_validate(delete_none_values(raw_scim_group))
|
scim_group = SCIMGroupSchema.model_validate(delete_none_values(raw_scim_group))
|
||||||
except ValidationError as exc:
|
except ValidationError as exc:
|
||||||
raise StopSync(exc, obj) from exc
|
raise StopSync(exc, obj) from exc
|
||||||
|
if SCIM_GROUP_SCHEMA not in scim_group.schemas:
|
||||||
|
scim_group.schemas.insert(0, SCIM_GROUP_SCHEMA)
|
||||||
|
# As this might be unset, we need to tell pydantic it's set so ensure the schemas
|
||||||
|
# are included, even if its just the defaults
|
||||||
|
scim_group.schemas = list(scim_group.schemas)
|
||||||
if not scim_group.externalId:
|
if not scim_group.externalId:
|
||||||
scim_group.externalId = str(obj.pk)
|
scim_group.externalId = str(obj.pk)
|
||||||
|
|
||||||
|
@ -31,15 +31,16 @@ class SCIMUserClient(SCIMClient[User, SCIMProviderUser, SCIMUserSchema]):
|
|||||||
|
|
||||||
def to_schema(self, obj: User, connection: SCIMProviderUser) -> SCIMUserSchema:
|
def to_schema(self, obj: User, connection: SCIMProviderUser) -> SCIMUserSchema:
|
||||||
"""Convert authentik user into SCIM"""
|
"""Convert authentik user into SCIM"""
|
||||||
raw_scim_user = super().to_schema(
|
raw_scim_user = super().to_schema(obj, connection)
|
||||||
obj,
|
|
||||||
connection,
|
|
||||||
schemas=(SCIM_USER_SCHEMA,),
|
|
||||||
)
|
|
||||||
try:
|
try:
|
||||||
scim_user = SCIMUserSchema.model_validate(delete_none_values(raw_scim_user))
|
scim_user = SCIMUserSchema.model_validate(delete_none_values(raw_scim_user))
|
||||||
except ValidationError as exc:
|
except ValidationError as exc:
|
||||||
raise StopSync(exc, obj) from exc
|
raise StopSync(exc, obj) from exc
|
||||||
|
if SCIM_USER_SCHEMA not in scim_user.schemas:
|
||||||
|
scim_user.schemas.insert(0, SCIM_USER_SCHEMA)
|
||||||
|
# As this might be unset, we need to tell pydantic it's set so ensure the schemas
|
||||||
|
# are included, even if its just the defaults
|
||||||
|
scim_user.schemas = list(scim_user.schemas)
|
||||||
if not scim_user.externalId:
|
if not scim_user.externalId:
|
||||||
scim_user.externalId = str(obj.uid)
|
scim_user.externalId = str(obj.uid)
|
||||||
return scim_user
|
return scim_user
|
||||||
|
@ -91,6 +91,57 @@ class SCIMUserTests(TestCase):
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@Mocker()
|
||||||
|
def test_user_create_custom_schema(self, mock: Mocker):
|
||||||
|
"""Test user creation with custom schema"""
|
||||||
|
schema = SCIMMapping.objects.create(
|
||||||
|
name="custom_schema",
|
||||||
|
expression="""return {"schemas": ["foo"]}""",
|
||||||
|
)
|
||||||
|
self.provider.property_mappings.add(schema)
|
||||||
|
scim_id = generate_id()
|
||||||
|
mock.get(
|
||||||
|
"https://localhost/ServiceProviderConfig",
|
||||||
|
json={},
|
||||||
|
)
|
||||||
|
mock.post(
|
||||||
|
"https://localhost/Users",
|
||||||
|
json={
|
||||||
|
"id": scim_id,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
uid = generate_id()
|
||||||
|
user = User.objects.create(
|
||||||
|
username=uid,
|
||||||
|
name=f"{uid} {uid}",
|
||||||
|
email=f"{uid}@goauthentik.io",
|
||||||
|
)
|
||||||
|
self.assertEqual(mock.call_count, 2)
|
||||||
|
self.assertEqual(mock.request_history[0].method, "GET")
|
||||||
|
self.assertEqual(mock.request_history[1].method, "POST")
|
||||||
|
self.assertJSONEqual(
|
||||||
|
mock.request_history[1].body,
|
||||||
|
{
|
||||||
|
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:User", "foo"],
|
||||||
|
"active": True,
|
||||||
|
"emails": [
|
||||||
|
{
|
||||||
|
"primary": True,
|
||||||
|
"type": "other",
|
||||||
|
"value": f"{uid}@goauthentik.io",
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"externalId": user.uid,
|
||||||
|
"name": {
|
||||||
|
"familyName": uid,
|
||||||
|
"formatted": f"{uid} {uid}",
|
||||||
|
"givenName": uid,
|
||||||
|
},
|
||||||
|
"displayName": f"{uid} {uid}",
|
||||||
|
"userName": uid,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
@Mocker()
|
@Mocker()
|
||||||
def test_user_create_different_provider_same_id(self, mock: Mocker):
|
def test_user_create_different_provider_same_id(self, mock: Mocker):
|
||||||
"""Test user creation with multiple providers that happen
|
"""Test user creation with multiple providers that happen
|
||||||
@ -384,7 +435,7 @@ class SCIMUserTests(TestCase):
|
|||||||
self.assertIn(request.method, SAFE_METHODS)
|
self.assertIn(request.method, SAFE_METHODS)
|
||||||
task = SystemTask.objects.filter(uid=slugify(self.provider.name)).first()
|
task = SystemTask.objects.filter(uid=slugify(self.provider.name)).first()
|
||||||
self.assertIsNotNone(task)
|
self.assertIsNotNone(task)
|
||||||
drop_msg = task.messages[2]
|
drop_msg = task.messages[3]
|
||||||
self.assertEqual(drop_msg["event"], "Dropping mutating request due to dry run")
|
self.assertEqual(drop_msg["event"], "Dropping mutating request due to dry run")
|
||||||
self.assertIsNotNone(drop_msg["attributes"]["url"])
|
self.assertIsNotNone(drop_msg["attributes"]["url"])
|
||||||
self.assertIsNotNone(drop_msg["attributes"]["body"])
|
self.assertIsNotNone(drop_msg["attributes"]["body"])
|
||||||
|
@ -132,7 +132,7 @@ TENANT_CREATION_FAKES_MIGRATIONS = True
|
|||||||
TENANT_BASE_SCHEMA = "template"
|
TENANT_BASE_SCHEMA = "template"
|
||||||
PUBLIC_SCHEMA_NAME = CONFIG.get("postgresql.default_schema")
|
PUBLIC_SCHEMA_NAME = CONFIG.get("postgresql.default_schema")
|
||||||
|
|
||||||
GUARDIAN_MONKEY_PATCH = False
|
GUARDIAN_MONKEY_PATCH_USER = False
|
||||||
|
|
||||||
SPECTACULAR_SETTINGS = {
|
SPECTACULAR_SETTINGS = {
|
||||||
"TITLE": "authentik",
|
"TITLE": "authentik",
|
||||||
@ -424,7 +424,7 @@ else:
|
|||||||
"BACKEND": "authentik.root.storages.FileStorage",
|
"BACKEND": "authentik.root.storages.FileStorage",
|
||||||
"OPTIONS": {
|
"OPTIONS": {
|
||||||
"location": Path(CONFIG.get("storage.media.file.path")),
|
"location": Path(CONFIG.get("storage.media.file.path")),
|
||||||
"base_url": "/media/",
|
"base_url": CONFIG.get("web.path", "/") + "media/",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
# Compatibility for apps not supporting top-level STORAGES
|
# Compatibility for apps not supporting top-level STORAGES
|
||||||
|
@ -3,25 +3,44 @@
|
|||||||
import os
|
import os
|
||||||
from argparse import ArgumentParser
|
from argparse import ArgumentParser
|
||||||
from unittest import TestCase
|
from unittest import TestCase
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.test.runner import DiscoverRunner
|
from django.test.runner import DiscoverRunner
|
||||||
|
from structlog.stdlib import get_logger
|
||||||
|
|
||||||
from authentik.lib.config import CONFIG
|
from authentik.lib.config import CONFIG
|
||||||
from authentik.lib.sentry import sentry_init
|
from authentik.lib.sentry import sentry_init
|
||||||
from authentik.root.signals import post_startup, pre_startup, startup
|
from authentik.root.signals import post_startup, pre_startup, startup
|
||||||
from tests.e2e.utils import get_docker_tag
|
|
||||||
|
|
||||||
# globally set maxDiff to none to show full assert error
|
# globally set maxDiff to none to show full assert error
|
||||||
TestCase.maxDiff = None
|
TestCase.maxDiff = None
|
||||||
|
|
||||||
|
|
||||||
|
def get_docker_tag() -> str:
|
||||||
|
"""Get docker-tag based off of CI variables"""
|
||||||
|
env_pr_branch = "GITHUB_HEAD_REF"
|
||||||
|
default_branch = "GITHUB_REF"
|
||||||
|
branch_name = os.environ.get(default_branch, "main")
|
||||||
|
if os.environ.get(env_pr_branch, "") != "":
|
||||||
|
branch_name = os.environ[env_pr_branch]
|
||||||
|
branch_name = branch_name.replace("refs/heads/", "").replace("/", "-")
|
||||||
|
return f"gh-{branch_name}"
|
||||||
|
|
||||||
|
|
||||||
|
def patched__get_ct_cached(app_label, codename):
|
||||||
|
"""Caches `ContentType` instances like its `QuerySet` does."""
|
||||||
|
return ContentType.objects.get(app_label=app_label, permission__codename=codename)
|
||||||
|
|
||||||
|
|
||||||
class PytestTestRunner(DiscoverRunner): # pragma: no cover
|
class PytestTestRunner(DiscoverRunner): # pragma: no cover
|
||||||
"""Runs pytest to discover and run tests."""
|
"""Runs pytest to discover and run tests."""
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
|
self.logger = get_logger().bind(runner="pytest")
|
||||||
|
|
||||||
self.args = []
|
self.args = []
|
||||||
if self.failfast:
|
if self.failfast:
|
||||||
@ -31,6 +50,8 @@ class PytestTestRunner(DiscoverRunner): # pragma: no cover
|
|||||||
|
|
||||||
if kwargs.get("randomly_seed", None):
|
if kwargs.get("randomly_seed", None):
|
||||||
self.args.append(f"--randomly-seed={kwargs['randomly_seed']}")
|
self.args.append(f"--randomly-seed={kwargs['randomly_seed']}")
|
||||||
|
if kwargs.get("no_capture", False):
|
||||||
|
self.args.append("--capture=no")
|
||||||
|
|
||||||
settings.TEST = True
|
settings.TEST = True
|
||||||
settings.CELERY["task_always_eager"] = True
|
settings.CELERY["task_always_eager"] = True
|
||||||
@ -64,6 +85,11 @@ class PytestTestRunner(DiscoverRunner): # pragma: no cover
|
|||||||
"Default behaviour: use random.Random().getrandbits(32), so the seed is"
|
"Default behaviour: use random.Random().getrandbits(32), so the seed is"
|
||||||
"different on each run.",
|
"different on each run.",
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--no-capture",
|
||||||
|
action="store_true",
|
||||||
|
help="Disable any capturing of stdout/stderr during tests.",
|
||||||
|
)
|
||||||
|
|
||||||
def run_tests(self, test_labels, extra_tests=None, **kwargs):
|
def run_tests(self, test_labels, extra_tests=None, **kwargs):
|
||||||
"""Run pytest and return the exitcode.
|
"""Run pytest and return the exitcode.
|
||||||
@ -106,4 +132,10 @@ class PytestTestRunner(DiscoverRunner): # pragma: no cover
|
|||||||
f"path instead."
|
f"path instead."
|
||||||
)
|
)
|
||||||
|
|
||||||
return pytest.main(self.args)
|
self.logger.info("Running tests", test_files=self.args)
|
||||||
|
with patch("guardian.shortcuts._get_ct_cached", patched__get_ct_cached):
|
||||||
|
try:
|
||||||
|
return pytest.main(self.args)
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error("Error running tests", error=str(e), test_files=self.args)
|
||||||
|
return 1
|
||||||
|
@ -103,6 +103,7 @@ class LDAPSourceSerializer(SourceSerializer):
|
|||||||
"user_object_filter",
|
"user_object_filter",
|
||||||
"group_object_filter",
|
"group_object_filter",
|
||||||
"group_membership_field",
|
"group_membership_field",
|
||||||
|
"user_membership_attribute",
|
||||||
"object_uniqueness_field",
|
"object_uniqueness_field",
|
||||||
"password_login_update_internal_password",
|
"password_login_update_internal_password",
|
||||||
"sync_users",
|
"sync_users",
|
||||||
@ -111,6 +112,7 @@ class LDAPSourceSerializer(SourceSerializer):
|
|||||||
"sync_parent_group",
|
"sync_parent_group",
|
||||||
"connectivity",
|
"connectivity",
|
||||||
"lookup_groups_from_user",
|
"lookup_groups_from_user",
|
||||||
|
"delete_not_found_objects",
|
||||||
]
|
]
|
||||||
extra_kwargs = {"bind_password": {"write_only": True}}
|
extra_kwargs = {"bind_password": {"write_only": True}}
|
||||||
|
|
||||||
@ -138,6 +140,7 @@ class LDAPSourceViewSet(UsedByMixin, ModelViewSet):
|
|||||||
"user_object_filter",
|
"user_object_filter",
|
||||||
"group_object_filter",
|
"group_object_filter",
|
||||||
"group_membership_field",
|
"group_membership_field",
|
||||||
|
"user_membership_attribute",
|
||||||
"object_uniqueness_field",
|
"object_uniqueness_field",
|
||||||
"password_login_update_internal_password",
|
"password_login_update_internal_password",
|
||||||
"sync_users",
|
"sync_users",
|
||||||
@ -147,6 +150,7 @@ class LDAPSourceViewSet(UsedByMixin, ModelViewSet):
|
|||||||
"user_property_mappings",
|
"user_property_mappings",
|
||||||
"group_property_mappings",
|
"group_property_mappings",
|
||||||
"lookup_groups_from_user",
|
"lookup_groups_from_user",
|
||||||
|
"delete_not_found_objects",
|
||||||
]
|
]
|
||||||
search_fields = ["name", "slug"]
|
search_fields = ["name", "slug"]
|
||||||
ordering = ["name"]
|
ordering = ["name"]
|
||||||
|
@ -0,0 +1,48 @@
|
|||||||
|
# Generated by Django 5.1.9 on 2025-05-28 08:15
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("authentik_core", "0048_delete_oldauthenticatedsession_content_type"),
|
||||||
|
("authentik_sources_ldap", "0008_groupldapsourceconnection_userldapsourceconnection"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="groupldapsourceconnection",
|
||||||
|
name="validated_by",
|
||||||
|
field=models.UUIDField(
|
||||||
|
blank=True,
|
||||||
|
help_text="Unique ID used while checking if this object still exists in the directory.",
|
||||||
|
null=True,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="ldapsource",
|
||||||
|
name="delete_not_found_objects",
|
||||||
|
field=models.BooleanField(
|
||||||
|
default=False,
|
||||||
|
help_text="Delete authentik users and groups which were previously supplied by this source, but are now missing from it.",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="userldapsourceconnection",
|
||||||
|
name="validated_by",
|
||||||
|
field=models.UUIDField(
|
||||||
|
blank=True,
|
||||||
|
help_text="Unique ID used while checking if this object still exists in the directory.",
|
||||||
|
null=True,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddIndex(
|
||||||
|
model_name="groupldapsourceconnection",
|
||||||
|
index=models.Index(fields=["validated_by"], name="authentik_s_validat_b70447_idx"),
|
||||||
|
),
|
||||||
|
migrations.AddIndex(
|
||||||
|
model_name="userldapsourceconnection",
|
||||||
|
index=models.Index(fields=["validated_by"], name="authentik_s_validat_ff2ebc_idx"),
|
||||||
|
),
|
||||||
|
]
|
@ -0,0 +1,32 @@
|
|||||||
|
# Generated by Django 5.1.9 on 2025-05-29 11:22
|
||||||
|
|
||||||
|
from django.apps.registry import Apps
|
||||||
|
from django.db import migrations, models
|
||||||
|
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
|
||||||
|
|
||||||
|
|
||||||
|
def set_user_membership_attribute(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
|
||||||
|
LDAPSource = apps.get_model("authentik_sources_ldap", "LDAPSource")
|
||||||
|
db_alias = schema_editor.connection.alias
|
||||||
|
|
||||||
|
LDAPSource.objects.using(db_alias).filter(group_membership_field="memberUid").all().update(
|
||||||
|
user_membership_attribute="ldap_uniq"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("authentik_sources_ldap", "0009_groupldapsourceconnection_validated_by_and_more"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="ldapsource",
|
||||||
|
name="user_membership_attribute",
|
||||||
|
field=models.TextField(
|
||||||
|
default="distinguishedName",
|
||||||
|
help_text="Attribute which matches the value of `group_membership_field`.",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.RunPython(set_user_membership_attribute, migrations.RunPython.noop),
|
||||||
|
]
|
@ -100,6 +100,10 @@ class LDAPSource(Source):
|
|||||||
default="(objectClass=person)",
|
default="(objectClass=person)",
|
||||||
help_text=_("Consider Objects matching this filter to be Users."),
|
help_text=_("Consider Objects matching this filter to be Users."),
|
||||||
)
|
)
|
||||||
|
user_membership_attribute = models.TextField(
|
||||||
|
default=LDAP_DISTINGUISHED_NAME,
|
||||||
|
help_text=_("Attribute which matches the value of `group_membership_field`."),
|
||||||
|
)
|
||||||
group_membership_field = models.TextField(
|
group_membership_field = models.TextField(
|
||||||
default="member", help_text=_("Field which contains members of a group.")
|
default="member", help_text=_("Field which contains members of a group.")
|
||||||
)
|
)
|
||||||
@ -137,6 +141,14 @@ class LDAPSource(Source):
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
delete_not_found_objects = models.BooleanField(
|
||||||
|
default=False,
|
||||||
|
help_text=_(
|
||||||
|
"Delete authentik users and groups which were previously supplied by this source, "
|
||||||
|
"but are now missing from it."
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def component(self) -> str:
|
def component(self) -> str:
|
||||||
return "ak-source-ldap-form"
|
return "ak-source-ldap-form"
|
||||||
@ -321,6 +333,12 @@ class LDAPSourcePropertyMapping(PropertyMapping):
|
|||||||
|
|
||||||
|
|
||||||
class UserLDAPSourceConnection(UserSourceConnection):
|
class UserLDAPSourceConnection(UserSourceConnection):
|
||||||
|
validated_by = models.UUIDField(
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
help_text=_("Unique ID used while checking if this object still exists in the directory."),
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def serializer(self) -> type[Serializer]:
|
def serializer(self) -> type[Serializer]:
|
||||||
from authentik.sources.ldap.api import (
|
from authentik.sources.ldap.api import (
|
||||||
@ -332,9 +350,18 @@ class UserLDAPSourceConnection(UserSourceConnection):
|
|||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _("User LDAP Source Connection")
|
verbose_name = _("User LDAP Source Connection")
|
||||||
verbose_name_plural = _("User LDAP Source Connections")
|
verbose_name_plural = _("User LDAP Source Connections")
|
||||||
|
indexes = [
|
||||||
|
models.Index(fields=["validated_by"]),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class GroupLDAPSourceConnection(GroupSourceConnection):
|
class GroupLDAPSourceConnection(GroupSourceConnection):
|
||||||
|
validated_by = models.UUIDField(
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
help_text=_("Unique ID used while checking if this object still exists in the directory."),
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def serializer(self) -> type[Serializer]:
|
def serializer(self) -> type[Serializer]:
|
||||||
from authentik.sources.ldap.api import (
|
from authentik.sources.ldap.api import (
|
||||||
@ -346,3 +373,6 @@ class GroupLDAPSourceConnection(GroupSourceConnection):
|
|||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _("Group LDAP Source Connection")
|
verbose_name = _("Group LDAP Source Connection")
|
||||||
verbose_name_plural = _("Group LDAP Source Connections")
|
verbose_name_plural = _("Group LDAP Source Connections")
|
||||||
|
indexes = [
|
||||||
|
models.Index(fields=["validated_by"]),
|
||||||
|
]
|
||||||
|
@ -9,7 +9,7 @@ from structlog.stdlib import BoundLogger, get_logger
|
|||||||
from authentik.core.sources.mapper import SourceMapper
|
from authentik.core.sources.mapper import SourceMapper
|
||||||
from authentik.lib.config import CONFIG
|
from authentik.lib.config import CONFIG
|
||||||
from authentik.lib.sync.mapper import PropertyMappingManager
|
from authentik.lib.sync.mapper import PropertyMappingManager
|
||||||
from authentik.sources.ldap.models import LDAPSource
|
from authentik.sources.ldap.models import LDAPSource, flatten
|
||||||
|
|
||||||
|
|
||||||
class BaseLDAPSynchronizer:
|
class BaseLDAPSynchronizer:
|
||||||
@ -77,6 +77,16 @@ class BaseLDAPSynchronizer:
|
|||||||
"""Get objects from LDAP, implemented in subclass"""
|
"""Get objects from LDAP, implemented in subclass"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def get_attributes(self, object):
|
||||||
|
if "attributes" not in object:
|
||||||
|
return
|
||||||
|
return object.get("attributes", {})
|
||||||
|
|
||||||
|
def get_identifier(self, attributes: dict):
|
||||||
|
if not attributes.get(self._source.object_uniqueness_field):
|
||||||
|
return
|
||||||
|
return flatten(attributes[self._source.object_uniqueness_field])
|
||||||
|
|
||||||
def search_paginator( # noqa: PLR0913
|
def search_paginator( # noqa: PLR0913
|
||||||
self,
|
self,
|
||||||
search_base,
|
search_base,
|
||||||
|
61
authentik/sources/ldap/sync/forward_delete_groups.py
Normal file
61
authentik/sources/ldap/sync/forward_delete_groups.py
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
from collections.abc import Generator
|
||||||
|
from itertools import batched
|
||||||
|
from uuid import uuid4
|
||||||
|
|
||||||
|
from ldap3 import SUBTREE
|
||||||
|
|
||||||
|
from authentik.core.models import Group
|
||||||
|
from authentik.sources.ldap.models import GroupLDAPSourceConnection
|
||||||
|
from authentik.sources.ldap.sync.base import BaseLDAPSynchronizer
|
||||||
|
from authentik.sources.ldap.sync.forward_delete_users import DELETE_CHUNK_SIZE, UPDATE_CHUNK_SIZE
|
||||||
|
|
||||||
|
|
||||||
|
class GroupLDAPForwardDeletion(BaseLDAPSynchronizer):
|
||||||
|
"""Delete LDAP Groups from authentik"""
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def name() -> str:
|
||||||
|
return "group_deletions"
|
||||||
|
|
||||||
|
def get_objects(self, **kwargs) -> Generator:
|
||||||
|
if not self._source.sync_groups or not self._source.delete_not_found_objects:
|
||||||
|
self.message("Group syncing is disabled for this Source")
|
||||||
|
return iter(())
|
||||||
|
|
||||||
|
uuid = uuid4()
|
||||||
|
groups = self._source.connection().extend.standard.paged_search(
|
||||||
|
search_base=self.base_dn_groups,
|
||||||
|
search_filter=self._source.group_object_filter,
|
||||||
|
search_scope=SUBTREE,
|
||||||
|
attributes=[self._source.object_uniqueness_field],
|
||||||
|
generator=True,
|
||||||
|
**kwargs,
|
||||||
|
)
|
||||||
|
for batch in batched(groups, UPDATE_CHUNK_SIZE, strict=False):
|
||||||
|
identifiers = []
|
||||||
|
for group in batch:
|
||||||
|
if not (attributes := self.get_attributes(group)):
|
||||||
|
continue
|
||||||
|
if identifier := self.get_identifier(attributes):
|
||||||
|
identifiers.append(identifier)
|
||||||
|
GroupLDAPSourceConnection.objects.filter(identifier__in=identifiers).update(
|
||||||
|
validated_by=uuid
|
||||||
|
)
|
||||||
|
|
||||||
|
return batched(
|
||||||
|
GroupLDAPSourceConnection.objects.filter(source=self._source)
|
||||||
|
.exclude(validated_by=uuid)
|
||||||
|
.values_list("group", flat=True)
|
||||||
|
.iterator(chunk_size=DELETE_CHUNK_SIZE),
|
||||||
|
DELETE_CHUNK_SIZE,
|
||||||
|
strict=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
def sync(self, group_pks: tuple) -> int:
|
||||||
|
"""Delete authentik groups"""
|
||||||
|
if not self._source.sync_groups or not self._source.delete_not_found_objects:
|
||||||
|
self.message("Group syncing is disabled for this Source")
|
||||||
|
return -1
|
||||||
|
self._logger.debug("Deleting groups", group_pks=group_pks)
|
||||||
|
_, deleted_per_type = Group.objects.filter(pk__in=group_pks).delete()
|
||||||
|
return deleted_per_type.get(Group._meta.label, 0)
|
63
authentik/sources/ldap/sync/forward_delete_users.py
Normal file
63
authentik/sources/ldap/sync/forward_delete_users.py
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
from collections.abc import Generator
|
||||||
|
from itertools import batched
|
||||||
|
from uuid import uuid4
|
||||||
|
|
||||||
|
from ldap3 import SUBTREE
|
||||||
|
|
||||||
|
from authentik.core.models import User
|
||||||
|
from authentik.sources.ldap.models import UserLDAPSourceConnection
|
||||||
|
from authentik.sources.ldap.sync.base import BaseLDAPSynchronizer
|
||||||
|
|
||||||
|
UPDATE_CHUNK_SIZE = 10_000
|
||||||
|
DELETE_CHUNK_SIZE = 50
|
||||||
|
|
||||||
|
|
||||||
|
class UserLDAPForwardDeletion(BaseLDAPSynchronizer):
|
||||||
|
"""Delete LDAP Users from authentik"""
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def name() -> str:
|
||||||
|
return "user_deletions"
|
||||||
|
|
||||||
|
def get_objects(self, **kwargs) -> Generator:
|
||||||
|
if not self._source.sync_users or not self._source.delete_not_found_objects:
|
||||||
|
self.message("User syncing is disabled for this Source")
|
||||||
|
return iter(())
|
||||||
|
|
||||||
|
uuid = uuid4()
|
||||||
|
users = self._source.connection().extend.standard.paged_search(
|
||||||
|
search_base=self.base_dn_users,
|
||||||
|
search_filter=self._source.user_object_filter,
|
||||||
|
search_scope=SUBTREE,
|
||||||
|
attributes=[self._source.object_uniqueness_field],
|
||||||
|
generator=True,
|
||||||
|
**kwargs,
|
||||||
|
)
|
||||||
|
for batch in batched(users, UPDATE_CHUNK_SIZE, strict=False):
|
||||||
|
identifiers = []
|
||||||
|
for user in batch:
|
||||||
|
if not (attributes := self.get_attributes(user)):
|
||||||
|
continue
|
||||||
|
if identifier := self.get_identifier(attributes):
|
||||||
|
identifiers.append(identifier)
|
||||||
|
UserLDAPSourceConnection.objects.filter(identifier__in=identifiers).update(
|
||||||
|
validated_by=uuid
|
||||||
|
)
|
||||||
|
|
||||||
|
return batched(
|
||||||
|
UserLDAPSourceConnection.objects.filter(source=self._source)
|
||||||
|
.exclude(validated_by=uuid)
|
||||||
|
.values_list("user", flat=True)
|
||||||
|
.iterator(chunk_size=DELETE_CHUNK_SIZE),
|
||||||
|
DELETE_CHUNK_SIZE,
|
||||||
|
strict=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
def sync(self, user_pks: tuple) -> int:
|
||||||
|
"""Delete authentik users"""
|
||||||
|
if not self._source.sync_users or not self._source.delete_not_found_objects:
|
||||||
|
self.message("User syncing is disabled for this Source")
|
||||||
|
return -1
|
||||||
|
self._logger.debug("Deleting users", user_pks=user_pks)
|
||||||
|
_, deleted_per_type = User.objects.filter(pk__in=user_pks).delete()
|
||||||
|
return deleted_per_type.get(User._meta.label, 0)
|
@ -58,18 +58,16 @@ class GroupLDAPSynchronizer(BaseLDAPSynchronizer):
|
|||||||
return -1
|
return -1
|
||||||
group_count = 0
|
group_count = 0
|
||||||
for group in page_data:
|
for group in page_data:
|
||||||
if "attributes" not in group:
|
if (attributes := self.get_attributes(group)) is None:
|
||||||
continue
|
continue
|
||||||
attributes = group.get("attributes", {})
|
|
||||||
group_dn = flatten(flatten(group.get("entryDN", group.get("dn"))))
|
group_dn = flatten(flatten(group.get("entryDN", group.get("dn"))))
|
||||||
if not attributes.get(self._source.object_uniqueness_field):
|
if not (uniq := self.get_identifier(attributes)):
|
||||||
self.message(
|
self.message(
|
||||||
f"Uniqueness field not found/not set in attributes: '{group_dn}'",
|
f"Uniqueness field not found/not set in attributes: '{group_dn}'",
|
||||||
attributes=attributes.keys(),
|
attributes=attributes.keys(),
|
||||||
dn=group_dn,
|
dn=group_dn,
|
||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
uniq = flatten(attributes[self._source.object_uniqueness_field])
|
|
||||||
try:
|
try:
|
||||||
defaults = {
|
defaults = {
|
||||||
k: flatten(v)
|
k: flatten(v)
|
||||||
|
@ -63,25 +63,19 @@ class MembershipLDAPSynchronizer(BaseLDAPSynchronizer):
|
|||||||
group_member_dn = group_member.get("dn", {})
|
group_member_dn = group_member.get("dn", {})
|
||||||
members.append(group_member_dn)
|
members.append(group_member_dn)
|
||||||
else:
|
else:
|
||||||
if "attributes" not in group:
|
if (attributes := self.get_attributes(group)) is None:
|
||||||
continue
|
continue
|
||||||
members = group.get("attributes", {}).get(self._source.group_membership_field, [])
|
members = attributes.get(self._source.group_membership_field, [])
|
||||||
|
|
||||||
ak_group = self.get_group(group)
|
ak_group = self.get_group(group)
|
||||||
if not ak_group:
|
if not ak_group:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
membership_mapping_attribute = LDAP_DISTINGUISHED_NAME
|
|
||||||
if self._source.group_membership_field == "memberUid":
|
|
||||||
# If memberships are based on the posixGroup's 'memberUid'
|
|
||||||
# attribute we use the RDN instead of the FDN to lookup members.
|
|
||||||
membership_mapping_attribute = LDAP_UNIQUENESS
|
|
||||||
|
|
||||||
users = User.objects.filter(
|
users = User.objects.filter(
|
||||||
Q(**{f"attributes__{membership_mapping_attribute}__in": members})
|
Q(**{f"attributes__{self._source.user_membership_attribute}__in": members})
|
||||||
| Q(
|
| Q(
|
||||||
**{
|
**{
|
||||||
f"attributes__{membership_mapping_attribute}__isnull": True,
|
f"attributes__{self._source.user_membership_attribute}__isnull": True,
|
||||||
"ak_groups__in": [ak_group],
|
"ak_groups__in": [ak_group],
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -60,18 +60,16 @@ class UserLDAPSynchronizer(BaseLDAPSynchronizer):
|
|||||||
return -1
|
return -1
|
||||||
user_count = 0
|
user_count = 0
|
||||||
for user in page_data:
|
for user in page_data:
|
||||||
if "attributes" not in user:
|
if (attributes := self.get_attributes(user)) is None:
|
||||||
continue
|
continue
|
||||||
attributes = user.get("attributes", {})
|
|
||||||
user_dn = flatten(user.get("entryDN", user.get("dn")))
|
user_dn = flatten(user.get("entryDN", user.get("dn")))
|
||||||
if not attributes.get(self._source.object_uniqueness_field):
|
if not (uniq := self.get_identifier(attributes)):
|
||||||
self.message(
|
self.message(
|
||||||
f"Uniqueness field not found/not set in attributes: '{user_dn}'",
|
f"Uniqueness field not found/not set in attributes: '{user_dn}'",
|
||||||
attributes=attributes.keys(),
|
attributes=attributes.keys(),
|
||||||
dn=user_dn,
|
dn=user_dn,
|
||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
uniq = flatten(attributes[self._source.object_uniqueness_field])
|
|
||||||
try:
|
try:
|
||||||
defaults = {
|
defaults = {
|
||||||
k: flatten(v)
|
k: flatten(v)
|
||||||
|
@ -17,6 +17,8 @@ from authentik.lib.utils.reflection import class_to_path, path_to_class
|
|||||||
from authentik.root.celery import CELERY_APP
|
from authentik.root.celery import CELERY_APP
|
||||||
from authentik.sources.ldap.models import LDAPSource
|
from authentik.sources.ldap.models import LDAPSource
|
||||||
from authentik.sources.ldap.sync.base import BaseLDAPSynchronizer
|
from authentik.sources.ldap.sync.base import BaseLDAPSynchronizer
|
||||||
|
from authentik.sources.ldap.sync.forward_delete_groups import GroupLDAPForwardDeletion
|
||||||
|
from authentik.sources.ldap.sync.forward_delete_users import UserLDAPForwardDeletion
|
||||||
from authentik.sources.ldap.sync.groups import GroupLDAPSynchronizer
|
from authentik.sources.ldap.sync.groups import GroupLDAPSynchronizer
|
||||||
from authentik.sources.ldap.sync.membership import MembershipLDAPSynchronizer
|
from authentik.sources.ldap.sync.membership import MembershipLDAPSynchronizer
|
||||||
from authentik.sources.ldap.sync.users import UserLDAPSynchronizer
|
from authentik.sources.ldap.sync.users import UserLDAPSynchronizer
|
||||||
@ -52,11 +54,11 @@ def ldap_connectivity_check(pk: str | None = None):
|
|||||||
|
|
||||||
|
|
||||||
@CELERY_APP.task(
|
@CELERY_APP.task(
|
||||||
# We take the configured hours timeout time by 2.5 as we run user and
|
# We take the configured hours timeout time by 3.5 as we run user and
|
||||||
# group in parallel and then membership, so 2x is to cover the serial tasks,
|
# group in parallel and then membership, then deletions, so 3x is to cover the serial tasks,
|
||||||
# and 0.5x on top of that to give some more leeway
|
# and 0.5x on top of that to give some more leeway
|
||||||
soft_time_limit=(60 * 60 * CONFIG.get_int("ldap.task_timeout_hours")) * 2.5,
|
soft_time_limit=(60 * 60 * CONFIG.get_int("ldap.task_timeout_hours")) * 3.5,
|
||||||
task_time_limit=(60 * 60 * CONFIG.get_int("ldap.task_timeout_hours")) * 2.5,
|
task_time_limit=(60 * 60 * CONFIG.get_int("ldap.task_timeout_hours")) * 3.5,
|
||||||
)
|
)
|
||||||
def ldap_sync_single(source_pk: str):
|
def ldap_sync_single(source_pk: str):
|
||||||
"""Sync a single source"""
|
"""Sync a single source"""
|
||||||
@ -79,6 +81,25 @@ def ldap_sync_single(source_pk: str):
|
|||||||
group(
|
group(
|
||||||
ldap_sync_paginator(source, MembershipLDAPSynchronizer),
|
ldap_sync_paginator(source, MembershipLDAPSynchronizer),
|
||||||
),
|
),
|
||||||
|
# Finally, deletions. What we'd really like to do here is something like
|
||||||
|
# ```
|
||||||
|
# user_identifiers = <ldap query>
|
||||||
|
# User.objects.exclude(
|
||||||
|
# usersourceconnection__identifier__in=user_uniqueness_identifiers,
|
||||||
|
# ).delete()
|
||||||
|
# ```
|
||||||
|
# This runs into performance issues in large installations. So instead we spread the
|
||||||
|
# work out into three steps:
|
||||||
|
# 1. Get every object from the LDAP source.
|
||||||
|
# 2. Mark every object as "safe" in the database. This is quick, but any error could
|
||||||
|
# mean deleting users which should not be deleted, so we do it immediately, in
|
||||||
|
# large chunks, and only queue the deletion step afterwards.
|
||||||
|
# 3. Delete every unmarked item. This is slow, so we spread it over many tasks in
|
||||||
|
# small chunks.
|
||||||
|
group(
|
||||||
|
ldap_sync_paginator(source, UserLDAPForwardDeletion)
|
||||||
|
+ ldap_sync_paginator(source, GroupLDAPForwardDeletion),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
task()
|
task()
|
||||||
|
|
||||||
|
@ -2,6 +2,33 @@
|
|||||||
|
|
||||||
from ldap3 import MOCK_SYNC, OFFLINE_SLAPD_2_4, Connection, Server
|
from ldap3 import MOCK_SYNC, OFFLINE_SLAPD_2_4, Connection, Server
|
||||||
|
|
||||||
|
# The mock modifies these in place, so we have to define them per string
|
||||||
|
user_in_slapd_dn = "cn=user_in_slapd_cn,ou=users,dc=goauthentik,dc=io"
|
||||||
|
user_in_slapd_cn = "user_in_slapd_cn"
|
||||||
|
user_in_slapd_uid = "user_in_slapd_uid"
|
||||||
|
user_in_slapd_object_class = "person"
|
||||||
|
user_in_slapd = {
|
||||||
|
"dn": user_in_slapd_dn,
|
||||||
|
"attributes": {
|
||||||
|
"cn": user_in_slapd_cn,
|
||||||
|
"uid": user_in_slapd_uid,
|
||||||
|
"objectClass": user_in_slapd_object_class,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
group_in_slapd_dn = "cn=user_in_slapd_cn,ou=groups,dc=goauthentik,dc=io"
|
||||||
|
group_in_slapd_cn = "group_in_slapd_cn"
|
||||||
|
group_in_slapd_uid = "group_in_slapd_uid"
|
||||||
|
group_in_slapd_object_class = "groupOfNames"
|
||||||
|
group_in_slapd = {
|
||||||
|
"dn": group_in_slapd_dn,
|
||||||
|
"attributes": {
|
||||||
|
"cn": group_in_slapd_cn,
|
||||||
|
"uid": group_in_slapd_uid,
|
||||||
|
"objectClass": group_in_slapd_object_class,
|
||||||
|
"member": [user_in_slapd["dn"]],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def mock_slapd_connection(password: str) -> Connection:
|
def mock_slapd_connection(password: str) -> Connection:
|
||||||
"""Create mock SLAPD connection"""
|
"""Create mock SLAPD connection"""
|
||||||
@ -96,5 +123,14 @@ def mock_slapd_connection(password: str) -> Connection:
|
|||||||
"objectClass": "posixAccount",
|
"objectClass": "posixAccount",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
# Known user and group
|
||||||
|
connection.strategy.add_entry(
|
||||||
|
user_in_slapd["dn"],
|
||||||
|
user_in_slapd["attributes"],
|
||||||
|
)
|
||||||
|
connection.strategy.add_entry(
|
||||||
|
group_in_slapd["dn"],
|
||||||
|
group_in_slapd["attributes"],
|
||||||
|
)
|
||||||
connection.bind()
|
connection.bind()
|
||||||
return connection
|
return connection
|
||||||
|
@ -13,14 +13,26 @@ from authentik.events.system_tasks import TaskStatus
|
|||||||
from authentik.lib.generators import generate_id, generate_key
|
from authentik.lib.generators import generate_id, generate_key
|
||||||
from authentik.lib.sync.outgoing.exceptions import StopSync
|
from authentik.lib.sync.outgoing.exceptions import StopSync
|
||||||
from authentik.lib.utils.reflection import class_to_path
|
from authentik.lib.utils.reflection import class_to_path
|
||||||
from authentik.sources.ldap.models import LDAPSource, LDAPSourcePropertyMapping
|
from authentik.sources.ldap.models import (
|
||||||
|
GroupLDAPSourceConnection,
|
||||||
|
LDAPSource,
|
||||||
|
LDAPSourcePropertyMapping,
|
||||||
|
UserLDAPSourceConnection,
|
||||||
|
)
|
||||||
|
from authentik.sources.ldap.sync.forward_delete_users import DELETE_CHUNK_SIZE
|
||||||
from authentik.sources.ldap.sync.groups import GroupLDAPSynchronizer
|
from authentik.sources.ldap.sync.groups import GroupLDAPSynchronizer
|
||||||
from authentik.sources.ldap.sync.membership import MembershipLDAPSynchronizer
|
from authentik.sources.ldap.sync.membership import MembershipLDAPSynchronizer
|
||||||
from authentik.sources.ldap.sync.users import UserLDAPSynchronizer
|
from authentik.sources.ldap.sync.users import UserLDAPSynchronizer
|
||||||
from authentik.sources.ldap.tasks import ldap_sync, ldap_sync_all
|
from authentik.sources.ldap.tasks import ldap_sync, ldap_sync_all
|
||||||
from authentik.sources.ldap.tests.mock_ad import mock_ad_connection
|
from authentik.sources.ldap.tests.mock_ad import mock_ad_connection
|
||||||
from authentik.sources.ldap.tests.mock_freeipa import mock_freeipa_connection
|
from authentik.sources.ldap.tests.mock_freeipa import mock_freeipa_connection
|
||||||
from authentik.sources.ldap.tests.mock_slapd import mock_slapd_connection
|
from authentik.sources.ldap.tests.mock_slapd import (
|
||||||
|
group_in_slapd_cn,
|
||||||
|
group_in_slapd_uid,
|
||||||
|
mock_slapd_connection,
|
||||||
|
user_in_slapd_cn,
|
||||||
|
user_in_slapd_uid,
|
||||||
|
)
|
||||||
|
|
||||||
LDAP_PASSWORD = generate_key()
|
LDAP_PASSWORD = generate_key()
|
||||||
|
|
||||||
@ -257,12 +269,56 @@ class LDAPSyncTests(TestCase):
|
|||||||
self.source.group_membership_field = "memberUid"
|
self.source.group_membership_field = "memberUid"
|
||||||
self.source.user_object_filter = "(objectClass=posixAccount)"
|
self.source.user_object_filter = "(objectClass=posixAccount)"
|
||||||
self.source.group_object_filter = "(objectClass=posixGroup)"
|
self.source.group_object_filter = "(objectClass=posixGroup)"
|
||||||
|
self.source.user_membership_attribute = "uid"
|
||||||
self.source.user_property_mappings.set(
|
self.source.user_property_mappings.set(
|
||||||
|
[
|
||||||
|
*LDAPSourcePropertyMapping.objects.filter(
|
||||||
|
Q(managed__startswith="goauthentik.io/sources/ldap/default")
|
||||||
|
| Q(managed__startswith="goauthentik.io/sources/ldap/openldap")
|
||||||
|
).all(),
|
||||||
|
LDAPSourcePropertyMapping.objects.create(
|
||||||
|
name="name",
|
||||||
|
expression='return {"attributes": {"uid": list_flatten(ldap.get("uid"))}}',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
self.source.group_property_mappings.set(
|
||||||
LDAPSourcePropertyMapping.objects.filter(
|
LDAPSourcePropertyMapping.objects.filter(
|
||||||
Q(managed__startswith="goauthentik.io/sources/ldap/default")
|
managed="goauthentik.io/sources/ldap/openldap-cn"
|
||||||
| Q(managed__startswith="goauthentik.io/sources/ldap/openldap")
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
connection = MagicMock(return_value=mock_slapd_connection(LDAP_PASSWORD))
|
||||||
|
with patch("authentik.sources.ldap.models.LDAPSource.connection", connection):
|
||||||
|
self.source.save()
|
||||||
|
user_sync = UserLDAPSynchronizer(self.source)
|
||||||
|
user_sync.sync_full()
|
||||||
|
group_sync = GroupLDAPSynchronizer(self.source)
|
||||||
|
group_sync.sync_full()
|
||||||
|
membership_sync = MembershipLDAPSynchronizer(self.source)
|
||||||
|
membership_sync.sync_full()
|
||||||
|
# Test if membership mapping based on memberUid works.
|
||||||
|
posix_group = Group.objects.filter(name="group-posix").first()
|
||||||
|
self.assertTrue(posix_group.users.filter(name="user-posix").exists())
|
||||||
|
|
||||||
|
def test_sync_groups_openldap_posix_group_nonstandard_membership_attribute(self):
|
||||||
|
"""Test posix group sync"""
|
||||||
|
self.source.object_uniqueness_field = "cn"
|
||||||
|
self.source.group_membership_field = "memberUid"
|
||||||
|
self.source.user_object_filter = "(objectClass=posixAccount)"
|
||||||
|
self.source.group_object_filter = "(objectClass=posixGroup)"
|
||||||
|
self.source.user_membership_attribute = "cn"
|
||||||
|
self.source.user_property_mappings.set(
|
||||||
|
[
|
||||||
|
*LDAPSourcePropertyMapping.objects.filter(
|
||||||
|
Q(managed__startswith="goauthentik.io/sources/ldap/default")
|
||||||
|
| Q(managed__startswith="goauthentik.io/sources/ldap/openldap")
|
||||||
|
).all(),
|
||||||
|
LDAPSourcePropertyMapping.objects.create(
|
||||||
|
name="name",
|
||||||
|
expression='return {"attributes": {"cn": list_flatten(ldap.get("cn"))}}',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)
|
||||||
self.source.group_property_mappings.set(
|
self.source.group_property_mappings.set(
|
||||||
LDAPSourcePropertyMapping.objects.filter(
|
LDAPSourcePropertyMapping.objects.filter(
|
||||||
managed="goauthentik.io/sources/ldap/openldap-cn"
|
managed="goauthentik.io/sources/ldap/openldap-cn"
|
||||||
@ -308,3 +364,160 @@ class LDAPSyncTests(TestCase):
|
|||||||
connection = MagicMock(return_value=mock_slapd_connection(LDAP_PASSWORD))
|
connection = MagicMock(return_value=mock_slapd_connection(LDAP_PASSWORD))
|
||||||
with patch("authentik.sources.ldap.models.LDAPSource.connection", connection):
|
with patch("authentik.sources.ldap.models.LDAPSource.connection", connection):
|
||||||
ldap_sync_all.delay().get()
|
ldap_sync_all.delay().get()
|
||||||
|
|
||||||
|
def test_user_deletion(self):
|
||||||
|
"""Test user deletion"""
|
||||||
|
user = User.objects.create_user(username="not-in-the-source")
|
||||||
|
UserLDAPSourceConnection.objects.create(
|
||||||
|
user=user, source=self.source, identifier="not-in-the-source"
|
||||||
|
)
|
||||||
|
self.source.object_uniqueness_field = "uid"
|
||||||
|
self.source.group_object_filter = "(objectClass=groupOfNames)"
|
||||||
|
self.source.delete_not_found_objects = True
|
||||||
|
self.source.save()
|
||||||
|
|
||||||
|
connection = MagicMock(return_value=mock_slapd_connection(LDAP_PASSWORD))
|
||||||
|
with patch("authentik.sources.ldap.models.LDAPSource.connection", connection):
|
||||||
|
ldap_sync_all.delay().get()
|
||||||
|
self.assertFalse(User.objects.filter(username="not-in-the-source").exists())
|
||||||
|
|
||||||
|
def test_user_deletion_still_in_source(self):
|
||||||
|
"""Test that user is not deleted if it's still in the source"""
|
||||||
|
username = user_in_slapd_cn
|
||||||
|
identifier = user_in_slapd_uid
|
||||||
|
user = User.objects.create_user(username=username)
|
||||||
|
UserLDAPSourceConnection.objects.create(
|
||||||
|
user=user, source=self.source, identifier=identifier
|
||||||
|
)
|
||||||
|
self.source.object_uniqueness_field = "uid"
|
||||||
|
self.source.group_object_filter = "(objectClass=groupOfNames)"
|
||||||
|
self.source.delete_not_found_objects = True
|
||||||
|
self.source.save()
|
||||||
|
|
||||||
|
connection = MagicMock(return_value=mock_slapd_connection(LDAP_PASSWORD))
|
||||||
|
with patch("authentik.sources.ldap.models.LDAPSource.connection", connection):
|
||||||
|
ldap_sync_all.delay().get()
|
||||||
|
self.assertTrue(User.objects.filter(username=username).exists())
|
||||||
|
|
||||||
|
def test_user_deletion_no_sync(self):
|
||||||
|
"""Test that user is not deleted if sync_users is False"""
|
||||||
|
user = User.objects.create_user(username="not-in-the-source")
|
||||||
|
UserLDAPSourceConnection.objects.create(
|
||||||
|
user=user, source=self.source, identifier="not-in-the-source"
|
||||||
|
)
|
||||||
|
self.source.object_uniqueness_field = "uid"
|
||||||
|
self.source.group_object_filter = "(objectClass=groupOfNames)"
|
||||||
|
self.source.delete_not_found_objects = True
|
||||||
|
self.source.sync_users = False
|
||||||
|
self.source.save()
|
||||||
|
|
||||||
|
connection = MagicMock(return_value=mock_slapd_connection(LDAP_PASSWORD))
|
||||||
|
with patch("authentik.sources.ldap.models.LDAPSource.connection", connection):
|
||||||
|
ldap_sync_all.delay().get()
|
||||||
|
self.assertTrue(User.objects.filter(username="not-in-the-source").exists())
|
||||||
|
|
||||||
|
def test_user_deletion_no_delete(self):
|
||||||
|
"""Test that user is not deleted if delete_not_found_objects is False"""
|
||||||
|
user = User.objects.create_user(username="not-in-the-source")
|
||||||
|
UserLDAPSourceConnection.objects.create(
|
||||||
|
user=user, source=self.source, identifier="not-in-the-source"
|
||||||
|
)
|
||||||
|
self.source.object_uniqueness_field = "uid"
|
||||||
|
self.source.group_object_filter = "(objectClass=groupOfNames)"
|
||||||
|
self.source.save()
|
||||||
|
|
||||||
|
connection = MagicMock(return_value=mock_slapd_connection(LDAP_PASSWORD))
|
||||||
|
with patch("authentik.sources.ldap.models.LDAPSource.connection", connection):
|
||||||
|
ldap_sync_all.delay().get()
|
||||||
|
self.assertTrue(User.objects.filter(username="not-in-the-source").exists())
|
||||||
|
|
||||||
|
def test_group_deletion(self):
|
||||||
|
"""Test group deletion"""
|
||||||
|
group = Group.objects.create(name="not-in-the-source")
|
||||||
|
GroupLDAPSourceConnection.objects.create(
|
||||||
|
group=group, source=self.source, identifier="not-in-the-source"
|
||||||
|
)
|
||||||
|
self.source.object_uniqueness_field = "uid"
|
||||||
|
self.source.group_object_filter = "(objectClass=groupOfNames)"
|
||||||
|
self.source.delete_not_found_objects = True
|
||||||
|
self.source.save()
|
||||||
|
|
||||||
|
connection = MagicMock(return_value=mock_slapd_connection(LDAP_PASSWORD))
|
||||||
|
with patch("authentik.sources.ldap.models.LDAPSource.connection", connection):
|
||||||
|
ldap_sync_all.delay().get()
|
||||||
|
self.assertFalse(Group.objects.filter(name="not-in-the-source").exists())
|
||||||
|
|
||||||
|
def test_group_deletion_still_in_source(self):
|
||||||
|
"""Test that group is not deleted if it's still in the source"""
|
||||||
|
groupname = group_in_slapd_cn
|
||||||
|
identifier = group_in_slapd_uid
|
||||||
|
group = Group.objects.create(name=groupname)
|
||||||
|
GroupLDAPSourceConnection.objects.create(
|
||||||
|
group=group, source=self.source, identifier=identifier
|
||||||
|
)
|
||||||
|
self.source.object_uniqueness_field = "uid"
|
||||||
|
self.source.group_object_filter = "(objectClass=groupOfNames)"
|
||||||
|
self.source.delete_not_found_objects = True
|
||||||
|
self.source.save()
|
||||||
|
|
||||||
|
connection = MagicMock(return_value=mock_slapd_connection(LDAP_PASSWORD))
|
||||||
|
with patch("authentik.sources.ldap.models.LDAPSource.connection", connection):
|
||||||
|
ldap_sync_all.delay().get()
|
||||||
|
self.assertTrue(Group.objects.filter(name=groupname).exists())
|
||||||
|
|
||||||
|
def test_group_deletion_no_sync(self):
|
||||||
|
"""Test that group is not deleted if sync_groups is False"""
|
||||||
|
group = Group.objects.create(name="not-in-the-source")
|
||||||
|
GroupLDAPSourceConnection.objects.create(
|
||||||
|
group=group, source=self.source, identifier="not-in-the-source"
|
||||||
|
)
|
||||||
|
self.source.object_uniqueness_field = "uid"
|
||||||
|
self.source.group_object_filter = "(objectClass=groupOfNames)"
|
||||||
|
self.source.delete_not_found_objects = True
|
||||||
|
self.source.sync_groups = False
|
||||||
|
self.source.save()
|
||||||
|
|
||||||
|
connection = MagicMock(return_value=mock_slapd_connection(LDAP_PASSWORD))
|
||||||
|
with patch("authentik.sources.ldap.models.LDAPSource.connection", connection):
|
||||||
|
ldap_sync_all.delay().get()
|
||||||
|
self.assertTrue(Group.objects.filter(name="not-in-the-source").exists())
|
||||||
|
|
||||||
|
def test_group_deletion_no_delete(self):
|
||||||
|
"""Test that group is not deleted if delete_not_found_objects is False"""
|
||||||
|
group = Group.objects.create(name="not-in-the-source")
|
||||||
|
GroupLDAPSourceConnection.objects.create(
|
||||||
|
group=group, source=self.source, identifier="not-in-the-source"
|
||||||
|
)
|
||||||
|
self.source.object_uniqueness_field = "uid"
|
||||||
|
self.source.group_object_filter = "(objectClass=groupOfNames)"
|
||||||
|
self.source.save()
|
||||||
|
|
||||||
|
connection = MagicMock(return_value=mock_slapd_connection(LDAP_PASSWORD))
|
||||||
|
with patch("authentik.sources.ldap.models.LDAPSource.connection", connection):
|
||||||
|
ldap_sync_all.delay().get()
|
||||||
|
self.assertTrue(Group.objects.filter(name="not-in-the-source").exists())
|
||||||
|
|
||||||
|
def test_batch_deletion(self):
|
||||||
|
"""Test batch deletion"""
|
||||||
|
BATCH_SIZE = DELETE_CHUNK_SIZE + 1
|
||||||
|
for i in range(BATCH_SIZE):
|
||||||
|
user = User.objects.create_user(username=f"not-in-the-source-{i}")
|
||||||
|
group = Group.objects.create(name=f"not-in-the-source-{i}")
|
||||||
|
group.users.add(user)
|
||||||
|
UserLDAPSourceConnection.objects.create(
|
||||||
|
user=user, source=self.source, identifier=f"not-in-the-source-{i}-user"
|
||||||
|
)
|
||||||
|
GroupLDAPSourceConnection.objects.create(
|
||||||
|
group=group, source=self.source, identifier=f"not-in-the-source-{i}-group"
|
||||||
|
)
|
||||||
|
self.source.object_uniqueness_field = "uid"
|
||||||
|
self.source.group_object_filter = "(objectClass=groupOfNames)"
|
||||||
|
self.source.delete_not_found_objects = True
|
||||||
|
self.source.save()
|
||||||
|
|
||||||
|
connection = MagicMock(return_value=mock_slapd_connection(LDAP_PASSWORD))
|
||||||
|
with patch("authentik.sources.ldap.models.LDAPSource.connection", connection):
|
||||||
|
ldap_sync_all.delay().get()
|
||||||
|
|
||||||
|
self.assertFalse(User.objects.filter(username__startswith="not-in-the-source").exists())
|
||||||
|
self.assertFalse(Group.objects.filter(name__startswith="not-in-the-source").exists())
|
||||||
|
@ -9,6 +9,7 @@ from django.http.response import HttpResponseBadRequest
|
|||||||
from django.shortcuts import get_object_or_404, redirect
|
from django.shortcuts import get_object_or_404, redirect
|
||||||
from django.utils.decorators import method_decorator
|
from django.utils.decorators import method_decorator
|
||||||
from django.utils.http import urlencode
|
from django.utils.http import urlencode
|
||||||
|
from django.utils.translation import gettext as _
|
||||||
from django.views import View
|
from django.views import View
|
||||||
from django.views.decorators.csrf import csrf_exempt
|
from django.views.decorators.csrf import csrf_exempt
|
||||||
from structlog.stdlib import get_logger
|
from structlog.stdlib import get_logger
|
||||||
@ -128,7 +129,9 @@ class InitiateView(View):
|
|||||||
# otherwise we default to POST_AUTO, with direct redirect
|
# otherwise we default to POST_AUTO, with direct redirect
|
||||||
if source.binding_type == SAMLBindingTypes.POST:
|
if source.binding_type == SAMLBindingTypes.POST:
|
||||||
injected_stages.append(in_memory_stage(ConsentStageView))
|
injected_stages.append(in_memory_stage(ConsentStageView))
|
||||||
plan_kwargs[PLAN_CONTEXT_CONSENT_HEADER] = f"Continue to {source.name}"
|
plan_kwargs[PLAN_CONTEXT_CONSENT_HEADER] = _(
|
||||||
|
"Continue to {source_name}".format(source_name=source.name)
|
||||||
|
)
|
||||||
injected_stages.append(in_memory_stage(AutosubmitStageView))
|
injected_stages.append(in_memory_stage(AutosubmitStageView))
|
||||||
return self.handle_login_flow(
|
return self.handle_login_flow(
|
||||||
source,
|
source,
|
||||||
|
@ -97,7 +97,8 @@ class GroupsView(SCIMObjectView):
|
|||||||
self.logger.warning("Invalid group member", exc=exc)
|
self.logger.warning("Invalid group member", exc=exc)
|
||||||
continue
|
continue
|
||||||
query |= Q(uuid=member.value)
|
query |= Q(uuid=member.value)
|
||||||
group.users.set(User.objects.filter(query))
|
if query:
|
||||||
|
group.users.set(User.objects.filter(query))
|
||||||
if not connection:
|
if not connection:
|
||||||
connection, _ = SCIMSourceGroup.objects.get_or_create(
|
connection, _ = SCIMSourceGroup.objects.get_or_create(
|
||||||
source=self.source,
|
source=self.source,
|
||||||
|
@ -151,9 +151,7 @@ class AuthenticatorValidateStageWebAuthnTests(FlowTestCase):
|
|||||||
webauthn_user_verification=UserVerification.PREFERRED,
|
webauthn_user_verification=UserVerification.PREFERRED,
|
||||||
)
|
)
|
||||||
stage.webauthn_allowed_device_types.set(
|
stage.webauthn_allowed_device_types.set(
|
||||||
WebAuthnDeviceType.objects.filter(
|
WebAuthnDeviceType.objects.filter(description="YubiKey 5 Series")
|
||||||
description="Android Authenticator with SafetyNet Attestation"
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
session = self.client.session
|
session = self.client.session
|
||||||
plan = FlowPlan(flow_pk=flow.pk.hex)
|
plan = FlowPlan(flow_pk=flow.pk.hex)
|
||||||
@ -339,9 +337,7 @@ class AuthenticatorValidateStageWebAuthnTests(FlowTestCase):
|
|||||||
device_classes=[DeviceClasses.WEBAUTHN],
|
device_classes=[DeviceClasses.WEBAUTHN],
|
||||||
)
|
)
|
||||||
stage.webauthn_allowed_device_types.set(
|
stage.webauthn_allowed_device_types.set(
|
||||||
WebAuthnDeviceType.objects.filter(
|
WebAuthnDeviceType.objects.filter(description="YubiKey 5 Series")
|
||||||
description="Android Authenticator with SafetyNet Attestation"
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
session = self.client.session
|
session = self.client.session
|
||||||
plan = FlowPlan(flow_pk=flow.pk.hex)
|
plan = FlowPlan(flow_pk=flow.pk.hex)
|
||||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -141,9 +141,7 @@ class TestAuthenticatorWebAuthnStage(FlowTestCase):
|
|||||||
"""Test registration with restricted devices (fail)"""
|
"""Test registration with restricted devices (fail)"""
|
||||||
webauthn_mds_import.delay(force=True).get()
|
webauthn_mds_import.delay(force=True).get()
|
||||||
self.stage.device_type_restrictions.set(
|
self.stage.device_type_restrictions.set(
|
||||||
WebAuthnDeviceType.objects.filter(
|
WebAuthnDeviceType.objects.filter(description="YubiKey 5 Series")
|
||||||
description="Android Authenticator with SafetyNet Attestation"
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
|
plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
|
||||||
|
@ -4,6 +4,8 @@ from uuid import uuid4
|
|||||||
|
|
||||||
from django.http import HttpRequest, HttpResponse
|
from django.http import HttpRequest, HttpResponse
|
||||||
from django.utils.timezone import now
|
from django.utils.timezone import now
|
||||||
|
from django.utils.translation import gettext as _
|
||||||
|
from rest_framework.exceptions import ValidationError
|
||||||
from rest_framework.fields import CharField
|
from rest_framework.fields import CharField
|
||||||
|
|
||||||
from authentik.core.api.utils import PassiveSerializer
|
from authentik.core.api.utils import PassiveSerializer
|
||||||
@ -47,6 +49,11 @@ class ConsentChallengeResponse(ChallengeResponse):
|
|||||||
component = CharField(default="ak-stage-consent")
|
component = CharField(default="ak-stage-consent")
|
||||||
token = CharField(required=True)
|
token = CharField(required=True)
|
||||||
|
|
||||||
|
def validate_token(self, token: str):
|
||||||
|
if token != self.stage.executor.request.session[SESSION_KEY_CONSENT_TOKEN]:
|
||||||
|
raise ValidationError(_("Invalid consent token, re-showing prompt"))
|
||||||
|
return token
|
||||||
|
|
||||||
|
|
||||||
class ConsentStageView(ChallengeStageView):
|
class ConsentStageView(ChallengeStageView):
|
||||||
"""Simple consent checker."""
|
"""Simple consent checker."""
|
||||||
@ -120,9 +127,6 @@ class ConsentStageView(ChallengeStageView):
|
|||||||
return super().get(request, *args, **kwargs)
|
return super().get(request, *args, **kwargs)
|
||||||
|
|
||||||
def challenge_valid(self, response: ChallengeResponse) -> HttpResponse:
|
def challenge_valid(self, response: ChallengeResponse) -> HttpResponse:
|
||||||
if response.data["token"] != self.request.session[SESSION_KEY_CONSENT_TOKEN]:
|
|
||||||
self.logger.info("Invalid consent token, re-showing prompt")
|
|
||||||
return self.get(self.request)
|
|
||||||
if self.should_always_prompt():
|
if self.should_always_prompt():
|
||||||
return self.executor.stage_ok()
|
return self.executor.stage_ok()
|
||||||
current_stage: ConsentStage = self.executor.current_stage
|
current_stage: ConsentStage = self.executor.current_stage
|
||||||
|
@ -17,6 +17,7 @@ from authentik.flows.views.executor import SESSION_KEY_PLAN
|
|||||||
from authentik.lib.generators import generate_id
|
from authentik.lib.generators import generate_id
|
||||||
from authentik.stages.consent.models import ConsentMode, ConsentStage, UserConsent
|
from authentik.stages.consent.models import ConsentMode, ConsentStage, UserConsent
|
||||||
from authentik.stages.consent.stage import (
|
from authentik.stages.consent.stage import (
|
||||||
|
PLAN_CONTEXT_CONSENT_HEADER,
|
||||||
PLAN_CONTEXT_CONSENT_PERMISSIONS,
|
PLAN_CONTEXT_CONSENT_PERMISSIONS,
|
||||||
SESSION_KEY_CONSENT_TOKEN,
|
SESSION_KEY_CONSENT_TOKEN,
|
||||||
)
|
)
|
||||||
@ -33,6 +34,40 @@ class TestConsentStage(FlowTestCase):
|
|||||||
slug=generate_id(),
|
slug=generate_id(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_mismatched_token(self):
|
||||||
|
"""Test incorrect token"""
|
||||||
|
flow = create_test_flow(FlowDesignation.AUTHENTICATION)
|
||||||
|
stage = ConsentStage.objects.create(name=generate_id(), mode=ConsentMode.ALWAYS_REQUIRE)
|
||||||
|
binding = FlowStageBinding.objects.create(target=flow, stage=stage, order=2)
|
||||||
|
|
||||||
|
plan = FlowPlan(flow_pk=flow.pk.hex, bindings=[binding], markers=[StageMarker()])
|
||||||
|
session = self.client.session
|
||||||
|
session[SESSION_KEY_PLAN] = plan
|
||||||
|
session.save()
|
||||||
|
response = self.client.get(
|
||||||
|
reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
|
||||||
|
)
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
session = self.client.session
|
||||||
|
response = self.client.post(
|
||||||
|
reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
|
||||||
|
{
|
||||||
|
"token": generate_id(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertStageResponse(
|
||||||
|
response,
|
||||||
|
flow,
|
||||||
|
component="ak-stage-consent",
|
||||||
|
response_errors={
|
||||||
|
"token": [{"string": "Invalid consent token, re-showing prompt", "code": "invalid"}]
|
||||||
|
},
|
||||||
|
)
|
||||||
|
self.assertFalse(UserConsent.objects.filter(user=self.user).exists())
|
||||||
|
|
||||||
def test_always_required(self):
|
def test_always_required(self):
|
||||||
"""Test always required consent"""
|
"""Test always required consent"""
|
||||||
flow = create_test_flow(FlowDesignation.AUTHENTICATION)
|
flow = create_test_flow(FlowDesignation.AUTHENTICATION)
|
||||||
@ -158,6 +193,7 @@ class TestConsentStage(FlowTestCase):
|
|||||||
context={
|
context={
|
||||||
PLAN_CONTEXT_APPLICATION: self.application,
|
PLAN_CONTEXT_APPLICATION: self.application,
|
||||||
PLAN_CONTEXT_CONSENT_PERMISSIONS: [PermissionDict(id="foo", name="foo-desc")],
|
PLAN_CONTEXT_CONSENT_PERMISSIONS: [PermissionDict(id="foo", name="foo-desc")],
|
||||||
|
PLAN_CONTEXT_CONSENT_HEADER: "test header",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
session = self.client.session
|
session = self.client.session
|
||||||
|
38
authentik/stages/email/flow.py
Normal file
38
authentik/stages/email/flow.py
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
from base64 import b64encode
|
||||||
|
from copy import deepcopy
|
||||||
|
from pickle import dumps # nosec
|
||||||
|
|
||||||
|
from django.utils.translation import gettext as _
|
||||||
|
|
||||||
|
from authentik.flows.models import FlowToken, in_memory_stage
|
||||||
|
from authentik.flows.planner import PLAN_CONTEXT_IS_RESTORED, FlowPlan
|
||||||
|
from authentik.stages.consent.stage import PLAN_CONTEXT_CONSENT_HEADER, ConsentStageView
|
||||||
|
|
||||||
|
|
||||||
|
def pickle_flow_token_for_email(plan: FlowPlan):
|
||||||
|
"""Insert a consent stage into the flow plan and pickle it for a FlowToken,
|
||||||
|
to be sent via Email. This is to prevent automated email scanners, which sometimes
|
||||||
|
open links in emails in a full browser from breaking the link."""
|
||||||
|
plan_copy = deepcopy(plan)
|
||||||
|
plan_copy.insert_stage(in_memory_stage(EmailTokenRevocationConsentStageView), index=0)
|
||||||
|
plan_copy.context[PLAN_CONTEXT_CONSENT_HEADER] = _("Continue to confirm this email address.")
|
||||||
|
data = dumps(plan_copy)
|
||||||
|
return b64encode(data).decode()
|
||||||
|
|
||||||
|
|
||||||
|
class EmailTokenRevocationConsentStageView(ConsentStageView):
|
||||||
|
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
token: FlowToken = self.executor.plan.context[PLAN_CONTEXT_IS_RESTORED]
|
||||||
|
try:
|
||||||
|
token.refresh_from_db()
|
||||||
|
except FlowToken.DoesNotExist:
|
||||||
|
return self.executor.stage_invalid(
|
||||||
|
_("Link was already used, please request a new link.")
|
||||||
|
)
|
||||||
|
return super().get(request, *args, **kwargs)
|
||||||
|
|
||||||
|
def challenge_valid(self, response):
|
||||||
|
token: FlowToken = self.executor.plan.context[PLAN_CONTEXT_IS_RESTORED]
|
||||||
|
token.delete()
|
||||||
|
return super().challenge_valid(response)
|
@ -23,6 +23,7 @@ from authentik.flows.stage import ChallengeStageView
|
|||||||
from authentik.flows.views.executor import QS_KEY_TOKEN, QS_QUERY
|
from authentik.flows.views.executor import QS_KEY_TOKEN, QS_QUERY
|
||||||
from authentik.lib.utils.errors import exception_to_string
|
from authentik.lib.utils.errors import exception_to_string
|
||||||
from authentik.lib.utils.time import timedelta_from_string
|
from authentik.lib.utils.time import timedelta_from_string
|
||||||
|
from authentik.stages.email.flow import pickle_flow_token_for_email
|
||||||
from authentik.stages.email.models import EmailStage
|
from authentik.stages.email.models import EmailStage
|
||||||
from authentik.stages.email.tasks import send_mails
|
from authentik.stages.email.tasks import send_mails
|
||||||
from authentik.stages.email.utils import TemplateEmailMessage
|
from authentik.stages.email.utils import TemplateEmailMessage
|
||||||
@ -86,7 +87,8 @@ class EmailStageView(ChallengeStageView):
|
|||||||
user=pending_user,
|
user=pending_user,
|
||||||
identifier=identifier,
|
identifier=identifier,
|
||||||
flow=self.executor.flow,
|
flow=self.executor.flow,
|
||||||
_plan=FlowToken.pickle(self.executor.plan),
|
_plan=pickle_flow_token_for_email(self.executor.plan),
|
||||||
|
revoke_on_execution=False,
|
||||||
)
|
)
|
||||||
token = tokens.first()
|
token = tokens.first()
|
||||||
# Check if token is expired and rotate key if so
|
# Check if token is expired and rotate key if so
|
||||||
|
@ -100,9 +100,11 @@ def send_mail(
|
|||||||
# Because we use the Message-ID as UID for the task, manually assign it
|
# Because we use the Message-ID as UID for the task, manually assign it
|
||||||
message_object.extra_headers["Message-ID"] = message_id
|
message_object.extra_headers["Message-ID"] = message_id
|
||||||
|
|
||||||
# Add the logo (we can't add it in the previous message since MIMEImage
|
# Add the logo if it is used in the email body (we can't add it in the
|
||||||
# can't be converted to json)
|
# previous message since MIMEImage can't be converted to json)
|
||||||
message_object.attach(logo_data())
|
body = get_email_body(message_object)
|
||||||
|
if "cid:logo" in body:
|
||||||
|
message_object.attach(logo_data())
|
||||||
|
|
||||||
if (
|
if (
|
||||||
message_object.to
|
message_object.to
|
||||||
|
@ -96,7 +96,7 @@
|
|||||||
<table width="100%" style="background-color: #FFFFFF; border-spacing: 0; margin-top: 15px;">
|
<table width="100%" style="background-color: #FFFFFF; border-spacing: 0; margin-top: 15px;">
|
||||||
<tr height="80">
|
<tr height="80">
|
||||||
<td align="center" style="padding: 20px 0;">
|
<td align="center" style="padding: 20px 0;">
|
||||||
<img src="{% block logo_url %}cid:logo.png{% endblock %}" border="0=" alt="authentik logo" class="flexibleImage logo">
|
<img src="{% block logo_url %}cid:logo{% endblock %}" border="0=" alt="authentik logo" class="flexibleImage logo">
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
@ -174,5 +174,5 @@ class TestEmailStageSending(FlowTestCase):
|
|||||||
response = self.client.post(url)
|
response = self.client.post(url)
|
||||||
response = self.client.post(url)
|
response = self.client.post(url)
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertTrue(len(mail.outbox) >= 1)
|
self.assertGreaterEqual(len(mail.outbox), 1)
|
||||||
self.assertEqual(mail.outbox[0].subject, "authentik")
|
self.assertEqual(mail.outbox[0].subject, "authentik")
|
||||||
|
@ -17,6 +17,7 @@ from authentik.flows.tests import FlowTestCase
|
|||||||
from authentik.flows.views.executor import QS_KEY_TOKEN, SESSION_KEY_PLAN, FlowExecutorView
|
from authentik.flows.views.executor import QS_KEY_TOKEN, SESSION_KEY_PLAN, FlowExecutorView
|
||||||
from authentik.lib.config import CONFIG
|
from authentik.lib.config import CONFIG
|
||||||
from authentik.lib.generators import generate_id
|
from authentik.lib.generators import generate_id
|
||||||
|
from authentik.stages.consent.stage import SESSION_KEY_CONSENT_TOKEN
|
||||||
from authentik.stages.email.models import EmailStage
|
from authentik.stages.email.models import EmailStage
|
||||||
from authentik.stages.email.stage import PLAN_CONTEXT_EMAIL_OVERRIDE, EmailStageView
|
from authentik.stages.email.stage import PLAN_CONTEXT_EMAIL_OVERRIDE, EmailStageView
|
||||||
|
|
||||||
@ -160,6 +161,17 @@ class TestEmailStage(FlowTestCase):
|
|||||||
kwargs={"flow_slug": self.flow.slug},
|
kwargs={"flow_slug": self.flow.slug},
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
self.assertStageResponse(response, self.flow, component="ak-stage-consent")
|
||||||
|
response = self.client.post(
|
||||||
|
reverse(
|
||||||
|
"authentik_api:flow-executor",
|
||||||
|
kwargs={"flow_slug": self.flow.slug},
|
||||||
|
),
|
||||||
|
data={
|
||||||
|
"token": self.client.session[SESSION_KEY_CONSENT_TOKEN],
|
||||||
|
},
|
||||||
|
follow=True,
|
||||||
|
)
|
||||||
|
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertStageRedirects(response, reverse("authentik_core:root-redirect"))
|
self.assertStageRedirects(response, reverse("authentik_core:root-redirect"))
|
||||||
@ -182,6 +194,7 @@ class TestEmailStage(FlowTestCase):
|
|||||||
# Set flow token user to a different user
|
# Set flow token user to a different user
|
||||||
token: FlowToken = FlowToken.objects.get(user=self.user)
|
token: FlowToken = FlowToken.objects.get(user=self.user)
|
||||||
token.user = create_test_admin_user()
|
token.user = create_test_admin_user()
|
||||||
|
token.revoke_on_execution = True
|
||||||
token.save()
|
token.save()
|
||||||
|
|
||||||
with patch("authentik.flows.views.executor.FlowExecutorView.cancel", MagicMock()):
|
with patch("authentik.flows.views.executor.FlowExecutorView.cancel", MagicMock()):
|
||||||
|
@ -19,7 +19,8 @@ def logo_data() -> MIMEImage:
|
|||||||
path = Path("web/dist/assets/icons/icon_left_brand.png")
|
path = Path("web/dist/assets/icons/icon_left_brand.png")
|
||||||
with open(path, "rb") as _logo_file:
|
with open(path, "rb") as _logo_file:
|
||||||
logo = MIMEImage(_logo_file.read())
|
logo = MIMEImage(_logo_file.read())
|
||||||
logo.add_header("Content-ID", "logo.png")
|
logo.add_header("Content-ID", "<logo>")
|
||||||
|
logo.add_header("Content-Disposition", "inline", filename="logo.png")
|
||||||
return logo
|
return logo
|
||||||
|
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ from rest_framework.fields import BooleanField, CharField
|
|||||||
from authentik.core.models import Session, User
|
from authentik.core.models import Session, User
|
||||||
from authentik.events.middleware import audit_ignore
|
from authentik.events.middleware import audit_ignore
|
||||||
from authentik.flows.challenge import ChallengeResponse, WithUserInfoChallenge
|
from authentik.flows.challenge import ChallengeResponse, WithUserInfoChallenge
|
||||||
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER, PLAN_CONTEXT_SOURCE
|
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER
|
||||||
from authentik.flows.stage import ChallengeStageView
|
from authentik.flows.stage import ChallengeStageView
|
||||||
from authentik.lib.utils.time import timedelta_from_string
|
from authentik.lib.utils.time import timedelta_from_string
|
||||||
from authentik.root.middleware import ClientIPMiddleware
|
from authentik.root.middleware import ClientIPMiddleware
|
||||||
@ -108,10 +108,6 @@ class UserLoginStageView(ChallengeStageView):
|
|||||||
flow_slug=self.executor.flow.slug,
|
flow_slug=self.executor.flow.slug,
|
||||||
session_duration=delta,
|
session_duration=delta,
|
||||||
)
|
)
|
||||||
# Only show success message if we don't have a source in the flow
|
|
||||||
# as sources show their own success messages
|
|
||||||
if not self.executor.plan.context.get(PLAN_CONTEXT_SOURCE, None):
|
|
||||||
messages.success(self.request, _("Successfully logged in!"))
|
|
||||||
if self.executor.current_stage.terminate_other_sessions:
|
if self.executor.current_stage.terminate_other_sessions:
|
||||||
Session.objects.filter(
|
Session.objects.filter(
|
||||||
authenticatedsession__user=user,
|
authenticatedsession__user=user,
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
"$schema": "http://json-schema.org/draft-07/schema",
|
"$schema": "http://json-schema.org/draft-07/schema",
|
||||||
"$id": "https://goauthentik.io/blueprints/schema.json",
|
"$id": "https://goauthentik.io/blueprints/schema.json",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"title": "authentik 2025.4.1 Blueprint schema",
|
"title": "authentik 2025.6.2 Blueprint schema",
|
||||||
"required": [
|
"required": [
|
||||||
"version",
|
"version",
|
||||||
"entries"
|
"entries"
|
||||||
@ -8147,6 +8147,12 @@
|
|||||||
"title": "Group membership field",
|
"title": "Group membership field",
|
||||||
"description": "Field which contains members of a group."
|
"description": "Field which contains members of a group."
|
||||||
},
|
},
|
||||||
|
"user_membership_attribute": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1,
|
||||||
|
"title": "User membership attribute",
|
||||||
|
"description": "Attribute which matches the value of `group_membership_field`."
|
||||||
|
},
|
||||||
"object_uniqueness_field": {
|
"object_uniqueness_field": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"minLength": 1,
|
"minLength": 1,
|
||||||
@ -8180,6 +8186,11 @@
|
|||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"title": "Lookup groups from user",
|
"title": "Lookup groups from user",
|
||||||
"description": "Lookup group membership based on a user attribute instead of a group attribute. This allows nested group resolution on systems like FreeIPA and Active Directory"
|
"description": "Lookup group membership based on a user attribute instead of a group attribute. This allows nested group resolution on systems like FreeIPA and Active Directory"
|
||||||
|
},
|
||||||
|
"delete_not_found_objects": {
|
||||||
|
"type": "boolean",
|
||||||
|
"title": "Delete not found objects",
|
||||||
|
"description": "Delete authentik users and groups which were previously supplied by this source, but are now missing from it."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": []
|
"required": []
|
||||||
|
@ -31,7 +31,7 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- redis:/data
|
- redis:/data
|
||||||
server:
|
server:
|
||||||
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2025.4.1}
|
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2025.6.2}
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
command: server
|
command: server
|
||||||
environment:
|
environment:
|
||||||
@ -55,7 +55,7 @@ services:
|
|||||||
redis:
|
redis:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
worker:
|
worker:
|
||||||
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2025.4.1}
|
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2025.6.2}
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
command: worker
|
command: worker
|
||||||
environment:
|
environment:
|
||||||
|
2
go.mod
2
go.mod
@ -27,7 +27,7 @@ require (
|
|||||||
github.com/spf13/cobra v1.9.1
|
github.com/spf13/cobra v1.9.1
|
||||||
github.com/stretchr/testify v1.10.0
|
github.com/stretchr/testify v1.10.0
|
||||||
github.com/wwt/guac v1.3.2
|
github.com/wwt/guac v1.3.2
|
||||||
goauthentik.io/api/v3 v3.2025041.2
|
goauthentik.io/api/v3 v3.2025041.4
|
||||||
golang.org/x/exp v0.0.0-20230210204819-062eb4c674ab
|
golang.org/x/exp v0.0.0-20230210204819-062eb4c674ab
|
||||||
golang.org/x/oauth2 v0.30.0
|
golang.org/x/oauth2 v0.30.0
|
||||||
golang.org/x/sync v0.14.0
|
golang.org/x/sync v0.14.0
|
||||||
|
4
go.sum
4
go.sum
@ -290,8 +290,8 @@ go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y
|
|||||||
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
|
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 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||||
goauthentik.io/api/v3 v3.2025041.2 h1:vFYYnhcDcxL95RczZwhzt3i4LptFXMvIRN+vgf8sQYg=
|
goauthentik.io/api/v3 v3.2025041.4 h1:cGqzWYnUHrWDoaXWDpIL/kWnX9sFrIhkYDye0P0OEAo=
|
||||||
goauthentik.io/api/v3 v3.2025041.2/go.mod h1:zz+mEZg8rY/7eEjkMGWJ2DnGqk+zqxuybGCGrR2O4Kw=
|
goauthentik.io/api/v3 v3.2025041.4/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-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-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
@ -33,4 +33,4 @@ func UserAgent() string {
|
|||||||
return fmt.Sprintf("authentik@%s", FullVersion())
|
return fmt.Sprintf("authentik@%s", FullVersion())
|
||||||
}
|
}
|
||||||
|
|
||||||
const VERSION = "2025.4.1"
|
const VERSION = "2025.6.2"
|
||||||
|
@ -28,16 +28,18 @@ func NewSessionBinder(si server.LDAPServerInstance, oldBinder bind.Binder) *Sess
|
|||||||
si: si,
|
si: si,
|
||||||
log: log.WithField("logger", "authentik.outpost.ldap.binder.session"),
|
log: log.WithField("logger", "authentik.outpost.ldap.binder.session"),
|
||||||
}
|
}
|
||||||
if oldSb, ok := oldBinder.(*SessionBinder); ok {
|
if oldBinder != nil {
|
||||||
sb.DirectBinder = oldSb.DirectBinder
|
if oldSb, ok := oldBinder.(*SessionBinder); ok {
|
||||||
sb.sessions = oldSb.sessions
|
sb.DirectBinder = oldSb.DirectBinder
|
||||||
sb.log.Debug("re-initialised session binder")
|
sb.sessions = oldSb.sessions
|
||||||
} else {
|
sb.log.Debug("re-initialised session binder")
|
||||||
sb.sessions = ttlcache.New(ttlcache.WithDisableTouchOnHit[Credentials, ldap.LDAPResultCode]())
|
return sb
|
||||||
sb.DirectBinder = *direct.NewDirectBinder(si)
|
}
|
||||||
go sb.sessions.Start()
|
|
||||||
sb.log.Debug("initialised session binder")
|
|
||||||
}
|
}
|
||||||
|
sb.sessions = ttlcache.New(ttlcache.WithDisableTouchOnHit[Credentials, ldap.LDAPResultCode]())
|
||||||
|
sb.DirectBinder = *direct.NewDirectBinder(si)
|
||||||
|
go sb.sessions.Start()
|
||||||
|
sb.log.Debug("initialised session binder")
|
||||||
return sb
|
return sb
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,6 +16,7 @@ import (
|
|||||||
memorybind "goauthentik.io/internal/outpost/ldap/bind/memory"
|
memorybind "goauthentik.io/internal/outpost/ldap/bind/memory"
|
||||||
"goauthentik.io/internal/outpost/ldap/constants"
|
"goauthentik.io/internal/outpost/ldap/constants"
|
||||||
"goauthentik.io/internal/outpost/ldap/flags"
|
"goauthentik.io/internal/outpost/ldap/flags"
|
||||||
|
"goauthentik.io/internal/outpost/ldap/search"
|
||||||
directsearch "goauthentik.io/internal/outpost/ldap/search/direct"
|
directsearch "goauthentik.io/internal/outpost/ldap/search/direct"
|
||||||
memorysearch "goauthentik.io/internal/outpost/ldap/search/memory"
|
memorysearch "goauthentik.io/internal/outpost/ldap/search/memory"
|
||||||
)
|
)
|
||||||
@ -85,7 +86,11 @@ func (ls *LDAPServer) Refresh() error {
|
|||||||
providers[idx].certUUID = *kp
|
providers[idx].certUUID = *kp
|
||||||
}
|
}
|
||||||
if *provider.SearchMode.Ptr() == api.LDAPAPIACCESSMODE_CACHED {
|
if *provider.SearchMode.Ptr() == api.LDAPAPIACCESSMODE_CACHED {
|
||||||
providers[idx].searcher = memorysearch.NewMemorySearcher(providers[idx])
|
var oldSearcher search.Searcher
|
||||||
|
if existing != nil {
|
||||||
|
oldSearcher = existing.searcher
|
||||||
|
}
|
||||||
|
providers[idx].searcher = memorysearch.NewMemorySearcher(providers[idx], oldSearcher)
|
||||||
} else if *provider.SearchMode.Ptr() == api.LDAPAPIACCESSMODE_DIRECT {
|
} else if *provider.SearchMode.Ptr() == api.LDAPAPIACCESSMODE_DIRECT {
|
||||||
providers[idx].searcher = directsearch.NewDirectSearcher(providers[idx])
|
providers[idx].searcher = directsearch.NewDirectSearcher(providers[idx])
|
||||||
}
|
}
|
||||||
|
@ -31,13 +31,26 @@ type MemorySearcher struct {
|
|||||||
groups []api.Group
|
groups []api.Group
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewMemorySearcher(si server.LDAPServerInstance) *MemorySearcher {
|
func NewMemorySearcher(si server.LDAPServerInstance, existing search.Searcher) *MemorySearcher {
|
||||||
ms := &MemorySearcher{
|
ms := &MemorySearcher{
|
||||||
si: si,
|
si: si,
|
||||||
log: log.WithField("logger", "authentik.outpost.ldap.searcher.memory"),
|
log: log.WithField("logger", "authentik.outpost.ldap.searcher.memory"),
|
||||||
ds: direct.NewDirectSearcher(si),
|
ds: direct.NewDirectSearcher(si),
|
||||||
}
|
}
|
||||||
|
if existing != nil {
|
||||||
|
if ems, ok := existing.(*MemorySearcher); ok {
|
||||||
|
ems.si = si
|
||||||
|
ems.fetch()
|
||||||
|
ems.log.Debug("re-initialised memory searcher")
|
||||||
|
return ems
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ms.fetch()
|
||||||
ms.log.Debug("initialised memory searcher")
|
ms.log.Debug("initialised memory searcher")
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ms *MemorySearcher) fetch() {
|
||||||
// Error is not handled here, we get an empty/truncated list and the error is logged
|
// Error is not handled here, we get an empty/truncated list and the error is logged
|
||||||
users, _ := ak.Paginator(ms.si.GetAPIClient().CoreApi.CoreUsersList(context.TODO()).IncludeGroups(true), ak.PaginatorOptions{
|
users, _ := ak.Paginator(ms.si.GetAPIClient().CoreApi.CoreUsersList(context.TODO()).IncludeGroups(true), ak.PaginatorOptions{
|
||||||
PageSize: 100,
|
PageSize: 100,
|
||||||
@ -49,7 +62,6 @@ func NewMemorySearcher(si server.LDAPServerInstance) *MemorySearcher {
|
|||||||
Logger: ms.log,
|
Logger: ms.log,
|
||||||
})
|
})
|
||||||
ms.groups = groups
|
ms.groups = groups
|
||||||
return ms
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ms *MemorySearcher) SearchBase(req *search.Request) (ldap.ServerSearchResult, error) {
|
func (ms *MemorySearcher) SearchBase(req *search.Request) (ldap.ServerSearchResult, error) {
|
||||||
|
@ -5,6 +5,7 @@ import (
|
|||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"encoding/gob"
|
"encoding/gob"
|
||||||
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
"net/http"
|
"net/http"
|
||||||
@ -118,8 +119,8 @@ func NewApplication(p api.ProxyOutpostConfig, c *http.Client, server Server, old
|
|||||||
mux := mux.NewRouter()
|
mux := mux.NewRouter()
|
||||||
|
|
||||||
// Save cookie name, based on hashed client ID
|
// Save cookie name, based on hashed client ID
|
||||||
h := sha256.New()
|
hs := sha256.Sum256([]byte(*p.ClientId))
|
||||||
bs := string(h.Sum([]byte(*p.ClientId)))
|
bs := hex.EncodeToString(hs[:])
|
||||||
sessionName := fmt.Sprintf("authentik_proxy_%s", bs[:8])
|
sessionName := fmt.Sprintf("authentik_proxy_%s", bs[:8])
|
||||||
|
|
||||||
// When HOST_BROWSER is set, use that as Host header for token requests to make the issuer match
|
// When HOST_BROWSER is set, use that as Host header for token requests to make the issuer match
|
||||||
|
@ -3,6 +3,7 @@ package application
|
|||||||
type ProxyClaims struct {
|
type ProxyClaims struct {
|
||||||
UserAttributes map[string]interface{} `json:"user_attributes"`
|
UserAttributes map[string]interface{} `json:"user_attributes"`
|
||||||
BackendOverride string `json:"backend_override"`
|
BackendOverride string `json:"backend_override"`
|
||||||
|
HostHeader string `json:"host_header"`
|
||||||
IsSuperuser bool `json:"is_superuser"`
|
IsSuperuser bool `json:"is_superuser"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -74,13 +74,18 @@ func (a *Application) proxyModifyRequest(ou *url.URL) func(req *http.Request) {
|
|||||||
r.URL.Scheme = ou.Scheme
|
r.URL.Scheme = ou.Scheme
|
||||||
r.URL.Host = ou.Host
|
r.URL.Host = ou.Host
|
||||||
claims := a.getClaimsFromSession(r)
|
claims := a.getClaimsFromSession(r)
|
||||||
if claims != nil && claims.Proxy != nil && claims.Proxy.BackendOverride != "" {
|
if claims != nil && claims.Proxy != nil {
|
||||||
u, err := url.Parse(claims.Proxy.BackendOverride)
|
if claims.Proxy.BackendOverride != "" {
|
||||||
if err != nil {
|
u, err := url.Parse(claims.Proxy.BackendOverride)
|
||||||
a.log.WithField("backend_override", claims.Proxy.BackendOverride).WithError(err).Warning("failed parse user backend override")
|
if err != nil {
|
||||||
} else {
|
a.log.WithField("backend_override", claims.Proxy.BackendOverride).WithError(err).Warning("failed parse user backend override")
|
||||||
r.URL.Scheme = u.Scheme
|
} else {
|
||||||
r.URL.Host = u.Host
|
r.URL.Scheme = u.Scheme
|
||||||
|
r.URL.Host = u.Host
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if claims.Proxy.HostHeader != "" {
|
||||||
|
r.Host = claims.Proxy.HostHeader
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
a.log.WithField("upstream_url", r.URL.String()).Trace("final upstream url")
|
a.log.WithField("upstream_url", r.URL.String()).Trace("final upstream url")
|
||||||
|
@ -2,6 +2,7 @@ package radius
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/sha512"
|
"crypto/sha512"
|
||||||
|
"encoding/hex"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/getsentry/sentry-go"
|
"github.com/getsentry/sentry-go"
|
||||||
@ -68,7 +69,9 @@ func (rs *RadiusServer) ServeRADIUS(w radius.ResponseWriter, r *radius.Request)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if pi == nil {
|
if pi == nil {
|
||||||
nr.Log().WithField("hashed_secret", string(sha512.New().Sum(r.Secret))).Warning("No provider found")
|
hs := sha512.Sum512([]byte(r.Secret))
|
||||||
|
bs := hex.EncodeToString(hs[:])
|
||||||
|
nr.Log().WithField("hashed_secret", bs).Warning("No provider found")
|
||||||
_ = w.Write(r.Response(radius.CodeAccessReject))
|
_ = w.Write(r.Response(radius.CodeAccessReject))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -67,11 +67,15 @@ func (ws *WebServer) configureStatic() {
|
|||||||
|
|
||||||
// Media files, if backend is file
|
// Media files, if backend is file
|
||||||
if config.Get().Storage.Media.Backend == "file" {
|
if config.Get().Storage.Media.Backend == "file" {
|
||||||
fsMedia := http.StripPrefix("/media", http.FileServer(http.Dir(config.Get().Storage.Media.File.Path)))
|
fsMedia := http.FileServer(http.Dir(config.Get().Storage.Media.File.Path))
|
||||||
indexLessRouter.PathPrefix(config.Get().Web.Path).PathPrefix("/media/").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
indexLessRouter.PathPrefix(config.Get().Web.Path).PathPrefix("/media/").Handler(pathStripper(
|
||||||
w.Header().Set("Content-Security-Policy", "default-src 'none'; style-src 'unsafe-inline'; sandbox")
|
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
fsMedia.ServeHTTP(w, r)
|
w.Header().Set("Content-Security-Policy", "default-src 'none'; style-src 'unsafe-inline'; sandbox")
|
||||||
})
|
fsMedia.ServeHTTP(w, r)
|
||||||
|
}),
|
||||||
|
"media/",
|
||||||
|
config.Get().Web.Path,
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
staticRouter.PathPrefix(config.Get().Web.Path).PathPrefix("/if/help/").Handler(pathStripper(
|
staticRouter.PathPrefix(config.Get().Web.Path).PathPrefix("/if/help/").Handler(pathStripper(
|
||||||
|
@ -83,7 +83,8 @@ if [[ "$1" == "server" ]]; then
|
|||||||
run_authentik
|
run_authentik
|
||||||
elif [[ "$1" == "worker" ]]; then
|
elif [[ "$1" == "worker" ]]; then
|
||||||
set_mode "worker"
|
set_mode "worker"
|
||||||
check_if_root "python -m manage worker"
|
shift
|
||||||
|
check_if_root "python -m manage worker $@"
|
||||||
elif [[ "$1" == "worker-status" ]]; then
|
elif [[ "$1" == "worker-status" ]]; then
|
||||||
wait_for_db
|
wait_for_db
|
||||||
celery -A authentik.root.celery flower \
|
celery -A authentik.root.celery flower \
|
||||||
|
8
lifecycle/aws/package-lock.json
generated
8
lifecycle/aws/package-lock.json
generated
@ -9,7 +9,7 @@
|
|||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"aws-cdk": "^2.1016.0",
|
"aws-cdk": "^2.1016.1",
|
||||||
"cross-env": "^7.0.3"
|
"cross-env": "^7.0.3"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@ -17,9 +17,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/aws-cdk": {
|
"node_modules/aws-cdk": {
|
||||||
"version": "2.1016.0",
|
"version": "2.1016.1",
|
||||||
"resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.1016.0.tgz",
|
"resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.1016.1.tgz",
|
||||||
"integrity": "sha512-zdJ/tQp0iE/s8l8zLQPgdUJUHpS6KblkzdP5nOYC/NbD5OCdhS8QS7vLBkT8M7mNyZh3Ep3C+/m6NsxrurRe0A==",
|
"integrity": "sha512-248TBiluT8jHUjkpzvWJOHv2fS+An9fiII3eji8H7jwfTu5yMBk7on4B/AVNr9A1GXJk9I32qf9Q0A3rLWRYPQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"bin": {
|
"bin": {
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
"node": ">=20"
|
"node": ">=20"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"aws-cdk": "^2.1016.0",
|
"aws-cdk": "^2.1016.1",
|
||||||
"cross-env": "^7.0.3"
|
"cross-env": "^7.0.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,7 @@ Parameters:
|
|||||||
Description: authentik Docker image
|
Description: authentik Docker image
|
||||||
AuthentikVersion:
|
AuthentikVersion:
|
||||||
Type: String
|
Type: String
|
||||||
Default: 2025.4.1
|
Default: 2025.6.2
|
||||||
Description: authentik Docker image tag
|
Description: authentik Docker image tag
|
||||||
AuthentikServerCPU:
|
AuthentikServerCPU:
|
||||||
Type: Number
|
Type: Number
|
||||||
|
Binary file not shown.
@ -32,15 +32,17 @@
|
|||||||
# datenschmutz, 2025
|
# datenschmutz, 2025
|
||||||
# 97cce0ae0cad2a2cc552d3165d04643e_de3d740, 2025
|
# 97cce0ae0cad2a2cc552d3165d04643e_de3d740, 2025
|
||||||
# Dominic Wagner <mail@dominic-wagner.de>, 2025
|
# Dominic Wagner <mail@dominic-wagner.de>, 2025
|
||||||
|
# Till-Frederik Riechard, 2025
|
||||||
|
# Alexander Mnich, 2025
|
||||||
#
|
#
|
||||||
#, fuzzy
|
#, fuzzy
|
||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2025-04-23 09:00+0000\n"
|
"POT-Creation-Date: 2025-05-28 11:25+0000\n"
|
||||||
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
|
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
|
||||||
"Last-Translator: Dominic Wagner <mail@dominic-wagner.de>, 2025\n"
|
"Last-Translator: Alexander Mnich, 2025\n"
|
||||||
"Language-Team: German (https://app.transifex.com/authentik/teams/119923/de/)\n"
|
"Language-Team: German (https://app.transifex.com/authentik/teams/119923/de/)\n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
@ -132,6 +134,10 @@ msgstr ""
|
|||||||
msgid "Web Certificate used by the authentik Core webserver."
|
msgid "Web Certificate used by the authentik Core webserver."
|
||||||
msgstr "Vom Authentik-Core-Webserver verwendetes Zertifikat."
|
msgstr "Vom Authentik-Core-Webserver verwendetes Zertifikat."
|
||||||
|
|
||||||
|
#: authentik/brands/models.py
|
||||||
|
msgid "Certificates used for client authentication."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/brands/models.py
|
#: authentik/brands/models.py
|
||||||
msgid "Brand"
|
msgid "Brand"
|
||||||
msgstr "Marke"
|
msgstr "Marke"
|
||||||
@ -405,7 +411,7 @@ msgstr "Eigenschaften"
|
|||||||
|
|
||||||
#: authentik/core/models.py
|
#: authentik/core/models.py
|
||||||
msgid "session data"
|
msgid "session data"
|
||||||
msgstr ""
|
msgstr "Sitzungsdaten"
|
||||||
|
|
||||||
#: authentik/core/models.py
|
#: authentik/core/models.py
|
||||||
msgid "Session"
|
msgid "Session"
|
||||||
@ -533,7 +539,7 @@ msgstr ""
|
|||||||
|
|
||||||
#: authentik/enterprise/policies/unique_password/models.py
|
#: authentik/enterprise/policies/unique_password/models.py
|
||||||
msgid "Number of passwords to check against."
|
msgid "Number of passwords to check against."
|
||||||
msgstr ""
|
msgstr "Anzahl Passwörter, gegen die geprüft wird."
|
||||||
|
|
||||||
#: authentik/enterprise/policies/unique_password/models.py
|
#: authentik/enterprise/policies/unique_password/models.py
|
||||||
#: authentik/policies/password/models.py
|
#: authentik/policies/password/models.py
|
||||||
@ -543,18 +549,20 @@ msgstr "Passwort nicht im Kontext festgelegt"
|
|||||||
#: authentik/enterprise/policies/unique_password/models.py
|
#: authentik/enterprise/policies/unique_password/models.py
|
||||||
msgid "This password has been used previously. Please choose a different one."
|
msgid "This password has been used previously. Please choose a different one."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
"Dieses Passwort wurde in Vergangenheit bereits verwendet. Bitte nutzen Sie "
|
||||||
|
"ein anderes."
|
||||||
|
|
||||||
#: authentik/enterprise/policies/unique_password/models.py
|
#: authentik/enterprise/policies/unique_password/models.py
|
||||||
msgid "Password Uniqueness Policy"
|
msgid "Password Uniqueness Policy"
|
||||||
msgstr ""
|
msgstr "Passwort-Einzigartigkeits-Richtlinie"
|
||||||
|
|
||||||
#: authentik/enterprise/policies/unique_password/models.py
|
#: authentik/enterprise/policies/unique_password/models.py
|
||||||
msgid "Password Uniqueness Policies"
|
msgid "Password Uniqueness Policies"
|
||||||
msgstr ""
|
msgstr "Passwort-Einzigartigkeits-Richtlinien"
|
||||||
|
|
||||||
#: authentik/enterprise/policies/unique_password/models.py
|
#: authentik/enterprise/policies/unique_password/models.py
|
||||||
msgid "User Password History"
|
msgid "User Password History"
|
||||||
msgstr ""
|
msgstr "Nutzer-Passwort-Historie"
|
||||||
|
|
||||||
#: authentik/enterprise/policy.py
|
#: authentik/enterprise/policy.py
|
||||||
msgid "Enterprise required to access this feature."
|
msgid "Enterprise required to access this feature."
|
||||||
@ -693,6 +701,33 @@ msgstr "Endgeräte"
|
|||||||
msgid "Verifying your browser..."
|
msgid "Verifying your browser..."
|
||||||
msgstr "Verifiziere deinen Browser..."
|
msgstr "Verifiziere deinen Browser..."
|
||||||
|
|
||||||
|
#: authentik/enterprise/stages/mtls/models.py
|
||||||
|
msgid ""
|
||||||
|
"Configure certificate authorities to validate the certificate against. This "
|
||||||
|
"option has a higher priority than the `client_certificate` option on "
|
||||||
|
"`Brand`."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/enterprise/stages/mtls/models.py
|
||||||
|
msgid "Mutual TLS Stage"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/enterprise/stages/mtls/models.py
|
||||||
|
msgid "Mutual TLS Stages"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/enterprise/stages/mtls/models.py
|
||||||
|
msgid "Permissions to pass Certificates for outposts."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/enterprise/stages/mtls/stage.py
|
||||||
|
msgid "Certificate required but no certificate was given."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/enterprise/stages/mtls/stage.py
|
||||||
|
msgid "No user found for certificate."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/enterprise/stages/source/models.py
|
#: authentik/enterprise/stages/source/models.py
|
||||||
msgid ""
|
msgid ""
|
||||||
"Amount of time a user can take to return from the source to continue the "
|
"Amount of time a user can take to return from the source to continue the "
|
||||||
@ -988,7 +1023,7 @@ msgstr ""
|
|||||||
|
|
||||||
#: authentik/flows/models.py
|
#: authentik/flows/models.py
|
||||||
msgid "Evaluate policies when the Stage is presented to the user."
|
msgid "Evaluate policies when the Stage is presented to the user."
|
||||||
msgstr ""
|
msgstr "Richtlinien auswerten, wenn die Phase dem Benutzer angezeigt wird."
|
||||||
|
|
||||||
#: authentik/flows/models.py
|
#: authentik/flows/models.py
|
||||||
msgid ""
|
msgid ""
|
||||||
@ -1043,9 +1078,12 @@ msgid "Starting full provider sync"
|
|||||||
msgstr "Starte komplette Provider Synchronisation."
|
msgstr "Starte komplette Provider Synchronisation."
|
||||||
|
|
||||||
#: authentik/lib/sync/outgoing/tasks.py
|
#: authentik/lib/sync/outgoing/tasks.py
|
||||||
#, python-brace-format
|
msgid "Syncing users"
|
||||||
msgid "Syncing page {page} of users"
|
msgstr ""
|
||||||
msgstr "Synchonisiere Benutzer Seite {page}"
|
|
||||||
|
#: authentik/lib/sync/outgoing/tasks.py
|
||||||
|
msgid "Syncing groups"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/lib/sync/outgoing/tasks.py
|
#: authentik/lib/sync/outgoing/tasks.py
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
@ -1593,11 +1631,11 @@ msgstr "ES256 (Asymmetrische Verschlüsselung)"
|
|||||||
|
|
||||||
#: authentik/providers/oauth2/models.py
|
#: authentik/providers/oauth2/models.py
|
||||||
msgid "ES384 (Asymmetric Encryption)"
|
msgid "ES384 (Asymmetric Encryption)"
|
||||||
msgstr ""
|
msgstr "ES384 (Asymmetrische Verschlüsselung)"
|
||||||
|
|
||||||
#: authentik/providers/oauth2/models.py
|
#: authentik/providers/oauth2/models.py
|
||||||
msgid "ES512 (Asymmetric Encryption)"
|
msgid "ES512 (Asymmetric Encryption)"
|
||||||
msgstr ""
|
msgstr "ES5122 (Asymmetrische Verschlüsselung)"
|
||||||
|
|
||||||
#: authentik/providers/oauth2/models.py
|
#: authentik/providers/oauth2/models.py
|
||||||
msgid "Scope used by the client"
|
msgid "Scope used by the client"
|
||||||
@ -2183,11 +2221,11 @@ msgstr "Standard"
|
|||||||
|
|
||||||
#: authentik/providers/scim/models.py
|
#: authentik/providers/scim/models.py
|
||||||
msgid "AWS"
|
msgid "AWS"
|
||||||
msgstr ""
|
msgstr "AWS"
|
||||||
|
|
||||||
#: authentik/providers/scim/models.py
|
#: authentik/providers/scim/models.py
|
||||||
msgid "Slack"
|
msgid "Slack"
|
||||||
msgstr ""
|
msgstr "Slack"
|
||||||
|
|
||||||
#: authentik/providers/scim/models.py
|
#: authentik/providers/scim/models.py
|
||||||
msgid "Base URL to SCIM requests, usually ends in /v2"
|
msgid "Base URL to SCIM requests, usually ends in /v2"
|
||||||
@ -2199,7 +2237,7 @@ msgstr "Authentifizierungstoken"
|
|||||||
|
|
||||||
#: authentik/providers/scim/models.py
|
#: authentik/providers/scim/models.py
|
||||||
msgid "SCIM Compatibility Mode"
|
msgid "SCIM Compatibility Mode"
|
||||||
msgstr ""
|
msgstr "SCIM Kompatibilitätsmodus"
|
||||||
|
|
||||||
#: authentik/providers/scim/models.py
|
#: authentik/providers/scim/models.py
|
||||||
msgid "Alter authentik behavior for vendor-specific SCIM implementations."
|
msgid "Alter authentik behavior for vendor-specific SCIM implementations."
|
||||||
@ -2231,7 +2269,7 @@ msgstr "Rollen"
|
|||||||
|
|
||||||
#: authentik/rbac/models.py
|
#: authentik/rbac/models.py
|
||||||
msgid "Initial Permissions"
|
msgid "Initial Permissions"
|
||||||
msgstr ""
|
msgstr "Initiale Berechtigungen"
|
||||||
|
|
||||||
#: authentik/rbac/models.py
|
#: authentik/rbac/models.py
|
||||||
msgid "System permission"
|
msgid "System permission"
|
||||||
@ -2487,6 +2525,12 @@ msgid ""
|
|||||||
"Active Directory"
|
"Active Directory"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/sources/ldap/models.py
|
||||||
|
msgid ""
|
||||||
|
"Delete authentik users and groups which were previously supplied by this "
|
||||||
|
"source, but are now missing from it."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/sources/ldap/models.py
|
#: authentik/sources/ldap/models.py
|
||||||
msgid "LDAP Source"
|
msgid "LDAP Source"
|
||||||
msgstr "LDAP Quelle"
|
msgstr "LDAP Quelle"
|
||||||
@ -2504,20 +2548,25 @@ msgid "LDAP Source Property Mappings"
|
|||||||
msgstr "LDAP Quelle Eigenschafts-Zuordnungen"
|
msgstr "LDAP Quelle Eigenschafts-Zuordnungen"
|
||||||
|
|
||||||
#: authentik/sources/ldap/models.py
|
#: authentik/sources/ldap/models.py
|
||||||
msgid "User LDAP Source Connection"
|
msgid ""
|
||||||
|
"Unique ID used while checking if this object still exists in the directory."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/sources/ldap/models.py
|
||||||
|
msgid "User LDAP Source Connection"
|
||||||
|
msgstr "Benutzer LDAP-Quellverbindung"
|
||||||
|
|
||||||
#: authentik/sources/ldap/models.py
|
#: authentik/sources/ldap/models.py
|
||||||
msgid "User LDAP Source Connections"
|
msgid "User LDAP Source Connections"
|
||||||
msgstr ""
|
msgstr "Benutzer LDAP-Quellverbindungen"
|
||||||
|
|
||||||
#: authentik/sources/ldap/models.py
|
#: authentik/sources/ldap/models.py
|
||||||
msgid "Group LDAP Source Connection"
|
msgid "Group LDAP Source Connection"
|
||||||
msgstr ""
|
msgstr "LDAP Gruppen Quellverbindung"
|
||||||
|
|
||||||
#: authentik/sources/ldap/models.py
|
#: authentik/sources/ldap/models.py
|
||||||
msgid "Group LDAP Source Connections"
|
msgid "Group LDAP Source Connections"
|
||||||
msgstr ""
|
msgstr "LDAP Gruppen Quellverbindungen"
|
||||||
|
|
||||||
#: authentik/sources/ldap/signals.py
|
#: authentik/sources/ldap/signals.py
|
||||||
msgid "Password does not match Active Directory Complexity."
|
msgid "Password does not match Active Directory Complexity."
|
||||||
@ -2530,7 +2579,7 @@ msgstr "Kein Token empfangen."
|
|||||||
|
|
||||||
#: authentik/sources/oauth/models.py
|
#: authentik/sources/oauth/models.py
|
||||||
msgid "HTTP Basic Authentication"
|
msgid "HTTP Basic Authentication"
|
||||||
msgstr ""
|
msgstr "HTTP Basic Authentifizierung"
|
||||||
|
|
||||||
#: authentik/sources/oauth/models.py
|
#: authentik/sources/oauth/models.py
|
||||||
msgid "Include the client ID and secret as request parameters"
|
msgid "Include the client ID and secret as request parameters"
|
||||||
@ -2896,6 +2945,11 @@ msgstr "SAML Gruppen Quellverbindung"
|
|||||||
msgid "Group SAML Source Connections"
|
msgid "Group SAML Source Connections"
|
||||||
msgstr "SAML Gruppen Quellverbindungen"
|
msgstr "SAML Gruppen Quellverbindungen"
|
||||||
|
|
||||||
|
#: authentik/sources/saml/views.py
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Continue to {source_name}"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/sources/scim/models.py
|
#: authentik/sources/scim/models.py
|
||||||
msgid "SCIM Source"
|
msgid "SCIM Source"
|
||||||
msgstr "SCIM Quelle"
|
msgstr "SCIM Quelle"
|
||||||
@ -2930,7 +2984,7 @@ msgstr "Duo Geräte"
|
|||||||
|
|
||||||
#: authentik/stages/authenticator_email/models.py
|
#: authentik/stages/authenticator_email/models.py
|
||||||
msgid "Email OTP"
|
msgid "Email OTP"
|
||||||
msgstr ""
|
msgstr "E-Mail Einmalpasswort"
|
||||||
|
|
||||||
#: authentik/stages/authenticator_email/models.py
|
#: authentik/stages/authenticator_email/models.py
|
||||||
#: authentik/stages/email/models.py
|
#: authentik/stages/email/models.py
|
||||||
@ -2963,11 +3017,11 @@ msgstr "Beim Rendern der E-Mail-Vorlage ist ein Fehler aufgetreten"
|
|||||||
|
|
||||||
#: authentik/stages/authenticator_email/models.py
|
#: authentik/stages/authenticator_email/models.py
|
||||||
msgid "Email Device"
|
msgid "Email Device"
|
||||||
msgstr ""
|
msgstr "E-Mail Gerät"
|
||||||
|
|
||||||
#: authentik/stages/authenticator_email/models.py
|
#: authentik/stages/authenticator_email/models.py
|
||||||
msgid "Email Devices"
|
msgid "Email Devices"
|
||||||
msgstr ""
|
msgstr "E-Mail Geräte"
|
||||||
|
|
||||||
#: authentik/stages/authenticator_email/stage.py
|
#: authentik/stages/authenticator_email/stage.py
|
||||||
#: authentik/stages/authenticator_sms/stage.py
|
#: authentik/stages/authenticator_sms/stage.py
|
||||||
@ -2977,7 +3031,7 @@ msgstr "Code stimmt nicht überein"
|
|||||||
|
|
||||||
#: authentik/stages/authenticator_email/stage.py
|
#: authentik/stages/authenticator_email/stage.py
|
||||||
msgid "Invalid email"
|
msgid "Invalid email"
|
||||||
msgstr ""
|
msgstr "Ungültige E-Mail"
|
||||||
|
|
||||||
#: authentik/stages/authenticator_email/templates/email/email_otp.html
|
#: authentik/stages/authenticator_email/templates/email/email_otp.html
|
||||||
#: authentik/stages/email/templates/email/password_reset.html
|
#: authentik/stages/email/templates/email/password_reset.html
|
||||||
@ -3273,6 +3327,10 @@ msgstr "Zustimmung der Benutzer"
|
|||||||
msgid "User Consents"
|
msgid "User Consents"
|
||||||
msgstr "Zustimmungen der Benutzer"
|
msgstr "Zustimmungen der Benutzer"
|
||||||
|
|
||||||
|
#: authentik/stages/consent/stage.py
|
||||||
|
msgid "Invalid consent token, re-showing prompt"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/stages/deny/models.py
|
#: authentik/stages/deny/models.py
|
||||||
msgid "Deny Stage"
|
msgid "Deny Stage"
|
||||||
msgstr "Verweigerungsstufe"
|
msgstr "Verweigerungsstufe"
|
||||||
@ -3289,6 +3347,14 @@ msgstr "Dummy Stufe"
|
|||||||
msgid "Dummy Stages"
|
msgid "Dummy Stages"
|
||||||
msgstr "Dummy Stufen"
|
msgstr "Dummy Stufen"
|
||||||
|
|
||||||
|
#: authentik/stages/email/flow.py
|
||||||
|
msgid "Continue to confirm this email address."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/stages/email/flow.py
|
||||||
|
msgid "Link was already used, please request a new link."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/stages/email/models.py
|
#: authentik/stages/email/models.py
|
||||||
msgid "Password Reset"
|
msgid "Password Reset"
|
||||||
msgstr "Passwort zurücksetzen"
|
msgstr "Passwort zurücksetzen"
|
||||||
@ -3890,10 +3956,11 @@ msgstr ""
|
|||||||
#: authentik/tenants/models.py
|
#: authentik/tenants/models.py
|
||||||
msgid "Reputation cannot decrease lower than this value. Zero or negative."
|
msgid "Reputation cannot decrease lower than this value. Zero or negative."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
"Reputation kann nicht niedriger als dieser Wert sein. Null oder negativ."
|
||||||
|
|
||||||
#: authentik/tenants/models.py
|
#: authentik/tenants/models.py
|
||||||
msgid "Reputation cannot increase higher than this value. Zero or positive."
|
msgid "Reputation cannot increase higher than this value. Zero or positive."
|
||||||
msgstr ""
|
msgstr "Reputation kann nicht höher als dieser Wert sein. Null oder positiv."
|
||||||
|
|
||||||
#: authentik/tenants/models.py
|
#: authentik/tenants/models.py
|
||||||
msgid "The option configures the footer links on the flow executor pages."
|
msgid "The option configures the footer links on the flow executor pages."
|
||||||
|
@ -8,7 +8,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2025-04-23 09:00+0000\n"
|
"POT-Creation-Date: 2025-06-02 00:12+0000\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
@ -93,6 +93,10 @@ msgstr ""
|
|||||||
msgid "Web Certificate used by the authentik Core webserver."
|
msgid "Web Certificate used by the authentik Core webserver."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/brands/models.py
|
||||||
|
msgid "Certificates used for client authentication."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/brands/models.py
|
#: authentik/brands/models.py
|
||||||
msgid "Brand"
|
msgid "Brand"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -616,6 +620,32 @@ msgstr ""
|
|||||||
msgid "Verifying your browser..."
|
msgid "Verifying your browser..."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/enterprise/stages/mtls/models.py
|
||||||
|
msgid ""
|
||||||
|
"Configure certificate authorities to validate the certificate against. This "
|
||||||
|
"option has a higher priority than the `client_certificate` option on `Brand`."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/enterprise/stages/mtls/models.py
|
||||||
|
msgid "Mutual TLS Stage"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/enterprise/stages/mtls/models.py
|
||||||
|
msgid "Mutual TLS Stages"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/enterprise/stages/mtls/models.py
|
||||||
|
msgid "Permissions to pass Certificates for outposts."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/enterprise/stages/mtls/stage.py
|
||||||
|
msgid "Certificate required but no certificate was given."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/enterprise/stages/mtls/stage.py
|
||||||
|
msgid "No user found for certificate."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/enterprise/stages/source/models.py
|
#: authentik/enterprise/stages/source/models.py
|
||||||
msgid ""
|
msgid ""
|
||||||
"Amount of time a user can take to return from the source to continue the "
|
"Amount of time a user can take to return from the source to continue the "
|
||||||
@ -931,8 +961,11 @@ msgid "Starting full provider sync"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/lib/sync/outgoing/tasks.py
|
#: authentik/lib/sync/outgoing/tasks.py
|
||||||
#, python-brace-format
|
msgid "Syncing users"
|
||||||
msgid "Syncing page {page} of users"
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/lib/sync/outgoing/tasks.py
|
||||||
|
msgid "Syncing groups"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/lib/sync/outgoing/tasks.py
|
#: authentik/lib/sync/outgoing/tasks.py
|
||||||
@ -2193,6 +2226,10 @@ msgstr ""
|
|||||||
msgid "Consider Objects matching this filter to be Users."
|
msgid "Consider Objects matching this filter to be Users."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/sources/ldap/models.py
|
||||||
|
msgid "Attribute which matches the value of `group_membership_field`."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/sources/ldap/models.py
|
#: authentik/sources/ldap/models.py
|
||||||
msgid "Field which contains members of a group."
|
msgid "Field which contains members of a group."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -2222,6 +2259,12 @@ msgid ""
|
|||||||
"Active Directory"
|
"Active Directory"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/sources/ldap/models.py
|
||||||
|
msgid ""
|
||||||
|
"Delete authentik users and groups which were previously supplied by this "
|
||||||
|
"source, but are now missing from it."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/sources/ldap/models.py
|
#: authentik/sources/ldap/models.py
|
||||||
msgid "LDAP Source"
|
msgid "LDAP Source"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -2238,6 +2281,11 @@ msgstr ""
|
|||||||
msgid "LDAP Source Property Mappings"
|
msgid "LDAP Source Property Mappings"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/sources/ldap/models.py
|
||||||
|
msgid ""
|
||||||
|
"Unique ID used while checking if this object still exists in the directory."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/sources/ldap/models.py
|
#: authentik/sources/ldap/models.py
|
||||||
msgid "User LDAP Source Connection"
|
msgid "User LDAP Source Connection"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -2609,6 +2657,11 @@ msgstr ""
|
|||||||
msgid "Group SAML Source Connections"
|
msgid "Group SAML Source Connections"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/sources/saml/views.py
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Continue to {source_name}"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/sources/scim/models.py
|
#: authentik/sources/scim/models.py
|
||||||
msgid "SCIM Source"
|
msgid "SCIM Source"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -2964,6 +3017,10 @@ msgstr ""
|
|||||||
msgid "User Consents"
|
msgid "User Consents"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/stages/consent/stage.py
|
||||||
|
msgid "Invalid consent token, re-showing prompt"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/stages/deny/models.py
|
#: authentik/stages/deny/models.py
|
||||||
msgid "Deny Stage"
|
msgid "Deny Stage"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -2980,6 +3037,14 @@ msgstr ""
|
|||||||
msgid "Dummy Stages"
|
msgid "Dummy Stages"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/stages/email/flow.py
|
||||||
|
msgid "Continue to confirm this email address."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/stages/email/flow.py
|
||||||
|
msgid "Link was already used, please request a new link."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/stages/email/models.py
|
#: authentik/stages/email/models.py
|
||||||
msgid "Password Reset"
|
msgid "Password Reset"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -3432,10 +3497,6 @@ msgstr ""
|
|||||||
msgid "No Pending user to login."
|
msgid "No Pending user to login."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/stages/user_login/stage.py
|
|
||||||
msgid "Successfully logged in!"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: authentik/stages/user_logout/models.py
|
#: authentik/stages/user_logout/models.py
|
||||||
msgid "User Logout Stage"
|
msgid "User Logout Stage"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
Binary file not shown.
@ -15,7 +15,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2025-04-23 09:00+0000\n"
|
"POT-Creation-Date: 2025-05-28 11:25+0000\n"
|
||||||
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
|
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
|
||||||
"Last-Translator: Jens L. <jens@goauthentik.io>, 2025\n"
|
"Last-Translator: Jens L. <jens@goauthentik.io>, 2025\n"
|
||||||
"Language-Team: Spanish (https://app.transifex.com/authentik/teams/119923/es/)\n"
|
"Language-Team: Spanish (https://app.transifex.com/authentik/teams/119923/es/)\n"
|
||||||
@ -109,6 +109,10 @@ msgstr ""
|
|||||||
msgid "Web Certificate used by the authentik Core webserver."
|
msgid "Web Certificate used by the authentik Core webserver."
|
||||||
msgstr "Certificado Web usado por el servidor web Core de authentik"
|
msgstr "Certificado Web usado por el servidor web Core de authentik"
|
||||||
|
|
||||||
|
#: authentik/brands/models.py
|
||||||
|
msgid "Certificates used for client authentication."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/brands/models.py
|
#: authentik/brands/models.py
|
||||||
msgid "Brand"
|
msgid "Brand"
|
||||||
msgstr "Marca"
|
msgstr "Marca"
|
||||||
@ -671,6 +675,33 @@ msgstr "Dispositivos de Punto de Conexión"
|
|||||||
msgid "Verifying your browser..."
|
msgid "Verifying your browser..."
|
||||||
msgstr "Verificando tu navegador..."
|
msgstr "Verificando tu navegador..."
|
||||||
|
|
||||||
|
#: authentik/enterprise/stages/mtls/models.py
|
||||||
|
msgid ""
|
||||||
|
"Configure certificate authorities to validate the certificate against. This "
|
||||||
|
"option has a higher priority than the `client_certificate` option on "
|
||||||
|
"`Brand`."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/enterprise/stages/mtls/models.py
|
||||||
|
msgid "Mutual TLS Stage"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/enterprise/stages/mtls/models.py
|
||||||
|
msgid "Mutual TLS Stages"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/enterprise/stages/mtls/models.py
|
||||||
|
msgid "Permissions to pass Certificates for outposts."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/enterprise/stages/mtls/stage.py
|
||||||
|
msgid "Certificate required but no certificate was given."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/enterprise/stages/mtls/stage.py
|
||||||
|
msgid "No user found for certificate."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/enterprise/stages/source/models.py
|
#: authentik/enterprise/stages/source/models.py
|
||||||
msgid ""
|
msgid ""
|
||||||
"Amount of time a user can take to return from the source to continue the "
|
"Amount of time a user can take to return from the source to continue the "
|
||||||
@ -1009,9 +1040,12 @@ msgid "Starting full provider sync"
|
|||||||
msgstr "Iniciando sincronización completa de proveedor"
|
msgstr "Iniciando sincronización completa de proveedor"
|
||||||
|
|
||||||
#: authentik/lib/sync/outgoing/tasks.py
|
#: authentik/lib/sync/outgoing/tasks.py
|
||||||
#, python-brace-format
|
msgid "Syncing users"
|
||||||
msgid "Syncing page {page} of users"
|
msgstr ""
|
||||||
msgstr "Sincronizando página {page} de usuarios"
|
|
||||||
|
#: authentik/lib/sync/outgoing/tasks.py
|
||||||
|
msgid "Syncing groups"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/lib/sync/outgoing/tasks.py
|
#: authentik/lib/sync/outgoing/tasks.py
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
@ -2452,6 +2486,12 @@ msgid ""
|
|||||||
"Active Directory"
|
"Active Directory"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/sources/ldap/models.py
|
||||||
|
msgid ""
|
||||||
|
"Delete authentik users and groups which were previously supplied by this "
|
||||||
|
"source, but are now missing from it."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/sources/ldap/models.py
|
#: authentik/sources/ldap/models.py
|
||||||
msgid "LDAP Source"
|
msgid "LDAP Source"
|
||||||
msgstr "Fuente de LDAP"
|
msgstr "Fuente de LDAP"
|
||||||
@ -2468,6 +2508,11 @@ msgstr "Asignación de Propiedades de Fuente de LDAP"
|
|||||||
msgid "LDAP Source Property Mappings"
|
msgid "LDAP Source Property Mappings"
|
||||||
msgstr "Asignaciones de Propiedades de Fuente de LDAP"
|
msgstr "Asignaciones de Propiedades de Fuente de LDAP"
|
||||||
|
|
||||||
|
#: authentik/sources/ldap/models.py
|
||||||
|
msgid ""
|
||||||
|
"Unique ID used while checking if this object still exists in the directory."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/sources/ldap/models.py
|
#: authentik/sources/ldap/models.py
|
||||||
msgid "User LDAP Source Connection"
|
msgid "User LDAP Source Connection"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -2859,6 +2904,11 @@ msgstr "Conexión de Fuente de SAML de Grupo"
|
|||||||
msgid "Group SAML Source Connections"
|
msgid "Group SAML Source Connections"
|
||||||
msgstr "Conexiones de Fuente de SAML de Grupo"
|
msgstr "Conexiones de Fuente de SAML de Grupo"
|
||||||
|
|
||||||
|
#: authentik/sources/saml/views.py
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Continue to {source_name}"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/sources/scim/models.py
|
#: authentik/sources/scim/models.py
|
||||||
msgid "SCIM Source"
|
msgid "SCIM Source"
|
||||||
msgstr "Fuente de SCIM"
|
msgstr "Fuente de SCIM"
|
||||||
@ -3245,6 +3295,10 @@ msgstr "Consentimiento del usuario"
|
|||||||
msgid "User Consents"
|
msgid "User Consents"
|
||||||
msgstr "Consentimientos del usuario"
|
msgstr "Consentimientos del usuario"
|
||||||
|
|
||||||
|
#: authentik/stages/consent/stage.py
|
||||||
|
msgid "Invalid consent token, re-showing prompt"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/stages/deny/models.py
|
#: authentik/stages/deny/models.py
|
||||||
msgid "Deny Stage"
|
msgid "Deny Stage"
|
||||||
msgstr "Etapa de denegación"
|
msgstr "Etapa de denegación"
|
||||||
@ -3261,6 +3315,14 @@ msgstr "Escenario ficticio"
|
|||||||
msgid "Dummy Stages"
|
msgid "Dummy Stages"
|
||||||
msgstr "Etapas ficticias"
|
msgstr "Etapas ficticias"
|
||||||
|
|
||||||
|
#: authentik/stages/email/flow.py
|
||||||
|
msgid "Continue to confirm this email address."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/stages/email/flow.py
|
||||||
|
msgid "Link was already used, please request a new link."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/stages/email/models.py
|
#: authentik/stages/email/models.py
|
||||||
msgid "Password Reset"
|
msgid "Password Reset"
|
||||||
msgstr "Restablecimiento de contraseña"
|
msgstr "Restablecimiento de contraseña"
|
||||||
|
Binary file not shown.
@ -15,7 +15,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2025-04-23 09:00+0000\n"
|
"POT-Creation-Date: 2025-05-28 11:25+0000\n"
|
||||||
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
|
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
|
||||||
"Last-Translator: Ville Ranki, 2025\n"
|
"Last-Translator: Ville Ranki, 2025\n"
|
||||||
"Language-Team: Finnish (https://app.transifex.com/authentik/teams/119923/fi/)\n"
|
"Language-Team: Finnish (https://app.transifex.com/authentik/teams/119923/fi/)\n"
|
||||||
@ -106,6 +106,10 @@ msgstr ""
|
|||||||
msgid "Web Certificate used by the authentik Core webserver."
|
msgid "Web Certificate used by the authentik Core webserver."
|
||||||
msgstr "Web-sertifikaatti, jota authentik Core -verkkopalvelin käyttää."
|
msgstr "Web-sertifikaatti, jota authentik Core -verkkopalvelin käyttää."
|
||||||
|
|
||||||
|
#: authentik/brands/models.py
|
||||||
|
msgid "Certificates used for client authentication."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/brands/models.py
|
#: authentik/brands/models.py
|
||||||
msgid "Brand"
|
msgid "Brand"
|
||||||
msgstr "Brändi"
|
msgstr "Brändi"
|
||||||
@ -658,6 +662,33 @@ msgstr "Päätelaitteet"
|
|||||||
msgid "Verifying your browser..."
|
msgid "Verifying your browser..."
|
||||||
msgstr "Selaintasi varmennetaan..."
|
msgstr "Selaintasi varmennetaan..."
|
||||||
|
|
||||||
|
#: authentik/enterprise/stages/mtls/models.py
|
||||||
|
msgid ""
|
||||||
|
"Configure certificate authorities to validate the certificate against. This "
|
||||||
|
"option has a higher priority than the `client_certificate` option on "
|
||||||
|
"`Brand`."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/enterprise/stages/mtls/models.py
|
||||||
|
msgid "Mutual TLS Stage"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/enterprise/stages/mtls/models.py
|
||||||
|
msgid "Mutual TLS Stages"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/enterprise/stages/mtls/models.py
|
||||||
|
msgid "Permissions to pass Certificates for outposts."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/enterprise/stages/mtls/stage.py
|
||||||
|
msgid "Certificate required but no certificate was given."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/enterprise/stages/mtls/stage.py
|
||||||
|
msgid "No user found for certificate."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/enterprise/stages/source/models.py
|
#: authentik/enterprise/stages/source/models.py
|
||||||
msgid ""
|
msgid ""
|
||||||
"Amount of time a user can take to return from the source to continue the "
|
"Amount of time a user can take to return from the source to continue the "
|
||||||
@ -996,9 +1027,12 @@ msgid "Starting full provider sync"
|
|||||||
msgstr "Käynnistetään palveluntarjoajan täysi synkronisointi"
|
msgstr "Käynnistetään palveluntarjoajan täysi synkronisointi"
|
||||||
|
|
||||||
#: authentik/lib/sync/outgoing/tasks.py
|
#: authentik/lib/sync/outgoing/tasks.py
|
||||||
#, python-brace-format
|
msgid "Syncing users"
|
||||||
msgid "Syncing page {page} of users"
|
msgstr ""
|
||||||
msgstr "Synkronoidaan käyttäjien sivua {page}"
|
|
||||||
|
#: authentik/lib/sync/outgoing/tasks.py
|
||||||
|
msgid "Syncing groups"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/lib/sync/outgoing/tasks.py
|
#: authentik/lib/sync/outgoing/tasks.py
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
@ -2429,6 +2463,12 @@ msgid ""
|
|||||||
"Active Directory"
|
"Active Directory"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/sources/ldap/models.py
|
||||||
|
msgid ""
|
||||||
|
"Delete authentik users and groups which were previously supplied by this "
|
||||||
|
"source, but are now missing from it."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/sources/ldap/models.py
|
#: authentik/sources/ldap/models.py
|
||||||
msgid "LDAP Source"
|
msgid "LDAP Source"
|
||||||
msgstr "LDAP-lähde"
|
msgstr "LDAP-lähde"
|
||||||
@ -2445,6 +2485,11 @@ msgstr "LDAP-lähteen ominaisuuskytkentä"
|
|||||||
msgid "LDAP Source Property Mappings"
|
msgid "LDAP Source Property Mappings"
|
||||||
msgstr "LDAP-lähteen ominaisuuskytkennät"
|
msgstr "LDAP-lähteen ominaisuuskytkennät"
|
||||||
|
|
||||||
|
#: authentik/sources/ldap/models.py
|
||||||
|
msgid ""
|
||||||
|
"Unique ID used while checking if this object still exists in the directory."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/sources/ldap/models.py
|
#: authentik/sources/ldap/models.py
|
||||||
msgid "User LDAP Source Connection"
|
msgid "User LDAP Source Connection"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -2837,6 +2882,11 @@ msgstr "Ryhmän SAML-lähteen yhteys"
|
|||||||
msgid "Group SAML Source Connections"
|
msgid "Group SAML Source Connections"
|
||||||
msgstr "Ryhmän SAML-lähteen yhteydet"
|
msgstr "Ryhmän SAML-lähteen yhteydet"
|
||||||
|
|
||||||
|
#: authentik/sources/saml/views.py
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Continue to {source_name}"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/sources/scim/models.py
|
#: authentik/sources/scim/models.py
|
||||||
msgid "SCIM Source"
|
msgid "SCIM Source"
|
||||||
msgstr "SCIM-lähde"
|
msgstr "SCIM-lähde"
|
||||||
@ -3216,6 +3266,10 @@ msgstr "Käyttäjän hyväksyntä"
|
|||||||
msgid "User Consents"
|
msgid "User Consents"
|
||||||
msgstr "Käyttäjän hyväksynnät"
|
msgstr "Käyttäjän hyväksynnät"
|
||||||
|
|
||||||
|
#: authentik/stages/consent/stage.py
|
||||||
|
msgid "Invalid consent token, re-showing prompt"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/stages/deny/models.py
|
#: authentik/stages/deny/models.py
|
||||||
msgid "Deny Stage"
|
msgid "Deny Stage"
|
||||||
msgstr "Kieltovaihe"
|
msgstr "Kieltovaihe"
|
||||||
@ -3232,6 +3286,14 @@ msgstr "Valevaihe"
|
|||||||
msgid "Dummy Stages"
|
msgid "Dummy Stages"
|
||||||
msgstr "Valevaiheet"
|
msgstr "Valevaiheet"
|
||||||
|
|
||||||
|
#: authentik/stages/email/flow.py
|
||||||
|
msgid "Continue to confirm this email address."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/stages/email/flow.py
|
||||||
|
msgid "Link was already used, please request a new link."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/stages/email/models.py
|
#: authentik/stages/email/models.py
|
||||||
msgid "Password Reset"
|
msgid "Password Reset"
|
||||||
msgstr "Salasanan nollaus"
|
msgstr "Salasanan nollaus"
|
||||||
|
@ -19,7 +19,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2025-04-23 09:00+0000\n"
|
"POT-Creation-Date: 2025-05-28 11:25+0000\n"
|
||||||
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
|
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
|
||||||
"Last-Translator: Marc Schmitt, 2025\n"
|
"Last-Translator: Marc Schmitt, 2025\n"
|
||||||
"Language-Team: French (https://app.transifex.com/authentik/teams/119923/fr/)\n"
|
"Language-Team: French (https://app.transifex.com/authentik/teams/119923/fr/)\n"
|
||||||
@ -113,6 +113,10 @@ msgstr ""
|
|||||||
msgid "Web Certificate used by the authentik Core webserver."
|
msgid "Web Certificate used by the authentik Core webserver."
|
||||||
msgstr "Certificate Web utilisé par le serveur web d'authentik core."
|
msgstr "Certificate Web utilisé par le serveur web d'authentik core."
|
||||||
|
|
||||||
|
#: authentik/brands/models.py
|
||||||
|
msgid "Certificates used for client authentication."
|
||||||
|
msgstr "Certificats utilisés pour l'authentification client."
|
||||||
|
|
||||||
#: authentik/brands/models.py
|
#: authentik/brands/models.py
|
||||||
msgid "Brand"
|
msgid "Brand"
|
||||||
msgstr "Marque"
|
msgstr "Marque"
|
||||||
@ -675,6 +679,36 @@ msgstr "Appareils point de terminaison"
|
|||||||
msgid "Verifying your browser..."
|
msgid "Verifying your browser..."
|
||||||
msgstr "Vérification de votre navigateur..."
|
msgstr "Vérification de votre navigateur..."
|
||||||
|
|
||||||
|
#: authentik/enterprise/stages/mtls/models.py
|
||||||
|
msgid ""
|
||||||
|
"Configure certificate authorities to validate the certificate against. This "
|
||||||
|
"option has a higher priority than the `client_certificate` option on "
|
||||||
|
"`Brand`."
|
||||||
|
msgstr ""
|
||||||
|
"Configurez les autorités de certification pour valider le certificat. Cette "
|
||||||
|
"option a une priorité plus élevée que l'option `client_certificate` sur "
|
||||||
|
"`Marques`."
|
||||||
|
|
||||||
|
#: authentik/enterprise/stages/mtls/models.py
|
||||||
|
msgid "Mutual TLS Stage"
|
||||||
|
msgstr "Étape TLS mutuel"
|
||||||
|
|
||||||
|
#: authentik/enterprise/stages/mtls/models.py
|
||||||
|
msgid "Mutual TLS Stages"
|
||||||
|
msgstr "Étapes TLS mutuel"
|
||||||
|
|
||||||
|
#: authentik/enterprise/stages/mtls/models.py
|
||||||
|
msgid "Permissions to pass Certificates for outposts."
|
||||||
|
msgstr "Autorisations de délivrer des certificats pour les avant-postes."
|
||||||
|
|
||||||
|
#: authentik/enterprise/stages/mtls/stage.py
|
||||||
|
msgid "Certificate required but no certificate was given."
|
||||||
|
msgstr "Certificat requis mais aucun certificat n'a été fourni."
|
||||||
|
|
||||||
|
#: authentik/enterprise/stages/mtls/stage.py
|
||||||
|
msgid "No user found for certificate."
|
||||||
|
msgstr "Aucun utilisateur trouvé pour le certificat."
|
||||||
|
|
||||||
#: authentik/enterprise/stages/source/models.py
|
#: authentik/enterprise/stages/source/models.py
|
||||||
msgid ""
|
msgid ""
|
||||||
"Amount of time a user can take to return from the source to continue the "
|
"Amount of time a user can take to return from the source to continue the "
|
||||||
@ -1022,9 +1056,12 @@ msgid "Starting full provider sync"
|
|||||||
msgstr "Démarrage d'une synchronisation complète du fournisseur"
|
msgstr "Démarrage d'une synchronisation complète du fournisseur"
|
||||||
|
|
||||||
#: authentik/lib/sync/outgoing/tasks.py
|
#: authentik/lib/sync/outgoing/tasks.py
|
||||||
#, python-brace-format
|
msgid "Syncing users"
|
||||||
msgid "Syncing page {page} of users"
|
msgstr "Synchronisation des utilisateurs"
|
||||||
msgstr "Synchronisation de la page {page} d'utilisateurs"
|
|
||||||
|
#: authentik/lib/sync/outgoing/tasks.py
|
||||||
|
msgid "Syncing groups"
|
||||||
|
msgstr "Synchronisation des groupes"
|
||||||
|
|
||||||
#: authentik/lib/sync/outgoing/tasks.py
|
#: authentik/lib/sync/outgoing/tasks.py
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
@ -2474,6 +2511,14 @@ msgstr ""
|
|||||||
"plutôt que sur un attribut de groupe. Cela permet la résolution des groupes "
|
"plutôt que sur un attribut de groupe. Cela permet la résolution des groupes "
|
||||||
"imbriqués sur des systèmes tels que FreeIPA et Active Directory."
|
"imbriqués sur des systèmes tels que FreeIPA et Active Directory."
|
||||||
|
|
||||||
|
#: authentik/sources/ldap/models.py
|
||||||
|
msgid ""
|
||||||
|
"Delete authentik users and groups which were previously supplied by this "
|
||||||
|
"source, but are now missing from it."
|
||||||
|
msgstr ""
|
||||||
|
"Supprimer les utilisateurs et les groupes authentik qui étaient auparavant "
|
||||||
|
"fournis par cette source, mais qui en sont maintenant absents."
|
||||||
|
|
||||||
#: authentik/sources/ldap/models.py
|
#: authentik/sources/ldap/models.py
|
||||||
msgid "LDAP Source"
|
msgid "LDAP Source"
|
||||||
msgstr "Source LDAP"
|
msgstr "Source LDAP"
|
||||||
@ -2490,6 +2535,13 @@ msgstr "Mappage de propriété source LDAP"
|
|||||||
msgid "LDAP Source Property Mappings"
|
msgid "LDAP Source Property Mappings"
|
||||||
msgstr "Mappages de propriété source LDAP"
|
msgstr "Mappages de propriété source LDAP"
|
||||||
|
|
||||||
|
#: authentik/sources/ldap/models.py
|
||||||
|
msgid ""
|
||||||
|
"Unique ID used while checking if this object still exists in the directory."
|
||||||
|
msgstr ""
|
||||||
|
"ID unique utilisé pour vérifier si cet objet existe toujours dans le "
|
||||||
|
"répertoire."
|
||||||
|
|
||||||
#: authentik/sources/ldap/models.py
|
#: authentik/sources/ldap/models.py
|
||||||
msgid "User LDAP Source Connection"
|
msgid "User LDAP Source Connection"
|
||||||
msgstr "Connexion de l'utilisateur à la source LDAP"
|
msgstr "Connexion de l'utilisateur à la source LDAP"
|
||||||
@ -2884,6 +2936,11 @@ msgstr "Connexion du groupe à la source SAML"
|
|||||||
msgid "Group SAML Source Connections"
|
msgid "Group SAML Source Connections"
|
||||||
msgstr "Connexions du groupe à la source SAML"
|
msgstr "Connexions du groupe à la source SAML"
|
||||||
|
|
||||||
|
#: authentik/sources/saml/views.py
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Continue to {source_name}"
|
||||||
|
msgstr "Continuer vers {source_name}"
|
||||||
|
|
||||||
#: authentik/sources/scim/models.py
|
#: authentik/sources/scim/models.py
|
||||||
msgid "SCIM Source"
|
msgid "SCIM Source"
|
||||||
msgstr "Source SCIM"
|
msgstr "Source SCIM"
|
||||||
@ -3274,6 +3331,10 @@ msgstr "Consentement Utilisateur"
|
|||||||
msgid "User Consents"
|
msgid "User Consents"
|
||||||
msgstr "Consentements Utilisateur"
|
msgstr "Consentements Utilisateur"
|
||||||
|
|
||||||
|
#: authentik/stages/consent/stage.py
|
||||||
|
msgid "Invalid consent token, re-showing prompt"
|
||||||
|
msgstr "Jeton de consentement invalide, réaffichage de l'invite"
|
||||||
|
|
||||||
#: authentik/stages/deny/models.py
|
#: authentik/stages/deny/models.py
|
||||||
msgid "Deny Stage"
|
msgid "Deny Stage"
|
||||||
msgstr "Étape de Refus"
|
msgstr "Étape de Refus"
|
||||||
@ -3290,6 +3351,14 @@ msgstr "Étape factice"
|
|||||||
msgid "Dummy Stages"
|
msgid "Dummy Stages"
|
||||||
msgstr "Étapes factices"
|
msgstr "Étapes factices"
|
||||||
|
|
||||||
|
#: authentik/stages/email/flow.py
|
||||||
|
msgid "Continue to confirm this email address."
|
||||||
|
msgstr "Continuer pour confirmer cette adresse courriel."
|
||||||
|
|
||||||
|
#: authentik/stages/email/flow.py
|
||||||
|
msgid "Link was already used, please request a new link."
|
||||||
|
msgstr "Ce lien a déjà été utilisé, veuillez en demander un nouveau."
|
||||||
|
|
||||||
#: authentik/stages/email/models.py
|
#: authentik/stages/email/models.py
|
||||||
msgid "Password Reset"
|
msgid "Password Reset"
|
||||||
msgstr "Réinitialiser le Mot de Passe"
|
msgstr "Réinitialiser le Mot de Passe"
|
||||||
|
@ -20,7 +20,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2025-04-23 09:00+0000\n"
|
"POT-Creation-Date: 2025-05-28 11:25+0000\n"
|
||||||
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
|
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
|
||||||
"Last-Translator: Kowalski Dragon (kowalski7cc) <kowalski.7cc@gmail.com>, 2025\n"
|
"Last-Translator: Kowalski Dragon (kowalski7cc) <kowalski.7cc@gmail.com>, 2025\n"
|
||||||
"Language-Team: Italian (https://app.transifex.com/authentik/teams/119923/it/)\n"
|
"Language-Team: Italian (https://app.transifex.com/authentik/teams/119923/it/)\n"
|
||||||
@ -114,6 +114,10 @@ msgstr ""
|
|||||||
msgid "Web Certificate used by the authentik Core webserver."
|
msgid "Web Certificate used by the authentik Core webserver."
|
||||||
msgstr "Certificato Web utilizzato dal server Web authentik Core."
|
msgstr "Certificato Web utilizzato dal server Web authentik Core."
|
||||||
|
|
||||||
|
#: authentik/brands/models.py
|
||||||
|
msgid "Certificates used for client authentication."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/brands/models.py
|
#: authentik/brands/models.py
|
||||||
msgid "Brand"
|
msgid "Brand"
|
||||||
msgstr "Brand"
|
msgstr "Brand"
|
||||||
@ -672,6 +676,33 @@ msgstr "Dispositivi di Accesso"
|
|||||||
msgid "Verifying your browser..."
|
msgid "Verifying your browser..."
|
||||||
msgstr "Verifica del tuo browser..."
|
msgstr "Verifica del tuo browser..."
|
||||||
|
|
||||||
|
#: authentik/enterprise/stages/mtls/models.py
|
||||||
|
msgid ""
|
||||||
|
"Configure certificate authorities to validate the certificate against. This "
|
||||||
|
"option has a higher priority than the `client_certificate` option on "
|
||||||
|
"`Brand`."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/enterprise/stages/mtls/models.py
|
||||||
|
msgid "Mutual TLS Stage"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/enterprise/stages/mtls/models.py
|
||||||
|
msgid "Mutual TLS Stages"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/enterprise/stages/mtls/models.py
|
||||||
|
msgid "Permissions to pass Certificates for outposts."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/enterprise/stages/mtls/stage.py
|
||||||
|
msgid "Certificate required but no certificate was given."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/enterprise/stages/mtls/stage.py
|
||||||
|
msgid "No user found for certificate."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/enterprise/stages/source/models.py
|
#: authentik/enterprise/stages/source/models.py
|
||||||
msgid ""
|
msgid ""
|
||||||
"Amount of time a user can take to return from the source to continue the "
|
"Amount of time a user can take to return from the source to continue the "
|
||||||
@ -1018,9 +1049,12 @@ msgid "Starting full provider sync"
|
|||||||
msgstr "Avvio della sincronizzazione completa del provider"
|
msgstr "Avvio della sincronizzazione completa del provider"
|
||||||
|
|
||||||
#: authentik/lib/sync/outgoing/tasks.py
|
#: authentik/lib/sync/outgoing/tasks.py
|
||||||
#, python-brace-format
|
msgid "Syncing users"
|
||||||
msgid "Syncing page {page} of users"
|
msgstr ""
|
||||||
msgstr "Sincronizzando pagina {page} degli utenti"
|
|
||||||
|
#: authentik/lib/sync/outgoing/tasks.py
|
||||||
|
msgid "Syncing groups"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/lib/sync/outgoing/tasks.py
|
#: authentik/lib/sync/outgoing/tasks.py
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
@ -2463,6 +2497,12 @@ msgstr ""
|
|||||||
"attributo di gruppo. Questo consente la risoluzione di gruppi nidificati su "
|
"attributo di gruppo. Questo consente la risoluzione di gruppi nidificati su "
|
||||||
"sistemi come FreeIPA e Active Directory."
|
"sistemi come FreeIPA e Active Directory."
|
||||||
|
|
||||||
|
#: authentik/sources/ldap/models.py
|
||||||
|
msgid ""
|
||||||
|
"Delete authentik users and groups which were previously supplied by this "
|
||||||
|
"source, but are now missing from it."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/sources/ldap/models.py
|
#: authentik/sources/ldap/models.py
|
||||||
msgid "LDAP Source"
|
msgid "LDAP Source"
|
||||||
msgstr "Sorgente LDAP"
|
msgstr "Sorgente LDAP"
|
||||||
@ -2479,6 +2519,11 @@ msgstr "Mappatura delle proprietà sorgente LDAP"
|
|||||||
msgid "LDAP Source Property Mappings"
|
msgid "LDAP Source Property Mappings"
|
||||||
msgstr "Mappature delle proprietà della sorgente LDAP"
|
msgstr "Mappature delle proprietà della sorgente LDAP"
|
||||||
|
|
||||||
|
#: authentik/sources/ldap/models.py
|
||||||
|
msgid ""
|
||||||
|
"Unique ID used while checking if this object still exists in the directory."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/sources/ldap/models.py
|
#: authentik/sources/ldap/models.py
|
||||||
msgid "User LDAP Source Connection"
|
msgid "User LDAP Source Connection"
|
||||||
msgstr "Connessione Sorgente LDAP Utente"
|
msgstr "Connessione Sorgente LDAP Utente"
|
||||||
@ -2872,6 +2917,11 @@ msgstr "Connessione sorgente SAML di gruppo"
|
|||||||
msgid "Group SAML Source Connections"
|
msgid "Group SAML Source Connections"
|
||||||
msgstr "Connessioni sorgente SAML di gruppo"
|
msgstr "Connessioni sorgente SAML di gruppo"
|
||||||
|
|
||||||
|
#: authentik/sources/saml/views.py
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Continue to {source_name}"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/sources/scim/models.py
|
#: authentik/sources/scim/models.py
|
||||||
msgid "SCIM Source"
|
msgid "SCIM Source"
|
||||||
msgstr "Sorgente SCIM"
|
msgstr "Sorgente SCIM"
|
||||||
@ -3269,6 +3319,10 @@ msgstr "Consenso utente"
|
|||||||
msgid "User Consents"
|
msgid "User Consents"
|
||||||
msgstr "Consensi utente"
|
msgstr "Consensi utente"
|
||||||
|
|
||||||
|
#: authentik/stages/consent/stage.py
|
||||||
|
msgid "Invalid consent token, re-showing prompt"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/stages/deny/models.py
|
#: authentik/stages/deny/models.py
|
||||||
msgid "Deny Stage"
|
msgid "Deny Stage"
|
||||||
msgstr "Fase di negazione"
|
msgstr "Fase di negazione"
|
||||||
@ -3285,6 +3339,14 @@ msgstr "Fase fittizia"
|
|||||||
msgid "Dummy Stages"
|
msgid "Dummy Stages"
|
||||||
msgstr "Fasi fittizie"
|
msgstr "Fasi fittizie"
|
||||||
|
|
||||||
|
#: authentik/stages/email/flow.py
|
||||||
|
msgid "Continue to confirm this email address."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/stages/email/flow.py
|
||||||
|
msgid "Link was already used, please request a new link."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/stages/email/models.py
|
#: authentik/stages/email/models.py
|
||||||
msgid "Password Reset"
|
msgid "Password Reset"
|
||||||
msgstr "Ripristino password"
|
msgstr "Ripristino password"
|
||||||
|
@ -12,7 +12,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2025-04-23 09:00+0000\n"
|
"POT-Creation-Date: 2025-05-28 11:25+0000\n"
|
||||||
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
|
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
|
||||||
"Last-Translator: NavyStack, 2023\n"
|
"Last-Translator: NavyStack, 2023\n"
|
||||||
"Language-Team: Korean (https://app.transifex.com/authentik/teams/119923/ko/)\n"
|
"Language-Team: Korean (https://app.transifex.com/authentik/teams/119923/ko/)\n"
|
||||||
@ -99,6 +99,10 @@ msgstr ""
|
|||||||
msgid "Web Certificate used by the authentik Core webserver."
|
msgid "Web Certificate used by the authentik Core webserver."
|
||||||
msgstr "Authentik Core 웹서버에서 사용하는 웹 인증서."
|
msgstr "Authentik Core 웹서버에서 사용하는 웹 인증서."
|
||||||
|
|
||||||
|
#: authentik/brands/models.py
|
||||||
|
msgid "Certificates used for client authentication."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/brands/models.py
|
#: authentik/brands/models.py
|
||||||
msgid "Brand"
|
msgid "Brand"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -625,6 +629,33 @@ msgstr ""
|
|||||||
msgid "Verifying your browser..."
|
msgid "Verifying your browser..."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/enterprise/stages/mtls/models.py
|
||||||
|
msgid ""
|
||||||
|
"Configure certificate authorities to validate the certificate against. This "
|
||||||
|
"option has a higher priority than the `client_certificate` option on "
|
||||||
|
"`Brand`."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/enterprise/stages/mtls/models.py
|
||||||
|
msgid "Mutual TLS Stage"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/enterprise/stages/mtls/models.py
|
||||||
|
msgid "Mutual TLS Stages"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/enterprise/stages/mtls/models.py
|
||||||
|
msgid "Permissions to pass Certificates for outposts."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/enterprise/stages/mtls/stage.py
|
||||||
|
msgid "Certificate required but no certificate was given."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/enterprise/stages/mtls/stage.py
|
||||||
|
msgid "No user found for certificate."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/enterprise/stages/source/models.py
|
#: authentik/enterprise/stages/source/models.py
|
||||||
msgid ""
|
msgid ""
|
||||||
"Amount of time a user can take to return from the source to continue the "
|
"Amount of time a user can take to return from the source to continue the "
|
||||||
@ -946,8 +977,11 @@ msgid "Starting full provider sync"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/lib/sync/outgoing/tasks.py
|
#: authentik/lib/sync/outgoing/tasks.py
|
||||||
#, python-brace-format
|
msgid "Syncing users"
|
||||||
msgid "Syncing page {page} of users"
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/lib/sync/outgoing/tasks.py
|
||||||
|
msgid "Syncing groups"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/lib/sync/outgoing/tasks.py
|
#: authentik/lib/sync/outgoing/tasks.py
|
||||||
@ -2263,6 +2297,12 @@ msgid ""
|
|||||||
"Active Directory"
|
"Active Directory"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/sources/ldap/models.py
|
||||||
|
msgid ""
|
||||||
|
"Delete authentik users and groups which were previously supplied by this "
|
||||||
|
"source, but are now missing from it."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/sources/ldap/models.py
|
#: authentik/sources/ldap/models.py
|
||||||
msgid "LDAP Source"
|
msgid "LDAP Source"
|
||||||
msgstr "LDAP 소스"
|
msgstr "LDAP 소스"
|
||||||
@ -2279,6 +2319,11 @@ msgstr ""
|
|||||||
msgid "LDAP Source Property Mappings"
|
msgid "LDAP Source Property Mappings"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/sources/ldap/models.py
|
||||||
|
msgid ""
|
||||||
|
"Unique ID used while checking if this object still exists in the directory."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/sources/ldap/models.py
|
#: authentik/sources/ldap/models.py
|
||||||
msgid "User LDAP Source Connection"
|
msgid "User LDAP Source Connection"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -2657,6 +2702,11 @@ msgstr ""
|
|||||||
msgid "Group SAML Source Connections"
|
msgid "Group SAML Source Connections"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/sources/saml/views.py
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Continue to {source_name}"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/sources/scim/models.py
|
#: authentik/sources/scim/models.py
|
||||||
msgid "SCIM Source"
|
msgid "SCIM Source"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -3017,6 +3067,10 @@ msgstr "사용자 동의"
|
|||||||
msgid "User Consents"
|
msgid "User Consents"
|
||||||
msgstr "사용자 동의"
|
msgstr "사용자 동의"
|
||||||
|
|
||||||
|
#: authentik/stages/consent/stage.py
|
||||||
|
msgid "Invalid consent token, re-showing prompt"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/stages/deny/models.py
|
#: authentik/stages/deny/models.py
|
||||||
msgid "Deny Stage"
|
msgid "Deny Stage"
|
||||||
msgstr "거부 스테이지"
|
msgstr "거부 스테이지"
|
||||||
@ -3033,6 +3087,14 @@ msgstr "더미 스테이지"
|
|||||||
msgid "Dummy Stages"
|
msgid "Dummy Stages"
|
||||||
msgstr "더미 스테이지"
|
msgstr "더미 스테이지"
|
||||||
|
|
||||||
|
#: authentik/stages/email/flow.py
|
||||||
|
msgid "Continue to confirm this email address."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/stages/email/flow.py
|
||||||
|
msgid "Link was already used, please request a new link."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/stages/email/models.py
|
#: authentik/stages/email/models.py
|
||||||
msgid "Password Reset"
|
msgid "Password Reset"
|
||||||
msgstr "비밀번호 초기화"
|
msgstr "비밀번호 초기화"
|
||||||
|
Binary file not shown.
@ -19,7 +19,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2025-04-11 00:10+0000\n"
|
"POT-Creation-Date: 2025-05-28 11:25+0000\n"
|
||||||
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
|
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
|
||||||
"Last-Translator: Dany Sluijk, 2025\n"
|
"Last-Translator: Dany Sluijk, 2025\n"
|
||||||
"Language-Team: Dutch (https://app.transifex.com/authentik/teams/119923/nl/)\n"
|
"Language-Team: Dutch (https://app.transifex.com/authentik/teams/119923/nl/)\n"
|
||||||
@ -113,6 +113,10 @@ msgstr ""
|
|||||||
msgid "Web Certificate used by the authentik Core webserver."
|
msgid "Web Certificate used by the authentik Core webserver."
|
||||||
msgstr "Webcertificaat gebruikt door de authentik Core-webserver."
|
msgstr "Webcertificaat gebruikt door de authentik Core-webserver."
|
||||||
|
|
||||||
|
#: authentik/brands/models.py
|
||||||
|
msgid "Certificates used for client authentication."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/brands/models.py
|
#: authentik/brands/models.py
|
||||||
msgid "Brand"
|
msgid "Brand"
|
||||||
msgstr "Merk"
|
msgstr "Merk"
|
||||||
@ -191,6 +195,7 @@ msgid "User's display name."
|
|||||||
msgstr "Weergavenaam van de gebruiker."
|
msgstr "Weergavenaam van de gebruiker."
|
||||||
|
|
||||||
#: authentik/core/models.py authentik/providers/oauth2/models.py
|
#: authentik/core/models.py authentik/providers/oauth2/models.py
|
||||||
|
#: authentik/rbac/models.py
|
||||||
msgid "User"
|
msgid "User"
|
||||||
msgstr "Gebruiker"
|
msgstr "Gebruiker"
|
||||||
|
|
||||||
@ -379,6 +384,18 @@ msgstr "Eigenschapskoppeling"
|
|||||||
msgid "Property Mappings"
|
msgid "Property Mappings"
|
||||||
msgstr "Eigenschapskoppelingen"
|
msgstr "Eigenschapskoppelingen"
|
||||||
|
|
||||||
|
#: authentik/core/models.py
|
||||||
|
msgid "session data"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/core/models.py
|
||||||
|
msgid "Session"
|
||||||
|
msgstr "Sessie"
|
||||||
|
|
||||||
|
#: authentik/core/models.py
|
||||||
|
msgid "Sessions"
|
||||||
|
msgstr "Sessies"
|
||||||
|
|
||||||
#: authentik/core/models.py
|
#: authentik/core/models.py
|
||||||
msgid "Authenticated Session"
|
msgid "Authenticated Session"
|
||||||
msgstr "Geauthenticeerde Sessie"
|
msgstr "Geauthenticeerde Sessie"
|
||||||
@ -486,6 +503,38 @@ msgstr "Licentie Gebruik"
|
|||||||
msgid "License Usage Records"
|
msgid "License Usage Records"
|
||||||
msgstr "Licentie Gebruik Records"
|
msgstr "Licentie Gebruik Records"
|
||||||
|
|
||||||
|
#: authentik/enterprise/policies/unique_password/models.py
|
||||||
|
#: authentik/policies/password/models.py
|
||||||
|
msgid "Field key to check, field keys defined in Prompt stages are available."
|
||||||
|
msgstr ""
|
||||||
|
"Veldsleutel om te controleren, veldsleutels gedefinieerd in Prompt-stadia "
|
||||||
|
"zijn beschikbaar."
|
||||||
|
|
||||||
|
#: authentik/enterprise/policies/unique_password/models.py
|
||||||
|
msgid "Number of passwords to check against."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/enterprise/policies/unique_password/models.py
|
||||||
|
#: authentik/policies/password/models.py
|
||||||
|
msgid "Password not set in context"
|
||||||
|
msgstr "Wachtwoord niet ingesteld in context"
|
||||||
|
|
||||||
|
#: authentik/enterprise/policies/unique_password/models.py
|
||||||
|
msgid "This password has been used previously. Please choose a different one."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/enterprise/policies/unique_password/models.py
|
||||||
|
msgid "Password Uniqueness Policy"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/enterprise/policies/unique_password/models.py
|
||||||
|
msgid "Password Uniqueness Policies"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/enterprise/policies/unique_password/models.py
|
||||||
|
msgid "User Password History"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/enterprise/policy.py
|
#: authentik/enterprise/policy.py
|
||||||
msgid "Enterprise required to access this feature."
|
msgid "Enterprise required to access this feature."
|
||||||
msgstr "Enterprise benodigd voor toegang tot deze functie."
|
msgstr "Enterprise benodigd voor toegang tot deze functie."
|
||||||
@ -622,6 +671,33 @@ msgstr ""
|
|||||||
msgid "Verifying your browser..."
|
msgid "Verifying your browser..."
|
||||||
msgstr "Uw browser wordt geverifieerd..."
|
msgstr "Uw browser wordt geverifieerd..."
|
||||||
|
|
||||||
|
#: authentik/enterprise/stages/mtls/models.py
|
||||||
|
msgid ""
|
||||||
|
"Configure certificate authorities to validate the certificate against. This "
|
||||||
|
"option has a higher priority than the `client_certificate` option on "
|
||||||
|
"`Brand`."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/enterprise/stages/mtls/models.py
|
||||||
|
msgid "Mutual TLS Stage"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/enterprise/stages/mtls/models.py
|
||||||
|
msgid "Mutual TLS Stages"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/enterprise/stages/mtls/models.py
|
||||||
|
msgid "Permissions to pass Certificates for outposts."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/enterprise/stages/mtls/stage.py
|
||||||
|
msgid "Certificate required but no certificate was given."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/enterprise/stages/mtls/stage.py
|
||||||
|
msgid "No user found for certificate."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/enterprise/stages/source/models.py
|
#: authentik/enterprise/stages/source/models.py
|
||||||
msgid ""
|
msgid ""
|
||||||
"Amount of time a user can take to return from the source to continue the "
|
"Amount of time a user can take to return from the source to continue the "
|
||||||
@ -963,8 +1039,11 @@ msgid "Starting full provider sync"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/lib/sync/outgoing/tasks.py
|
#: authentik/lib/sync/outgoing/tasks.py
|
||||||
#, python-brace-format
|
msgid "Syncing users"
|
||||||
msgid "Syncing page {page} of users"
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/lib/sync/outgoing/tasks.py
|
||||||
|
msgid "Syncing groups"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/lib/sync/outgoing/tasks.py
|
#: authentik/lib/sync/outgoing/tasks.py
|
||||||
@ -1265,12 +1344,6 @@ msgstr ""
|
|||||||
msgid "Clear Policy's cache metrics"
|
msgid "Clear Policy's cache metrics"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/policies/password/models.py
|
|
||||||
msgid "Field key to check, field keys defined in Prompt stages are available."
|
|
||||||
msgstr ""
|
|
||||||
"Veldsleutel om te controleren, veldsleutels gedefinieerd in Prompt-stadia "
|
|
||||||
"zijn beschikbaar."
|
|
||||||
|
|
||||||
#: authentik/policies/password/models.py
|
#: authentik/policies/password/models.py
|
||||||
msgid "How many times the password hash is allowed to be on haveibeenpwned"
|
msgid "How many times the password hash is allowed to be on haveibeenpwned"
|
||||||
msgstr "Hoe vaak het wachtwoordhash op haveibeenpwned mag voorkomen"
|
msgstr "Hoe vaak het wachtwoordhash op haveibeenpwned mag voorkomen"
|
||||||
@ -1282,10 +1355,6 @@ msgstr ""
|
|||||||
"Als de zxcvbn-score gelijk is aan of lager is dan deze waarde, zal het "
|
"Als de zxcvbn-score gelijk is aan of lager is dan deze waarde, zal het "
|
||||||
"beleid falen."
|
"beleid falen."
|
||||||
|
|
||||||
#: authentik/policies/password/models.py
|
|
||||||
msgid "Password not set in context"
|
|
||||||
msgstr "Wachtwoord niet ingesteld in context"
|
|
||||||
|
|
||||||
#: authentik/policies/password/models.py
|
#: authentik/policies/password/models.py
|
||||||
msgid "Invalid password."
|
msgid "Invalid password."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -1327,20 +1396,6 @@ msgstr "Reputatie Score"
|
|||||||
msgid "Reputation Scores"
|
msgid "Reputation Scores"
|
||||||
msgstr "Reputatie Scores"
|
msgstr "Reputatie Scores"
|
||||||
|
|
||||||
#: authentik/policies/templates/policies/buffer.html
|
|
||||||
msgid "Waiting for authentication..."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: authentik/policies/templates/policies/buffer.html
|
|
||||||
msgid ""
|
|
||||||
"You're already authenticating in another tab. This page will refresh once "
|
|
||||||
"authentication is completed."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: authentik/policies/templates/policies/buffer.html
|
|
||||||
msgid "Authenticate in this tab"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: authentik/policies/templates/policies/denied.html
|
#: authentik/policies/templates/policies/denied.html
|
||||||
msgid "Permission denied"
|
msgid "Permission denied"
|
||||||
msgstr "Toestemming geweigerd"
|
msgstr "Toestemming geweigerd"
|
||||||
@ -2160,6 +2215,10 @@ msgstr ""
|
|||||||
msgid "Roles"
|
msgid "Roles"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/rbac/models.py
|
||||||
|
msgid "Initial Permissions"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/rbac/models.py
|
#: authentik/rbac/models.py
|
||||||
msgid "System permission"
|
msgid "System permission"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -2392,6 +2451,12 @@ msgid ""
|
|||||||
"Active Directory"
|
"Active Directory"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/sources/ldap/models.py
|
||||||
|
msgid ""
|
||||||
|
"Delete authentik users and groups which were previously supplied by this "
|
||||||
|
"source, but are now missing from it."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/sources/ldap/models.py
|
#: authentik/sources/ldap/models.py
|
||||||
msgid "LDAP Source"
|
msgid "LDAP Source"
|
||||||
msgstr "LDAP-bron"
|
msgstr "LDAP-bron"
|
||||||
@ -2408,6 +2473,27 @@ msgstr ""
|
|||||||
msgid "LDAP Source Property Mappings"
|
msgid "LDAP Source Property Mappings"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/sources/ldap/models.py
|
||||||
|
msgid ""
|
||||||
|
"Unique ID used while checking if this object still exists in the directory."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/sources/ldap/models.py
|
||||||
|
msgid "User LDAP Source Connection"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/sources/ldap/models.py
|
||||||
|
msgid "User LDAP Source Connections"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/sources/ldap/models.py
|
||||||
|
msgid "Group LDAP Source Connection"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/sources/ldap/models.py
|
||||||
|
msgid "Group LDAP Source Connections"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/sources/ldap/signals.py
|
#: authentik/sources/ldap/signals.py
|
||||||
msgid "Password does not match Active Directory Complexity."
|
msgid "Password does not match Active Directory Complexity."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -2417,6 +2503,14 @@ msgstr ""
|
|||||||
msgid "No token received."
|
msgid "No token received."
|
||||||
msgstr "Geen token ontvangen."
|
msgstr "Geen token ontvangen."
|
||||||
|
|
||||||
|
#: authentik/sources/oauth/models.py
|
||||||
|
msgid "HTTP Basic Authentication"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/sources/oauth/models.py
|
||||||
|
msgid "Include the client ID and secret as request parameters"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/sources/oauth/models.py
|
#: authentik/sources/oauth/models.py
|
||||||
msgid "Request Token URL"
|
msgid "Request Token URL"
|
||||||
msgstr "URL voor aanvragen van token"
|
msgstr "URL voor aanvragen van token"
|
||||||
@ -2458,6 +2552,12 @@ msgstr ""
|
|||||||
msgid "Additional Scopes"
|
msgid "Additional Scopes"
|
||||||
msgstr "Aanvullende scopes"
|
msgstr "Aanvullende scopes"
|
||||||
|
|
||||||
|
#: authentik/sources/oauth/models.py
|
||||||
|
msgid ""
|
||||||
|
"How to perform authentication during an authorization_code token request "
|
||||||
|
"flow"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/sources/oauth/models.py
|
#: authentik/sources/oauth/models.py
|
||||||
msgid "OAuth Source"
|
msgid "OAuth Source"
|
||||||
msgstr "OAuth-bron"
|
msgstr "OAuth-bron"
|
||||||
@ -2769,6 +2869,11 @@ msgstr ""
|
|||||||
msgid "Group SAML Source Connections"
|
msgid "Group SAML Source Connections"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/sources/saml/views.py
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Continue to {source_name}"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/sources/scim/models.py
|
#: authentik/sources/scim/models.py
|
||||||
msgid "SCIM Source"
|
msgid "SCIM Source"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -3142,6 +3247,10 @@ msgstr "Gebruikerstoestemming"
|
|||||||
msgid "User Consents"
|
msgid "User Consents"
|
||||||
msgstr "Gebruikersinstemmingen"
|
msgstr "Gebruikersinstemmingen"
|
||||||
|
|
||||||
|
#: authentik/stages/consent/stage.py
|
||||||
|
msgid "Invalid consent token, re-showing prompt"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/stages/deny/models.py
|
#: authentik/stages/deny/models.py
|
||||||
msgid "Deny Stage"
|
msgid "Deny Stage"
|
||||||
msgstr "Weigerfase"
|
msgstr "Weigerfase"
|
||||||
@ -3158,6 +3267,14 @@ msgstr "Dummystadium"
|
|||||||
msgid "Dummy Stages"
|
msgid "Dummy Stages"
|
||||||
msgstr "Dummystadia"
|
msgstr "Dummystadia"
|
||||||
|
|
||||||
|
#: authentik/stages/email/flow.py
|
||||||
|
msgid "Continue to confirm this email address."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/stages/email/flow.py
|
||||||
|
msgid "Link was already used, please request a new link."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/stages/email/models.py
|
#: authentik/stages/email/models.py
|
||||||
msgid "Password Reset"
|
msgid "Password Reset"
|
||||||
msgstr "Wachtwoordherstel"
|
msgstr "Wachtwoordherstel"
|
||||||
@ -3357,6 +3474,12 @@ msgstr ""
|
|||||||
"Wanneer ingeschakeld, slaagt de stap en gaat verder wanneer ongeldige "
|
"Wanneer ingeschakeld, slaagt de stap en gaat verder wanneer ongeldige "
|
||||||
"gebruikersgegevens zijn ingevoerd."
|
"gebruikersgegevens zijn ingevoerd."
|
||||||
|
|
||||||
|
#: authentik/stages/identification/models.py
|
||||||
|
msgid ""
|
||||||
|
"Show the user the 'Remember me on this device' toggle, allowing repeat users"
|
||||||
|
" to skip straight to entering their password."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/stages/identification/models.py
|
#: authentik/stages/identification/models.py
|
||||||
msgid "Optional enrollment flow, which is linked at the bottom of the page."
|
msgid "Optional enrollment flow, which is linked at the bottom of the page."
|
||||||
msgstr "Optionele inschrijvingsflow, die onderaan de pagina is gekoppeld."
|
msgstr "Optionele inschrijvingsflow, die onderaan de pagina is gekoppeld."
|
||||||
@ -3742,6 +3865,14 @@ msgstr ""
|
|||||||
"Gebeurtenissen worden verwijderd na deze duur. (Indeling: "
|
"Gebeurtenissen worden verwijderd na deze duur. (Indeling: "
|
||||||
"weken=3;dagen=2;uren=3;seconden=2)."
|
"weken=3;dagen=2;uren=3;seconden=2)."
|
||||||
|
|
||||||
|
#: authentik/tenants/models.py
|
||||||
|
msgid "Reputation cannot decrease lower than this value. Zero or negative."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/tenants/models.py
|
||||||
|
msgid "Reputation cannot increase higher than this value. Zero or positive."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/tenants/models.py
|
#: authentik/tenants/models.py
|
||||||
msgid "The option configures the footer links on the flow executor pages."
|
msgid "The option configures the footer links on the flow executor pages."
|
||||||
msgstr "De optie stelt de voettekst links in op de flow uitvoer pagina's."
|
msgstr "De optie stelt de voettekst links in op de flow uitvoer pagina's."
|
||||||
|
@ -11,7 +11,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2025-04-23 09:00+0000\n"
|
"POT-Creation-Date: 2025-05-28 11:25+0000\n"
|
||||||
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
|
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
|
||||||
"Last-Translator: Hugo Bicho, 2025\n"
|
"Last-Translator: Hugo Bicho, 2025\n"
|
||||||
"Language-Team: Portuguese (https://app.transifex.com/authentik/teams/119923/pt/)\n"
|
"Language-Team: Portuguese (https://app.transifex.com/authentik/teams/119923/pt/)\n"
|
||||||
@ -105,6 +105,10 @@ msgstr ""
|
|||||||
msgid "Web Certificate used by the authentik Core webserver."
|
msgid "Web Certificate used by the authentik Core webserver."
|
||||||
msgstr "Certificado Web usado pelo servidor web authentik Core."
|
msgstr "Certificado Web usado pelo servidor web authentik Core."
|
||||||
|
|
||||||
|
#: authentik/brands/models.py
|
||||||
|
msgid "Certificates used for client authentication."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/brands/models.py
|
#: authentik/brands/models.py
|
||||||
msgid "Brand"
|
msgid "Brand"
|
||||||
msgstr "Marca"
|
msgstr "Marca"
|
||||||
@ -662,6 +666,33 @@ msgstr "Dispositivos do ponto de ligação"
|
|||||||
msgid "Verifying your browser..."
|
msgid "Verifying your browser..."
|
||||||
msgstr "A verificar o seu browser..."
|
msgstr "A verificar o seu browser..."
|
||||||
|
|
||||||
|
#: authentik/enterprise/stages/mtls/models.py
|
||||||
|
msgid ""
|
||||||
|
"Configure certificate authorities to validate the certificate against. This "
|
||||||
|
"option has a higher priority than the `client_certificate` option on "
|
||||||
|
"`Brand`."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/enterprise/stages/mtls/models.py
|
||||||
|
msgid "Mutual TLS Stage"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/enterprise/stages/mtls/models.py
|
||||||
|
msgid "Mutual TLS Stages"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/enterprise/stages/mtls/models.py
|
||||||
|
msgid "Permissions to pass Certificates for outposts."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/enterprise/stages/mtls/stage.py
|
||||||
|
msgid "Certificate required but no certificate was given."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/enterprise/stages/mtls/stage.py
|
||||||
|
msgid "No user found for certificate."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/enterprise/stages/source/models.py
|
#: authentik/enterprise/stages/source/models.py
|
||||||
msgid ""
|
msgid ""
|
||||||
"Amount of time a user can take to return from the source to continue the "
|
"Amount of time a user can take to return from the source to continue the "
|
||||||
@ -1007,9 +1038,12 @@ msgid "Starting full provider sync"
|
|||||||
msgstr "Iniciando a sincronização completa com o provedor"
|
msgstr "Iniciando a sincronização completa com o provedor"
|
||||||
|
|
||||||
#: authentik/lib/sync/outgoing/tasks.py
|
#: authentik/lib/sync/outgoing/tasks.py
|
||||||
#, python-brace-format
|
msgid "Syncing users"
|
||||||
msgid "Syncing page {page} of users"
|
msgstr ""
|
||||||
msgstr "A sincronizar a página {page} dos utilizadores"
|
|
||||||
|
#: authentik/lib/sync/outgoing/tasks.py
|
||||||
|
msgid "Syncing groups"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/lib/sync/outgoing/tasks.py
|
#: authentik/lib/sync/outgoing/tasks.py
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
@ -2456,6 +2490,12 @@ msgstr ""
|
|||||||
" um atributo do grupo. Isto permite a resolução de grupos hierárquicos em "
|
" um atributo do grupo. Isto permite a resolução de grupos hierárquicos em "
|
||||||
"sistemas como o FreeIPA e Active Directory."
|
"sistemas como o FreeIPA e Active Directory."
|
||||||
|
|
||||||
|
#: authentik/sources/ldap/models.py
|
||||||
|
msgid ""
|
||||||
|
"Delete authentik users and groups which were previously supplied by this "
|
||||||
|
"source, but are now missing from it."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/sources/ldap/models.py
|
#: authentik/sources/ldap/models.py
|
||||||
msgid "LDAP Source"
|
msgid "LDAP Source"
|
||||||
msgstr "Fonte LDAP"
|
msgstr "Fonte LDAP"
|
||||||
@ -2472,6 +2512,11 @@ msgstr "Mapeamento de propriedades de fonte LDAP"
|
|||||||
msgid "LDAP Source Property Mappings"
|
msgid "LDAP Source Property Mappings"
|
||||||
msgstr "Mapeamentos de propriedades de fonte LDAP"
|
msgstr "Mapeamentos de propriedades de fonte LDAP"
|
||||||
|
|
||||||
|
#: authentik/sources/ldap/models.py
|
||||||
|
msgid ""
|
||||||
|
"Unique ID used while checking if this object still exists in the directory."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/sources/ldap/models.py
|
#: authentik/sources/ldap/models.py
|
||||||
msgid "User LDAP Source Connection"
|
msgid "User LDAP Source Connection"
|
||||||
msgstr "Ligação à fonte LDAP de Utilizador"
|
msgstr "Ligação à fonte LDAP de Utilizador"
|
||||||
@ -2865,6 +2910,11 @@ msgstr "Ligação à fonte SAML de Grupo"
|
|||||||
msgid "Group SAML Source Connections"
|
msgid "Group SAML Source Connections"
|
||||||
msgstr "Ligações à fonte SAML de Grupo"
|
msgstr "Ligações à fonte SAML de Grupo"
|
||||||
|
|
||||||
|
#: authentik/sources/saml/views.py
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Continue to {source_name}"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/sources/scim/models.py
|
#: authentik/sources/scim/models.py
|
||||||
msgid "SCIM Source"
|
msgid "SCIM Source"
|
||||||
msgstr "Fonte SCIM"
|
msgstr "Fonte SCIM"
|
||||||
@ -3255,6 +3305,10 @@ msgstr "Consentimento do Utilizador"
|
|||||||
msgid "User Consents"
|
msgid "User Consents"
|
||||||
msgstr "Consentimentos do Utilizador"
|
msgstr "Consentimentos do Utilizador"
|
||||||
|
|
||||||
|
#: authentik/stages/consent/stage.py
|
||||||
|
msgid "Invalid consent token, re-showing prompt"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/stages/deny/models.py
|
#: authentik/stages/deny/models.py
|
||||||
msgid "Deny Stage"
|
msgid "Deny Stage"
|
||||||
msgstr "Etapa de negação"
|
msgstr "Etapa de negação"
|
||||||
@ -3271,6 +3325,14 @@ msgstr "Etapa fictícia"
|
|||||||
msgid "Dummy Stages"
|
msgid "Dummy Stages"
|
||||||
msgstr "Etapas fictícias"
|
msgstr "Etapas fictícias"
|
||||||
|
|
||||||
|
#: authentik/stages/email/flow.py
|
||||||
|
msgid "Continue to confirm this email address."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/stages/email/flow.py
|
||||||
|
msgid "Link was already used, please request a new link."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/stages/email/models.py
|
#: authentik/stages/email/models.py
|
||||||
msgid "Password Reset"
|
msgid "Password Reset"
|
||||||
msgstr "Redefinição de Palavra-Passe"
|
msgstr "Redefinição de Palavra-Passe"
|
||||||
|
Binary file not shown.
@ -8,19 +8,19 @@
|
|||||||
# Josenivaldo Benito Junior, 2023
|
# Josenivaldo Benito Junior, 2023
|
||||||
# Caio Lima, 2023
|
# Caio Lima, 2023
|
||||||
# Hacklab, 2023
|
# Hacklab, 2023
|
||||||
# Wagner Santos, 2024
|
|
||||||
# Rafael Mundel, 2024
|
# Rafael Mundel, 2024
|
||||||
# Anderson Silva Andrade <anderson.asa89@gmail.com>, 2025
|
# Anderson Silva Andrade <anderson.asa89@gmail.com>, 2025
|
||||||
# Gil Poiares-Oliveira, 2025
|
# Gil Poiares-Oliveira, 2025
|
||||||
|
# Wagner Santos, 2025
|
||||||
#
|
#
|
||||||
#, fuzzy
|
#, fuzzy
|
||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2025-04-23 09:00+0000\n"
|
"POT-Creation-Date: 2025-05-28 11:25+0000\n"
|
||||||
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
|
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
|
||||||
"Last-Translator: Gil Poiares-Oliveira, 2025\n"
|
"Last-Translator: Wagner Santos, 2025\n"
|
||||||
"Language-Team: Portuguese (Brazil) (https://app.transifex.com/authentik/teams/119923/pt_BR/)\n"
|
"Language-Team: Portuguese (Brazil) (https://app.transifex.com/authentik/teams/119923/pt_BR/)\n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
@ -112,6 +112,10 @@ msgstr ""
|
|||||||
msgid "Web Certificate used by the authentik Core webserver."
|
msgid "Web Certificate used by the authentik Core webserver."
|
||||||
msgstr "Certificado da Web usado pelo servidor da web authentik Core."
|
msgstr "Certificado da Web usado pelo servidor da web authentik Core."
|
||||||
|
|
||||||
|
#: authentik/brands/models.py
|
||||||
|
msgid "Certificates used for client authentication."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/brands/models.py
|
#: authentik/brands/models.py
|
||||||
msgid "Brand"
|
msgid "Brand"
|
||||||
msgstr "Brand"
|
msgstr "Brand"
|
||||||
@ -271,11 +275,11 @@ msgstr "Aplicativos"
|
|||||||
|
|
||||||
#: authentik/core/models.py
|
#: authentik/core/models.py
|
||||||
msgid "Application Entitlement"
|
msgid "Application Entitlement"
|
||||||
msgstr ""
|
msgstr "Autorização de aplicação"
|
||||||
|
|
||||||
#: authentik/core/models.py
|
#: authentik/core/models.py
|
||||||
msgid "Application Entitlements"
|
msgid "Application Entitlements"
|
||||||
msgstr ""
|
msgstr "Autorizações de aplicação"
|
||||||
|
|
||||||
#: authentik/core/models.py
|
#: authentik/core/models.py
|
||||||
msgid "Use the source-specific identifier"
|
msgid "Use the source-specific identifier"
|
||||||
@ -379,15 +383,15 @@ msgstr "Mapeamentos de propriedades"
|
|||||||
|
|
||||||
#: authentik/core/models.py
|
#: authentik/core/models.py
|
||||||
msgid "session data"
|
msgid "session data"
|
||||||
msgstr ""
|
msgstr "dados de sessão"
|
||||||
|
|
||||||
#: authentik/core/models.py
|
#: authentik/core/models.py
|
||||||
msgid "Session"
|
msgid "Session"
|
||||||
msgstr ""
|
msgstr "Sessão"
|
||||||
|
|
||||||
#: authentik/core/models.py
|
#: authentik/core/models.py
|
||||||
msgid "Sessions"
|
msgid "Sessions"
|
||||||
msgstr ""
|
msgstr "Sessões"
|
||||||
|
|
||||||
#: authentik/core/models.py
|
#: authentik/core/models.py
|
||||||
msgid "Authenticated Session"
|
msgid "Authenticated Session"
|
||||||
@ -505,7 +509,7 @@ msgstr ""
|
|||||||
|
|
||||||
#: authentik/enterprise/policies/unique_password/models.py
|
#: authentik/enterprise/policies/unique_password/models.py
|
||||||
msgid "Number of passwords to check against."
|
msgid "Number of passwords to check against."
|
||||||
msgstr ""
|
msgstr "Número de senhas para verificar."
|
||||||
|
|
||||||
#: authentik/enterprise/policies/unique_password/models.py
|
#: authentik/enterprise/policies/unique_password/models.py
|
||||||
#: authentik/policies/password/models.py
|
#: authentik/policies/password/models.py
|
||||||
@ -514,19 +518,19 @@ msgstr "Senha não definida no contexto"
|
|||||||
|
|
||||||
#: authentik/enterprise/policies/unique_password/models.py
|
#: authentik/enterprise/policies/unique_password/models.py
|
||||||
msgid "This password has been used previously. Please choose a different one."
|
msgid "This password has been used previously. Please choose a different one."
|
||||||
msgstr ""
|
msgstr "A senha já foi utilizada antes. Por favor, escolha uma diferente."
|
||||||
|
|
||||||
#: authentik/enterprise/policies/unique_password/models.py
|
#: authentik/enterprise/policies/unique_password/models.py
|
||||||
msgid "Password Uniqueness Policy"
|
msgid "Password Uniqueness Policy"
|
||||||
msgstr ""
|
msgstr "Política de exclusividade de senha"
|
||||||
|
|
||||||
#: authentik/enterprise/policies/unique_password/models.py
|
#: authentik/enterprise/policies/unique_password/models.py
|
||||||
msgid "Password Uniqueness Policies"
|
msgid "Password Uniqueness Policies"
|
||||||
msgstr ""
|
msgstr "Políticas de exclusividade de senha"
|
||||||
|
|
||||||
#: authentik/enterprise/policies/unique_password/models.py
|
#: authentik/enterprise/policies/unique_password/models.py
|
||||||
msgid "User Password History"
|
msgid "User Password History"
|
||||||
msgstr ""
|
msgstr "Histórico de senhas do usuário"
|
||||||
|
|
||||||
#: authentik/enterprise/policy.py
|
#: authentik/enterprise/policy.py
|
||||||
msgid "Enterprise required to access this feature."
|
msgid "Enterprise required to access this feature."
|
||||||
@ -610,39 +614,39 @@ msgstr "Chave de Assinatura"
|
|||||||
|
|
||||||
#: authentik/enterprise/providers/ssf/models.py
|
#: authentik/enterprise/providers/ssf/models.py
|
||||||
msgid "Key used to sign the SSF Events."
|
msgid "Key used to sign the SSF Events."
|
||||||
msgstr ""
|
msgstr "Chave utilizada para assinar os eventos SSF."
|
||||||
|
|
||||||
#: authentik/enterprise/providers/ssf/models.py
|
#: authentik/enterprise/providers/ssf/models.py
|
||||||
msgid "Shared Signals Framework Provider"
|
msgid "Shared Signals Framework Provider"
|
||||||
msgstr ""
|
msgstr "Provedor de Shared Signals Framework"
|
||||||
|
|
||||||
#: authentik/enterprise/providers/ssf/models.py
|
#: authentik/enterprise/providers/ssf/models.py
|
||||||
msgid "Shared Signals Framework Providers"
|
msgid "Shared Signals Framework Providers"
|
||||||
msgstr ""
|
msgstr "Provedores de Shared Signals Framework"
|
||||||
|
|
||||||
#: authentik/enterprise/providers/ssf/models.py
|
#: authentik/enterprise/providers/ssf/models.py
|
||||||
msgid "Add stream to SSF provider"
|
msgid "Add stream to SSF provider"
|
||||||
msgstr ""
|
msgstr "Adicionar stream ao fornecedor SSF"
|
||||||
|
|
||||||
#: authentik/enterprise/providers/ssf/models.py
|
#: authentik/enterprise/providers/ssf/models.py
|
||||||
msgid "SSF Stream"
|
msgid "SSF Stream"
|
||||||
msgstr ""
|
msgstr "Stream SSF"
|
||||||
|
|
||||||
#: authentik/enterprise/providers/ssf/models.py
|
#: authentik/enterprise/providers/ssf/models.py
|
||||||
msgid "SSF Streams"
|
msgid "SSF Streams"
|
||||||
msgstr ""
|
msgstr "Streams SSF"
|
||||||
|
|
||||||
#: authentik/enterprise/providers/ssf/models.py
|
#: authentik/enterprise/providers/ssf/models.py
|
||||||
msgid "SSF Stream Event"
|
msgid "SSF Stream Event"
|
||||||
msgstr ""
|
msgstr "Evento de stream SSF"
|
||||||
|
|
||||||
#: authentik/enterprise/providers/ssf/models.py
|
#: authentik/enterprise/providers/ssf/models.py
|
||||||
msgid "SSF Stream Events"
|
msgid "SSF Stream Events"
|
||||||
msgstr ""
|
msgstr "Eventos de stream SSF"
|
||||||
|
|
||||||
#: authentik/enterprise/providers/ssf/tasks.py
|
#: authentik/enterprise/providers/ssf/tasks.py
|
||||||
msgid "Failed to send request"
|
msgid "Failed to send request"
|
||||||
msgstr ""
|
msgstr "Falha ao enviar requisição"
|
||||||
|
|
||||||
#: authentik/enterprise/stages/authenticator_endpoint_gdtc/models.py
|
#: authentik/enterprise/stages/authenticator_endpoint_gdtc/models.py
|
||||||
msgid "Endpoint Authenticator Google Device Trust Connector Stage"
|
msgid "Endpoint Authenticator Google Device Trust Connector Stage"
|
||||||
@ -664,6 +668,33 @@ msgstr ""
|
|||||||
msgid "Verifying your browser..."
|
msgid "Verifying your browser..."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/enterprise/stages/mtls/models.py
|
||||||
|
msgid ""
|
||||||
|
"Configure certificate authorities to validate the certificate against. This "
|
||||||
|
"option has a higher priority than the `client_certificate` option on "
|
||||||
|
"`Brand`."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/enterprise/stages/mtls/models.py
|
||||||
|
msgid "Mutual TLS Stage"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/enterprise/stages/mtls/models.py
|
||||||
|
msgid "Mutual TLS Stages"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/enterprise/stages/mtls/models.py
|
||||||
|
msgid "Permissions to pass Certificates for outposts."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/enterprise/stages/mtls/stage.py
|
||||||
|
msgid "Certificate required but no certificate was given."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/enterprise/stages/mtls/stage.py
|
||||||
|
msgid "No user found for certificate."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/enterprise/stages/source/models.py
|
#: authentik/enterprise/stages/source/models.py
|
||||||
msgid ""
|
msgid ""
|
||||||
"Amount of time a user can take to return from the source to continue the "
|
"Amount of time a user can take to return from the source to continue the "
|
||||||
@ -681,7 +712,7 @@ msgstr ""
|
|||||||
#: authentik/events/api/tasks.py
|
#: authentik/events/api/tasks.py
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Successfully started task {name}."
|
msgid "Successfully started task {name}."
|
||||||
msgstr ""
|
msgstr "Tarefa {name} iniciada com sucesso."
|
||||||
|
|
||||||
#: authentik/events/models.py
|
#: authentik/events/models.py
|
||||||
msgid "Event"
|
msgid "Event"
|
||||||
@ -713,12 +744,16 @@ msgid ""
|
|||||||
"Customize the body of the request. Mapping should return data that is JSON-"
|
"Customize the body of the request. Mapping should return data that is JSON-"
|
||||||
"serializable."
|
"serializable."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
"Personalize o corpo do pedido. O mapeamento deve retornar dados que sejam "
|
||||||
|
"serializáveis em JSON."
|
||||||
|
|
||||||
#: authentik/events/models.py
|
#: authentik/events/models.py
|
||||||
msgid ""
|
msgid ""
|
||||||
"Configure additional headers to be sent. Mapping should return a dictionary "
|
"Configure additional headers to be sent. Mapping should return a dictionary "
|
||||||
"of key-value pairs"
|
"of key-value pairs"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
"Configurar cabeçalhos adicionais a serem enviados. O mapeamento deve "
|
||||||
|
"retornar um dicionário de pares chave-valor"
|
||||||
|
|
||||||
#: authentik/events/models.py
|
#: authentik/events/models.py
|
||||||
msgid ""
|
msgid ""
|
||||||
@ -998,8 +1033,11 @@ msgid "Starting full provider sync"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/lib/sync/outgoing/tasks.py
|
#: authentik/lib/sync/outgoing/tasks.py
|
||||||
#, python-brace-format
|
msgid "Syncing users"
|
||||||
msgid "Syncing page {page} of users"
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/lib/sync/outgoing/tasks.py
|
||||||
|
msgid "Syncing groups"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/lib/sync/outgoing/tasks.py
|
#: authentik/lib/sync/outgoing/tasks.py
|
||||||
@ -1314,7 +1352,7 @@ msgstr ""
|
|||||||
#: authentik/policies/password/models.py
|
#: authentik/policies/password/models.py
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Password exists on {count} online lists."
|
msgid "Password exists on {count} online lists."
|
||||||
msgstr ""
|
msgstr "A senha está presente em {count} listas de senhas vulneráveis."
|
||||||
|
|
||||||
#: authentik/policies/password/models.py
|
#: authentik/policies/password/models.py
|
||||||
msgid "Password is too weak."
|
msgid "Password is too weak."
|
||||||
@ -2396,6 +2434,12 @@ msgid ""
|
|||||||
"Active Directory"
|
"Active Directory"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/sources/ldap/models.py
|
||||||
|
msgid ""
|
||||||
|
"Delete authentik users and groups which were previously supplied by this "
|
||||||
|
"source, but are now missing from it."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/sources/ldap/models.py
|
#: authentik/sources/ldap/models.py
|
||||||
msgid "LDAP Source"
|
msgid "LDAP Source"
|
||||||
msgstr "Fonte LDAP"
|
msgstr "Fonte LDAP"
|
||||||
@ -2412,6 +2456,11 @@ msgstr ""
|
|||||||
msgid "LDAP Source Property Mappings"
|
msgid "LDAP Source Property Mappings"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/sources/ldap/models.py
|
||||||
|
msgid ""
|
||||||
|
"Unique ID used while checking if this object still exists in the directory."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/sources/ldap/models.py
|
#: authentik/sources/ldap/models.py
|
||||||
msgid "User LDAP Source Connection"
|
msgid "User LDAP Source Connection"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -2802,6 +2851,11 @@ msgstr ""
|
|||||||
msgid "Group SAML Source Connections"
|
msgid "Group SAML Source Connections"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/sources/saml/views.py
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Continue to {source_name}"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/sources/scim/models.py
|
#: authentik/sources/scim/models.py
|
||||||
msgid "SCIM Source"
|
msgid "SCIM Source"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -3174,6 +3228,10 @@ msgstr "Consentimento do usuário"
|
|||||||
msgid "User Consents"
|
msgid "User Consents"
|
||||||
msgstr "Consentimentos do usuário"
|
msgstr "Consentimentos do usuário"
|
||||||
|
|
||||||
|
#: authentik/stages/consent/stage.py
|
||||||
|
msgid "Invalid consent token, re-showing prompt"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/stages/deny/models.py
|
#: authentik/stages/deny/models.py
|
||||||
msgid "Deny Stage"
|
msgid "Deny Stage"
|
||||||
msgstr "Negar Estágio"
|
msgstr "Negar Estágio"
|
||||||
@ -3190,6 +3248,14 @@ msgstr "Palco fictício"
|
|||||||
msgid "Dummy Stages"
|
msgid "Dummy Stages"
|
||||||
msgstr "Fases fictícias"
|
msgstr "Fases fictícias"
|
||||||
|
|
||||||
|
#: authentik/stages/email/flow.py
|
||||||
|
msgid "Continue to confirm this email address."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/stages/email/flow.py
|
||||||
|
msgid "Link was already used, please request a new link."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/stages/email/models.py
|
#: authentik/stages/email/models.py
|
||||||
msgid "Password Reset"
|
msgid "Password Reset"
|
||||||
msgstr "Redefinição de senha"
|
msgstr "Redefinição de senha"
|
||||||
|
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user