Merge branch 'main' into dev

* main:
  web/admin: fix SAML Provider preview (#9192)
  core, web: update translations (#9183)
  web: bump chromedriver from 123.0.1 to 123.0.2 in /tests/wdio (#9188)
  website: bump @types/react from 18.2.74 to 18.2.75 in /website (#9185)
  website/docs: update Postgresql username (#9190)
  core: bump maxmind/geoipupdate from v6.1 to v7.0 (#9186)
  events: add context manager to ignore/modify audit events being written (#9181)
  web: fix application library list display length and capability (#9094)
This commit is contained in:
Ken Sternberg
2024-04-09 08:47:08 -07:00
28 changed files with 374 additions and 91 deletions

View File

@ -70,7 +70,7 @@ RUN --mount=type=cache,sharing=locked,target=/go/pkg/mod \
GOARM="${TARGETVARIANT#v}" go build -o /go/authentik ./cmd/server GOARM="${TARGETVARIANT#v}" go build -o /go/authentik ./cmd/server
# Stage 4: MaxMind GeoIP # Stage 4: MaxMind GeoIP
FROM --platform=${BUILDPLATFORM} ghcr.io/maxmind/geoipupdate:v6.1 as geoip FROM --platform=${BUILDPLATFORM} ghcr.io/maxmind/geoipupdate:v7.0 as geoip
ENV GEOIPUPDATE_EDITION_IDS="GeoLite2-City GeoLite2-ASN" ENV GEOIPUPDATE_EDITION_IDS="GeoLite2-City GeoLite2-ASN"
ENV GEOIPUPDATE_VERBOSE="true" ENV GEOIPUPDATE_VERBOSE="true"

View File

@ -22,6 +22,7 @@ from rest_framework.viewsets import ModelViewSet
from structlog.stdlib import get_logger from structlog.stdlib import get_logger
from authentik.admin.api.metrics import CoordinateSerializer from authentik.admin.api.metrics import CoordinateSerializer
from authentik.api.pagination import Pagination
from authentik.blueprints.v1.importer import SERIALIZER_CONTEXT_BLUEPRINT from authentik.blueprints.v1.importer import SERIALIZER_CONTEXT_BLUEPRINT
from authentik.core.api.providers import ProviderSerializer from authentik.core.api.providers import ProviderSerializer
from authentik.core.api.used_by import UsedByMixin from authentik.core.api.used_by import UsedByMixin
@ -43,9 +44,12 @@ from authentik.rbac.filters import ObjectFilter
LOGGER = get_logger() LOGGER = get_logger()
def user_app_cache_key(user_pk: str) -> str: def user_app_cache_key(user_pk: str, page_number: int | None = None) -> str:
"""Cache key where application list for user is saved""" """Cache key where application list for user is saved"""
return f"{CACHE_PREFIX}/app_access/{user_pk}" key = f"{CACHE_PREFIX}/app_access/{user_pk}"
if page_number:
key += f"/{page_number}"
return key
class ApplicationSerializer(ModelSerializer): class ApplicationSerializer(ModelSerializer):
@ -213,7 +217,8 @@ class ApplicationViewSet(UsedByMixin, ModelViewSet):
return super().list(request) return super().list(request)
queryset = self._filter_queryset_for_list(self.get_queryset()) queryset = self._filter_queryset_for_list(self.get_queryset())
paginated_apps = self.paginate_queryset(queryset) paginator: Pagination = self.paginator
paginated_apps = paginator.paginate_queryset(queryset, request)
if "for_user" in request.query_params: if "for_user" in request.query_params:
try: try:
@ -235,12 +240,14 @@ class ApplicationViewSet(UsedByMixin, ModelViewSet):
if not should_cache: if not should_cache:
allowed_applications = self._get_allowed_applications(paginated_apps) allowed_applications = self._get_allowed_applications(paginated_apps)
if should_cache: if should_cache:
allowed_applications = cache.get(user_app_cache_key(self.request.user.pk)) allowed_applications = cache.get(
user_app_cache_key(self.request.user.pk, paginator.page.number)
)
if not allowed_applications: if not allowed_applications:
LOGGER.debug("Caching allowed application list") LOGGER.debug("Caching allowed application list", page=paginator.page.number)
allowed_applications = self._get_allowed_applications(paginated_apps) allowed_applications = self._get_allowed_applications(paginated_apps)
cache.set( cache.set(
user_app_cache_key(self.request.user.pk), user_app_cache_key(self.request.user.pk, paginator.page.number),
allowed_applications, allowed_applications,
timeout=86400, timeout=86400,
) )

View File

@ -1,6 +1,8 @@
"""Events middleware""" """Events middleware"""
from collections.abc import Callable from collections.abc import Callable
from contextlib import contextmanager
from contextvars import ContextVar
from functools import partial from functools import partial
from threading import Thread from threading import Thread
from typing import Any from typing import Any
@ -31,6 +33,9 @@ IGNORED_MODELS = tuple(
) )
) )
_CTX_OVERWRITE_USER = ContextVar[User | None]("authentik_events_log_overwrite_user", default=None)
_CTX_IGNORE = ContextVar[bool]("authentik_events_log_ignore", default=False)
def should_log_model(model: Model) -> bool: def should_log_model(model: Model) -> bool:
"""Return true if operation on `model` should be logged""" """Return true if operation on `model` should be logged"""
@ -44,6 +49,28 @@ def should_log_m2m(model: Model) -> bool:
return False return False
@contextmanager
def audit_overwrite_user(user: User):
"""Overwrite user being logged for model AuditMiddleware. Commonly used
for example in flows where a pending user is given, but the request is not authenticated yet"""
_CTX_OVERWRITE_USER.set(user)
try:
yield
finally:
_CTX_OVERWRITE_USER.set(None)
@contextmanager
def audit_ignore():
"""Ignore model operations in the block. Useful for objects which need to be modified
but are not excluded (e.g. WebAuthn devices)"""
_CTX_IGNORE.set(True)
try:
yield
finally:
_CTX_IGNORE.set(False)
class EventNewThread(Thread): class EventNewThread(Thread):
"""Create Event in background thread""" """Create Event in background thread"""
@ -158,6 +185,10 @@ class AuditMiddleware:
"""Signal handler for all object's post_save""" """Signal handler for all object's post_save"""
if not should_log_model(instance): if not should_log_model(instance):
return return
if _CTX_IGNORE.get():
return
if _new_user := _CTX_OVERWRITE_USER.get():
user = _new_user
action = EventAction.MODEL_CREATED if created else EventAction.MODEL_UPDATED action = EventAction.MODEL_CREATED if created else EventAction.MODEL_UPDATED
thread = EventNewThread(action, request, user=user, model=model_to_dict(instance)) thread = EventNewThread(action, request, user=user, model=model_to_dict(instance))
@ -168,6 +199,10 @@ class AuditMiddleware:
"""Signal handler for all object's pre_delete""" """Signal handler for all object's pre_delete"""
if not should_log_model(instance): # pragma: no cover if not should_log_model(instance): # pragma: no cover
return return
if _CTX_IGNORE.get():
return
if _new_user := _CTX_OVERWRITE_USER.get():
user = _new_user
EventNewThread( EventNewThread(
EventAction.MODEL_DELETED, EventAction.MODEL_DELETED,
@ -184,6 +219,10 @@ class AuditMiddleware:
return return
if not should_log_m2m(instance): if not should_log_m2m(instance):
return return
if _CTX_IGNORE.get():
return
if _new_user := _CTX_OVERWRITE_USER.get():
user = _new_user
EventNewThread( EventNewThread(
EventAction.MODEL_UPDATED, EventAction.MODEL_UPDATED,

View File

@ -5,7 +5,9 @@ from rest_framework.test import APITestCase
from authentik.core.models import Application from authentik.core.models import Application
from authentik.core.tests.utils import create_test_admin_user from authentik.core.tests.utils import create_test_admin_user
from authentik.events.middleware import audit_ignore, audit_overwrite_user
from authentik.events.models import Event, EventAction from authentik.events.models import Event, EventAction
from authentik.lib.generators import generate_id
class TestEventsMiddleware(APITestCase): class TestEventsMiddleware(APITestCase):
@ -15,35 +17,74 @@ class TestEventsMiddleware(APITestCase):
super().setUp() super().setUp()
self.user = create_test_admin_user() self.user = create_test_admin_user()
self.client.force_login(self.user) self.client.force_login(self.user)
Event.objects.all().delete()
def test_create(self): def test_create(self):
"""Test model creation event""" """Test model creation event"""
uid = generate_id()
self.client.post( self.client.post(
reverse("authentik_api:application-list"), reverse("authentik_api:application-list"),
data={"name": "test-create", "slug": "test-create"}, data={"name": uid, "slug": uid},
) )
self.assertTrue(Application.objects.filter(name="test-create").exists()) self.assertTrue(Application.objects.filter(name=uid).exists())
self.assertTrue( self.assertTrue(
Event.objects.filter( Event.objects.filter(
action=EventAction.MODEL_CREATED, action=EventAction.MODEL_CREATED,
context__model__model_name="application", context__model__model_name="application",
context__model__app="authentik_core", context__model__app="authentik_core",
context__model__name="test-create", context__model__name=uid,
).exists() ).exists()
) )
def test_delete(self): def test_delete(self):
"""Test model creation event""" """Test model creation event"""
Application.objects.create(name="test-delete", slug="test-delete") uid = generate_id()
self.client.delete( Application.objects.create(name=uid, slug=uid)
reverse("authentik_api:application-detail", kwargs={"slug": "test-delete"}) self.client.delete(reverse("authentik_api:application-detail", kwargs={"slug": uid}))
)
self.assertFalse(Application.objects.filter(name="test").exists()) self.assertFalse(Application.objects.filter(name="test").exists())
self.assertTrue( self.assertTrue(
Event.objects.filter( Event.objects.filter(
action=EventAction.MODEL_DELETED, action=EventAction.MODEL_DELETED,
context__model__model_name="application", context__model__model_name="application",
context__model__app="authentik_core", context__model__app="authentik_core",
context__model__name="test-delete", context__model__name=uid,
).exists()
)
def test_audit_ignore(self):
"""Test audit_ignore context manager"""
uid = generate_id()
with audit_ignore():
self.client.post(
reverse("authentik_api:application-list"),
data={"name": uid, "slug": uid},
)
self.assertTrue(Application.objects.filter(name=uid).exists())
self.assertFalse(
Event.objects.filter(
action=EventAction.MODEL_CREATED,
context__model__model_name="application",
context__model__app="authentik_core",
context__model__name=uid,
).exists()
)
def test_audit_overwrite_user(self):
"""Test audit_overwrite_user context manager"""
uid = generate_id()
new_user = create_test_admin_user()
with audit_overwrite_user(new_user):
self.client.post(
reverse("authentik_api:application-list"),
data={"name": uid, "slug": uid},
)
self.assertTrue(Application.objects.filter(name=uid).exists())
self.assertTrue(
Event.objects.filter(
action=EventAction.MODEL_CREATED,
context__model__model_name="application",
context__model__app="authentik_core",
context__model__name=uid,
user__username=new_user.username,
).exists() ).exists()
) )

View File

@ -31,6 +31,7 @@ from authentik.core.models import (
User, User,
UserTypes, UserTypes,
) )
from authentik.events.middleware import audit_ignore
from authentik.events.models import Event, EventAction from authentik.events.models import Event, EventAction
from authentik.events.signals import get_login_event from authentik.events.signals import get_login_event
from authentik.flows.planner import PLAN_CONTEXT_APPLICATION from authentik.flows.planner import PLAN_CONTEXT_APPLICATION
@ -465,22 +466,25 @@ class TokenParams:
def __create_user_from_jwt(self, token: dict[str, Any], app: Application, source: OAuthSource): def __create_user_from_jwt(self, token: dict[str, Any], app: Application, source: OAuthSource):
"""Create user from JWT""" """Create user from JWT"""
self.user, created = User.objects.update_or_create( with audit_ignore():
username=f"{self.provider.name}-{token.get('sub')}", self.user, created = User.objects.update_or_create(
defaults={ username=f"{self.provider.name}-{token.get('sub')}",
"attributes": { defaults={
USER_ATTRIBUTE_GENERATED: True, "attributes": {
USER_ATTRIBUTE_GENERATED: True,
},
"last_login": timezone.now(),
"name": (
f"Autogenerated user from application {app.name} (client credentials JWT)"
),
"path": source.get_user_path(),
"type": UserTypes.SERVICE_ACCOUNT,
}, },
"last_login": timezone.now(), )
"name": f"Autogenerated user from application {app.name} (client credentials JWT)", exp = token.get("exp")
"path": source.get_user_path(), if created and exp:
"type": UserTypes.SERVICE_ACCOUNT, self.user.attributes[USER_ATTRIBUTE_EXPIRES] = exp
}, self.user.save()
)
exp = token.get("exp")
if created and exp:
self.user.attributes[USER_ATTRIBUTE_EXPIRES] = exp
self.user.save()
@method_decorator(csrf_exempt, name="dispatch") @method_decorator(csrf_exempt, name="dispatch")

View File

@ -21,6 +21,7 @@ from webauthn.helpers.structs import UserVerificationRequirement
from authentik.core.api.utils import JSONDictField, PassiveSerializer from authentik.core.api.utils import JSONDictField, PassiveSerializer
from authentik.core.models import Application, User from authentik.core.models import Application, User
from authentik.core.signals import login_failed from authentik.core.signals import login_failed
from authentik.events.middleware import audit_ignore
from authentik.events.models import Event, EventAction from authentik.events.models import Event, EventAction
from authentik.flows.stage import StageView from authentik.flows.stage import StageView
from authentik.flows.views.executor import SESSION_KEY_APPLICATION_PRE from authentik.flows.views.executor import SESSION_KEY_APPLICATION_PRE
@ -167,7 +168,8 @@ def validate_challenge_webauthn(data: dict, stage_view: StageView, user: User) -
) )
raise ValidationError("Assertion failed") from exc raise ValidationError("Assertion failed") from exc
device.set_sign_count(authentication_verification.new_sign_count) with audit_ignore():
device.set_sign_count(authentication_verification.new_sign_count)
return device return device

View File

@ -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: 2024-04-01 23:02+0000\n" "POT-Creation-Date: 2024-04-09 00:08+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"
@ -2251,6 +2251,19 @@ msgstr ""
msgid "WebAuthn Devices" msgid "WebAuthn Devices"
msgstr "" msgstr ""
#: authentik/stages/authenticator_webauthn/models.py
msgid "WebAuthn Device type"
msgstr ""
#: authentik/stages/authenticator_webauthn/models.py
msgid "WebAuthn Device types"
msgstr ""
#: authentik/stages/authenticator_webauthn/stage.py
#, python-brace-format
msgid "Invalid device type. Contact your {brand} administrator for help."
msgstr ""
#: authentik/stages/captcha/models.py #: authentik/stages/captcha/models.py
msgid "Public key, acquired your captcha Provider." msgid "Public key, acquired your captcha Provider."
msgstr "" msgstr ""

View File

@ -6,7 +6,7 @@
"": { "": {
"name": "@goauthentik/web-tests", "name": "@goauthentik/web-tests",
"dependencies": { "dependencies": {
"chromedriver": "^123.0.1" "chromedriver": "^123.0.2"
}, },
"devDependencies": { "devDependencies": {
"@trivago/prettier-plugin-sort-imports": "^4.3.0", "@trivago/prettier-plugin-sort-imports": "^4.3.0",
@ -2084,9 +2084,9 @@
} }
}, },
"node_modules/chromedriver": { "node_modules/chromedriver": {
"version": "123.0.1", "version": "123.0.2",
"resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-123.0.1.tgz", "resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-123.0.2.tgz",
"integrity": "sha512-YQUIP/zdlzDIRCZNCv6rEVDSY4RAxo/tDL0OiGPPuai+z8unRNqJr/9V6XTBypVFyDheXNalKt9QxEqdMPuLAQ==", "integrity": "sha512-Kx0r/IGULm7eciaUtX/OKaFbdBdHRDSguiV1Q4zuQncz11gvymDdMtELa7ppk+kTL5113NLPud92nuIMNTRhww==",
"hasInstallScript": true, "hasInstallScript": true,
"dependencies": { "dependencies": {
"@testim/chrome-version": "^1.1.4", "@testim/chrome-version": "^1.1.4",

View File

@ -32,6 +32,6 @@
"node": ">=20" "node": ">=20"
}, },
"dependencies": { "dependencies": {
"chromedriver": "^123.0.1" "chromedriver": "^123.0.2"
} }
} }

View File

@ -575,7 +575,11 @@ export class SAMLProviderViewPage extends AKElement {
</dt> </dt>
<dd class="pf-c-description-list__description"> <dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text"> <div class="pf-c-description-list__text">
<ul class="pf-c-list"></ul> <ul class="pf-c-list">
${attr.Value.map((value) => {
return html` <li><pre>${value}</pre></li> `;
})}
</ul>
</div> </div>
</dd> </dd>
</div>`; </div>`;

View File

@ -2,7 +2,6 @@ import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { me } from "@goauthentik/common/users"; import { me } from "@goauthentik/common/users";
import { AKElement, rootInterface } from "@goauthentik/elements/Base"; import { AKElement, rootInterface } from "@goauthentik/elements/Base";
import "@goauthentik/elements/EmptyState"; import "@goauthentik/elements/EmptyState";
import { PaginatedResponse } from "@goauthentik/elements/table/Table";
import { localized, msg } from "@lit/localize"; import { localized, msg } from "@lit/localize";
import { html } from "lit"; import { html } from "lit";
@ -25,6 +24,8 @@ import type { PageUIConfig } from "./types";
* *
*/ */
const coreApi = () => new CoreApi(DEFAULT_CONFIG);
@localized() @localized()
@customElement("ak-library") @customElement("ak-library")
export class LibraryPage extends AKElement { export class LibraryPage extends AKElement {
@ -35,15 +36,13 @@ export class LibraryPage extends AKElement {
isAdmin = false; isAdmin = false;
@state() @state()
apps!: PaginatedResponse<Application>; apps: Application[] = [];
@state() @state()
uiConfig: PageUIConfig; uiConfig: PageUIConfig;
constructor() { constructor() {
super(); super();
const applicationListFetch = new CoreApi(DEFAULT_CONFIG).coreApplicationsList({});
const meFetch = me();
const uiConfig = rootInterface()?.uiConfig; const uiConfig = rootInterface()?.uiConfig;
if (!uiConfig) { if (!uiConfig) {
throw new Error("Could not retrieve uiConfig. Reason: unknown. Check logs."); throw new Error("Could not retrieve uiConfig. Reason: unknown. Check logs.");
@ -55,22 +54,41 @@ export class LibraryPage extends AKElement {
searchEnabled: uiConfig.enabledFeatures.search, searchEnabled: uiConfig.enabledFeatures.search,
}; };
Promise.allSettled([applicationListFetch, meFetch]).then( Promise.all([this.fetchApplications(), me()]).then(([applications, meStatus]) => {
([applicationListStatus, meStatus]) => { this.isAdmin = meStatus.user.isSuperuser;
if (meStatus.status === "rejected") { this.apps = applications;
throw new Error( this.ready = true;
`Could not determine status of user. Reason: ${meStatus.reason}`, });
); }
async fetchApplications(): Promise<Application[]> {
const applicationListParams = (page = 1) => ({
ordering: "name",
page,
pageSize: 100,
});
const applicationListFetch = await coreApi().coreApplicationsList(applicationListParams(1));
const pageCount = applicationListFetch.pagination.totalPages;
if (pageCount === 1) {
return applicationListFetch.results;
}
const applicationLaterPages = await Promise.allSettled(
Array.from({ length: pageCount - 1 }).map((_a, idx) =>
coreApi().coreApplicationsList(applicationListParams(idx + 2)),
),
);
return applicationLaterPages.reduce(
function (acc, result) {
if (result.status === "rejected") {
const reason = JSON.stringify(result.reason, null, 2);
throw new Error(`Could not retrieve list of applications. Reason: ${reason}`);
} }
if (applicationListStatus.status === "rejected") { return [...acc, ...result.value.results];
throw new Error(
`Could not retrieve list of applications. Reason: ${applicationListStatus.reason}`,
);
}
this.isAdmin = meStatus.value.user.isSuperuser;
this.apps = applicationListStatus.value;
this.ready = true;
}, },
[...applicationListFetch.results],
); );
} }

View File

@ -1,7 +1,6 @@
import { groupBy } from "@goauthentik/common/utils"; import { groupBy } from "@goauthentik/common/utils";
import { AKElement } from "@goauthentik/elements/Base"; import { AKElement } from "@goauthentik/elements/Base";
import "@goauthentik/elements/EmptyState"; import "@goauthentik/elements/EmptyState";
import { PaginatedResponse } from "@goauthentik/elements/table/Table";
import "@goauthentik/user/LibraryApplication"; import "@goauthentik/user/LibraryApplication";
import { msg } from "@lit/localize"; import { msg } from "@lit/localize";
@ -42,8 +41,8 @@ export class LibraryPage extends AKElement {
@property({ attribute: "isadmin", type: Boolean }) @property({ attribute: "isadmin", type: Boolean })
isAdmin = false; isAdmin = false;
@property({ attribute: false }) @property({ attribute: false, type: Array })
apps!: PaginatedResponse<Application>; apps!: Application[];
@property({ attribute: false }) @property({ attribute: false })
uiConfig!: PageUIConfig; uiConfig!: PageUIConfig;
@ -66,7 +65,7 @@ export class LibraryPage extends AKElement {
connectedCallback() { connectedCallback() {
super.connectedCallback(); super.connectedCallback();
this.filteredApps = this.apps?.results; this.filteredApps = this.apps;
if (this.filteredApps === undefined) { if (this.filteredApps === undefined) {
throw new Error( throw new Error(
"Application.results should never be undefined when passed to the Library Page.", "Application.results should never be undefined when passed to the Library Page.",
@ -89,7 +88,7 @@ export class LibraryPage extends AKElement {
event.stopPropagation(); event.stopPropagation();
const apps = event.detail.apps; const apps = event.detail.apps;
this.selectedApp = undefined; this.selectedApp = undefined;
this.filteredApps = this.apps.results; this.filteredApps = this.apps;
if (apps.length > 0) { if (apps.length > 0) {
this.selectedApp = apps[0]; this.selectedApp = apps[0];
this.filteredApps = event.detail.apps; this.filteredApps = event.detail.apps;
@ -132,7 +131,7 @@ export class LibraryPage extends AKElement {
} }
renderSearch() { renderSearch() {
return html`<ak-library-list-search .apps=${this.apps.results}></ak-library-list-search>`; return html`<ak-library-list-search .apps=${this.apps}></ak-library-list-search>`;
} }
render() { render() {

View File

@ -6475,6 +6475,18 @@ Bindings to groups/users are checked against the user of the event.</source>
</trans-unit> </trans-unit>
<trans-unit id="sc7d071fb5cc1f6bf"> <trans-unit id="sc7d071fb5cc1f6bf">
<source>A selection is required</source> <source>A selection is required</source>
</trans-unit>
<trans-unit id="sa64fb483becc9c2c">
<source>Device type restrictions</source>
</trans-unit>
<trans-unit id="sbb928551c84cd63f">
<source>Available Device types</source>
</trans-unit>
<trans-unit id="s6446c35d6b411e53">
<source>Selected Device types</source>
</trans-unit>
<trans-unit id="s1f4df216b56de4ac">
<source>Optionally restrict which WebAuthn device types may be used. When no device types are selected, all devices are allowed.</source>
</trans-unit> </trans-unit>
</body> </body>
</file> </file>

View File

@ -6744,6 +6744,18 @@ Bindings to groups/users are checked against the user of the event.</source>
</trans-unit> </trans-unit>
<trans-unit id="sc7d071fb5cc1f6bf"> <trans-unit id="sc7d071fb5cc1f6bf">
<source>A selection is required</source> <source>A selection is required</source>
</trans-unit>
<trans-unit id="sa64fb483becc9c2c">
<source>Device type restrictions</source>
</trans-unit>
<trans-unit id="sbb928551c84cd63f">
<source>Available Device types</source>
</trans-unit>
<trans-unit id="s6446c35d6b411e53">
<source>Selected Device types</source>
</trans-unit>
<trans-unit id="s1f4df216b56de4ac">
<source>Optionally restrict which WebAuthn device types may be used. When no device types are selected, all devices are allowed.</source>
</trans-unit> </trans-unit>
</body> </body>
</file> </file>

View File

@ -6391,6 +6391,18 @@ Bindings to groups/users are checked against the user of the event.</source>
</trans-unit> </trans-unit>
<trans-unit id="sc7d071fb5cc1f6bf"> <trans-unit id="sc7d071fb5cc1f6bf">
<source>A selection is required</source> <source>A selection is required</source>
</trans-unit>
<trans-unit id="sa64fb483becc9c2c">
<source>Device type restrictions</source>
</trans-unit>
<trans-unit id="sbb928551c84cd63f">
<source>Available Device types</source>
</trans-unit>
<trans-unit id="s6446c35d6b411e53">
<source>Selected Device types</source>
</trans-unit>
<trans-unit id="s1f4df216b56de4ac">
<source>Optionally restrict which WebAuthn device types may be used. When no device types are selected, all devices are allowed.</source>
</trans-unit> </trans-unit>
</body> </body>
</file> </file>

View File

@ -8504,6 +8504,18 @@ Les liaisons avec les groupes/utilisateurs sont vérifiées par rapport à l'uti
</trans-unit> </trans-unit>
<trans-unit id="sc7d071fb5cc1f6bf"> <trans-unit id="sc7d071fb5cc1f6bf">
<source>A selection is required</source> <source>A selection is required</source>
</trans-unit>
<trans-unit id="sa64fb483becc9c2c">
<source>Device type restrictions</source>
</trans-unit>
<trans-unit id="sbb928551c84cd63f">
<source>Available Device types</source>
</trans-unit>
<trans-unit id="s6446c35d6b411e53">
<source>Selected Device types</source>
</trans-unit>
<trans-unit id="s1f4df216b56de4ac">
<source>Optionally restrict which WebAuthn device types may be used. When no device types are selected, all devices are allowed.</source>
</trans-unit> </trans-unit>
</body> </body>
</file> </file>

View File

@ -8332,6 +8332,18 @@ Bindings to groups/users are checked against the user of the event.</source>
</trans-unit> </trans-unit>
<trans-unit id="sc7d071fb5cc1f6bf"> <trans-unit id="sc7d071fb5cc1f6bf">
<source>A selection is required</source> <source>A selection is required</source>
</trans-unit>
<trans-unit id="sa64fb483becc9c2c">
<source>Device type restrictions</source>
</trans-unit>
<trans-unit id="sbb928551c84cd63f">
<source>Available Device types</source>
</trans-unit>
<trans-unit id="s6446c35d6b411e53">
<source>Selected Device types</source>
</trans-unit>
<trans-unit id="s1f4df216b56de4ac">
<source>Optionally restrict which WebAuthn device types may be used. When no device types are selected, all devices are allowed.</source>
</trans-unit> </trans-unit>
</body> </body>
</file> </file>

View File

@ -8175,6 +8175,18 @@ Bindingen naar groepen/gebruikers worden gecontroleerd tegen de gebruiker van de
</trans-unit> </trans-unit>
<trans-unit id="sc7d071fb5cc1f6bf"> <trans-unit id="sc7d071fb5cc1f6bf">
<source>A selection is required</source> <source>A selection is required</source>
</trans-unit>
<trans-unit id="sa64fb483becc9c2c">
<source>Device type restrictions</source>
</trans-unit>
<trans-unit id="sbb928551c84cd63f">
<source>Available Device types</source>
</trans-unit>
<trans-unit id="s6446c35d6b411e53">
<source>Selected Device types</source>
</trans-unit>
<trans-unit id="s1f4df216b56de4ac">
<source>Optionally restrict which WebAuthn device types may be used. When no device types are selected, all devices are allowed.</source>
</trans-unit> </trans-unit>
</body> </body>
</file> </file>

View File

@ -6596,6 +6596,18 @@ Bindings to groups/users are checked against the user of the event.</source>
</trans-unit> </trans-unit>
<trans-unit id="sc7d071fb5cc1f6bf"> <trans-unit id="sc7d071fb5cc1f6bf">
<source>A selection is required</source> <source>A selection is required</source>
</trans-unit>
<trans-unit id="sa64fb483becc9c2c">
<source>Device type restrictions</source>
</trans-unit>
<trans-unit id="sbb928551c84cd63f">
<source>Available Device types</source>
</trans-unit>
<trans-unit id="s6446c35d6b411e53">
<source>Selected Device types</source>
</trans-unit>
<trans-unit id="s1f4df216b56de4ac">
<source>Optionally restrict which WebAuthn device types may be used. When no device types are selected, all devices are allowed.</source>
</trans-unit> </trans-unit>
</body> </body>
</file> </file>

View File

@ -8448,4 +8448,16 @@ Bindings to groups/users are checked against the user of the event.</source>
<trans-unit id="sc7d071fb5cc1f6bf"> <trans-unit id="sc7d071fb5cc1f6bf">
<source>A selection is required</source> <source>A selection is required</source>
</trans-unit> </trans-unit>
<trans-unit id="sa64fb483becc9c2c">
<source>Device type restrictions</source>
</trans-unit>
<trans-unit id="sbb928551c84cd63f">
<source>Available Device types</source>
</trans-unit>
<trans-unit id="s6446c35d6b411e53">
<source>Selected Device types</source>
</trans-unit>
<trans-unit id="s1f4df216b56de4ac">
<source>Optionally restrict which WebAuthn device types may be used. When no device types are selected, all devices are allowed.</source>
</trans-unit>
</body></file></xliff> </body></file></xliff>

View File

@ -6384,6 +6384,18 @@ Bindings to groups/users are checked against the user of the event.</source>
</trans-unit> </trans-unit>
<trans-unit id="sc7d071fb5cc1f6bf"> <trans-unit id="sc7d071fb5cc1f6bf">
<source>A selection is required</source> <source>A selection is required</source>
</trans-unit>
<trans-unit id="sa64fb483becc9c2c">
<source>Device type restrictions</source>
</trans-unit>
<trans-unit id="sbb928551c84cd63f">
<source>Available Device types</source>
</trans-unit>
<trans-unit id="s6446c35d6b411e53">
<source>Selected Device types</source>
</trans-unit>
<trans-unit id="s1f4df216b56de4ac">
<source>Optionally restrict which WebAuthn device types may be used. When no device types are selected, all devices are allowed.</source>
</trans-unit> </trans-unit>
</body> </body>
</file> </file>

View File

@ -5302,6 +5302,18 @@ Bindings to groups/users are checked against the user of the event.</source>
<trans-unit id="sc7d071fb5cc1f6bf"> <trans-unit id="sc7d071fb5cc1f6bf">
<source>A selection is required</source> <source>A selection is required</source>
</trans-unit> </trans-unit>
<trans-unit id="sa64fb483becc9c2c">
<source>Device type restrictions</source>
</trans-unit>
<trans-unit id="sbb928551c84cd63f">
<source>Available Device types</source>
</trans-unit>
<trans-unit id="s6446c35d6b411e53">
<source>Selected Device types</source>
</trans-unit>
<trans-unit id="s1f4df216b56de4ac">
<source>Optionally restrict which WebAuthn device types may be used. When no device types are selected, all devices are allowed.</source>
</trans-unit>
</body> </body>
</file> </file>
</xliff> </xliff>

View File

@ -1,4 +1,4 @@
<?xml version="1.0" ?><xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2"> <?xml version="1.0"?><xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
<file target-language="zh-Hans" source-language="en" original="lit-localize-inputs" datatype="plaintext"> <file target-language="zh-Hans" source-language="en" original="lit-localize-inputs" datatype="plaintext">
<body> <body>
<trans-unit id="s4caed5b7a7e5d89b"> <trans-unit id="s4caed5b7a7e5d89b">
@ -596,9 +596,9 @@
</trans-unit> </trans-unit>
<trans-unit id="saa0e2675da69651b"> <trans-unit id="saa0e2675da69651b">
<source>The URL &quot;<x id="0" equiv-text="${this.url}"/>&quot; was not found.</source> <source>The URL "<x id="0" equiv-text="${this.url}"/>" was not found.</source>
<target>未找到 URL &quot; <target>未找到 URL "
<x id="0" equiv-text="${this.url}"/>&quot;。</target> <x id="0" equiv-text="${this.url}"/>"。</target>
</trans-unit> </trans-unit>
<trans-unit id="s58cd9c2fe836d9c6"> <trans-unit id="s58cd9c2fe836d9c6">
@ -1040,8 +1040,8 @@
</trans-unit> </trans-unit>
<trans-unit id="sa8384c9c26731f83"> <trans-unit id="sa8384c9c26731f83">
<source>To allow any redirect URI, set this value to &quot;.*&quot;. Be aware of the possible security implications this can have.</source> <source>To allow any redirect URI, set this value to ".*". Be aware of the possible security implications this can have.</source>
<target>要允许任何重定向 URI请将此值设置为 &quot;.*&quot;。请注意这可能带来的安全影响。</target> <target>要允许任何重定向 URI请将此值设置为 ".*"。请注意这可能带来的安全影响。</target>
</trans-unit> </trans-unit>
<trans-unit id="s55787f4dfcdce52b"> <trans-unit id="s55787f4dfcdce52b">
@ -1782,8 +1782,8 @@
</trans-unit> </trans-unit>
<trans-unit id="sa90b7809586c35ce"> <trans-unit id="sa90b7809586c35ce">
<source>Either input a full URL, a relative path, or use 'fa://fa-test' to use the Font Awesome icon &quot;fa-test&quot;.</source> <source>Either input a full URL, a relative path, or use 'fa://fa-test' to use the Font Awesome icon "fa-test".</source>
<target>输入完整 URL、相对路径或者使用 'fa://fa-test' 来使用 Font Awesome 图标 &quot;fa-test&quot;。</target> <target>输入完整 URL、相对路径或者使用 'fa://fa-test' 来使用 Font Awesome 图标 "fa-test"。</target>
</trans-unit> </trans-unit>
<trans-unit id="s0410779cb47de312"> <trans-unit id="s0410779cb47de312">
@ -2961,8 +2961,8 @@ doesn't pass when either or both of the selected options are equal or above the
</trans-unit> </trans-unit>
<trans-unit id="s76768bebabb7d543"> <trans-unit id="s76768bebabb7d543">
<source>Field which contains members of a group. Note that if using the &quot;memberUid&quot; field, the value is assumed to contain a relative distinguished name. e.g. 'memberUid=some-user' instead of 'memberUid=cn=some-user,ou=groups,...'</source> <source>Field which contains members of a group. Note that if using the "memberUid" field, the value is assumed to contain a relative distinguished name. e.g. 'memberUid=some-user' instead of 'memberUid=cn=some-user,ou=groups,...'</source>
<target>包含组成员的字段。请注意,如果使用 &quot;memberUid&quot; 字段,则假定该值包含相对可分辨名称。例如,'memberUid=some-user' 而不是 'memberUid=cn=some-user,ou=groups,...'</target> <target>包含组成员的字段。请注意,如果使用 "memberUid" 字段,则假定该值包含相对可分辨名称。例如,'memberUid=some-user' 而不是 'memberUid=cn=some-user,ou=groups,...'</target>
</trans-unit> </trans-unit>
<trans-unit id="s026555347e589f0e"> <trans-unit id="s026555347e589f0e">
@ -3739,8 +3739,8 @@ doesn't pass when either or both of the selected options are equal or above the
</trans-unit> </trans-unit>
<trans-unit id="s7b1fba26d245cb1c"> <trans-unit id="s7b1fba26d245cb1c">
<source>When using an external logging solution for archiving, this can be set to &quot;minutes=5&quot;.</source> <source>When using an external logging solution for archiving, this can be set to "minutes=5".</source>
<target>使用外部日志记录解决方案进行存档时,可以将其设置为 &quot;minutes=5&quot;。</target> <target>使用外部日志记录解决方案进行存档时,可以将其设置为 "minutes=5"。</target>
</trans-unit> </trans-unit>
<trans-unit id="s44536d20bb5c8257"> <trans-unit id="s44536d20bb5c8257">
@ -3916,10 +3916,10 @@ doesn't pass when either or both of the selected options are equal or above the
</trans-unit> </trans-unit>
<trans-unit id="sa95a538bfbb86111"> <trans-unit id="sa95a538bfbb86111">
<source>Are you sure you want to update <x id="0" equiv-text="${this.objectLabel}"/> &quot;<x id="1" equiv-text="${this.obj?.name}"/>&quot;?</source> <source>Are you sure you want to update <x id="0" equiv-text="${this.objectLabel}"/> "<x id="1" equiv-text="${this.obj?.name}"/>"?</source>
<target>您确定要更新 <target>您确定要更新
<x id="0" equiv-text="${this.objectLabel}"/>&quot; <x id="0" equiv-text="${this.objectLabel}"/>"
<x id="1" equiv-text="${this.obj?.name}"/>&quot; 吗?</target> <x id="1" equiv-text="${this.obj?.name}"/>" 吗?</target>
</trans-unit> </trans-unit>
<trans-unit id="sc92d7cfb6ee1fec6"> <trans-unit id="sc92d7cfb6ee1fec6">
@ -5000,7 +5000,7 @@ doesn't pass when either or both of the selected options are equal or above the
</trans-unit> </trans-unit>
<trans-unit id="sdf1d8edef27236f0"> <trans-unit id="sdf1d8edef27236f0">
<source>A &quot;roaming&quot; authenticator, like a YubiKey</source> <source>A "roaming" authenticator, like a YubiKey</source>
<target>像 YubiKey 这样的“漫游”身份验证器</target> <target>像 YubiKey 这样的“漫游”身份验证器</target>
</trans-unit> </trans-unit>
@ -5335,10 +5335,10 @@ doesn't pass when either or both of the selected options are equal or above the
</trans-unit> </trans-unit>
<trans-unit id="s2d5f69929bb7221d"> <trans-unit id="s2d5f69929bb7221d">
<source><x id="0" equiv-text="${prompt.name}"/> (&quot;<x id="1" equiv-text="${prompt.fieldKey}"/>&quot;, of type <x id="2" equiv-text="${prompt.type}"/>)</source> <source><x id="0" equiv-text="${prompt.name}"/> ("<x id="1" equiv-text="${prompt.fieldKey}"/>", of type <x id="2" equiv-text="${prompt.type}"/>)</source>
<target> <target>
<x id="0" equiv-text="${prompt.name}"/>&quot; <x id="0" equiv-text="${prompt.name}"/>"
<x id="1" equiv-text="${prompt.fieldKey}"/>&quot;,类型为 <x id="1" equiv-text="${prompt.fieldKey}"/>",类型为
<x id="2" equiv-text="${prompt.type}"/></target> <x id="2" equiv-text="${prompt.type}"/></target>
</trans-unit> </trans-unit>
@ -5387,7 +5387,7 @@ doesn't pass when either or both of the selected options are equal or above the
</trans-unit> </trans-unit>
<trans-unit id="s1608b2f94fa0dbd4"> <trans-unit id="s1608b2f94fa0dbd4">
<source>If set to a duration above 0, the user will have the option to choose to &quot;stay signed in&quot;, which will extend their session by the time specified here.</source> <source>If set to a duration above 0, the user will have the option to choose to "stay signed in", which will extend their session by the time specified here.</source>
<target>如果设置时长大于 0用户可以选择“保持登录”选项这将使用户的会话延长此处设置的时间。</target> <target>如果设置时长大于 0用户可以选择“保持登录”选项这将使用户的会话延长此处设置的时间。</target>
</trans-unit> </trans-unit>
@ -7839,7 +7839,7 @@ Bindings to groups/users are checked against the user of the event.</source>
<target>成功创建用户并添加到组 <x id="0" equiv-text="${this.group.name}"/></target> <target>成功创建用户并添加到组 <x id="0" equiv-text="${this.group.name}"/></target>
</trans-unit> </trans-unit>
<trans-unit id="s824e0943a7104668"> <trans-unit id="s824e0943a7104668">
<source>This user will be added to the group &quot;<x id="0" equiv-text="${this.targetGroup.name}"/>&quot;.</source> <source>This user will be added to the group "<x id="0" equiv-text="${this.targetGroup.name}"/>".</source>
<target>此用户将会被添加到组 &amp;quot;<x id="0" equiv-text="${this.targetGroup.name}"/>&amp;quot;。</target> <target>此用户将会被添加到组 &amp;quot;<x id="0" equiv-text="${this.targetGroup.name}"/>&amp;quot;。</target>
</trans-unit> </trans-unit>
<trans-unit id="s62e7f6ed7d9cb3ca"> <trans-unit id="s62e7f6ed7d9cb3ca">
@ -8523,7 +8523,19 @@ Bindings to groups/users are checked against the user of the event.</source>
<trans-unit id="sc7d071fb5cc1f6bf"> <trans-unit id="sc7d071fb5cc1f6bf">
<source>A selection is required</source> <source>A selection is required</source>
<target>需要进行选择</target> <target>需要进行选择</target>
</trans-unit>
<trans-unit id="sa64fb483becc9c2c">
<source>Device type restrictions</source>
</trans-unit>
<trans-unit id="sbb928551c84cd63f">
<source>Available Device types</source>
</trans-unit>
<trans-unit id="s6446c35d6b411e53">
<source>Selected Device types</source>
</trans-unit>
<trans-unit id="s1f4df216b56de4ac">
<source>Optionally restrict which WebAuthn device types may be used. When no device types are selected, all devices are allowed.</source>
</trans-unit> </trans-unit>
</body> </body>
</file> </file>
</xliff> </xliff>

View File

@ -6432,6 +6432,18 @@ Bindings to groups/users are checked against the user of the event.</source>
</trans-unit> </trans-unit>
<trans-unit id="sc7d071fb5cc1f6bf"> <trans-unit id="sc7d071fb5cc1f6bf">
<source>A selection is required</source> <source>A selection is required</source>
</trans-unit>
<trans-unit id="sa64fb483becc9c2c">
<source>Device type restrictions</source>
</trans-unit>
<trans-unit id="sbb928551c84cd63f">
<source>Available Device types</source>
</trans-unit>
<trans-unit id="s6446c35d6b411e53">
<source>Selected Device types</source>
</trans-unit>
<trans-unit id="s1f4df216b56de4ac">
<source>Optionally restrict which WebAuthn device types may be used. When no device types are selected, all devices are allowed.</source>
</trans-unit> </trans-unit>
</body> </body>
</file> </file>

View File

@ -8293,6 +8293,18 @@ Bindings to groups/users are checked against the user of the event.</source>
</trans-unit> </trans-unit>
<trans-unit id="sc7d071fb5cc1f6bf"> <trans-unit id="sc7d071fb5cc1f6bf">
<source>A selection is required</source> <source>A selection is required</source>
</trans-unit>
<trans-unit id="sa64fb483becc9c2c">
<source>Device type restrictions</source>
</trans-unit>
<trans-unit id="sbb928551c84cd63f">
<source>Available Device types</source>
</trans-unit>
<trans-unit id="s6446c35d6b411e53">
<source>Selected Device types</source>
</trans-unit>
<trans-unit id="s1f4df216b56de4ac">
<source>Optionally restrict which WebAuthn device types may be used. When no device types are selected, all devices are allowed.</source>
</trans-unit> </trans-unit>
</body> </body>
</file> </file>

View File

@ -27,7 +27,7 @@ cd /bitnami/postgresql/
# Set the postgres password based on the `POSTGRES_POSTGRES_PASSWORD` environment variable # Set the postgres password based on the `POSTGRES_POSTGRES_PASSWORD` environment variable
export PGPASSWORD=$POSTGRES_POSTGRES_PASSWORD export PGPASSWORD=$POSTGRES_POSTGRES_PASSWORD
# Dump the authentik database into an sql file # Dump the authentik database into an sql file
pg_dump -U postgres $POSTGRES_DB > dump-11.sql pg_dump -U $POSTGRES_USER $POSTGRES_DB > dump-11.sql
``` ```
### Stop PostgreSQL and start the upgrade ### Stop PostgreSQL and start the upgrade
@ -88,7 +88,7 @@ Run the following commands to restore the data:
cd /bitnami/postgresql/ cd /bitnami/postgresql/
# Set the Postgres password based on the `POSTGRES_POSTGRES_PASSWORD` environment variable. # Set the Postgres password based on the `POSTGRES_POSTGRES_PASSWORD` environment variable.
export PGPASSWORD=$POSTGRES_POSTGRES_PASSWORD export PGPASSWORD=$POSTGRES_POSTGRES_PASSWORD
psql -U postgres $POSTGRES_DB < dump-11.sql psql -U $POSTGRES_USER $POSTGRES_DB < dump-11.sql
``` ```
After the last command finishes, all of the data is restored, and you can restart authentik. After the last command finishes, all of the data is restored, and you can restart authentik.

View File

@ -33,7 +33,7 @@
"@docusaurus/module-type-aliases": "3.2.1", "@docusaurus/module-type-aliases": "3.2.1",
"@docusaurus/tsconfig": "3.2.1", "@docusaurus/tsconfig": "3.2.1",
"@docusaurus/types": "3.2.1", "@docusaurus/types": "3.2.1",
"@types/react": "^18.2.74", "@types/react": "^18.2.75",
"prettier": "3.2.5", "prettier": "3.2.5",
"typescript": "~5.4.4" "typescript": "~5.4.4"
}, },
@ -3902,9 +3902,9 @@
"integrity": "sha512-+0autS93xyXizIYiyL02FCY8N+KkKPhILhcUSA276HxzreZ16kl+cmwvV2qAM/PuCCwPXzOXOWhiPcw20uSFcA==" "integrity": "sha512-+0autS93xyXizIYiyL02FCY8N+KkKPhILhcUSA276HxzreZ16kl+cmwvV2qAM/PuCCwPXzOXOWhiPcw20uSFcA=="
}, },
"node_modules/@types/react": { "node_modules/@types/react": {
"version": "18.2.74", "version": "18.2.75",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.74.tgz", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.75.tgz",
"integrity": "sha512-9AEqNZZyBx8OdZpxzQlaFEVCSFUM2YXJH46yPOiOpm078k6ZLOCcuAzGum/zK8YBwY+dbahVNbHrbgrAwIRlqw==", "integrity": "sha512-+DNnF7yc5y0bHkBTiLKqXFe+L4B3nvOphiMY3tuA5X10esmjqk7smyBZzbGTy2vsiy/Bnzj8yFIBL8xhRacoOg==",
"dependencies": { "dependencies": {
"@types/prop-types": "*", "@types/prop-types": "*",
"csstype": "^3.0.2" "csstype": "^3.0.2"

View File

@ -52,7 +52,7 @@
"@docusaurus/module-type-aliases": "3.2.1", "@docusaurus/module-type-aliases": "3.2.1",
"@docusaurus/tsconfig": "3.2.1", "@docusaurus/tsconfig": "3.2.1",
"@docusaurus/types": "3.2.1", "@docusaurus/types": "3.2.1",
"@types/react": "^18.2.74", "@types/react": "^18.2.75",
"prettier": "3.2.5", "prettier": "3.2.5",
"typescript": "~5.4.4" "typescript": "~5.4.4"
}, },