Skip to content
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions authentik/core/api/groups.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ class GroupSerializer(ModelSerializer):
source="roles",
required=False,
)
inherited_roles_obj = SerializerMethodField(allow_null=True)
num_pk = IntegerField(read_only=True)

@property
Expand All @@ -108,6 +109,13 @@ def _should_include_parents(self) -> bool:
return True
return str(request.query_params.get("include_parents", "false")).lower() == "true"

@property
def _should_include_inherited_roles(self) -> bool:
request: Request = self.context.get("request", None)
if not request:
return True
return str(request.query_params.get("include_inherited_roles", "false")).lower() == "true"

@extend_schema_field(PartialUserSerializer(many=True))
def get_users_obj(self, instance: Group) -> list[PartialUserSerializer] | None:
if not self._should_include_users:
Expand All @@ -126,6 +134,15 @@ def get_parents_obj(self, instance: Group) -> list[RelatedGroupSerializer] | Non
return None
return RelatedGroupSerializer(instance.parents, many=True).data

@extend_schema_field(RoleSerializer(many=True))
def get_inherited_roles_obj(self, instance: Group) -> list | None:
"""Return only inherited roles from ancestor groups (excludes direct roles)"""
if not self._should_include_inherited_roles:
return None
direct_role_pks = instance.roles.values_list("pk", flat=True)
inherited_roles = instance.all_roles().exclude(pk__in=direct_role_pks)
return RoleSerializer(inherited_roles, many=True).data

def validate_is_superuser(self, superuser: bool):
"""Ensure that the user creating this group has permissions to set the superuser flag"""
request: Request = self.context.get("request", None)
Expand Down Expand Up @@ -167,6 +184,7 @@ class Meta:
"attributes",
"roles",
"roles_obj",
"inherited_roles_obj",
"children",
"children_obj",
]
Expand Down Expand Up @@ -289,6 +307,7 @@ def get_queryset(self):
OpenApiParameter("include_users", bool, default=True),
OpenApiParameter("include_children", bool, default=False),
OpenApiParameter("include_parents", bool, default=False),
OpenApiParameter("include_inherited_roles", bool, default=False),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Almost nobody should ever use this in list, but I don't see any harm including it.

]
)
def list(self, request, *args, **kwargs):
Expand All @@ -299,6 +318,7 @@ def list(self, request, *args, **kwargs):
OpenApiParameter("include_users", bool, default=True),
OpenApiParameter("include_children", bool, default=False),
OpenApiParameter("include_parents", bool, default=False),
OpenApiParameter("include_inherited_roles", bool, default=False),
]
)
def retrieve(self, request, *args, **kwargs):
Expand Down
56 changes: 52 additions & 4 deletions authentik/rbac/api/roles.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from django.contrib.auth.models import Permission
from django.http import Http404
from django_filters.filters import AllValuesMultipleFilter, BooleanFilter
from django_filters.filters import AllValuesMultipleFilter, BooleanFilter, CharFilter, NumberFilter
from django_filters.filterset import FilterSet
from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import OpenApiResponse, extend_schema, extend_schema_field
Expand All @@ -22,7 +22,7 @@
from authentik.blueprints.v1.importer import SERIALIZER_CONTEXT_BLUEPRINT
from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.utils import ModelSerializer, PassiveSerializer
from authentik.core.models import User
from authentik.core.models import Group, User
from authentik.rbac.decorators import permission_required
from authentik.rbac.models import Role, get_permission_choices

Expand Down Expand Up @@ -65,15 +65,63 @@ class Meta:


class RoleFilterSet(FilterSet):
"""Filter for PropertyMapping"""
"""Filter for Role"""

managed = extend_schema_field(OpenApiTypes.STR)(AllValuesMultipleFilter(field_name="managed"))

managed__isnull = BooleanFilter(field_name="managed", lookup_expr="isnull")

inherited = BooleanFilter(
method="filter_inherited",
label="Include inherited roles (requires users or ak_groups filter)",
)

users = extend_schema_field(OpenApiTypes.INT)(
NumberFilter(
method="filter_users",
label="Filter by user (use with inherited=true for all roles)",
)
)

ak_groups = extend_schema_field(OpenApiTypes.UUID)(
CharFilter(
method="filter_ak_groups",
label="Filter by group (use with inherited=true for all roles)",
)
)

def filter_inherited(self, queryset, name, value):
"""This filter is handled by filter_users and filter_ak_groups"""
return queryset

def filter_users(self, queryset, name, value):
"""Filter roles by user, optionally including inherited roles"""
user = User.objects.filter(pk=value).first()
if not user:
return queryset.none()

include_inherited = self.data.get("inherited", "").lower() == "true"
if include_inherited:
return user.all_roles()
return queryset.filter(users=user)

def filter_ak_groups(self, queryset, name, value):
"""Filter roles by group, optionally including inherited roles"""
group = Group.objects.filter(pk=value).first()
if not group:
return queryset.none()

include_inherited = self.data.get("inherited", "").lower() == "true"
if include_inherited:
return group.all_roles()
return queryset.filter(ak_groups=group)

class Meta:
model = Role
fields = ["name", "users", "managed"]
fields = [
"name",
"managed",
]


class RoleViewSet(UsedByMixin, ModelViewSet):
Expand Down
33 changes: 28 additions & 5 deletions schema.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3385,6 +3385,11 @@ paths:
schema:
type: boolean
default: false
- in: query
name: include_inherited_roles
schema:
type: boolean
default: false
- in: query
name: include_parents
schema:
Expand Down Expand Up @@ -3478,6 +3483,11 @@ paths:
schema:
type: boolean
default: false
- in: query
name: include_inherited_roles
schema:
type: boolean
default: false
- in: query
name: include_parents
schema:
Expand Down Expand Up @@ -20047,6 +20057,16 @@ paths:
operationId: rbac_roles_list
description: Role viewset
parameters:
- in: query
name: ak_groups
schema:
type: string
format: uuid
- in: query
name: inherited
schema:
type: boolean
description: Include inherited roles (requires users or ak_groups filter)
- in: query
name: managed
schema:
Expand All @@ -20067,11 +20087,7 @@ paths:
- in: query
name: users
schema:
type: array
items:
type: integer
explode: true
style: form
type: integer
tags:
- rbac
security:
Expand Down Expand Up @@ -38770,6 +38786,12 @@ components:
items:
$ref: '#/components/schemas/Role'
readOnly: true
inherited_roles_obj:
type: array
items:
$ref: '#/components/schemas/Role'
readOnly: true
nullable: true
children:
type: array
items:
Expand All @@ -38785,6 +38807,7 @@ components:
required:
- children
- children_obj
- inherited_roles_obj
- name
- num_pk
- parents_obj
Expand Down
77 changes: 76 additions & 1 deletion web/src/admin/groups/GroupViewPage.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import "#admin/groups/GroupForm";
import "#admin/groups/RelatedUserList";
import "#admin/rbac/ObjectPermissionsPage";
import "#admin/roles/RelatedRoleList";
import "#components/ak-status-label";
import "#components/events/ObjectChangelog";
import "#elements/CodeMirror";
Expand All @@ -21,7 +22,7 @@ import { setPageDetails } from "#components/ak-page-navbar";
import { CoreApi, Group, RbacPermissionsAssignedByRolesListModelEnum } from "@goauthentik/api";

import { msg, str } from "@lit/localize";
import { CSSResult, html, nothing, PropertyValues } from "lit";
import { CSSResult, html, nothing, PropertyValues, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators.js";

import PFButton from "@patternfly/patternfly/components/Button/button.css";
Expand All @@ -43,6 +44,7 @@ export class GroupViewPage extends AKElement {
.coreGroupsRetrieve({
groupUuid: id,
includeUsers: false,
includeInheritedRoles: true,
})
.then((group) => {
this.group = group;
Expand Down Expand Up @@ -137,6 +139,34 @@ export class GroupViewPage extends AKElement {
</a>
</li>`;
})}
${(this.group.inheritedRolesObj ?? []).map(
(role) => {
return html`<li>
<a
href=${`#/identity/roles/${role.pk}`}
>${role.name}
</a>
<pf-tooltip
position="top"
content=${msg(
"Inherited from parent group",
)}
>
<span
class="pf-c-label pf-m-outline pf-m-cyan"
style="margin-left: 0.5rem;"
>
<span
class="pf-c-label__content"
>${msg(
"Inherited",
)}</span
>
</span>
</pf-tooltip>
</li>`;
},
)}
</ul>
</div>
</dd>
Expand Down Expand Up @@ -203,6 +233,15 @@ export class GroupViewPage extends AKElement {
</div>
</div>
</section>
<section
role="tabpanel"
tabindex="0"
slot="page-roles"
id="page-roles"
aria-label="${msg("Roles")}"
>
${this.renderTabRoles(this.group)}
</section>
<ak-rbac-object-permission-page
role="tabpanel"
tabindex="0"
Expand All @@ -216,6 +255,42 @@ export class GroupViewPage extends AKElement {
</main>`;
}

protected renderTabRoles(group: Group): TemplateResult {
return html`
<ak-tabs pageIdentifier="groupRoles" vertical>
<div
role="tabpanel"
tabindex="0"
slot="page-assigned-roles"
id="page-assigned-roles"
aria-label=${msg("Assigned Roles")}
class="pf-c-page__main-section pf-m-no-padding-mobile"
>
<div class="pf-c-card">
<div class="pf-c-card__body">
<ak-role-related-list .targetGroup=${group}> </ak-role-related-list>
</div>
</div>
</div>
<div
role="tabpanel"
tabindex="0"
slot="page-all-roles"
id="page-all-roles"
aria-label=${msg("All Roles")}
class="pf-c-page__main-section pf-m-no-padding-mobile"
>
<div class="pf-c-card">
<div class="pf-c-card__body">
<ak-role-related-list .targetGroup=${group} showInherited>
</ak-role-related-list>
</div>
</div>
</div>
</ak-tabs>
`;
}

updated(changed: PropertyValues<this>) {
super.updated(changed);
setPageDetails({
Expand Down
Loading
Loading