AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
SKILL verified MIT Self-run

Rbac Permissions

skill-deadlymind-nanolama-rbac-permissions · by Deadlymind

Gates access inside a tenant with a {resource}_{action} role system on Django/DRF — a deny-by-default permission class that maps the ViewSet action to a codename and checks has_permission/has_object_permission, plus explicit guards on every custom @action. Use when adding or reviewing a DRF permission class, wiring permission_classes on a ViewSet, deciding who may list/create/update/delete a reso…

No reviews yet
0 installs
21 views
0.0% view→install

Install

$ agentstack add skill-deadlymind-nanolama-rbac-permissions

✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access Used
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets No
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

View the full security report →

Verified badge

Passed review? Show it. Paste this badge into your README, it links to the public security report.

AgentStack Verified badge Links to your public security report.
[![AgentStack Verified](https://agentstack.voostack.com/badges/verified.svg)](https://agentstack.voostack.com/security/report/skill-deadlymind-nanolama-rbac-permissions)

Reliability & compatibility

Security review passed
0 installs to date
no reviews yet
24d ago

Declared compatibility

Claude CodeClaude Desktop

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.

How agent discovery & health will work →
Are you the author of Rbac Permissions? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

RBAC permissions ({resource}_{action}, deny-by-default)

When to use

Deciding who inside a tenant may perform an action on a resource. Tenant scoping (multi-tenancy) answers "which rows exist for this user"; RBAC answers "may this user list/create/update/delete them, or fire this custom action". Keep the two separate — a queryset filter is not an authorization check.

Pattern

Roles grant named permissions of the form {resource}_{action} (e.g. invoice_create, invoice_delete). One DRF permission class:

  1. Deny by default. Return False unless the user's roles grant the required

codename. No codename resolved → deny; anonymous → deny.

  1. Map the DRF action to the codename. create/list/retrieve/update/

partial_update/destroy and every custom @action map to one {resource}_{action} string. has_object_permission re-checks per object.

  1. Guard every custom @action explicitly. A custom action gets no codename for

free; an unmapped action must fail closed, never fall through to allow.

One reusable class carries all three rules; the ViewSet declares its perm_base and maps each custom @action to its codename half in perm_action_map, so the class-level check resolves the codename before the handler body runs (DRF checks permissions in initial(), ahead of your action code):

# accounts/permissions.py — one deny-by-default class + its wiring
from rest_framework.decorators import action
from rest_framework.permissions import BasePermission

_ACTION_MAP = {  # DRF action -> {action} half of the codename
    "list": "read", "retrieve": "read", "create": "create",
    "update": "update", "partial_update": "update", "destroy": "delete",
}

class HasResourcePermission(BasePermission):
    def has_permission(self, request, view):
        if not request.user.is_authenticated:
            return False
        base = getattr(view, "perm_base", None)             # e.g. "invoice"
        # custom @actions declare their half in perm_action_map; else map the DRF action
        act = getattr(view, "perm_action_map", {}).get(view.action) or _ACTION_MAP.get(view.action)
        if not base or not act:
            return False                                    # unmapped -> deny closed
        return request.user.has_perm(f"{base}_{act}")       # your role lookup

    def has_object_permission(self, request, view, obj):
        return self.has_permission(request, view)           # re-check per object

class InvoiceViewSet(TenantScopedViewSet):                  # scoping from multi-tenancy
    permission_classes = [HasResourcePermission]
    perm_base = "invoice"
    perm_action_map = {"approve": "approve"}               # custom @action -> codename half

    @action(detail=True, methods=["post"])
    def approve(self, request, pk=None):
        invoice = self.get_object()                        # runs has_object_permission -> invoice_approve
        ...                                                # class-level check already passed in initial()

Adapt to your repo

Replace has_perm with your actual role lookup (a roles/permissions M2M, a cached set on the user, a claim in the JWT — resolve it server-side, never from the request body). Rename perm_base values to your resources and settle the codename verbs (read/create/update/delete, or view/add/change). Confirm the tenant FK is still named entreprise (rename to your tenant) and that scoping lives in get_queryset, not here.

Gotchas

  • Row-scoping is not authorization. get_queryset filtering by entreprise hides

other tenants' rows; it does not stop an in-tenant user without the role. Keep both.

  • Custom @actions bypass the default action map — map each one in perm_action_map

(resolved at check time, not in the handler body) so an unmapped action denies, and fetch detail objects via get_object() so the per-object check runs.

  • has_object_permission runs only on single-object lookups fetched via

get_object(); list endpoints are gated by has_permission alone.

  • Never trust a role/permission name sent in the request — resolve it from

request.user server-side (see security-review).

  • Order matters — an empty or missing perm_base/action must fail closed, so guard

the None case before building the codename string.

  • UI-level checks are UX, never access control. Hiding a button or nav item by role

only tidies the screen; the API endpoint is directly callable (curl, devtools, a scripted client), so the server must re-authorize every request. A hidden button protects nothing.

  • A deny-by-default class returns 403 for a codename that was never registered/seeded.

A new {resource}_{action} must exist in the permission store before the gate can grant it — otherwise has_perm finds nothing and denies. This is the number-one cause of "my new endpoint always returns 403"; seed the codename (and grant it to a role) first.

See also

  • multi-tenancy
  • drf-api
  • security-review

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.