Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
9 changes: 9 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(read_only=True)
num_pk = IntegerField(read_only=True)

@property
Expand Down Expand Up @@ -126,6 +127,13 @@ 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:
"""Return only inherited roles from ancestor groups (excludes direct roles)"""
direct_role_pks = set(instance.roles.values_list("pk", flat=True))
inherited_roles = instance.all_roles().exclude(pk__in=direct_role_pks)
Copy link
Member

Choose a reason for hiding this comment

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

logic wise looks fine but I think this is gonna cost us a lot of query time when listing groups, I think we either have to pre-fetch things or only include this field in the detail view

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I went with with an include, let me know if it looks good

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 +175,7 @@ class Meta:
"attributes",
"roles",
"roles_obj",
"inherited_roles_obj",
"children",
"children_obj",
]
Expand Down
47 changes: 43 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,54 @@ 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_user_roles = extend_schema_field(OpenApiTypes.INT)(
NumberFilter(
method="filter_inherited_user_roles",
label="Filter by inherited roles from groups (excludes direct)",
)
)

inherited_group_roles = extend_schema_field(OpenApiTypes.UUID)(
CharFilter(
method="filter_inherited_group_roles",
label="Filter by inherited roles from ancestor groups (excludes direct)",
)
)

def filter_inherited_user_roles(self, queryset, name, value):
"""Filter roles inherited from groups (excludes direct user roles)"""
try:
user = User.objects.get(pk=value)
except User.DoesNotExist:
return queryset.none()
direct_role_pks = set(user.roles.values_list("pk", flat=True))
return user.all_roles().exclude(pk__in=direct_role_pks)

def filter_inherited_group_roles(self, queryset, name, value):
"""Filter roles inherited from ancestor groups (excludes direct roles)"""
try:
group = Group.objects.get(pk=value)
except Group.DoesNotExist:
return queryset.none()
direct_role_pks = set(group.roles.values_list("pk", flat=True))
return group.all_roles().exclude(pk__in=direct_role_pks)

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


class RoleViewSet(UsedByMixin, ModelViewSet):
Expand Down
24 changes: 24 additions & 0 deletions schema.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20047,6 +20047,24 @@ paths:
operationId: rbac_roles_list
description: Role viewset
parameters:
- in: query
name: ak_groups
schema:
type: array
items:
type: string
format: uuid
explode: true
style: form
- in: query
name: inherited_group_roles
schema:
type: string
format: uuid
- in: query
name: inherited_user_roles
schema:
type: integer
- in: query
name: managed
schema:
Expand Down Expand Up @@ -38770,6 +38788,11 @@ components:
items:
$ref: '#/components/schemas/Role'
readOnly: true
inherited_roles_obj:
type: array
items:
$ref: '#/components/schemas/Role'
readOnly: true
children:
type: array
items:
Expand All @@ -38785,6 +38808,7 @@ components:
required:
- children
- children_obj
- inherited_roles_obj
- name
- num_pk
- parents_obj
Expand Down
71 changes: 70 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 Down Expand Up @@ -137,6 +138,29 @@ 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 +227,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 +249,42 @@ export class GroupViewPage extends AKElement {
</main>`;
}

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