casbin-rs

repository·master·Indexed 22 days ago

https://github.com/apache/casbin-rs

An efficient open-source authorization library for Rust that supports access control models including ACL, RBAC, ABAC, and RESTful. It utilizes the PERM metamodel to define access control logic via configuration files, allowing developers to switch authorization mechanisms without changing application code. The library provides a Management API, a high-level RBAC API, and support for custom functions using Rhai's Dynamic type and custom storage via the Adapter trait.

Tokens
12.8K
Snippets
50
Records
65
Agent score
78%

What's inside casbin-rs

  1. Supported access control models

    master

    Casbin-rs supports a wide variety of access control models, including:

    • ACL (Access Control List): Basic permission mapping.
    • RBAC (Role-Based Access Control): Users assigned to roles.
    • RBAC with domains/tenants: Roles scoped to specific domains.
    • ABAC (Attribute-Based Access Control): Permissions based on attributes (e.g., resource.Owner).
    • RESTful: Supports path patterns (e.g., /res/*) and HTTP methods.
    • Deny-override: Explicit deny rules that override allow rules.
    • Priority: Rules that can be prioritized like firewall rules.
  2. How Casbin models work (PERM metamodel)

    master

    Casbin uses the PERM metamodel to define access control logic. An access control model is defined in a .conf file consisting of four parts:

    1. Request definition (r): Defines the structure of the request being checked (e.g., sub, obj, act).
    2. Policy definition (p): Defines the structure of the stored policies.
    3. Policy effect (e): Defines how the results of multiple policy matches are combined (e.g., some(where (p.eft == allow)) means if any policy allows, the result is allow).
    4. Matchers (m): Defines the logic used to match a request against a policy.

    By modifying this configuration, you can switch between different authorization mechanisms (like ACL, RBAC, or ABAC) without changing your application code.

    # Request definition
    [request_definition]
    r = sub, obj, act
    
    # Policy definition
    [policy_definition]
    p = sub, obj, act
    
    # Policy effect
    [policy_effect]
    e = some(where (p.eft == allow))
    
    # Matchers
    [matchers]
    m = r.sub == p.sub && r.obj == p.obj && r.act == p.act
  3. Implement custom functions with different data types

    master

    When implementing custom functions, use Rhai's Dynamic methods to extract the required types from arguments:

    • Strings: Use dynamic_to_str(&dynamic) from casbin::model::function_map or .to_string().
    • Integers: Use .as_int().unwrap_or(default_value).
    • Booleans: Use .as_bool().unwrap_or(default_value).
    • Floats: Use .as_float().unwrap_or(default_value).

    Examples

    String-based (using dynamic_to_str)

    use casbin::model::function_map::dynamic_to_str;
    
    e.add_function(
        "stringContains",
        OperatorFunction::Arg2(|haystack: Dynamic, needle: Dynamic| {
            let haystack_str = dynamic_to_str(&haystack);
            let needle_str = dynamic_to_str(&needle);
            haystack_str.contains(needle_str.as_ref()).into()
        }),
    );

    Integer-based

    e.add_function(
        "greaterThan",
        OperatorFunction::Arg2(|a: Dynamic, b: Dynamic| {
            let a_int = a.as_int().unwrap_or(0);
            let b_int = b.as_int().unwrap_or(0);
            (a_int > b_int).into()
        }),
    );

    Multi-argument and Mixed-type

    e.add_function(
        "complexCheck",
        OperatorFunction::Arg3(|name: Dynamic, age: Dynamic, is_admin: Dynamic| {
            let name_str = name.to_string();
            let age_int = age.as_int().unwrap_or(0);
            let admin_bool = is_admin.as_bool().unwrap_or(false);
            
            let result = name_str.len() > 3 && age_int >= 18 && admin_bool;
            result.into()
        }),
    );
  4. Use Casbin authz middlewares for web frameworks

    master

    If you are building web applications, you can use specialized authorization middlewares to integrate Casbin's access control logic directly into your web framework's request lifecycle. These middlewares handle the extraction of subjects, objects, and actions from incoming requests automatically.

    For a list of supported middlewares and integration guides, refer to the official Casbin documentation: https://casbin.org/docs/middlewares

  5. Migrate custom functions from ImmutableString to Dynamic

    master

    If you are upgrading from an older version of Casbin-RS where custom functions used ImmutableString, you must update the function signatures to use Dynamic and manually convert the arguments.

    Old Pattern (ImmutableString):

    e.add_function(
        "myFunc",
        OperatorFunction::Arg2(
            |s1: ImmutableString, s2: ImmutableString| {
                // logic here
                true.into()
            }
        ),
    );

    New Pattern (Dynamic):

    e.add_function(
        "myFunc",
        OperatorFunction::Arg2(|s1: Dynamic, s2: Dynamic| {
            let str1 = s1.to_string();
            let str2 = s2.to_string();
            // logic here
            true.into()
        }),
    );
  6. Get started with casbin-rs

    master

    To use Casbin, initialize an Enforcer with a model configuration file and a policy file. You can then use the .enforce() method to check if a specific request is authorized.

    Important: The Enforcer instance is not thread-safe. If you need to access it across multiple threads, wrap it in an Arc<RwLock<Enforcer>>.

    use casbin::prelude::*;
    
    #[tokio::main]
    async fn main() -> Result<()> {
        // 1. Initialize the enforcer
        let mut e = Enforcer::new("examples/rbac_with_domains_model.conf", "examples/rbac_with_domains_policy.csv").await?;
        e.enable_log(true);
    
        // 2. Enforce a request
        // The arguments passed to enforce must match the [request_definition] in your model
        e.enforce(("alice", "domain1", "data1", "read"))?;
        Ok(())
    }
  7. Install casbin-rs

    master

    Add casbin to your Cargo.toml. It is recommended to use tokio v1.0 or later with casbin v2.0.6 or higher. You may need to enable specific features like runtime-async-std, logging, or incremental depending on your requirements.

    [dependencies]
    casbin = { version = "2.8.0", default-features = false, features = ["runtime-async-std", "logging", "incremental"] }
    tokio = { version = "1.10.0", features = ["fs", "io-util"] }
  8. What are Event and EventKey in casbin-rs?

    master

    In casbin-rs, events are categorized using the Event enum and identified via EventKey.

    • Event: A standard enum provided by the library containing core event types:

      • PolicyChange: Triggered when policies are added, removed, or cleared.
      • ClearCache: Triggered when the internal cache is invalidated.
    • EventKey: A trait used to constrain the type of keys used in an EventEmitter. Any type that implements Hash + PartialEq + Eq + Send + Sync automatically implements EventKey. This allows you to use the built-in Event enum or your own custom event types as keys for event subscription.

  9. How EnforceContext works

    master

    An EnforceContext allows the Enforcer to target specific model sections when multiple sets of request/policy/matcher/effector definitions exist in a single model.

    When you create a context with a suffix, the Enforcer generates type-specific keys for the lookup:

    • r_type: r + suffix
    • p_type: p + suffix
    • e_type: e + suffix
    • m_type: m + suffix

    This is essential for complex models where different rulesets coexist.

    let ctx = EnforceContext::new("2");
    // This context will target r2, p2, e2, and m2
  10. Initialize the Enforcer

    master

    The Enforcer is the primary interface for authorization enforcement and policy management. You can initialize it using a model and an adapter. If you use the new method, it will automatically load the policy from the adapter unless the adapter is filtered.

    Note: The initialization is asynchronous and requires a runtime like tokio or async-std.

    use casbin::prelude::*;
    
    #[cfg(feature = "runtime-tokio")]
    #[tokio::main]
    async fn main() -> Result<()> {
        let mut e = Enforcer::new("examples/basic_model.conf", "examples/basic_policy.csv").await?;
        assert_eq!(true, e.enforce(("alice", "data1", "read"))?);
        Ok(())
    }