diff --git a/authentik/core/api/groups.py b/authentik/core/api/groups.py index 570c08c44..9b0a8b43f 100644 --- a/authentik/core/api/groups.py +++ b/authentik/core/api/groups.py @@ -1,9 +1,11 @@ """Groups API Viewset""" +from json import loads + from django.db.models.query import QuerySet -from django_filters.filters import ModelMultipleChoiceFilter +from django_filters.filters import CharFilter, ModelMultipleChoiceFilter from django_filters.filterset import FilterSet from rest_framework.fields import CharField, JSONField -from rest_framework.serializers import ListSerializer, ModelSerializer +from rest_framework.serializers import ListSerializer, ModelSerializer, ValidationError from rest_framework.viewsets import ModelViewSet from rest_framework_guardian.filters import ObjectPermissionsFilter @@ -62,6 +64,13 @@ class GroupSerializer(ModelSerializer): class GroupFilter(FilterSet): """Filter for groups""" + attributes = CharFilter( + field_name="attributes", + lookup_expr="", + label="Attributes", + method="filter_attributes", + ) + members_by_username = ModelMultipleChoiceFilter( field_name="users__username", to_field_name="username", @@ -72,10 +81,28 @@ class GroupFilter(FilterSet): queryset=User.objects.all(), ) + # pylint: disable=unused-argument + def filter_attributes(self, queryset, name, value): + """Filter attributes by query args""" + try: + value = loads(value) + except ValueError: + raise ValidationError(detail="filter: failed to parse JSON") + if not isinstance(value, dict): + raise ValidationError(detail="filter: value must be key:value mapping") + qs = {} + for key, _value in value.items(): + qs[f"attributes__{key}"] = _value + try: + _ = len(queryset.filter(**qs)) + return queryset.filter(**qs) + except ValueError: + return queryset + class Meta: model = Group - fields = ["name", "is_superuser", "members_by_pk", "members_by_username"] + fields = ["name", "is_superuser", "members_by_pk", "attributes", "members_by_username"] class GroupViewSet(UsedByMixin, ModelViewSet): diff --git a/authentik/core/api/users.py b/authentik/core/api/users.py index 13d1ad756..c22a63da7 100644 --- a/authentik/core/api/users.py +++ b/authentik/core/api/users.py @@ -233,7 +233,11 @@ class UsersFilter(FilterSet): qs = {} for key, _value in value.items(): qs[f"attributes__{key}"] = _value - return queryset.filter(**qs) + try: + _ = len(queryset.filter(**qs)) + return queryset.filter(**qs) + except ValueError: + return queryset class Meta: model = User diff --git a/schema.yml b/schema.yml index a0899416e..7247a8723 100644 --- a/schema.yml +++ b/schema.yml @@ -2058,6 +2058,11 @@ paths: operationId: core_groups_list description: Group Viewset parameters: + - in: query + name: attributes + schema: + type: string + description: Attributes - in: query name: is_superuser schema: @@ -11846,6 +11851,7 @@ paths: - ml - mn - mr + - ms - my - nb - ne diff --git a/web/src/pages/users/UserListPage.ts b/web/src/pages/users/UserListPage.ts index c9779a454..95e5e85f2 100644 --- a/web/src/pages/users/UserListPage.ts +++ b/web/src/pages/users/UserListPage.ts @@ -62,7 +62,7 @@ export class UserListPage extends TablePage { search: this.search || "", attributes: this.hideServiceAccounts ? JSON.stringify({ - "goauthentik.io/user/service-account__isnull": "true", + "goauthentik.io/user/service-account__isnull": true, }) : undefined, });