Compare commits
	
		
			9 Commits
		
	
	
		
			tests/e2e/
			...
			user-direc
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| f97f4a902c | |||
| de8da15293 | |||
| cd7d96cc58 | |||
| dcaa41716b | |||
| 910b430d25 | |||
| 832c00c155 | |||
| 399fa0120c | |||
| 3de3c98ed8 | |||
| 92911d1d0f | 
							
								
								
									
										99
									
								
								authentik/core/api/user_directory.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										99
									
								
								authentik/core/api/user_directory.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,99 @@ | ||||
| """User directory API Views""" | ||||
|  | ||||
| from typing import Any | ||||
|  | ||||
| from drf_spectacular.utils import extend_schema, inline_serializer | ||||
| from guardian.shortcuts import get_anonymous_user | ||||
| from rest_framework.decorators import action | ||||
| from rest_framework.fields import SerializerMethodField | ||||
| from rest_framework.serializers import CharField, DictField, ListField, ModelSerializer | ||||
| from rest_framework.views import Request, Response | ||||
| from rest_framework.viewsets import ReadOnlyModelViewSet | ||||
| from structlog.stdlib import get_logger | ||||
|  | ||||
| from authentik.core.models import User | ||||
| from authentik.rbac.permissions import HasPermission | ||||
| from authentik.tenants.utils import get_current_tenant | ||||
|  | ||||
| LOGGER = get_logger() | ||||
|  | ||||
|  | ||||
| class UserDirectorySerializer(ModelSerializer): | ||||
|     """User Directory Serializer""" | ||||
|  | ||||
|     user_fields = SerializerMethodField() | ||||
|     attributes = SerializerMethodField() | ||||
|  | ||||
|     class Meta: | ||||
|         model = User | ||||
|         fields = [ | ||||
|             "pk", | ||||
|             "user_fields", | ||||
|             "attributes", | ||||
|         ] | ||||
|  | ||||
|     def get_user_fields(self, obj: User) -> dict[str, Any]: | ||||
|         """Get directory fields""" | ||||
|         fields = {} | ||||
|         user_directory_fields = get_current_tenant().user_directory_fields | ||||
|         for f in ("name", "username", "email", "avatar"): | ||||
|             if f in user_directory_fields: | ||||
|                 fields[f] = getattr(obj, f) | ||||
|         if "groups" in user_directory_fields: | ||||
|             fields["groups"] = [g.name for g in obj.all_groups().order_by("name")] | ||||
|         return fields | ||||
|  | ||||
|     def get_attributes(self, obj: User) -> dict[str, Any]: | ||||
|         """Get directory attributes""" | ||||
|         attributes = {} | ||||
|         for field in get_current_tenant().user_directory_attributes: | ||||
|             path = field.get("attribute", None) | ||||
|             if path is not None: | ||||
|                 attributes[path] = obj.attributes.get(path, None) | ||||
|         return attributes | ||||
|  | ||||
|  | ||||
| class UserDirectoryViewSet(ReadOnlyModelViewSet): | ||||
|     """User Directory Viewset""" | ||||
|  | ||||
|     queryset = User.objects.none() | ||||
|     ordering = ["username"] | ||||
|     ordering_fields = ["username", "email", "name"] | ||||
|     serializer_class = UserDirectorySerializer | ||||
|     permission_classes = [HasPermission("authentik_rbac.view_user_directory")] | ||||
|  | ||||
|     def get_queryset(self): | ||||
|         return User.objects.all().exclude(pk=get_anonymous_user().pk).filter(is_active=True) | ||||
|  | ||||
|     @property | ||||
|     def search_fields(self): | ||||
|         """Get search fields""" | ||||
|         current_tenant = get_current_tenant() | ||||
|         return list( | ||||
|             f for f in current_tenant.user_directory_fields if f not in ("avatar", "groups") | ||||
|         ) + list( | ||||
|             f"attributes__{attr['attribute']}" | ||||
|             for attr in current_tenant.user_directory_attributes | ||||
|             if "attribute" in attr | ||||
|         ) | ||||
|  | ||||
|     @extend_schema( | ||||
|         responses={ | ||||
|             200: inline_serializer( | ||||
|                 "UserDirectoryFieldsSerializer", | ||||
|                 { | ||||
|                     "fields": ListField(child=CharField()), | ||||
|                     "attributes": ListField(child=DictField(child=CharField())), | ||||
|                 }, | ||||
|             ) | ||||
|         }, | ||||
|     ) | ||||
|     @action(detail=False, pagination_class=None) | ||||
|     def fields(self, request: Request) -> Response: | ||||
|         """Get user directory fields""" | ||||
|         return Response( | ||||
|             { | ||||
|                 "fields": request.tenant.user_directory_fields, | ||||
|                 "attributes": request.tenant.user_directory_attributes, | ||||
|             } | ||||
|         ) | ||||
| @ -17,6 +17,7 @@ from authentik.core.api.providers import ProviderViewSet | ||||
| from authentik.core.api.sources import SourceViewSet, UserSourceConnectionViewSet | ||||
| from authentik.core.api.tokens import TokenViewSet | ||||
| from authentik.core.api.transactional_applications import TransactionalApplicationView | ||||
| from authentik.core.api.user_directory import UserDirectoryViewSet | ||||
| from authentik.core.api.users import UserViewSet | ||||
| from authentik.core.views import apps | ||||
| from authentik.core.views.debug import AccessDeniedView | ||||
| @ -82,6 +83,7 @@ api_urlpatterns = [ | ||||
|     ), | ||||
|     ("core/groups", GroupViewSet), | ||||
|     ("core/users", UserViewSet), | ||||
|     ("core/user_directory", UserDirectoryViewSet), | ||||
|     ("core/tokens", TokenViewSet), | ||||
|     ("sources/all", SourceViewSet), | ||||
|     ("sources/user_connections/all", UserSourceConnectionViewSet), | ||||
|  | ||||
| @ -4,7 +4,6 @@ from django.db import migrations | ||||
|  | ||||
|  | ||||
| class Migration(migrations.Migration): | ||||
|  | ||||
|     dependencies = [ | ||||
|         ("authentik_rbac", "0003_alter_systempermission_options"), | ||||
|     ] | ||||
| @ -17,6 +16,9 @@ class Migration(migrations.Migration): | ||||
|                 "managed": False, | ||||
|                 "permissions": [ | ||||
|                     ("view_system_info", "Can view system info"), | ||||
|                     ("view_system_tasks", "Can view system tasks"), | ||||
|                     ("view_user_directory", "Can view users in the user directory"), | ||||
|                     ("run_system_tasks", "Can run system tasks"), | ||||
|                     ("access_admin_interface", "Can access admin interface"), | ||||
|                     ("view_system_settings", "Can view system settings"), | ||||
|                     ("edit_system_settings", "Can edit system settings"), | ||||
|  | ||||
| @ -67,6 +67,9 @@ class SystemPermission(models.Model): | ||||
|         verbose_name_plural = _("System permissions") | ||||
|         permissions = [ | ||||
|             ("view_system_info", _("Can view system info")), | ||||
|             ("view_system_tasks", _("Can view system tasks")), | ||||
|             ("view_user_directory", _("Can view users in the user directory")), | ||||
|             ("run_system_tasks", _("Can run system tasks")), | ||||
|             ("access_admin_interface", _("Can access admin interface")), | ||||
|             ("view_system_settings", _("Can view system settings")), | ||||
|             ("edit_system_settings", _("Can edit system settings")), | ||||
|  | ||||
| @ -23,6 +23,8 @@ class SettingsSerializer(ModelSerializer): | ||||
|             "footer_links", | ||||
|             "gdpr_compliance", | ||||
|             "impersonation", | ||||
|             "user_directory_fields", | ||||
|             "user_directory_attributes", | ||||
|             "default_token_duration", | ||||
|             "default_token_length", | ||||
|         ] | ||||
|  | ||||
| @ -0,0 +1,30 @@ | ||||
| # Generated by Django 5.0.1 on 2024-01-24 14:27 | ||||
|  | ||||
| from django.db import migrations, models | ||||
|  | ||||
| import authentik.tenants.models | ||||
|  | ||||
|  | ||||
| class Migration(migrations.Migration): | ||||
|     dependencies = [ | ||||
|         ("authentik_tenants", "0001_initial"), | ||||
|     ] | ||||
|  | ||||
|     operations = [ | ||||
|         migrations.AddField( | ||||
|             model_name="tenant", | ||||
|             name="user_directory_attributes", | ||||
|             field=models.JSONField( | ||||
|                 blank=True, default=list, help_text="Attributes to show in the user directory." | ||||
|             ), | ||||
|         ), | ||||
|         migrations.AddField( | ||||
|             model_name="tenant", | ||||
|             name="user_directory_fields", | ||||
|             field=models.JSONField( | ||||
|                 blank=True, | ||||
|                 default=authentik.tenants.models._default_user_directory_fields, | ||||
|                 help_text="Fields to show in the user directory.", | ||||
|             ), | ||||
|         ), | ||||
|     ] | ||||
							
								
								
									
										13
									
								
								authentik/tenants/migrations/0004_merge_20240524_1807.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								authentik/tenants/migrations/0004_merge_20240524_1807.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,13 @@ | ||||
| # Generated by Django 5.0.6 on 2024-05-24 18:07 | ||||
|  | ||||
| from django.db import migrations | ||||
|  | ||||
|  | ||||
| class Migration(migrations.Migration): | ||||
|  | ||||
|     dependencies = [ | ||||
|         ("authentik_tenants", "0002_tenant_user_directory_and_more"), | ||||
|         ("authentik_tenants", "0003_alter_tenant_default_token_duration"), | ||||
|     ] | ||||
|  | ||||
|     operations = [] | ||||
| @ -37,6 +37,10 @@ def _validate_schema_name(name): | ||||
|         ) | ||||
|  | ||||
|  | ||||
| def _default_user_directory_fields(): | ||||
|     return ["avatar", "name", "username", "email", "groups"] | ||||
|  | ||||
|  | ||||
| class Tenant(TenantMixin, SerializerModel): | ||||
|     """Tenant""" | ||||
|  | ||||
| @ -85,6 +89,14 @@ class Tenant(TenantMixin, SerializerModel): | ||||
|     impersonation = models.BooleanField( | ||||
|         help_text=_("Globally enable/disable impersonation."), default=True | ||||
|     ) | ||||
|     user_directory_fields = models.JSONField( | ||||
|         help_text=_("Fields to show in the user directory."), | ||||
|         default=_default_user_directory_fields, | ||||
|         blank=True, | ||||
|     ) | ||||
|     user_directory_attributes = models.JSONField( | ||||
|         help_text=_("Attributes to show in the user directory."), default=list, blank=True | ||||
|     ) | ||||
|     default_token_duration = models.TextField( | ||||
|         help_text=_("Default token duration"), | ||||
|         default=DEFAULT_TOKEN_DURATION, | ||||
|  | ||||
							
								
								
									
										175
									
								
								schema.yml
									
									
									
									
									
								
							
							
						
						
									
										175
									
								
								schema.yml
									
									
									
									
									
								
							| @ -4537,6 +4537,119 @@ paths: | ||||
|               schema: | ||||
|                 $ref: '#/components/schemas/GenericError' | ||||
|           description: '' | ||||
|   /core/user_directory/: | ||||
|     get: | ||||
|       operationId: core_user_directory_list | ||||
|       description: User Directory Viewset | ||||
|       parameters: | ||||
|       - name: ordering | ||||
|         required: false | ||||
|         in: query | ||||
|         description: Which field to use when ordering the results. | ||||
|         schema: | ||||
|           type: string | ||||
|       - name: page | ||||
|         required: false | ||||
|         in: query | ||||
|         description: A page number within the paginated result set. | ||||
|         schema: | ||||
|           type: integer | ||||
|       - name: page_size | ||||
|         required: false | ||||
|         in: query | ||||
|         description: Number of results to return per page. | ||||
|         schema: | ||||
|           type: integer | ||||
|       - name: search | ||||
|         required: false | ||||
|         in: query | ||||
|         description: A search term. | ||||
|         schema: | ||||
|           type: string | ||||
|       tags: | ||||
|       - core | ||||
|       security: | ||||
|       - authentik: [] | ||||
|       responses: | ||||
|         '200': | ||||
|           content: | ||||
|             application/json: | ||||
|               schema: | ||||
|                 $ref: '#/components/schemas/PaginatedUserDirectoryList' | ||||
|           description: '' | ||||
|         '400': | ||||
|           content: | ||||
|             application/json: | ||||
|               schema: | ||||
|                 $ref: '#/components/schemas/ValidationError' | ||||
|           description: '' | ||||
|         '403': | ||||
|           content: | ||||
|             application/json: | ||||
|               schema: | ||||
|                 $ref: '#/components/schemas/GenericError' | ||||
|           description: '' | ||||
|   /core/user_directory/{id}/: | ||||
|     get: | ||||
|       operationId: core_user_directory_retrieve | ||||
|       description: User Directory Viewset | ||||
|       parameters: | ||||
|       - in: path | ||||
|         name: id | ||||
|         schema: | ||||
|           type: integer | ||||
|         description: A unique integer value identifying this User. | ||||
|         required: true | ||||
|       tags: | ||||
|       - core | ||||
|       security: | ||||
|       - authentik: [] | ||||
|       responses: | ||||
|         '200': | ||||
|           content: | ||||
|             application/json: | ||||
|               schema: | ||||
|                 $ref: '#/components/schemas/UserDirectory' | ||||
|           description: '' | ||||
|         '400': | ||||
|           content: | ||||
|             application/json: | ||||
|               schema: | ||||
|                 $ref: '#/components/schemas/ValidationError' | ||||
|           description: '' | ||||
|         '403': | ||||
|           content: | ||||
|             application/json: | ||||
|               schema: | ||||
|                 $ref: '#/components/schemas/GenericError' | ||||
|           description: '' | ||||
|   /core/user_directory/fields/: | ||||
|     get: | ||||
|       operationId: core_user_directory_fields_retrieve | ||||
|       description: Get user directory fields | ||||
|       tags: | ||||
|       - core | ||||
|       security: | ||||
|       - authentik: [] | ||||
|       responses: | ||||
|         '200': | ||||
|           content: | ||||
|             application/json: | ||||
|               schema: | ||||
|                 $ref: '#/components/schemas/UserDirectoryFields' | ||||
|           description: '' | ||||
|         '400': | ||||
|           content: | ||||
|             application/json: | ||||
|               schema: | ||||
|                 $ref: '#/components/schemas/ValidationError' | ||||
|           description: '' | ||||
|         '403': | ||||
|           content: | ||||
|             application/json: | ||||
|               schema: | ||||
|                 $ref: '#/components/schemas/GenericError' | ||||
|           description: '' | ||||
|   /core/users/: | ||||
|     get: | ||||
|       operationId: core_users_list | ||||
| @ -40859,6 +40972,18 @@ components: | ||||
|       required: | ||||
|       - pagination | ||||
|       - results | ||||
|     PaginatedUserDirectoryList: | ||||
|       type: object | ||||
|       properties: | ||||
|         pagination: | ||||
|           $ref: '#/components/schemas/Pagination' | ||||
|         results: | ||||
|           type: array | ||||
|           items: | ||||
|             $ref: '#/components/schemas/UserDirectory' | ||||
|       required: | ||||
|       - pagination | ||||
|       - results | ||||
|     PaginatedUserList: | ||||
|       type: object | ||||
|       properties: | ||||
| @ -43614,6 +43739,10 @@ components: | ||||
|         impersonation: | ||||
|           type: boolean | ||||
|           description: Globally enable/disable impersonation. | ||||
|         user_directory_fields: | ||||
|           description: Fields to show in the user directory. | ||||
|         user_directory_attributes: | ||||
|           description: Attributes to show in the user directory. | ||||
|         default_token_duration: | ||||
|           type: string | ||||
|           minLength: 1 | ||||
| @ -46901,6 +47030,10 @@ components: | ||||
|         impersonation: | ||||
|           type: boolean | ||||
|           description: Globally enable/disable impersonation. | ||||
|         user_directory_fields: | ||||
|           description: Fields to show in the user directory. | ||||
|         user_directory_attributes: | ||||
|           description: Attributes to show in the user directory. | ||||
|         default_token_duration: | ||||
|           type: string | ||||
|           description: Default token duration | ||||
| @ -46940,6 +47073,10 @@ components: | ||||
|         impersonation: | ||||
|           type: boolean | ||||
|           description: Globally enable/disable impersonation. | ||||
|         user_directory_fields: | ||||
|           description: Fields to show in the user directory. | ||||
|         user_directory_attributes: | ||||
|           description: Attributes to show in the user directory. | ||||
|         default_token_duration: | ||||
|           type: string | ||||
|           minLength: 1 | ||||
| @ -48032,6 +48169,44 @@ components: | ||||
|             $ref: '#/components/schemas/FlowSetRequest' | ||||
|       required: | ||||
|       - name | ||||
|     UserDirectory: | ||||
|       type: object | ||||
|       description: User Directory Serializer | ||||
|       properties: | ||||
|         pk: | ||||
|           type: integer | ||||
|           readOnly: true | ||||
|           title: ID | ||||
|         user_fields: | ||||
|           type: object | ||||
|           additionalProperties: {} | ||||
|           description: Get directory fields | ||||
|           readOnly: true | ||||
|         attributes: | ||||
|           type: object | ||||
|           additionalProperties: {} | ||||
|           description: Get directory attributes | ||||
|           readOnly: true | ||||
|       required: | ||||
|       - attributes | ||||
|       - pk | ||||
|       - user_fields | ||||
|     UserDirectoryFields: | ||||
|       type: object | ||||
|       properties: | ||||
|         fields: | ||||
|           type: array | ||||
|           items: | ||||
|             type: string | ||||
|         attributes: | ||||
|           type: array | ||||
|           items: | ||||
|             type: object | ||||
|             additionalProperties: | ||||
|               type: string | ||||
|       required: | ||||
|       - attributes | ||||
|       - fields | ||||
|     UserFieldsEnum: | ||||
|       enum: | ||||
|       - email | ||||
|  | ||||
| @ -193,6 +193,42 @@ export class AdminSettingsForm extends Form<SettingsRequest> { | ||||
|                 help=${msg("Globally enable/disable impersonation.")} | ||||
|             > | ||||
|             </ak-switch-input> | ||||
|             <ak-form-element-horizontal | ||||
|                 label=${msg("User directory fields")} | ||||
|                 name="userDirectoryFields" | ||||
|             > | ||||
|                 <ak-codemirror | ||||
|                     mode=${CodeMirrorMode.YAML} | ||||
|                     .value="${first(this._settings?.userDirectoryFields, [ | ||||
|                         "name", | ||||
|                         "username", | ||||
|                         "email", | ||||
|                         "avatars", | ||||
|                         "groups", | ||||
|                     ])}" | ||||
|                 ></ak-codemirror> | ||||
|                 <p class="pf-c-form__helper-text"> | ||||
|                     ${msg( | ||||
|                         "This option configures what user fields are shown in the user directory. It must be a valid JSON list and can be used as follows, with all possible values included:", | ||||
|                     )} | ||||
|                     <code>["name", "username", "email", "avatars", "groups"]</code> | ||||
|                 </p> | ||||
|             </ak-form-element-horizontal> | ||||
|             <ak-form-element-horizontal | ||||
|                 label=${msg("User directory attributes")} | ||||
|                 name="userDirectoryAttributes" | ||||
|             > | ||||
|                 <ak-codemirror | ||||
|                     mode=${CodeMirrorMode.YAML} | ||||
|                     .value="${first(this._settings?.userDirectoryAttributes, [])}" | ||||
|                 ></ak-codemirror> | ||||
|                 <p class="pf-c-form__helper-text"> | ||||
|                     ${msg( | ||||
|                         "This option configures what user attributes are shown in the user directory. It must be a valid JSON list and can be used as follows:", | ||||
|                     )} | ||||
|                     <code>[{"attribute": "phone_number", "display_name": "Phone"}]</code> | ||||
|                 </p> | ||||
|             </ak-form-element-horizontal> | ||||
|             <ak-text-input | ||||
|                 name="defaultTokenDuration" | ||||
|                 label=${msg("Default token duration")} | ||||
|  | ||||
| @ -53,7 +53,7 @@ export class BlueprintListPage extends TablePage<BlueprintInstance> { | ||||
|         return msg("Automate and template configuration within authentik."); | ||||
|     } | ||||
|     pageIcon(): string { | ||||
|         return "pf-icon pf-icon-blueprint"; | ||||
|         return "fa fa-user"; | ||||
|     } | ||||
|  | ||||
|     expandable = true; | ||||
|  | ||||
| @ -141,6 +141,7 @@ export class PageHeader extends WithBrandConfig(AKElement) { | ||||
|         return html` <ak-enterprise-status interface="admin"></ak-enterprise-status> | ||||
|             <div class="bar"> | ||||
|                 <button | ||||
|                     part="sidebar-trigger" | ||||
|                     class="sidebar-trigger pf-c-button pf-m-plain" | ||||
|                     @click=${() => { | ||||
|                         this.dispatchEvent( | ||||
|  | ||||
| @ -70,14 +70,17 @@ export abstract class TablePage<T> extends Table<T> { | ||||
|         </button>`; | ||||
|     } | ||||
|  | ||||
|     render(): TemplateResult { | ||||
|     renderPageHeader(): TemplateResult { | ||||
|         return html`<ak-page-header | ||||
|                 icon=${this.pageIcon()} | ||||
|                 header=${this.pageTitle()} | ||||
|                 description=${ifDefined(this.pageDescription())} | ||||
|             > | ||||
|             </ak-page-header> | ||||
|             ${this.renderSectionBefore()} | ||||
|             icon=${this.pageIcon()} | ||||
|             header=${this.pageTitle()} | ||||
|             description=${ifDefined(this.pageDescription())} | ||||
|         > | ||||
|         </ak-page-header>`; | ||||
|     } | ||||
|  | ||||
|     render(): TemplateResult { | ||||
|         return html`${this.renderPageHeader()} ${this.renderSectionBefore()} | ||||
|             <section class="pf-c-page__main-section pf-m-no-padding-mobile"> | ||||
|                 <div class="pf-c-sidebar pf-m-gutter"> | ||||
|                     <div class="pf-c-sidebar__main"> | ||||
|  | ||||
| @ -8,6 +8,10 @@ export const ROUTES: Route[] = [ | ||||
|     new Route(new RegExp("^/$")).redirect("/library"), | ||||
|     new Route(new RegExp("^#.*")).redirect("/library"), | ||||
|     new Route(new RegExp("^/library$"), async () => html`<ak-library></ak-library>`), | ||||
|     new Route(new RegExp("^/directory"), async () => { | ||||
|         await import("@goauthentik/user/user-directory/UserDirectoryPage"); | ||||
|         return html`<ak-user-directory></ak-user-directory>`; | ||||
|     }), | ||||
|     new Route(new RegExp("^/settings$"), async () => { | ||||
|         await import("@goauthentik/user/user-settings/UserSettingsPage"); | ||||
|         return html`<ak-user-settings></ak-user-settings>`; | ||||
|  | ||||
| @ -159,6 +159,13 @@ class UserInterfacePresentation extends AKElement { | ||||
|             .otherwise(() => this.me.user.username); | ||||
|     } | ||||
|  | ||||
|     get canAccessUserDirectory() { | ||||
|         return ( | ||||
|             this.me.user.isSuperuser || | ||||
|             this.me.user.systemPermissions.includes("can_view_user_directory") | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     get canAccessAdmin() { | ||||
|         return ( | ||||
|             this.me.user.isSuperuser || | ||||
| @ -204,6 +211,8 @@ class UserInterfacePresentation extends AKElement { | ||||
|                             <!-- --> | ||||
|                             ${this.renderNotificationDrawerTrigger()} | ||||
|                             <!-- --> | ||||
|                             ${this.renderUserDirectory()} | ||||
|                             <!-- --> | ||||
|                             ${this.renderSettings()} | ||||
|                             <div class="pf-c-page__header-tools-item"> | ||||
|                                 <a | ||||
| @ -355,6 +364,20 @@ class UserInterfacePresentation extends AKElement { | ||||
|         </a>`; | ||||
|     } | ||||
|  | ||||
|     renderUserDirectory() { | ||||
|         if (!this.canAccessUserDirectory) { | ||||
|             return nothing; | ||||
|         } | ||||
|  | ||||
|         return html` <div class="pf-c-page__header-tools-item"> | ||||
|             <a class="pf-c-button pf-m-plain" type="button" href="#/directory"> | ||||
|                 <pf-tooltip position="top" content=${msg("User directory")}> | ||||
|                     <i class="pf-icon pf-icon-project" aria-hidden="true"></i> | ||||
|                 </pf-tooltip> | ||||
|             </a> | ||||
|         </div>`; | ||||
|     } | ||||
|  | ||||
|     renderImpersonation() { | ||||
|         if (!this.me.original) { | ||||
|             return nothing; | ||||
|  | ||||
							
								
								
									
										131
									
								
								web/src/user/user-directory/UserDirectoryPage.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										131
									
								
								web/src/user/user-directory/UserDirectoryPage.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,131 @@ | ||||
| import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; | ||||
| import { renderDescriptionList } from "@goauthentik/components/DescriptionList.js"; | ||||
| import { PaginatedResponse } from "@goauthentik/elements/table/Table"; | ||||
| import { TableColumn } from "@goauthentik/elements/table/Table"; | ||||
| import { TablePage } from "@goauthentik/elements/table/TablePage"; | ||||
|  | ||||
| import { msg } from "@lit/localize"; | ||||
| import { css, html } from "lit"; | ||||
| import { customElement, property, state } from "lit/decorators.js"; | ||||
|  | ||||
| import PFAlert from "@patternfly/patternfly/components/Alert/alert.css"; | ||||
| import PFAvatar from "@patternfly/patternfly/components/Avatar/avatar.css"; | ||||
| import PFCard from "@patternfly/patternfly/components/Card/card.css"; | ||||
| import PFDescriptionList from "@patternfly/patternfly/components/DescriptionList/description-list.css"; | ||||
|  | ||||
| import { CoreApi, UserDirectory } from "@goauthentik/api"; | ||||
|  | ||||
| const knownFields: Record<string, string> = { | ||||
|     avatar: "", | ||||
|     username: msg("Username"), | ||||
|     name: msg("Name"), | ||||
|     email: msg("Email"), | ||||
| }; | ||||
|  | ||||
| type UserFieldAttributes = { display_name: string; attribute: string }; | ||||
|  | ||||
| @customElement("ak-user-directory") | ||||
| export class UserDirectoryPage extends TablePage<UserDirectory> { | ||||
|     expandable = true; | ||||
|  | ||||
|     searchEnabled(): boolean { | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
|     pageTitle(): string { | ||||
|         return msg("User Directory"); | ||||
|     } | ||||
|  | ||||
|     pageDescription(): string { | ||||
|         return msg("Display a list of users on this system."); | ||||
|     } | ||||
|  | ||||
|     pageIcon(): string { | ||||
|         return "pf-icon pf-icon-project"; | ||||
|     } | ||||
|  | ||||
|     @property() | ||||
|     order = "username"; | ||||
|  | ||||
|     @state() | ||||
|     fields?: string[]; | ||||
|  | ||||
|     @state() | ||||
|     userFieldAttributes?: object[] = []; | ||||
|  | ||||
|     static get styles() { | ||||
|         return [ | ||||
|             ...super.styles, | ||||
|             PFDescriptionList, | ||||
|             PFCard, | ||||
|             PFAlert, | ||||
|             PFAvatar, | ||||
|             css` | ||||
|                 ak-page-header::part(sidebar-trigger) { | ||||
|                     display: none; | ||||
|                 } | ||||
|             `, | ||||
|         ]; | ||||
|     } | ||||
|  | ||||
|     async apiEndpoint(): Promise<PaginatedResponse<UserDirectory>> { | ||||
|         const fields = await new CoreApi(DEFAULT_CONFIG).coreUserDirectoryFieldsRetrieve(); | ||||
|         this.fields = fields.fields; | ||||
|         this.userFieldAttributes = fields.attributes; | ||||
|         return await new CoreApi(DEFAULT_CONFIG).coreUserDirectoryList( | ||||
|             await this.defaultEndpointConfig(), | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     columns() { | ||||
|         return (this.fields ?? []) | ||||
|             .filter((item) => item in knownFields) | ||||
|             .map((item) => | ||||
|                 item === "avatar" | ||||
|                     ? new TableColumn(knownFields[item]) | ||||
|                     : new TableColumn(knownFields[item], item), | ||||
|             ); | ||||
|     } | ||||
|  | ||||
|     row(item: UserDirectory) { | ||||
|         return (this.fields ?? []) | ||||
|             .filter((field: string) => Object.hasOwn(knownFields, field)) | ||||
|             .map((field: string) => | ||||
|                 field !== "avatar" | ||||
|                     ? html`${item.userFields[field]}` | ||||
|                     : html` <img | ||||
|                           class="pf-c-avatar" | ||||
|                           src=${item.userFields[field]} | ||||
|                           alt="${msg("Avatar image")}" | ||||
|                       />`, | ||||
|             ); | ||||
|     } | ||||
|  | ||||
|     renderExpanded(item: UserDirectory) { | ||||
|         const groupDescription = | ||||
|             this.fields?.includes("groups") && (item.userFields["groups"] ?? []).length > 0 | ||||
|                 ? [ | ||||
|                       [msg("Groups")], | ||||
|                       item.userFields["groups"].map( | ||||
|                           (group: string) => html` | ||||
|                               <div class="pf-c-description-list__text">${group}</div> | ||||
|                           `, | ||||
|                       ), | ||||
|                   ] | ||||
|                 : []; | ||||
|  | ||||
|         const userDescriptions = ((this.userFieldAttributes ?? []) as UserFieldAttributes[]) | ||||
|             .filter(({ attribute }) => attribute !== null) | ||||
|             .map(({ display_name, attribute }) => [display_name, item.attributes[attribute]]); | ||||
|  | ||||
|         const toShow = [...groupDescription, ...userDescriptions]; | ||||
|  | ||||
|         return toShow.length > 1 | ||||
|             ? html`<td role="cell" colspan="3"> | ||||
|                   <div class="pf-c-table__expandable-row-content"> | ||||
|                       ${renderDescriptionList(toShow)} | ||||
|                   </div> | ||||
|               </td>` | ||||
|             : html``; | ||||
|     } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user
	