Pattern

Policy Engines

A policy engine evaluates rules to determine authorization and data visibility. It separates access control logic from business logic, making permissions explicit, testable, and maintainable.

Part 1: Policy Engines

Overview

A policy engine is a system that evaluates rules to make authorization decisions. Instead of embedding permission checks throughout your application code, you centralize them in a policy engine that can evaluate rules based on the current context: the user, the resource, the action, and any relevant attributes.

Policy engines provide a declarative way to express access control rules. They separate the "what" (what actions are allowed) from the "who" (who can perform them), making authorization logic easier to understand, test, and modify.

In collaborative systems, policy engines are essential for managing complex permission models where different users have different capabilities based on their roles, relationships to resources, or other contextual factors.

When to use this pattern

Use a policy engine when you have:

  • Complex authorization rules: Permissions that depend on multiple factors: user roles, resource ownership, state, relationships, or contextual attributes
  • Dynamic permissions: Rules that change based on state, time, or other runtime conditions
  • Multiple authorization concerns: You need to control both who can perform actions and what data they can see
  • Need for maintainability: Authorization rules change frequently, and you want to update them without modifying application code
  • Audit requirements: You need to track and explain why certain actions were allowed or denied
  • Role-based or attribute-based access control: Permissions based on user attributes, roles, or relationships to resources

Policy engines are particularly valuable in multi-tenant systems, collaborative applications, and any system where authorization logic is complex or frequently changing.

Core concepts

Rules Engine

The core component that processes the rules and applies them to data. The rules engine takes rules from the repository and evaluates them against incoming requests or data.

Rules Repository

Stores the policies or rules in a separate, configurable location like a database or file. This separation allows rules to be managed independently from the application code, making them easier to update and maintain.

Policy Evaluator

The part of the engine that evaluates requests against the rules. The evaluator takes a request (containing user, action, resource, and context) and applies the relevant rules to determine the outcome (allow, deny, or filtered data).

Example: Document review authorization

Consider a document review system where authorization depends on multiple factors:

Scenario: User "alice" wants to approve document "doc-123"
Context:
  • User: alice (role: Reviewer)
  • Resource: doc-123 (status: Submitted, owner: bob)
  • Action: ApproveDocument
Policy evaluation:
  1. Check if user has "Reviewer" role → ✓
  2. Check if document is in "Submitted" state → ✓
  3. Check if user is not the document owner → ✓ (alice ≠ bob)
  4. Check if user has not already reviewed this document → ✓
Result: Allow (all policy conditions met)

The policy engine evaluates all relevant rules and returns a decision. If any rule fails, the action is denied. This makes authorization logic explicit and auditable.

Benefits

  • Separation of concerns: Keeps complex rules out of the main application code, making the codebase cleaner and easier to understand
  • Maintainability: Allows rules to be updated or changed without modifying the application's core logic
  • Flexibility: Makes it easier to add or remove rules as requirements change
  • Testability: Individual rules can be tested in isolation, making it easier to verify correctness and catch bugs

Part 2: Policy Engines in the State-Based Collaboration Framework

How policy engines fit in the framework

In the State-Based Collaboration Framework, policy engines are used for two primary purposes:

Transition authorization

The policy engine determines whether a specific user is allowed to trigger a specific action/transition. This is separate from the state machine's role: the finite state machine (FSM) determines which transitions are allowed for an object (e.g., a document can transition from "Draft" to "Submitted"), while the policy engine determines whether a given user is allowed to trigger that transition (e.g., only the document owner can submit it).

Data filters

The policy engine also controls data visibility through filters. When users query projections or collections, the policy engine determines what data they can see based on their permissions, relationships, and other contextual factors.

FSM vs. Policy Engine: A key distinction

It's important to understand the separation of concerns between the state machine and the policy engine:

Finite State Machine (FSM):
  • Defines which transitions are structurally allowed for an object
  • Answers: "Can this object transition from state A to state B?"
  • Example: A document can transition from "Draft" to "Submitted" (structural rule)
Policy Engine:
  • Defines who is allowed to trigger a transition
  • Answers: "Is this user allowed to trigger this transition?"
  • Example: Only the document owner can submit it (authorization rule)

This separation allows you to model business rules (what transitions are possible) separately from authorization rules (who can trigger them). Both must pass for an action to succeed: the FSM must allow the transition, and the policy engine must authorize the user.

Action-control policies

In the framework, action-control policies are evaluated before a transition is executed. The policy engine receives:

  • The user attempting the action
  • The action/transition being triggered
  • The current state of the object
  • Any relevant attributes (ownership, relationships, etc.)

The policy engine evaluates all relevant rules and returns either allow or deny. If denied, the action fails before any state transition occurs.

Data filters

The framework also uses policy engines to filter data in projections. When a user queries a projection or collection, the policy engine determines what data they can see:

  • Collection filtering: Which items appear in a list (e.g., users only see documents they own or have access to)
  • Field filtering: Which fields are visible in an object (e.g., sensitive fields hidden from certain roles)
  • Relationship-based access: Data visibility based on user relationships to resources

This ensures that users only see data they're authorized to access, maintaining data privacy and security at the projection level.

Related patterns

This pattern works well with:

Explore more patterns

This pattern is one of many for building collaborative systems. Check out other patterns or dive deeper into the framework components.