Crate gatehouse

Source
Expand description

A flexible authorization library that combines role‐based (RBAC), attribute‐based (ABAC), and relationship‐based (ReBAC) policies. The library provides a generic Policy trait for defining custom policies, a builder pattern for creating custom policies as well as several built-in policies for common use cases, and combinators for composing complex authorization logic.

§Overview

A Policy is an asynchronous decision unit that checks if a given subject may perform an action on a resource within a given context. Policies implement the Policy trait. A PermissionChecker aggregates multiple policies and uses OR logic by default (i.e. if any policy grants access, then access is allowed). The PolicyBuilder offers a builder pattern for creating custom policies.

§Built in Policies

The library provides a few built-in policies:

  • RbacPolicy: A role-based access control policy.
  • AbacPolicy: An attribute-based access control policy.
  • RebacPolicy: A relationship-based access control policy.

§Custom Policies

Below we define a simple system where a user may read a document if they are an admin (via a simple role-based policy) or if they are the owner of the document (via an attribute-based policy).


// Define our core types.
#[derive(Debug, Clone)]
pub struct User {
    pub id: Uuid,
    pub roles: Vec<String>,
}

#[derive(Debug, Clone)]
pub struct Document {
    pub id: Uuid,
    pub owner_id: Uuid,
}

#[derive(Debug, Clone)]
pub struct ReadAction;

#[derive(Debug, Clone)]
pub struct EmptyContext;

// A simple RBAC policy: grant access if the user has the "admin" role.
struct AdminPolicy;
#[async_trait]
impl Policy<User, Document, ReadAction, EmptyContext> for AdminPolicy {
    async fn evaluate_access(
        &self,
        user: &User,
        _action: &ReadAction,
        _resource: &Document,
        _context: &EmptyContext,
    ) -> PolicyEvalResult {
        if user.roles.contains(&"admin".to_string()) {
            PolicyEvalResult::Granted {
                policy_type: self.policy_type(),
                reason: Some("User is admin".to_string()),
            }
        } else {
            PolicyEvalResult::Denied {
                policy_type: self.policy_type(),
                reason: "User is not admin".to_string(),
            }
        }
    }
    fn policy_type(&self) -> String { "AdminPolicy".to_string() }
}

// An ABAC policy: grant access if the user is the owner of the document.
struct OwnerPolicy;

#[async_trait]
impl Policy<User, Document, ReadAction, EmptyContext> for OwnerPolicy {
    async fn evaluate_access(
        &self,
        user: &User,
        _action: &ReadAction,
        document: &Document,
        _context: &EmptyContext,
    ) -> PolicyEvalResult {
        if user.id == document.owner_id {
            PolicyEvalResult::Granted {
                policy_type: self.policy_type(),
                reason: Some("User is the owner".to_string()),
            }
        } else {
            PolicyEvalResult::Denied {
               policy_type: self.policy_type(),
               reason: "User is not the owner".to_string(),
           }
        }
    }
    fn policy_type(&self) -> String {
        "OwnerPolicy".to_string()
    }
}

// Create a PermissionChecker (which uses OR semantics by default) and add both policies.
fn create_document_checker() -> PermissionChecker<User, Document, ReadAction, EmptyContext> {
    let mut checker = PermissionChecker::new();
    checker.add_policy(AdminPolicy);
    checker.add_policy(OwnerPolicy);
    checker
}

let admin_user = User {
    id: Uuid::new_v4(),
    roles: vec!["admin".into()],
};

let owner_user = User {
    id: Uuid::new_v4(),
    roles: vec!["user".into()],
};

let document = Document {
    id: Uuid::new_v4(),
    owner_id: owner_user.id,
};

let checker = create_document_checker();

// An admin should have access.
assert!(checker.evaluate_access(&admin_user, &ReadAction, &document, &EmptyContext).await.is_granted());

// The owner should have access.
assert!(checker.evaluate_access(&owner_user, &ReadAction, &document, &EmptyContext).await.is_granted());

// A random user should be denied access.
let random_user = User {
    id: Uuid::new_v4(),
    roles: vec!["user".into()],
};
assert!(!checker.evaluate_access(&random_user, &ReadAction, &document, &EmptyContext).await.is_granted());

§Evaluation Tracing

The permission system provides detailed tracing of policy decisions, see AccessEvaluation for an example.

§Combinators

Sometimes you may want to require that several policies pass (AND), require that at least one passes (OR), or even invert a policy (NOT). gatehouse provides combinators for this purpose:

  • AndPolicy: Grants access only if all inner policies allow access. Otherwise, returns a combined error.
  • OrPolicy: Grants access if any inner policy allows access; otherwise returns a combined error.
  • NotPolicy: Inverts the decision of an inner policy.

Structs§

AbacPolicy
An attribute-based access control policy. Define a condition closure that determines whether a subject is allowed to perform an action on a resource, given the additional context. If it returns true, access is granted. Otherwise, access is denied.
AndPolicy

EmptyPoliciesError
Error returned when no policies are provided to a combinator policy.
EvalTrace
Container for the evaluation tree Detailed trace of all policy evaluations
NotPolicy
NotPolicy
OrPolicy
OrPolicy
PermissionChecker
A container for multiple policies, applied in an “OR” fashion. (If any policy returns Ok, access is granted) Important: If no policies are added, access is always denied.
PolicyBuilder
A builder API for creating custom policies.
RbacPolicy
A role-based access control policy.
RebacPolicy
ReBAC Policy

Enums§

AccessEvaluation
The complete result of a permission evaluation. Contains both the final decision and a detailed trace for debugging.
CombineOp
The type of boolean combining operation a policy might represent.
Effect
Represents the intended effect of a policy.
PolicyEvalResult
The result of evaluating a single policy (or a combination).

Traits§

Policy
A generic async trait representing a single authorization policy. A policy determines if a subject is allowed to perform an action on a resource within a given context.
RelationshipResolver
A trait that abstracts a relationship resolver. Given a subject and a resource, the resolver answers whether the specified relationship e.g. “creator”, “manager” exists between them.