providers/scim: use lock for sync (#7948)
* providers/scim: use lock for sync Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix Signed-off-by: Jens Langhammer <jens@goauthentik.io> --------- Signed-off-by: Jens Langhammer <jens@goauthentik.io>
This commit is contained in:
parent
ec8f2d4bf9
commit
2521073dba
|
@ -2,6 +2,7 @@
|
||||||
from django.utils.text import slugify
|
from django.utils.text import slugify
|
||||||
from drf_spectacular.utils import OpenApiResponse, extend_schema
|
from drf_spectacular.utils import OpenApiResponse, extend_schema
|
||||||
from rest_framework.decorators import action
|
from rest_framework.decorators import action
|
||||||
|
from rest_framework.fields import BooleanField
|
||||||
from rest_framework.request import Request
|
from rest_framework.request import Request
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework.viewsets import ModelViewSet
|
from rest_framework.viewsets import ModelViewSet
|
||||||
|
@ -9,6 +10,7 @@ from rest_framework.viewsets import ModelViewSet
|
||||||
from authentik.admin.api.tasks import TaskSerializer
|
from authentik.admin.api.tasks import TaskSerializer
|
||||||
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
|
||||||
|
from authentik.core.api.utils import PassiveSerializer
|
||||||
from authentik.events.monitored_tasks import TaskInfo
|
from authentik.events.monitored_tasks import TaskInfo
|
||||||
from authentik.providers.scim.models import SCIMProvider
|
from authentik.providers.scim.models import SCIMProvider
|
||||||
|
|
||||||
|
@ -37,6 +39,13 @@ class SCIMProviderSerializer(ProviderSerializer):
|
||||||
extra_kwargs = {}
|
extra_kwargs = {}
|
||||||
|
|
||||||
|
|
||||||
|
class SCIMSyncStatusSerializer(PassiveSerializer):
|
||||||
|
"""SCIM Provider sync status"""
|
||||||
|
|
||||||
|
is_running = BooleanField(read_only=True)
|
||||||
|
tasks = TaskSerializer(many=True, read_only=True)
|
||||||
|
|
||||||
|
|
||||||
class SCIMProviderViewSet(UsedByMixin, ModelViewSet):
|
class SCIMProviderViewSet(UsedByMixin, ModelViewSet):
|
||||||
"""SCIMProvider Viewset"""
|
"""SCIMProvider Viewset"""
|
||||||
|
|
||||||
|
@ -48,15 +57,18 @@ class SCIMProviderViewSet(UsedByMixin, ModelViewSet):
|
||||||
|
|
||||||
@extend_schema(
|
@extend_schema(
|
||||||
responses={
|
responses={
|
||||||
200: TaskSerializer(),
|
200: SCIMSyncStatusSerializer(),
|
||||||
404: OpenApiResponse(description="Task not found"),
|
404: OpenApiResponse(description="Task not found"),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@action(methods=["GET"], detail=True, pagination_class=None, filter_backends=[])
|
@action(methods=["GET"], detail=True, pagination_class=None, filter_backends=[])
|
||||||
def sync_status(self, request: Request, pk: int) -> Response:
|
def sync_status(self, request: Request, pk: int) -> Response:
|
||||||
"""Get provider's sync status"""
|
"""Get provider's sync status"""
|
||||||
provider = self.get_object()
|
provider: SCIMProvider = self.get_object()
|
||||||
task = TaskInfo.by_name(f"scim_sync:{slugify(provider.name)}")
|
task = TaskInfo.by_name(f"scim_sync:{slugify(provider.name)}")
|
||||||
if not task:
|
tasks = [task] if task else []
|
||||||
return Response(status=404)
|
status = {
|
||||||
return Response(TaskSerializer(task).data)
|
"tasks": tasks,
|
||||||
|
"is_running": provider.sync_lock.locked(),
|
||||||
|
}
|
||||||
|
return Response(SCIMSyncStatusSerializer(status).data)
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
"""SCIM Provider models"""
|
"""SCIM Provider models"""
|
||||||
|
from django.core.cache import cache
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models import QuerySet
|
from django.db.models import QuerySet
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from guardian.shortcuts import get_anonymous_user
|
from guardian.shortcuts import get_anonymous_user
|
||||||
|
from redis.lock import Lock
|
||||||
from rest_framework.serializers import Serializer
|
from rest_framework.serializers import Serializer
|
||||||
|
|
||||||
from authentik.core.models import BackchannelProvider, Group, PropertyMapping, User, UserTypes
|
from authentik.core.models import BackchannelProvider, Group, PropertyMapping, User, UserTypes
|
||||||
|
from authentik.providers.scim.clients import PAGE_TIMEOUT
|
||||||
|
|
||||||
|
|
||||||
class SCIMProvider(BackchannelProvider):
|
class SCIMProvider(BackchannelProvider):
|
||||||
|
@ -27,6 +30,15 @@ class SCIMProvider(BackchannelProvider):
|
||||||
help_text=_("Property mappings used for group creation/updating."),
|
help_text=_("Property mappings used for group creation/updating."),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def sync_lock(self) -> Lock:
|
||||||
|
"""Redis lock for syncing SCIM to prevent multiple parallel syncs happening"""
|
||||||
|
return Lock(
|
||||||
|
cache.client.get_client(),
|
||||||
|
name=f"goauthentik.io/providers/scim/sync-{str(self.pk)}",
|
||||||
|
timeout=(60 * 60 * PAGE_TIMEOUT) * 3,
|
||||||
|
)
|
||||||
|
|
||||||
def get_user_qs(self) -> QuerySet[User]:
|
def get_user_qs(self) -> QuerySet[User]:
|
||||||
"""Get queryset of all users with consistent ordering
|
"""Get queryset of all users with consistent ordering
|
||||||
according to the provider's settings"""
|
according to the provider's settings"""
|
||||||
|
|
|
@ -47,6 +47,10 @@ def scim_sync(self: MonitoredTask, provider_pk: int) -> None:
|
||||||
).first()
|
).first()
|
||||||
if not provider:
|
if not provider:
|
||||||
return
|
return
|
||||||
|
lock = provider.sync_lock
|
||||||
|
if lock.locked():
|
||||||
|
LOGGER.debug("SCIM sync locked, skipping task", source=provider.name)
|
||||||
|
return
|
||||||
self.set_uid(slugify(provider.name))
|
self.set_uid(slugify(provider.name))
|
||||||
result = TaskResult(TaskResultStatus.SUCCESSFUL, [])
|
result = TaskResult(TaskResultStatus.SUCCESSFUL, [])
|
||||||
result.messages.append(_("Starting full SCIM sync"))
|
result.messages.append(_("Starting full SCIM sync"))
|
||||||
|
|
17
schema.yml
17
schema.yml
|
@ -17079,7 +17079,7 @@ paths:
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/Task'
|
$ref: '#/components/schemas/SCIMSyncStatus'
|
||||||
description: ''
|
description: ''
|
||||||
'404':
|
'404':
|
||||||
description: Task not found
|
description: Task not found
|
||||||
|
@ -40645,6 +40645,21 @@ components:
|
||||||
- name
|
- name
|
||||||
- token
|
- token
|
||||||
- url
|
- url
|
||||||
|
SCIMSyncStatus:
|
||||||
|
type: object
|
||||||
|
description: SCIM Provider sync status
|
||||||
|
properties:
|
||||||
|
is_running:
|
||||||
|
type: boolean
|
||||||
|
readOnly: true
|
||||||
|
tasks:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/Task'
|
||||||
|
readOnly: true
|
||||||
|
required:
|
||||||
|
- is_running
|
||||||
|
- tasks
|
||||||
SMSDevice:
|
SMSDevice:
|
||||||
type: object
|
type: object
|
||||||
description: Serializer for sms authenticator devices
|
description: Serializer for sms authenticator devices
|
||||||
|
|
|
@ -93,15 +93,16 @@ export class LDAPSyncStatusChart extends AKChart<SyncStatus[]> {
|
||||||
const health = await api.providersScimSyncStatusRetrieve({
|
const health = await api.providersScimSyncStatusRetrieve({
|
||||||
id: element.pk,
|
id: element.pk,
|
||||||
});
|
});
|
||||||
|
health.tasks.forEach((task) => {
|
||||||
if (health.status !== TaskStatusEnum.Successful) {
|
if (task.status !== TaskStatusEnum.Successful) {
|
||||||
sourceKey = "failed";
|
sourceKey = "failed";
|
||||||
}
|
}
|
||||||
const now = new Date().getTime();
|
const now = new Date().getTime();
|
||||||
const maxDelta = 3600000; // 1 hour
|
const maxDelta = 3600000; // 1 hour
|
||||||
if (!health || now - health.taskFinishTimestamp.getTime() > maxDelta) {
|
if (!health || now - task.taskFinishTimestamp.getTime() > maxDelta) {
|
||||||
sourceKey = "unsynced";
|
sourceKey = "unsynced";
|
||||||
}
|
}
|
||||||
|
});
|
||||||
} catch {
|
} catch {
|
||||||
sourceKey = "unsynced";
|
sourceKey = "unsynced";
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ import "@goauthentik/elements/Tabs";
|
||||||
import "@goauthentik/elements/buttons/ActionButton";
|
import "@goauthentik/elements/buttons/ActionButton";
|
||||||
import "@goauthentik/elements/buttons/ModalButton";
|
import "@goauthentik/elements/buttons/ModalButton";
|
||||||
|
|
||||||
import { msg } from "@lit/localize";
|
import { msg, str } from "@lit/localize";
|
||||||
import { CSSResult, TemplateResult, html } from "lit";
|
import { CSSResult, TemplateResult, html } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators.js";
|
import { customElement, property, state } from "lit/decorators.js";
|
||||||
|
|
||||||
|
@ -31,7 +31,8 @@ import {
|
||||||
ProvidersApi,
|
ProvidersApi,
|
||||||
RbacPermissionsAssignedByUsersListModelEnum,
|
RbacPermissionsAssignedByUsersListModelEnum,
|
||||||
SCIMProvider,
|
SCIMProvider,
|
||||||
Task,
|
SCIMSyncStatus,
|
||||||
|
TaskStatusEnum,
|
||||||
} from "@goauthentik/api";
|
} from "@goauthentik/api";
|
||||||
|
|
||||||
@customElement("ak-provider-scim-view")
|
@customElement("ak-provider-scim-view")
|
||||||
|
@ -54,7 +55,7 @@ export class SCIMProviderViewPage extends AKElement {
|
||||||
provider?: SCIMProvider;
|
provider?: SCIMProvider;
|
||||||
|
|
||||||
@state()
|
@state()
|
||||||
syncState?: Task;
|
syncState?: SCIMSyncStatus;
|
||||||
|
|
||||||
static get styles(): CSSResult[] {
|
static get styles(): CSSResult[] {
|
||||||
return [
|
return [
|
||||||
|
@ -128,6 +129,41 @@ export class SCIMProviderViewPage extends AKElement {
|
||||||
</ak-tabs>`;
|
</ak-tabs>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderSyncStatus(): TemplateResult {
|
||||||
|
if (!this.syncState) {
|
||||||
|
return html`${msg("No sync status.")}`;
|
||||||
|
}
|
||||||
|
if (this.syncState.isRunning) {
|
||||||
|
return html`${msg("Sync currently running.")}`;
|
||||||
|
}
|
||||||
|
if (this.syncState.tasks.length < 1) {
|
||||||
|
return html`${msg("Not synced yet.")}`;
|
||||||
|
}
|
||||||
|
return html`
|
||||||
|
<ul class="pf-c-list">
|
||||||
|
${this.syncState.tasks.map((task) => {
|
||||||
|
let header = "";
|
||||||
|
if (task.status === TaskStatusEnum.Warning) {
|
||||||
|
header = msg("Task finished with warnings");
|
||||||
|
} else if (task.status === TaskStatusEnum.Error) {
|
||||||
|
header = msg("Task finished with errors");
|
||||||
|
} else {
|
||||||
|
header = msg(str`Last sync: ${task.taskFinishTimestamp.toLocaleString()}`);
|
||||||
|
}
|
||||||
|
return html`<li>
|
||||||
|
<p>${task.taskName}</p>
|
||||||
|
<ul class="pf-c-list">
|
||||||
|
<li>${header}</li>
|
||||||
|
${task.messages.map((m) => {
|
||||||
|
return html`<li>${m}</li>`;
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
</li> `;
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
renderTabOverview(): TemplateResult {
|
renderTabOverview(): TemplateResult {
|
||||||
if (!this.provider) {
|
if (!this.provider) {
|
||||||
return html``;
|
return html``;
|
||||||
|
@ -186,16 +222,7 @@ export class SCIMProviderViewPage extends AKElement {
|
||||||
<div class="pf-c-card__title">
|
<div class="pf-c-card__title">
|
||||||
<p>${msg("Sync status")}</p>
|
<p>${msg("Sync status")}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="pf-c-card__body">
|
<div class="pf-c-card__body">${this.renderSyncStatus()}</div>
|
||||||
${this.syncState
|
|
||||||
? html` <ul class="pf-c-list">
|
|
||||||
${this.syncState.messages.map((m) => {
|
|
||||||
return html`<li>${m}</li>`;
|
|
||||||
})}
|
|
||||||
</ul>`
|
|
||||||
: html` ${msg("Sync not run yet.")} `}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="pf-c-card__footer">
|
<div class="pf-c-card__footer">
|
||||||
<ak-action-button
|
<ak-action-button
|
||||||
class="pf-m-secondary"
|
class="pf-m-secondary"
|
||||||
|
|
|
@ -1688,9 +1688,6 @@
|
||||||
<trans-unit id="sc6c575c5ff64cdb1">
|
<trans-unit id="sc6c575c5ff64cdb1">
|
||||||
<source>Update SCIM Provider</source>
|
<source>Update SCIM Provider</source>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s7da38af36522ff6a">
|
|
||||||
<source>Sync not run yet.</source>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="sbecf8dc03c978d15">
|
<trans-unit id="sbecf8dc03c978d15">
|
||||||
<source>Run sync again</source>
|
<source>Run sync again</source>
|
||||||
<target>Synchronisation erneut ausführen</target>
|
<target>Synchronisation erneut ausführen</target>
|
||||||
|
|
|
@ -1779,10 +1779,6 @@
|
||||||
<source>Update SCIM Provider</source>
|
<source>Update SCIM Provider</source>
|
||||||
<target>Update SCIM Provider</target>
|
<target>Update SCIM Provider</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s7da38af36522ff6a">
|
|
||||||
<source>Sync not run yet.</source>
|
|
||||||
<target>Sync not run yet.</target>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="sbecf8dc03c978d15">
|
<trans-unit id="sbecf8dc03c978d15">
|
||||||
<source>Run sync again</source>
|
<source>Run sync again</source>
|
||||||
<target>Run sync again</target>
|
<target>Run sync again</target>
|
||||||
|
|
|
@ -1660,9 +1660,6 @@
|
||||||
<trans-unit id="sc6c575c5ff64cdb1">
|
<trans-unit id="sc6c575c5ff64cdb1">
|
||||||
<source>Update SCIM Provider</source>
|
<source>Update SCIM Provider</source>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s7da38af36522ff6a">
|
|
||||||
<source>Sync not run yet.</source>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="sbecf8dc03c978d15">
|
<trans-unit id="sbecf8dc03c978d15">
|
||||||
<source>Run sync again</source>
|
<source>Run sync again</source>
|
||||||
<target>Vuelve a ejecutar la sincronización</target>
|
<target>Vuelve a ejecutar la sincronización</target>
|
||||||
|
|
|
@ -2216,11 +2216,6 @@ Il y a <x id="0" equiv-text="${ago}"/> jour(s)</target>
|
||||||
<source>Update SCIM Provider</source>
|
<source>Update SCIM Provider</source>
|
||||||
<target>Mettre à jour le fournisseur SCIM</target>
|
<target>Mettre à jour le fournisseur SCIM</target>
|
||||||
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="s7da38af36522ff6a">
|
|
||||||
<source>Sync not run yet.</source>
|
|
||||||
<target>La synchronisation n'a pas encore été lancée.</target>
|
|
||||||
|
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="sbecf8dc03c978d15">
|
<trans-unit id="sbecf8dc03c978d15">
|
||||||
<source>Run sync again</source>
|
<source>Run sync again</source>
|
||||||
|
|
|
@ -1714,9 +1714,6 @@
|
||||||
<trans-unit id="sc6c575c5ff64cdb1">
|
<trans-unit id="sc6c575c5ff64cdb1">
|
||||||
<source>Update SCIM Provider</source>
|
<source>Update SCIM Provider</source>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s7da38af36522ff6a">
|
|
||||||
<source>Sync not run yet.</source>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="sbecf8dc03c978d15">
|
<trans-unit id="sbecf8dc03c978d15">
|
||||||
<source>Run sync again</source>
|
<source>Run sync again</source>
|
||||||
<target>Uruchom ponownie synchronizację</target>
|
<target>Uruchom ponownie synchronizację</target>
|
||||||
|
|
|
@ -2196,11 +2196,6 @@
|
||||||
<source>Update SCIM Provider</source>
|
<source>Update SCIM Provider</source>
|
||||||
<target>Ũƥďàţē ŚĆĨM Ƥŕōvĩďēŕ</target>
|
<target>Ũƥďàţē ŚĆĨM Ƥŕōvĩďēŕ</target>
|
||||||
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="s7da38af36522ff6a">
|
|
||||||
<source>Sync not run yet.</source>
|
|
||||||
<target>Śŷńć ńōţ ŕũń ŷēţ.</target>
|
|
||||||
|
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="sbecf8dc03c978d15">
|
<trans-unit id="sbecf8dc03c978d15">
|
||||||
<source>Run sync again</source>
|
<source>Run sync again</source>
|
||||||
|
|
|
@ -1659,9 +1659,6 @@
|
||||||
<trans-unit id="sc6c575c5ff64cdb1">
|
<trans-unit id="sc6c575c5ff64cdb1">
|
||||||
<source>Update SCIM Provider</source>
|
<source>Update SCIM Provider</source>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s7da38af36522ff6a">
|
|
||||||
<source>Sync not run yet.</source>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="sbecf8dc03c978d15">
|
<trans-unit id="sbecf8dc03c978d15">
|
||||||
<source>Run sync again</source>
|
<source>Run sync again</source>
|
||||||
<target>Eşzamanlamayı tekrar çalıştır</target>
|
<target>Eşzamanlamayı tekrar çalıştır</target>
|
||||||
|
|
|
@ -613,9 +613,9 @@
|
||||||
|
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="saa0e2675da69651b">
|
<trans-unit id="saa0e2675da69651b">
|
||||||
<source>The URL "<x id="0" equiv-text="${this.url}"/>" was not found.</source>
|
<source>The URL "<x id="0" equiv-text="${this.url}"/>" was not found.</source>
|
||||||
<target>未找到 URL "
|
<target>未找到 URL "
|
||||||
<x id="0" equiv-text="${this.url}"/>"。</target>
|
<x id="0" equiv-text="${this.url}"/>"。</target>
|
||||||
|
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s58cd9c2fe836d9c6">
|
<trans-unit id="s58cd9c2fe836d9c6">
|
||||||
|
@ -1057,8 +1057,8 @@
|
||||||
|
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="sa8384c9c26731f83">
|
<trans-unit id="sa8384c9c26731f83">
|
||||||
<source>To allow any redirect URI, set this value to ".*". 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,请将此值设置为 ".*"。请注意这可能带来的安全影响。</target>
|
<target>要允许任何重定向 URI,请将此值设置为 ".*"。请注意这可能带来的安全影响。</target>
|
||||||
|
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s55787f4dfcdce52b">
|
<trans-unit id="s55787f4dfcdce52b">
|
||||||
|
@ -1799,8 +1799,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 "fa-test".</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 图标 "fa-test"。</target>
|
<target>输入完整 URL、相对路径,或者使用 'fa://fa-test' 来使用 Font Awesome 图标 "fa-test"。</target>
|
||||||
|
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s0410779cb47de312">
|
<trans-unit id="s0410779cb47de312">
|
||||||
|
@ -2217,11 +2217,6 @@
|
||||||
<source>Update SCIM Provider</source>
|
<source>Update SCIM Provider</source>
|
||||||
<target>更新 SCIM 提供程序</target>
|
<target>更新 SCIM 提供程序</target>
|
||||||
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="s7da38af36522ff6a">
|
|
||||||
<source>Sync not run yet.</source>
|
|
||||||
<target>尚未同步过。</target>
|
|
||||||
|
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="sbecf8dc03c978d15">
|
<trans-unit id="sbecf8dc03c978d15">
|
||||||
<source>Run sync again</source>
|
<source>Run sync again</source>
|
||||||
|
@ -2988,8 +2983,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 "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>
|
<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>包含组成员的字段。请注意,如果使用 "memberUid" 字段,则假定该值包含相对可分辨名称。例如,'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">
|
||||||
|
@ -3781,8 +3776,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 "minutes=5".</source>
|
<source>When using an external logging solution for archiving, this can be set to "minutes=5".</source>
|
||||||
<target>使用外部日志记录解决方案进行存档时,可以将其设置为 "minutes=5"。</target>
|
<target>使用外部日志记录解决方案进行存档时,可以将其设置为 "minutes=5"。</target>
|
||||||
|
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s44536d20bb5c8257">
|
<trans-unit id="s44536d20bb5c8257">
|
||||||
|
@ -3791,8 +3786,8 @@ doesn't pass when either or both of the selected options are equal or above the
|
||||||
|
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s3bb51cabb02b997e">
|
<trans-unit id="s3bb51cabb02b997e">
|
||||||
<source>Format: "weeks=3;days=2;hours=3,seconds=2".</source>
|
<source>Format: "weeks=3;days=2;hours=3,seconds=2".</source>
|
||||||
<target>格式:"weeks=3;days=2;hours=3,seconds=2"。</target>
|
<target>格式:"weeks=3;days=2;hours=3,seconds=2"。</target>
|
||||||
|
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s04bfd02201db5ab8">
|
<trans-unit id="s04bfd02201db5ab8">
|
||||||
|
@ -3988,10 +3983,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}"/> "<x id="1" equiv-text="${this.obj?.name}"/>"?</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}"/>"
|
<x id="0" equiv-text="${this.objectLabel}"/>"
|
||||||
<x id="1" equiv-text="${this.obj?.name}"/>" 吗?</target>
|
<x id="1" equiv-text="${this.obj?.name}"/>" 吗?</target>
|
||||||
|
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="sc92d7cfb6ee1fec6">
|
<trans-unit id="sc92d7cfb6ee1fec6">
|
||||||
|
@ -5077,7 +5072,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 "roaming" authenticator, like a YubiKey</source>
|
<source>A "roaming" authenticator, like a YubiKey</source>
|
||||||
<target>像 YubiKey 这样的“漫游”身份验证器</target>
|
<target>像 YubiKey 这样的“漫游”身份验证器</target>
|
||||||
|
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
@ -5412,10 +5407,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}"/> ("<x id="1" equiv-text="${prompt.fieldKey}"/>", 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}"/>("
|
<x id="0" equiv-text="${prompt.name}"/>("
|
||||||
<x id="1" equiv-text="${prompt.fieldKey}"/>",类型为
|
<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>
|
||||||
|
@ -5464,7 +5459,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 "stay signed in", 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>
|
||||||
|
@ -7970,7 +7965,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 "<x id="0" equiv-text="${this.targetGroup.name}"/>".</source>
|
<source>This user will be added to the group "<x id="0" equiv-text="${this.targetGroup.name}"/>".</source>
|
||||||
<target>此用户将会被添加到组 &quot;<x id="0" equiv-text="${this.targetGroup.name}"/>&quot;。</target>
|
<target>此用户将会被添加到组 &quot;<x id="0" equiv-text="${this.targetGroup.name}"/>&quot;。</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s62e7f6ed7d9cb3ca">
|
<trans-unit id="s62e7f6ed7d9cb3ca">
|
||||||
|
|
|
@ -1673,9 +1673,6 @@
|
||||||
<trans-unit id="sc6c575c5ff64cdb1">
|
<trans-unit id="sc6c575c5ff64cdb1">
|
||||||
<source>Update SCIM Provider</source>
|
<source>Update SCIM Provider</source>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s7da38af36522ff6a">
|
|
||||||
<source>Sync not run yet.</source>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="sbecf8dc03c978d15">
|
<trans-unit id="sbecf8dc03c978d15">
|
||||||
<source>Run sync again</source>
|
<source>Run sync again</source>
|
||||||
<target>再次运行同步</target>
|
<target>再次运行同步</target>
|
||||||
|
|
|
@ -2198,11 +2198,6 @@
|
||||||
<source>Update SCIM Provider</source>
|
<source>Update SCIM Provider</source>
|
||||||
<target>更新 SCIM 供應商</target>
|
<target>更新 SCIM 供應商</target>
|
||||||
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="s7da38af36522ff6a">
|
|
||||||
<source>Sync not run yet.</source>
|
|
||||||
<target>尚未執行同步。</target>
|
|
||||||
|
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="sbecf8dc03c978d15">
|
<trans-unit id="sbecf8dc03c978d15">
|
||||||
<source>Run sync again</source>
|
<source>Run sync again</source>
|
||||||
|
|
Reference in New Issue