providers/scim: add API endpoint to sync single user (#8486)
* add api Signed-off-by: Jens Langhammer <jens@goauthentik.io> * add UI Signed-off-by: Jens Langhammer <jens@goauthentik.io> * format Signed-off-by: Jens Langhammer <jens@goauthentik.io> --------- Signed-off-by: Jens Langhammer <jens@goauthentik.io>
This commit is contained in:
@ -6,7 +6,10 @@ 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.enterprise.api import EnterpriseRequiredMixin
|
from authentik.enterprise.api import EnterpriseRequiredMixin
|
||||||
from authentik.enterprise.providers.google_workspace.models import GoogleWorkspaceProvider
|
from authentik.enterprise.providers.google_workspace.models import GoogleWorkspaceProvider
|
||||||
from authentik.enterprise.providers.google_workspace.tasks import google_workspace_sync
|
from authentik.enterprise.providers.google_workspace.tasks import (
|
||||||
|
google_workspace_sync,
|
||||||
|
google_workspace_sync_objects,
|
||||||
|
)
|
||||||
from authentik.lib.sync.outgoing.api import OutgoingSyncProviderStatusMixin
|
from authentik.lib.sync.outgoing.api import OutgoingSyncProviderStatusMixin
|
||||||
|
|
||||||
|
|
||||||
@ -52,3 +55,4 @@ class GoogleWorkspaceProviderViewSet(OutgoingSyncProviderStatusMixin, UsedByMixi
|
|||||||
search_fields = ["name"]
|
search_fields = ["name"]
|
||||||
ordering = ["name"]
|
ordering = ["name"]
|
||||||
sync_single_task = google_workspace_sync
|
sync_single_task = google_workspace_sync
|
||||||
|
sync_objects_task = google_workspace_sync_objects
|
||||||
|
@ -6,7 +6,10 @@ 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.enterprise.api import EnterpriseRequiredMixin
|
from authentik.enterprise.api import EnterpriseRequiredMixin
|
||||||
from authentik.enterprise.providers.microsoft_entra.models import MicrosoftEntraProvider
|
from authentik.enterprise.providers.microsoft_entra.models import MicrosoftEntraProvider
|
||||||
from authentik.enterprise.providers.microsoft_entra.tasks import microsoft_entra_sync
|
from authentik.enterprise.providers.microsoft_entra.tasks import (
|
||||||
|
microsoft_entra_sync,
|
||||||
|
microsoft_entra_sync_objects,
|
||||||
|
)
|
||||||
from authentik.lib.sync.outgoing.api import OutgoingSyncProviderStatusMixin
|
from authentik.lib.sync.outgoing.api import OutgoingSyncProviderStatusMixin
|
||||||
|
|
||||||
|
|
||||||
@ -50,3 +53,4 @@ class MicrosoftEntraProviderViewSet(OutgoingSyncProviderStatusMixin, UsedByMixin
|
|||||||
search_fields = ["name"]
|
search_fields = ["name"]
|
||||||
ordering = ["name"]
|
ordering = ["name"]
|
||||||
sync_single_task = microsoft_entra_sync
|
sync_single_task = microsoft_entra_sync
|
||||||
|
sync_objects_task = microsoft_entra_sync_objects
|
||||||
|
@ -1,16 +1,19 @@
|
|||||||
from collections.abc import Callable
|
from celery import Task
|
||||||
|
|
||||||
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 guardian.shortcuts import get_objects_for_user
|
from guardian.shortcuts import get_objects_for_user
|
||||||
from rest_framework.decorators import action
|
from rest_framework.decorators import action
|
||||||
from rest_framework.fields import BooleanField
|
from rest_framework.fields import BooleanField, CharField, ChoiceField
|
||||||
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 authentik.core.api.utils import ModelSerializer, PassiveSerializer
|
from authentik.core.api.utils import ModelSerializer, PassiveSerializer
|
||||||
|
from authentik.core.models import Group, User
|
||||||
from authentik.events.api.tasks import SystemTaskSerializer
|
from authentik.events.api.tasks import SystemTaskSerializer
|
||||||
|
from authentik.events.logs import LogEvent, LogEventSerializer
|
||||||
from authentik.lib.sync.outgoing.models import OutgoingSyncProvider
|
from authentik.lib.sync.outgoing.models import OutgoingSyncProvider
|
||||||
|
from authentik.lib.utils.reflection import class_to_path
|
||||||
|
from authentik.rbac.filters import ObjectFilter
|
||||||
|
|
||||||
|
|
||||||
class SyncStatusSerializer(PassiveSerializer):
|
class SyncStatusSerializer(PassiveSerializer):
|
||||||
@ -20,10 +23,29 @@ class SyncStatusSerializer(PassiveSerializer):
|
|||||||
tasks = SystemTaskSerializer(many=True, read_only=True)
|
tasks = SystemTaskSerializer(many=True, read_only=True)
|
||||||
|
|
||||||
|
|
||||||
|
class SyncObjectSerializer(PassiveSerializer):
|
||||||
|
"""Sync object serializer"""
|
||||||
|
|
||||||
|
sync_object_model = ChoiceField(
|
||||||
|
choices=(
|
||||||
|
(class_to_path(User), "user"),
|
||||||
|
(class_to_path(Group), "group"),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
sync_object_id = CharField()
|
||||||
|
|
||||||
|
|
||||||
|
class SyncObjectResultSerializer(PassiveSerializer):
|
||||||
|
"""Result of a single object sync"""
|
||||||
|
|
||||||
|
messages = LogEventSerializer(many=True, read_only=True)
|
||||||
|
|
||||||
|
|
||||||
class OutgoingSyncProviderStatusMixin:
|
class OutgoingSyncProviderStatusMixin:
|
||||||
"""Common API Endpoints for Outgoing sync providers"""
|
"""Common API Endpoints for Outgoing sync providers"""
|
||||||
|
|
||||||
sync_single_task: Callable = None
|
sync_single_task: type[Task] = None
|
||||||
|
sync_objects_task: type[Task] = None
|
||||||
|
|
||||||
@extend_schema(
|
@extend_schema(
|
||||||
responses={
|
responses={
|
||||||
@ -36,7 +58,7 @@ class OutgoingSyncProviderStatusMixin:
|
|||||||
detail=True,
|
detail=True,
|
||||||
pagination_class=None,
|
pagination_class=None,
|
||||||
url_path="sync/status",
|
url_path="sync/status",
|
||||||
filter_backends=[],
|
filter_backends=[ObjectFilter],
|
||||||
)
|
)
|
||||||
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"""
|
||||||
@ -55,6 +77,30 @@ class OutgoingSyncProviderStatusMixin:
|
|||||||
}
|
}
|
||||||
return Response(SyncStatusSerializer(status).data)
|
return Response(SyncStatusSerializer(status).data)
|
||||||
|
|
||||||
|
@extend_schema(
|
||||||
|
request=SyncObjectSerializer,
|
||||||
|
responses={200: SyncObjectResultSerializer()},
|
||||||
|
)
|
||||||
|
@action(
|
||||||
|
methods=["POST"],
|
||||||
|
detail=True,
|
||||||
|
pagination_class=None,
|
||||||
|
url_path="sync/object",
|
||||||
|
filter_backends=[ObjectFilter],
|
||||||
|
)
|
||||||
|
def sync_object(self, request: Request, pk: int) -> Response:
|
||||||
|
"""Sync/Re-sync a single user/group object"""
|
||||||
|
provider: OutgoingSyncProvider = self.get_object()
|
||||||
|
params = SyncObjectSerializer(data=request.data)
|
||||||
|
params.is_valid(raise_exception=True)
|
||||||
|
res: list[LogEvent] = self.sync_objects_task.delay(
|
||||||
|
params.validated_data["sync_object_model"],
|
||||||
|
page=1,
|
||||||
|
provider_pk=provider.pk,
|
||||||
|
pk=params.validated_data["sync_object_id"],
|
||||||
|
).get()
|
||||||
|
return Response(SyncObjectResultSerializer(instance={"messages": res}).data)
|
||||||
|
|
||||||
|
|
||||||
class OutgoingSyncConnectionCreateMixin:
|
class OutgoingSyncConnectionCreateMixin:
|
||||||
"""Mixin for connection objects that fetches remote data upon creation"""
|
"""Mixin for connection objects that fetches remote data upon creation"""
|
||||||
|
@ -105,7 +105,7 @@ class SyncTasks:
|
|||||||
return
|
return
|
||||||
task.set_status(TaskStatus.SUCCESSFUL, *messages)
|
task.set_status(TaskStatus.SUCCESSFUL, *messages)
|
||||||
|
|
||||||
def sync_objects(self, object_type: str, page: int, provider_pk: int):
|
def sync_objects(self, object_type: str, page: int, provider_pk: int, **filter):
|
||||||
_object_type = path_to_class(object_type)
|
_object_type = 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),
|
||||||
@ -120,7 +120,7 @@ class SyncTasks:
|
|||||||
client = provider.client_for_model(_object_type)
|
client = provider.client_for_model(_object_type)
|
||||||
except TransientSyncException:
|
except TransientSyncException:
|
||||||
return messages
|
return messages
|
||||||
paginator = Paginator(provider.get_object_qs(_object_type), PAGE_SIZE)
|
paginator = Paginator(provider.get_object_qs(_object_type).filter(**filter), PAGE_SIZE)
|
||||||
if client.can_discover:
|
if client.can_discover:
|
||||||
self.logger.debug("starting discover")
|
self.logger.debug("starting discover")
|
||||||
client.discover()
|
client.discover()
|
||||||
|
@ -6,7 +6,7 @@ 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.lib.sync.outgoing.api import OutgoingSyncProviderStatusMixin
|
from authentik.lib.sync.outgoing.api import OutgoingSyncProviderStatusMixin
|
||||||
from authentik.providers.scim.models import SCIMProvider
|
from authentik.providers.scim.models import SCIMProvider
|
||||||
from authentik.providers.scim.tasks import scim_sync
|
from authentik.providers.scim.tasks import scim_sync, scim_sync_objects
|
||||||
|
|
||||||
|
|
||||||
class SCIMProviderSerializer(ProviderSerializer):
|
class SCIMProviderSerializer(ProviderSerializer):
|
||||||
@ -42,3 +42,4 @@ class SCIMProviderViewSet(OutgoingSyncProviderStatusMixin, UsedByMixin, ModelVie
|
|||||||
search_fields = ["name", "url"]
|
search_fields = ["name", "url"]
|
||||||
ordering = ["name", "url"]
|
ordering = ["name", "url"]
|
||||||
sync_single_task = scim_sync
|
sync_single_task = scim_sync
|
||||||
|
sync_objects_task = scim_sync_objects
|
||||||
|
148
schema.yml
148
schema.yml
@ -17853,6 +17853,46 @@ paths:
|
|||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/GenericError'
|
$ref: '#/components/schemas/GenericError'
|
||||||
description: ''
|
description: ''
|
||||||
|
/providers/google_workspace/{id}/sync/object/:
|
||||||
|
post:
|
||||||
|
operationId: providers_google_workspace_sync_object_create
|
||||||
|
description: Sync/Re-sync a single user/group object
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: id
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
description: A unique integer value identifying this Google Workspace Provider.
|
||||||
|
required: true
|
||||||
|
tags:
|
||||||
|
- providers
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/SyncObjectRequest'
|
||||||
|
required: true
|
||||||
|
security:
|
||||||
|
- authentik: []
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/SyncObjectResult'
|
||||||
|
description: ''
|
||||||
|
'400':
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/ValidationError'
|
||||||
|
description: ''
|
||||||
|
'403':
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/GenericError'
|
||||||
|
description: ''
|
||||||
/providers/google_workspace/{id}/sync/status/:
|
/providers/google_workspace/{id}/sync/status/:
|
||||||
get:
|
get:
|
||||||
operationId: providers_google_workspace_sync_status_retrieve
|
operationId: providers_google_workspace_sync_status_retrieve
|
||||||
@ -18856,6 +18896,46 @@ paths:
|
|||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/GenericError'
|
$ref: '#/components/schemas/GenericError'
|
||||||
description: ''
|
description: ''
|
||||||
|
/providers/microsoft_entra/{id}/sync/object/:
|
||||||
|
post:
|
||||||
|
operationId: providers_microsoft_entra_sync_object_create
|
||||||
|
description: Sync/Re-sync a single user/group object
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: id
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
description: A unique integer value identifying this Microsoft Entra Provider.
|
||||||
|
required: true
|
||||||
|
tags:
|
||||||
|
- providers
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/SyncObjectRequest'
|
||||||
|
required: true
|
||||||
|
security:
|
||||||
|
- authentik: []
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/SyncObjectResult'
|
||||||
|
description: ''
|
||||||
|
'400':
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/ValidationError'
|
||||||
|
description: ''
|
||||||
|
'403':
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/GenericError'
|
||||||
|
description: ''
|
||||||
/providers/microsoft_entra/{id}/sync/status/:
|
/providers/microsoft_entra/{id}/sync/status/:
|
||||||
get:
|
get:
|
||||||
operationId: providers_microsoft_entra_sync_status_retrieve
|
operationId: providers_microsoft_entra_sync_status_retrieve
|
||||||
@ -21346,6 +21426,46 @@ paths:
|
|||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/GenericError'
|
$ref: '#/components/schemas/GenericError'
|
||||||
description: ''
|
description: ''
|
||||||
|
/providers/scim/{id}/sync/object/:
|
||||||
|
post:
|
||||||
|
operationId: providers_scim_sync_object_create
|
||||||
|
description: Sync/Re-sync a single user/group object
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: id
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
description: A unique integer value identifying this SCIM Provider.
|
||||||
|
required: true
|
||||||
|
tags:
|
||||||
|
- providers
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/SyncObjectRequest'
|
||||||
|
required: true
|
||||||
|
security:
|
||||||
|
- authentik: []
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/SyncObjectResult'
|
||||||
|
description: ''
|
||||||
|
'400':
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/ValidationError'
|
||||||
|
description: ''
|
||||||
|
'403':
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/GenericError'
|
||||||
|
description: ''
|
||||||
/providers/scim/{id}/sync/status/:
|
/providers/scim/{id}/sync/status/:
|
||||||
get:
|
get:
|
||||||
operationId: providers_scim_sync_status_retrieve
|
operationId: providers_scim_sync_status_retrieve
|
||||||
@ -51354,6 +51474,34 @@ components:
|
|||||||
- user_email
|
- user_email
|
||||||
- user_upn
|
- user_upn
|
||||||
type: string
|
type: string
|
||||||
|
SyncObjectModelEnum:
|
||||||
|
enum:
|
||||||
|
- authentik.core.models.User
|
||||||
|
- authentik.core.models.Group
|
||||||
|
type: string
|
||||||
|
SyncObjectRequest:
|
||||||
|
type: object
|
||||||
|
description: Sync object serializer
|
||||||
|
properties:
|
||||||
|
sync_object_model:
|
||||||
|
$ref: '#/components/schemas/SyncObjectModelEnum'
|
||||||
|
sync_object_id:
|
||||||
|
type: string
|
||||||
|
minLength: 1
|
||||||
|
required:
|
||||||
|
- sync_object_id
|
||||||
|
- sync_object_model
|
||||||
|
SyncObjectResult:
|
||||||
|
type: object
|
||||||
|
description: Result of a single object sync
|
||||||
|
properties:
|
||||||
|
messages:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/LogEvent'
|
||||||
|
readOnly: true
|
||||||
|
required:
|
||||||
|
- messages
|
||||||
SyncStatus:
|
SyncStatus:
|
||||||
type: object
|
type: object
|
||||||
description: Provider sync status
|
description: Provider sync status
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||||
import "@goauthentik/elements/forms/DeleteBulkForm";
|
import "@goauthentik/elements/forms/DeleteBulkForm";
|
||||||
|
import "@goauthentik/elements/forms/ModalForm";
|
||||||
|
import "@goauthentik/elements/sync/SyncObjectForm";
|
||||||
import { PaginatedResponse, Table, TableColumn } from "@goauthentik/elements/table/Table";
|
import { PaginatedResponse, Table, TableColumn } from "@goauthentik/elements/table/Table";
|
||||||
|
|
||||||
import { msg } from "@lit/localize";
|
import { msg } from "@lit/localize";
|
||||||
import { TemplateResult, html } from "lit";
|
import { TemplateResult, html } from "lit";
|
||||||
import { customElement, property } from "lit/decorators.js";
|
import { customElement, property } from "lit/decorators.js";
|
||||||
|
|
||||||
import { GoogleWorkspaceProviderGroup, ProvidersApi } from "@goauthentik/api";
|
import { GoogleWorkspaceProviderGroup, ProvidersApi, SyncObjectModelEnum } from "@goauthentik/api";
|
||||||
|
|
||||||
@customElement("ak-provider-google-workspace-groups-list")
|
@customElement("ak-provider-google-workspace-groups-list")
|
||||||
export class GoogleWorkspaceProviderGroupList extends Table<GoogleWorkspaceProviderGroup> {
|
export class GoogleWorkspaceProviderGroupList extends Table<GoogleWorkspaceProviderGroup> {
|
||||||
@ -22,6 +24,23 @@ export class GoogleWorkspaceProviderGroupList extends Table<GoogleWorkspaceProvi
|
|||||||
checkbox = true;
|
checkbox = true;
|
||||||
clearOnRefresh = true;
|
clearOnRefresh = true;
|
||||||
|
|
||||||
|
renderToolbar(): TemplateResult {
|
||||||
|
return html`<ak-forms-modal cancelText=${msg("Close")} ?closeAfterSuccessfulSubmit=${false}>
|
||||||
|
<span slot="submit">${msg("Sync")}</span>
|
||||||
|
<span slot="header">${msg("Sync User")}</span>
|
||||||
|
<ak-sync-object-form
|
||||||
|
.provider=${this.providerId}
|
||||||
|
model=${SyncObjectModelEnum.Group}
|
||||||
|
.sync=${new ProvidersApi(DEFAULT_CONFIG)
|
||||||
|
.providersGoogleWorkspaceSyncObjectCreate}
|
||||||
|
slot="form"
|
||||||
|
>
|
||||||
|
</ak-sync-object-form>
|
||||||
|
<button slot="trigger" class="pf-c-button pf-m-primary">${msg("Sync")}</button>
|
||||||
|
</ak-forms-modal>
|
||||||
|
${super.renderToolbar()}`;
|
||||||
|
}
|
||||||
|
|
||||||
renderToolbarSelected(): TemplateResult {
|
renderToolbarSelected(): TemplateResult {
|
||||||
const disabled = this.selectedElements.length < 1;
|
const disabled = this.selectedElements.length < 1;
|
||||||
return html`<ak-forms-delete-bulk
|
return html`<ak-forms-delete-bulk
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||||
import "@goauthentik/elements/forms/DeleteBulkForm";
|
import "@goauthentik/elements/forms/DeleteBulkForm";
|
||||||
|
import "@goauthentik/elements/forms/ModalForm";
|
||||||
|
import "@goauthentik/elements/sync/SyncObjectForm";
|
||||||
import { PaginatedResponse, Table, TableColumn } from "@goauthentik/elements/table/Table";
|
import { PaginatedResponse, Table, TableColumn } from "@goauthentik/elements/table/Table";
|
||||||
|
|
||||||
import { msg } from "@lit/localize";
|
import { msg } from "@lit/localize";
|
||||||
import { TemplateResult, html } from "lit";
|
import { TemplateResult, html } from "lit";
|
||||||
import { customElement, property } from "lit/decorators.js";
|
import { customElement, property } from "lit/decorators.js";
|
||||||
|
|
||||||
import { GoogleWorkspaceProviderUser, ProvidersApi } from "@goauthentik/api";
|
import { GoogleWorkspaceProviderUser, ProvidersApi, SyncObjectModelEnum } from "@goauthentik/api";
|
||||||
|
|
||||||
@customElement("ak-provider-google-workspace-users-list")
|
@customElement("ak-provider-google-workspace-users-list")
|
||||||
export class GoogleWorkspaceProviderUserList extends Table<GoogleWorkspaceProviderUser> {
|
export class GoogleWorkspaceProviderUserList extends Table<GoogleWorkspaceProviderUser> {
|
||||||
@ -22,6 +24,23 @@ export class GoogleWorkspaceProviderUserList extends Table<GoogleWorkspaceProvid
|
|||||||
checkbox = true;
|
checkbox = true;
|
||||||
clearOnRefresh = true;
|
clearOnRefresh = true;
|
||||||
|
|
||||||
|
renderToolbar(): TemplateResult {
|
||||||
|
return html`<ak-forms-modal cancelText=${msg("Close")} ?closeAfterSuccessfulSubmit=${false}>
|
||||||
|
<span slot="submit">${msg("Sync")}</span>
|
||||||
|
<span slot="header">${msg("Sync User")}</span>
|
||||||
|
<ak-sync-object-form
|
||||||
|
.provider=${this.providerId}
|
||||||
|
model=${SyncObjectModelEnum.User}
|
||||||
|
.sync=${new ProvidersApi(DEFAULT_CONFIG)
|
||||||
|
.providersGoogleWorkspaceSyncObjectCreate}
|
||||||
|
slot="form"
|
||||||
|
>
|
||||||
|
</ak-sync-object-form>
|
||||||
|
<button slot="trigger" class="pf-c-button pf-m-primary">${msg("Sync")}</button>
|
||||||
|
</ak-forms-modal>
|
||||||
|
${super.renderToolbar()}`;
|
||||||
|
}
|
||||||
|
|
||||||
renderToolbarSelected(): TemplateResult {
|
renderToolbarSelected(): TemplateResult {
|
||||||
const disabled = this.selectedElements.length < 1;
|
const disabled = this.selectedElements.length < 1;
|
||||||
return html`<ak-forms-delete-bulk
|
return html`<ak-forms-delete-bulk
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||||
import "@goauthentik/elements/forms/DeleteBulkForm";
|
import "@goauthentik/elements/forms/DeleteBulkForm";
|
||||||
|
import "@goauthentik/elements/forms/ModalForm";
|
||||||
|
import "@goauthentik/elements/sync/SyncObjectForm";
|
||||||
import { PaginatedResponse, Table, TableColumn } from "@goauthentik/elements/table/Table";
|
import { PaginatedResponse, Table, TableColumn } from "@goauthentik/elements/table/Table";
|
||||||
|
|
||||||
import { msg } from "@lit/localize";
|
import { msg } from "@lit/localize";
|
||||||
import { TemplateResult, html } from "lit";
|
import { TemplateResult, html } from "lit";
|
||||||
import { customElement, property } from "lit/decorators.js";
|
import { customElement, property } from "lit/decorators.js";
|
||||||
|
|
||||||
import { MicrosoftEntraProviderGroup, ProvidersApi } from "@goauthentik/api";
|
import { MicrosoftEntraProviderGroup, ProvidersApi, SyncObjectModelEnum } from "@goauthentik/api";
|
||||||
|
|
||||||
@customElement("ak-provider-microsoft-entra-groups-list")
|
@customElement("ak-provider-microsoft-entra-groups-list")
|
||||||
export class MicrosoftEntraProviderGroupList extends Table<MicrosoftEntraProviderGroup> {
|
export class MicrosoftEntraProviderGroupList extends Table<MicrosoftEntraProviderGroup> {
|
||||||
@ -19,6 +21,23 @@ export class MicrosoftEntraProviderGroupList extends Table<MicrosoftEntraProvide
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderToolbar(): TemplateResult {
|
||||||
|
return html`<ak-forms-modal cancelText=${msg("Close")} ?closeAfterSuccessfulSubmit=${false}>
|
||||||
|
<span slot="submit">${msg("Sync")}</span>
|
||||||
|
<span slot="header">${msg("Sync User")}</span>
|
||||||
|
<ak-sync-object-form
|
||||||
|
.provider=${this.providerId}
|
||||||
|
model=${SyncObjectModelEnum.Group}
|
||||||
|
.sync=${new ProvidersApi(DEFAULT_CONFIG)
|
||||||
|
.providersMicrosoftEntraSyncObjectCreate}
|
||||||
|
slot="form"
|
||||||
|
>
|
||||||
|
</ak-sync-object-form>
|
||||||
|
<button slot="trigger" class="pf-c-button pf-m-primary">${msg("Sync")}</button>
|
||||||
|
</ak-forms-modal>
|
||||||
|
${super.renderToolbar()}`;
|
||||||
|
}
|
||||||
|
|
||||||
renderToolbarSelected(): TemplateResult {
|
renderToolbarSelected(): TemplateResult {
|
||||||
const disabled = this.selectedElements.length < 1;
|
const disabled = this.selectedElements.length < 1;
|
||||||
return html`<ak-forms-delete-bulk
|
return html`<ak-forms-delete-bulk
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||||
import "@goauthentik/elements/forms/DeleteBulkForm";
|
import "@goauthentik/elements/forms/DeleteBulkForm";
|
||||||
|
import "@goauthentik/elements/forms/ModalForm";
|
||||||
|
import "@goauthentik/elements/sync/SyncObjectForm";
|
||||||
import { PaginatedResponse, Table, TableColumn } from "@goauthentik/elements/table/Table";
|
import { PaginatedResponse, Table, TableColumn } from "@goauthentik/elements/table/Table";
|
||||||
|
|
||||||
import { msg } from "@lit/localize";
|
import { msg } from "@lit/localize";
|
||||||
import { TemplateResult, html } from "lit";
|
import { TemplateResult, html } from "lit";
|
||||||
import { customElement, property } from "lit/decorators.js";
|
import { customElement, property } from "lit/decorators.js";
|
||||||
|
|
||||||
import { MicrosoftEntraProviderUser, ProvidersApi } from "@goauthentik/api";
|
import { MicrosoftEntraProviderUser, ProvidersApi, SyncObjectModelEnum } from "@goauthentik/api";
|
||||||
|
|
||||||
@customElement("ak-provider-microsoft-entra-users-list")
|
@customElement("ak-provider-microsoft-entra-users-list")
|
||||||
export class MicrosoftEntraProviderUserList extends Table<MicrosoftEntraProviderUser> {
|
export class MicrosoftEntraProviderUserList extends Table<MicrosoftEntraProviderUser> {
|
||||||
@ -22,6 +24,23 @@ export class MicrosoftEntraProviderUserList extends Table<MicrosoftEntraProvider
|
|||||||
checkbox = true;
|
checkbox = true;
|
||||||
clearOnRefresh = true;
|
clearOnRefresh = true;
|
||||||
|
|
||||||
|
renderToolbar(): TemplateResult {
|
||||||
|
return html`<ak-forms-modal cancelText=${msg("Close")} ?closeAfterSuccessfulSubmit=${false}>
|
||||||
|
<span slot="submit">${msg("Sync")}</span>
|
||||||
|
<span slot="header">${msg("Sync User")}</span>
|
||||||
|
<ak-sync-object-form
|
||||||
|
.provider=${this.providerId}
|
||||||
|
model=${SyncObjectModelEnum.User}
|
||||||
|
.sync=${new ProvidersApi(DEFAULT_CONFIG)
|
||||||
|
.providersMicrosoftEntraSyncObjectCreate}
|
||||||
|
slot="form"
|
||||||
|
>
|
||||||
|
</ak-sync-object-form>
|
||||||
|
<button slot="trigger" class="pf-c-button pf-m-primary">${msg("Sync")}</button>
|
||||||
|
</ak-forms-modal>
|
||||||
|
${super.renderToolbar()}`;
|
||||||
|
}
|
||||||
|
|
||||||
renderToolbarSelected(): TemplateResult {
|
renderToolbarSelected(): TemplateResult {
|
||||||
const disabled = this.selectedElements.length < 1;
|
const disabled = this.selectedElements.length < 1;
|
||||||
return html`<ak-forms-delete-bulk
|
return html`<ak-forms-delete-bulk
|
||||||
|
131
web/src/elements/sync/SyncObjectForm.ts
Normal file
131
web/src/elements/sync/SyncObjectForm.ts
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
import "@goauthentik/admin/common/ak-flow-search/ak-flow-search-no-default";
|
||||||
|
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||||
|
import { Form } from "@goauthentik/elements/forms/Form";
|
||||||
|
import "@goauthentik/elements/forms/HorizontalFormElement";
|
||||||
|
import "@goauthentik/elements/forms/SearchSelect";
|
||||||
|
|
||||||
|
import { msg } from "@lit/localize";
|
||||||
|
import { TemplateResult, html, nothing } from "lit";
|
||||||
|
import { customElement, property } from "lit/decorators.js";
|
||||||
|
|
||||||
|
import {
|
||||||
|
CoreApi,
|
||||||
|
CoreGroupsListRequest,
|
||||||
|
CoreUsersListRequest,
|
||||||
|
Group,
|
||||||
|
InitOverrideFunction,
|
||||||
|
SyncObjectModelEnum,
|
||||||
|
SyncObjectRequest,
|
||||||
|
SyncObjectResult,
|
||||||
|
User,
|
||||||
|
} from "@goauthentik/api";
|
||||||
|
|
||||||
|
@customElement("ak-sync-object-form")
|
||||||
|
export class SyncObjectForm extends Form<SyncObjectRequest> {
|
||||||
|
@property({ type: Number })
|
||||||
|
provider?: number;
|
||||||
|
|
||||||
|
@property()
|
||||||
|
model: SyncObjectModelEnum = SyncObjectModelEnum.UnknownDefaultOpenApi;
|
||||||
|
|
||||||
|
@property({ attribute: false })
|
||||||
|
result?: SyncObjectResult;
|
||||||
|
|
||||||
|
@property({ attribute: false })
|
||||||
|
sync: (
|
||||||
|
requestParameters: {
|
||||||
|
id: number;
|
||||||
|
syncObjectRequest: SyncObjectRequest;
|
||||||
|
},
|
||||||
|
initOverrides?: RequestInit | InitOverrideFunction,
|
||||||
|
) => Promise<SyncObjectResult> = (_, __) => {
|
||||||
|
return Promise.reject();
|
||||||
|
};
|
||||||
|
|
||||||
|
getSuccessMessage(): string {
|
||||||
|
return msg("Successfully triggered sync.");
|
||||||
|
}
|
||||||
|
|
||||||
|
async send(data: SyncObjectRequest): Promise<void> {
|
||||||
|
data.syncObjectModel = this.model;
|
||||||
|
this.result = await this.sync({
|
||||||
|
id: this.provider || 0,
|
||||||
|
syncObjectRequest: data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
renderSelectUser() {
|
||||||
|
return html`<ak-form-element-horizontal label=${msg("User")} name="syncObjectId">
|
||||||
|
<ak-search-select
|
||||||
|
.fetchObjects=${async (query?: string): Promise<User[]> => {
|
||||||
|
const args: CoreUsersListRequest = {
|
||||||
|
ordering: "username",
|
||||||
|
};
|
||||||
|
if (query !== undefined) {
|
||||||
|
args.search = query;
|
||||||
|
}
|
||||||
|
const users = await new CoreApi(DEFAULT_CONFIG).coreUsersList(args);
|
||||||
|
return users.results;
|
||||||
|
}}
|
||||||
|
.renderElement=${(user: User): string => {
|
||||||
|
return user.username;
|
||||||
|
}}
|
||||||
|
.renderDescription=${(user: User): TemplateResult => {
|
||||||
|
return html`${user.name}`;
|
||||||
|
}}
|
||||||
|
.value=${(user: User | undefined): number | undefined => {
|
||||||
|
return user?.pk;
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
</ak-search-select>
|
||||||
|
</ak-form-element-horizontal>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
renderSelectGroup() {
|
||||||
|
return html` <ak-form-element-horizontal label=${msg("Group")} name="syncObjectId">
|
||||||
|
<ak-search-select
|
||||||
|
.fetchObjects=${async (query?: string): Promise<Group[]> => {
|
||||||
|
const args: CoreGroupsListRequest = {
|
||||||
|
ordering: "name",
|
||||||
|
};
|
||||||
|
if (query !== undefined) {
|
||||||
|
args.search = query;
|
||||||
|
}
|
||||||
|
const groups = await new CoreApi(DEFAULT_CONFIG).coreGroupsList(args);
|
||||||
|
return groups.results;
|
||||||
|
}}
|
||||||
|
.renderElement=${(group: Group): string => {
|
||||||
|
return group.name;
|
||||||
|
}}
|
||||||
|
.value=${(group: Group | undefined): string | undefined => {
|
||||||
|
return group?.pk;
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
</ak-search-select>
|
||||||
|
</ak-form-element-horizontal>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
renderResult(): TemplateResult {
|
||||||
|
return html`<ak-form-element-horizontal label=${msg("Log messages")}>
|
||||||
|
<div class="pf-c-form__group-label">
|
||||||
|
<div class="c-form__horizontal-group">
|
||||||
|
<dl class="pf-c-description-list pf-m-horizontal">
|
||||||
|
<ak-log-viewer .logs=${this.result?.messages}></ak-log-viewer>
|
||||||
|
</dl>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ak-form-element-horizontal> `;
|
||||||
|
}
|
||||||
|
|
||||||
|
renderForm() {
|
||||||
|
return html` ${this.model === SyncObjectModelEnum.User ? this.renderSelectUser() : nothing}
|
||||||
|
${this.model === SyncObjectModelEnum.Group ? this.renderSelectGroup() : nothing}
|
||||||
|
${this.result ? this.renderResult() : html``}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ak-sync-object-form": SyncObjectForm;
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user