Policies¶
The core.policies package defines the backend authorization layer.
It provides reusable, testable permission logic that can be called from services, views, or other infrastructure code.
Unlike DRF permission classes, policies are domain-level authorization rules.
They answer questions like:
- Can this user manage org members?
- Can this user manage teams?
- Can this user view this team?
- Can this user manage this team membership?
Purpose¶
The policy layer exists to prevent authorization logic from being scattered across the codebase.
Without policies, apps tend to grow:
- repeated role checks
- inconsistent authorization behavior
- view-specific permission logic
- duplicated service rules
The policy layer centralizes those decisions into a single reusable abstraction.
Design Goals¶
Policies are designed to be:
- explicit
- reusable
- testable
- independent from HTTP
- independent from serializers
- independent from view structure
They should represent authorization intent, not framework plumbing.
Relationship to Other Layers¶
Policies sit between raw role data and business services.
- Models store memberships and roles
- Policies translate those roles into capabilities
- Services use those capabilities to decide whether an action is allowed
- Views should avoid direct role checks
High-Level Flow¶
flowchart LR
A[Memberships and Roles] --> B[Policy Layer]
B --> C[Capabilities]
C --> D[Service Authorization]
D --> E[Business Action]
What a Policy Does¶
A policy helper should answer a focused authorization question.
Examples:
- can_manage_org_members(user, org)
- can_manage_teams(user, org)
- can_view_team(user, team)
- can_manage_team_membership(user, membership)
These helpers should return a boolean and avoid side effects.
What a Policy Should NOT Do¶
Policies should not: - save database state - raise HTTP responses - contain business workflow mutations - create audit logs - parse serializers - replace service-layer validation
Policies only answer:
Is this actor allowed to do this kind of thing?
Package Structure¶
The current policy package is split into:
common.pyorgs.pyteams.pypermissions.py
common.py¶
This module contains basic helpers shared by all policy modules.
Examples:
- is_authenticated_user
- is_superuser
These functions are intentionally small and reusable.
orgs.py¶
This module defines org-scoped capabilities and role-to-capability mapping.
Main responsibilities¶
- resolve active org membership
- resolve org role
- compute org capabilities
- expose capability helpers
Capability model¶
The policy layer uses a capability-based design.
Instead of scattering raw role checks everywhere, it maps roles to capabilities such as:
manage_org_settingsview_org_membersmanage_org_membersview_customer_linksmanage_customer_linksview_all_teamsmanage_teamsmanage_team_memberships
Example usage¶
can_manage_org_members(user, org)can_manage_teams(user, org)can_view_all_teams(user, org)
Why this matters¶
This allows the system to evolve from simple role checks toward a more flexible authorization model without rewriting all service logic.
teams.py¶
This module defines team-scoped authorization helpers.
Main responsibilities¶
- resolve active team membership
- determine whether a user is a team member
- determine whether a user can view a team
- determine whether a user can manage team members or team memberships
Example usage¶
can_view_team(user, team)can_manage_team(user, team)can_manage_team_members(user, team)can_manage_team_membership(user, membership)
Team visibility rule¶
A user can view a team if: - they are a superuser, or - they have org-level capability to view all teams, or - they are an active member of that team
This allows privileged users to see all teams while preserving limited visibility for normal team members.
permissions.py¶
This module contains DRF permission classes that bridge request-level access control to the policy/context system.
Examples:
- HasCurrentOrg
- HasCurrentCustomerOrg
- IsInternalUser
- IsCustomerUser
These are framework adapters, not domain policies.
They are packaged here for convenience, but conceptually they differ from capability helpers.
Role-to-Capability Mapping¶
The current system maps org roles to capabilities.
Owner¶
Has full org capabilities.
Admin¶
Has strong operational capabilities, including: - member management - team management - settings access
Manager¶
Has team and visibility capabilities, but not full org administration.
Engineer¶
Has no broad management capabilities by default.
Viewer¶
Has no broad management capabilities by default.
Policy vs Service Rules¶
This distinction is important.
Policies decide broad authorization¶
Examples: - user may manage org members - user may manage teams - user may view all teams
Services decide workflow constraints¶
Examples: - admin cannot manage owners - user cannot manage their own membership in admin flows - organization must have at least one active owner - inactive team membership cannot become primary - user must belong to org before being added to a team
This keeps policies clean and services responsible for domain-state validation.
Example: Org Membership Change¶
When changing an org membership role:
- Service receives request
- Service asks policy layer whether actor can manage org members
- Service applies target-specific workflow rules
- Service performs mutation
- Service writes audit log
The policy layer does not perform the mutation itself.
Example: Team Visibility¶
When listing team members:
- Service resolves team
- Service asks
can_view_team(user, team) - If true, list members
- If false, raise permission error
This prevents views from duplicating visibility logic.
Why Policies Improve the Codebase¶
Policies improve the backend by making authorization:
- centralized
- understandable
- easier to test
- easier to change
- less duplicated
They also reduce coupling between:
- views and roles
- services and raw membership queries
- individual apps and authorization details
Testing Strategy¶
Policies are tested directly in the core test suite.
Tests verify:
- role resolution
- capability mapping
- team visibility
- team management permissions
- membership management permissions
This is important because policies are foundational infrastructure.
If a policy changes incorrectly, many apps can break at once.
Usage Guidelines¶
Use policies when:
- deciding whether a user can perform an action
- checking org- or team-scoped capabilities
- replacing raw role checks in services or views
Do not use policies for:
- validation of business state
- persistence logic
- audit logging
- serializer validation
Current Direction¶
The current policy layer is intentionally simple.
It is not yet a full RBAC or ABAC framework.
It provides:
- capability-based authorization
- role abstraction
- reusable helper functions
This gives the project a strong foundation while keeping complexity under control.
Future Evolution¶
The policy layer can later evolve into:
- richer capability sets
- object-specific policies
- feature-flag-aware permissions
- hybrid RBAC / ABAC authorization
The current design supports that evolution without requiring large-scale rewrites.
Summary¶
The core.policies package is the authorization backbone of the backend.
It converts raw role and membership data into reusable capabilities and access decisions.
This makes the system safer, more consistent, and easier to maintain across all apps.