2019-10-28 13:44:46 +00:00
|
|
|
"""Groups API Viewset"""
|
2021-12-09 18:53:47 +00:00
|
|
|
from json import loads
|
2023-06-20 18:21:58 +00:00
|
|
|
from typing import Optional
|
2021-12-09 18:53:47 +00:00
|
|
|
|
2022-12-28 09:50:30 +00:00
|
|
|
from django.http import Http404
|
2021-12-09 18:53:47 +00:00
|
|
|
from django_filters.filters import CharFilter, ModelMultipleChoiceFilter
|
2021-07-23 17:35:41 +00:00
|
|
|
from django_filters.filterset import FilterSet
|
2023-01-01 22:50:42 +00:00
|
|
|
from drf_spectacular.utils import OpenApiResponse, extend_schema
|
2022-12-28 09:50:30 +00:00
|
|
|
from guardian.shortcuts import get_objects_for_user
|
|
|
|
from rest_framework.decorators import action
|
2022-03-22 20:37:11 +00:00
|
|
|
from rest_framework.fields import CharField, IntegerField, JSONField
|
2022-12-28 09:50:30 +00:00
|
|
|
from rest_framework.request import Request
|
|
|
|
from rest_framework.response import Response
|
2021-12-09 18:53:47 +00:00
|
|
|
from rest_framework.serializers import ListSerializer, ModelSerializer, ValidationError
|
2019-10-28 13:27:43 +00:00
|
|
|
from rest_framework.viewsets import ModelViewSet
|
|
|
|
|
2022-12-28 09:50:30 +00:00
|
|
|
from authentik.api.decorators import permission_required
|
2021-06-10 09:58:12 +00:00
|
|
|
from authentik.core.api.used_by import UsedByMixin
|
2023-01-01 22:16:44 +00:00
|
|
|
from authentik.core.api.utils import PassiveSerializer, is_dict
|
2021-07-13 16:24:18 +00:00
|
|
|
from authentik.core.models import Group, User
|
2023-10-16 15:31:50 +00:00
|
|
|
from authentik.rbac.api.roles import RoleSerializer
|
2021-07-13 16:24:18 +00:00
|
|
|
|
|
|
|
|
|
|
|
class GroupMemberSerializer(ModelSerializer):
|
|
|
|
"""Stripped down user serializer to show relevant users for groups"""
|
|
|
|
|
|
|
|
attributes = JSONField(validators=[is_dict], required=False)
|
|
|
|
uid = CharField(read_only=True)
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
model = User
|
|
|
|
fields = [
|
|
|
|
"pk",
|
|
|
|
"username",
|
|
|
|
"name",
|
|
|
|
"is_active",
|
|
|
|
"last_login",
|
|
|
|
"email",
|
|
|
|
"attributes",
|
|
|
|
"uid",
|
|
|
|
]
|
2019-10-28 13:27:43 +00:00
|
|
|
|
|
|
|
|
|
|
|
class GroupSerializer(ModelSerializer):
|
2019-10-28 13:44:46 +00:00
|
|
|
"""Group Serializer"""
|
2019-10-28 13:27:43 +00:00
|
|
|
|
2021-04-11 21:20:45 +00:00
|
|
|
attributes = JSONField(validators=[is_dict], required=False)
|
2021-07-13 16:24:18 +00:00
|
|
|
users_obj = ListSerializer(
|
|
|
|
child=GroupMemberSerializer(), read_only=True, source="users", required=False
|
|
|
|
)
|
2023-10-16 15:31:50 +00:00
|
|
|
roles_obj = ListSerializer(
|
|
|
|
child=RoleSerializer(),
|
|
|
|
read_only=True,
|
|
|
|
source="roles",
|
|
|
|
required=False,
|
|
|
|
)
|
2023-08-30 18:39:57 +00:00
|
|
|
parent_name = CharField(source="parent.name", read_only=True, allow_null=True)
|
2021-04-11 21:05:19 +00:00
|
|
|
|
2022-03-22 20:37:11 +00:00
|
|
|
num_pk = IntegerField(read_only=True)
|
|
|
|
|
2023-06-20 18:21:58 +00:00
|
|
|
def validate_parent(self, parent: Optional[Group]):
|
|
|
|
"""Validate group parent (if set), ensuring the parent isn't itself"""
|
|
|
|
if not self.instance or not parent:
|
|
|
|
return parent
|
|
|
|
if str(parent.group_uuid) == str(self.instance.group_uuid):
|
|
|
|
raise ValidationError("Cannot set group as parent of itself.")
|
|
|
|
return parent
|
|
|
|
|
2019-10-28 13:27:43 +00:00
|
|
|
class Meta:
|
|
|
|
model = Group
|
2021-07-13 16:24:18 +00:00
|
|
|
fields = [
|
|
|
|
"pk",
|
2022-03-22 20:37:11 +00:00
|
|
|
"num_pk",
|
2021-07-13 16:24:18 +00:00
|
|
|
"name",
|
|
|
|
"is_superuser",
|
|
|
|
"parent",
|
2021-11-04 23:11:17 +00:00
|
|
|
"parent_name",
|
2021-07-13 16:24:18 +00:00
|
|
|
"users",
|
|
|
|
"users_obj",
|
2023-10-16 15:31:50 +00:00
|
|
|
"attributes",
|
|
|
|
"roles",
|
|
|
|
"roles_obj",
|
2021-07-13 16:24:18 +00:00
|
|
|
]
|
2022-08-05 22:52:12 +00:00
|
|
|
extra_kwargs = {
|
|
|
|
"users": {
|
|
|
|
"default": list,
|
|
|
|
}
|
|
|
|
}
|
2019-10-28 13:27:43 +00:00
|
|
|
|
|
|
|
|
2021-07-23 17:35:41 +00:00
|
|
|
class GroupFilter(FilterSet):
|
|
|
|
"""Filter for groups"""
|
|
|
|
|
2021-12-09 18:53:47 +00:00
|
|
|
attributes = CharFilter(
|
|
|
|
field_name="attributes",
|
|
|
|
lookup_expr="",
|
|
|
|
label="Attributes",
|
|
|
|
method="filter_attributes",
|
|
|
|
)
|
|
|
|
|
2021-07-23 17:35:41 +00:00
|
|
|
members_by_username = ModelMultipleChoiceFilter(
|
|
|
|
field_name="users__username",
|
|
|
|
to_field_name="username",
|
|
|
|
queryset=User.objects.all(),
|
|
|
|
)
|
|
|
|
members_by_pk = ModelMultipleChoiceFilter(
|
|
|
|
field_name="users",
|
|
|
|
queryset=User.objects.all(),
|
|
|
|
)
|
|
|
|
|
2021-12-09 18:53:47 +00:00
|
|
|
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
|
|
|
|
|
2021-07-23 17:35:41 +00:00
|
|
|
class Meta:
|
|
|
|
model = Group
|
2021-12-09 18:53:47 +00:00
|
|
|
fields = ["name", "is_superuser", "members_by_pk", "attributes", "members_by_username"]
|
2021-07-23 17:35:41 +00:00
|
|
|
|
|
|
|
|
2023-01-01 22:16:44 +00:00
|
|
|
class UserAccountSerializer(PassiveSerializer):
|
|
|
|
"""Account adding/removing operations"""
|
|
|
|
|
|
|
|
pk = IntegerField(required=True)
|
|
|
|
|
|
|
|
|
2021-06-10 09:58:12 +00:00
|
|
|
class GroupViewSet(UsedByMixin, ModelViewSet):
|
2019-10-28 13:44:46 +00:00
|
|
|
"""Group Viewset"""
|
2019-10-28 13:27:43 +00:00
|
|
|
|
2023-10-26 17:57:11 +00:00
|
|
|
# pylint: disable=no-member
|
2021-09-03 12:02:57 +00:00
|
|
|
queryset = Group.objects.all().select_related("parent").prefetch_related("users")
|
2019-10-28 13:27:43 +00:00
|
|
|
serializer_class = GroupSerializer
|
2021-02-19 17:59:24 +00:00
|
|
|
search_fields = ["name", "is_superuser"]
|
2021-07-23 17:35:41 +00:00
|
|
|
filterset_class = GroupFilter
|
2021-03-09 09:21:53 +00:00
|
|
|
ordering = ["name"]
|
2021-05-04 23:02:47 +00:00
|
|
|
|
2022-12-28 09:50:30 +00:00
|
|
|
@permission_required(None, ["authentik_core.add_user"])
|
|
|
|
@extend_schema(
|
2023-01-01 22:16:44 +00:00
|
|
|
request=UserAccountSerializer,
|
2022-12-28 09:50:30 +00:00
|
|
|
responses={
|
|
|
|
204: OpenApiResponse(description="User added"),
|
|
|
|
404: OpenApiResponse(description="User not found"),
|
|
|
|
},
|
|
|
|
)
|
|
|
|
@action(detail=True, methods=["POST"], pagination_class=None, filter_backends=[])
|
|
|
|
def add_user(self, request: Request, pk: str) -> Response:
|
|
|
|
"""Add user to group"""
|
|
|
|
group: Group = self.get_object()
|
|
|
|
user: User = (
|
|
|
|
get_objects_for_user(request.user, "authentik_core.view_user")
|
|
|
|
.filter(
|
|
|
|
pk=request.data.get("pk"),
|
|
|
|
)
|
|
|
|
.first()
|
|
|
|
)
|
|
|
|
if not user:
|
|
|
|
raise Http404
|
|
|
|
group.users.add(user)
|
|
|
|
return Response(status=204)
|
|
|
|
|
|
|
|
@permission_required(None, ["authentik_core.add_user"])
|
|
|
|
@extend_schema(
|
2023-01-01 22:16:44 +00:00
|
|
|
request=UserAccountSerializer,
|
2022-12-28 09:50:30 +00:00
|
|
|
responses={
|
|
|
|
204: OpenApiResponse(description="User added"),
|
|
|
|
404: OpenApiResponse(description="User not found"),
|
|
|
|
},
|
|
|
|
)
|
|
|
|
@action(detail=True, methods=["POST"], pagination_class=None, filter_backends=[])
|
|
|
|
def remove_user(self, request: Request, pk: str) -> Response:
|
|
|
|
"""Add user to group"""
|
|
|
|
group: Group = self.get_object()
|
|
|
|
user: User = (
|
|
|
|
get_objects_for_user(request.user, "authentik_core.view_user")
|
|
|
|
.filter(
|
|
|
|
pk=request.data.get("pk"),
|
|
|
|
)
|
|
|
|
.first()
|
|
|
|
)
|
|
|
|
if not user:
|
|
|
|
raise Http404
|
|
|
|
group.users.remove(user)
|
|
|
|
return Response(status=204)
|