Skip to content

rbac: check subclass permissions on objects from base viewsets - #27366

Open
dominic-r wants to merge 2 commits into
mainfrom
dominic/rbac-subclass-object-perms
Open

dominic-r wants to merge 2 commits into
mainfrom
dominic/rbac-subclass-object-perms

Conversation

@dominic-r

Copy link
Copy Markdown
Member

Details

What does this PR change?

permission_required and ObjectPermissions.has_object_permission now check a base model's default permission against the subclass the object really is. On an ExpressionPolicy, authentik_policies.view_policy is checked as authentik_policies_expression.view_expressionpolicy, globally or on the object. That's what /policies/expression/<pk>/ already requires. A global grant of the base permission still takes priority, and permissions that aren't a parent model's default permission are checked unchanged.

The translation lives in one helper, permission_for_object in authentik/rbac/permissions.py, which both checks use.

Why is this change needed?

The base viewsets (/policies/all/, /propertymappings/all/, /sources/all/, /providers/all/, /stages/all/, /outposts/service_connections/all/, /endpoints/connectors/) use select_subclasses(), so get_object() returns the subclass. Both checks then passed the base model's permission with that object to guardian. guardian raises WrongAppError when the permission's app doesn't match the object's, and that surfaced as a 500.

  • POST /api/v3/policies/all/<pk>/test/ and POST /api/v3/propertymappings/all/<pk>/test/ returned 500 for every non-superuser, including users with no permissions at all and users with the global view_policy. The POST makes ObjectPermissions check add_policy on the object before the action's own view_policy check runs.
  • DELETE through a base viewset returned 500 for a user with the global base view permission but no global delete permission.

Object grants on these objects can only exist under the subclass's content type, because guardian refuses to store the base model's permission on a subclass instance. That's why the check maps to the subclass permission instead of catching the exception. A missing permission is now a 403.

This doesn't change two existing behaviors:

  • List and retrieve on the base viewsets still filter by the base model's view permission (ObjectFilter).
  • The two test actions still need the add permission as well as view, because ObjectPermissions maps POST to add_<model>.

How was this tested?

Added API tests, run against Postgres:

  • The policy test endpoint with object-level view and add grants on an ExpressionPolicy returns 200.
  • The same endpoint returns 403 with no grants. It also returns 403 with only the add grant, which exercises the decorator's own check.
  • The property mapping test endpoint with global ScopeMapping grants returns 200, and 403 without them.
  • Deleting an ExpressionPolicy through /policies/all/ returns 403 without a delete grant and 204 with an object-level delete_expressionpolicy.

All of these failed with WrongAppError before the change. Reverting only the decorator half still fails the two success cases. The existing authentik/rbac tests pass.

Linked issues

None found. I searched open issues for WrongAppError and for 500s on test endpoints with RBAC.


Checklist

  • The project has been linted, built, and tested (make all)
  • The documentation has been updated and formatted (make docs)
  • I have read the AI usage policy.
The base viewsets such as /policies/all/ return the concrete subclass
from get_object(), but permission_required and ObjectPermissions
checked the base model's permission, like authentik_policies.view_policy,
on that object. guardian raises WrongAppError when the subclass is in
another app, so every user without the global permission got a 500 from
endpoints like the policy and property mapping test actions, and from
deleting through a base viewset.

guardian stores object permissions under the subclass's content type
and refuses to store the base model's on a subclass instance, so check
the subclass's matching default permission instead. That is what the
subclass's own viewset requires. A global base permission still takes
priority.
@dominic-r dominic-r added area:backend backport/version-2026.8 Add this label to PRs to backport changes to version-2026.8 labels Oct 30, 2026
@dominic-r
dominic-r requested a review from a team as a code owner October 30, 2026 03:33
@dominic-r dominic-r self-assigned this Oct 30, 2026
@netlify

netlify Bot commented Oct 30, 2026

Copy link
Copy Markdown

✅ Deploy Preview for authentik-docs ready!

Name Link
🔨 Latest commit ff7adff
🔍 Latest deploy log https://app.netlify.com/projects/authentik-docs/deploys/6ae41011ef8bceb71b6d0949
😎 Deploy Preview https://deploy-preview-27366--authentik-docs.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@codecov

codecov Bot commented Oct 30, 2026 •

Copy link
Copy Markdown

❌ 1 Tests Failed:

Tests completed Failed Passed Skipped
4840 1 4839 14
View the top 1 failed test(s) by shortest run time
400-groups.test.ts::Groups › Simple group
Stack Traces | 121s run time
Test timeout of 60000ms exceeded.

To view more test analytics, go to the Test Analytics Dashboard
📋 Got 3 mins? Take this short survey to help us improve Test Analytics.

@github-actions

github-actions Bot commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

Playwright e2e

Download this run's HTML report and traces, then open the report:

gh run download 40998258428 -D playwright-artifacts
npx playwright show-report playwright-artifacts/playwright-report

Installation instructions

Instructions for docker-compose

Add the following block to your .env file:

AUTHENTIK_IMAGE=ghcr.io/goauthentik/dev-server
AUTHENTIK_TAG=gh-6bcc55949d64620a9dfebddaa39b397be4b3cf1b
# Distroless image: AUTHENTIK_TAG=gh-6bcc55949d64620a9dfebddaa39b397be4b3cf1b-distroless
AUTHENTIK_OUTPOSTS__CONTAINER_IMAGE_BASE=ghcr.io/goauthentik/dev-%(type)s:gh-%(build_hash)s

Afterwards, run the upgrade commands from the latest release notes.

Instructions for Kubernetes

Add the following block to your values.yml file:

authentik:
    outposts:
        container_image_base: ghcr.io/goauthentik/dev-%(type)s:gh-%(build_hash)s
global:
    image:
        repository: ghcr.io/goauthentik/dev-server
        tag: gh-6bcc55949d64620a9dfebddaa39b397be4b3cf1b
        # Distroless image: tag: gh-6bcc55949d64620a9dfebddaa39b397be4b3cf1b-distroless

Afterwards, run the upgrade commands from the latest release notes.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:backend backport/version-2026.8 Add this label to PRs to backport changes to version-2026.8

1 participant