8.5 KiB
Architecture of the macros
Crate
The macros
crate provides authorization mechanisms and procedural macros for Rhai functions that interact with databases. It implements a comprehensive security layer that ensures proper access control for all database operations within the Rhai scripting environment.
Core Architecture
The crate follows a macro-driven approach to authorization, providing declarative macros that generate secure database access functions:
graph TD
A[macros Crate] --> B[Authorization Functions]
A --> C[Utility Functions]
A --> D[Registration Macros]
B --> B1[Context Extraction]
B --> B2[Access Control Checks]
B --> B3[Circle Membership Validation]
C --> C1[ID Conversion]
C --> C2[Error Handling]
D --> D1[register_authorized_get_by_id_fn!]
D --> D2[register_authorized_create_by_id_fn!]
D --> D3[register_authorized_delete_by_id_fn!]
D --> D4[register_authorized_list_fn!]
D1 --> E[Generated Rhai Functions]
D2 --> E
D3 --> E
D4 --> E
E --> F[Database Operations]
F --> G[Secure Data Access]
Security Model
Authentication Context
All operations require authentication context passed through Rhai's NativeCallContext
:
CALLER_ID
: Identifies the requesting userCONTEXT_ID
: Identifies the target context (the circle)DB_PATH
: Specifies the database location
Authorization Levels
- Owner Access: Direct access when
CALLER_ID == CONTEXT_ID
- Circle Member Access: Verified through
is_circle_member()
function - Resource-Specific Access: Granular permissions via
can_access_resource()
Access Control Flow
sequenceDiagram
participant Script as Rhai Script
participant Macro as Generated Function
participant Auth as Authorization Layer
participant DB as Database
Script->>Macro: Call authorized function
Macro->>Auth: Extract caller context
Auth->>Auth: Validate CALLER_ID
Auth->>Auth: Check circle membership
Auth->>Auth: Verify resource access
Auth->>DB: Execute database operation
DB->>Macro: Return result
Macro->>Script: Return authorized data
Core Components
1. Utility Functions
ID Conversion (id_from_i64_to_u32
)
pub fn id_from_i64_to_u32(id_i64: i64) -> Result<u32, Box<EvalAltResult>>
Safely converts Rhai's i64
integers to Rust's u32
IDs with proper error handling.
2. Authorization Macros
Get By ID (register_authorized_get_by_id_fn!
)
Generates functions for retrieving single resources by ID with authorization checks.
Features:
- ID validation and conversion
- Caller authentication
- Resource-specific access control
- Database error handling
- Not found error handling
Create Resource (register_authorized_create_by_id_fn!
)
Generates functions for creating new resources with authorization.
Features:
- Circle membership validation
- Object persistence
- Creation authorization
- Database transaction handling
Delete By ID (register_authorized_delete_by_id_fn!
)
Generates functions for deleting resources by ID with authorization.
Features:
- Deletion authorization
- Circle membership validation
- Cascade deletion handling
- Audit trail support
List Resources (register_authorized_list_fn!
)
Generates functions for listing resources with filtering based on access rights.
Features:
- Bulk authorization checking
- Result filtering
- Collection wrapping
- Performance optimization
Generated Function Architecture
Function Signature Pattern
All generated functions follow a consistent pattern:
move |context: rhai::NativeCallContext, /* parameters */| -> Result<ReturnType, Box<EvalAltResult>>
Context Extraction Pattern
let tag_map = context
.tag()
.and_then(|tag| tag.read_lock::<rhai::Map>())
.ok_or_else(|| /* error */)?;
let caller_pk = tag_map.get("CALLER_ID")?.into_string()?;
let context_id = tag_map.get("CONTEXT_ID")?.into_string()?;
let db_path = tag_map.get("DB_PATH")?.into_string()?;
Database Connection Pattern
let db_path = format!("{}/{}", db_path, context_id);
let db = Arc::new(OurDB::new(db_path, false).expect("Failed to create DB"));
Authorization Strategies
1. Circle-Based Authorization
if context_id != caller_pk_str {
let is_circle_member = heromodels::models::access::access::is_circle_member(
db.clone(),
&caller_pk_str,
);
if !is_circle_member {
return Err(/* authorization error */);
}
}
2. Resource-Specific Authorization
let has_access = heromodels::models::access::access::can_access_resource(
db.clone(),
&caller_pk_str,
actual_id,
resource_type_str,
);
if !has_access {
return Err(/* access denied error */);
}
3. Bulk Authorization (for lists)
let authorized_items: Vec<ResourceType> = all_items
.into_iter()
.filter(|item| {
let resource_id = item.id();
heromodels::models::access::access::can_access_resource(
db.clone(),
&caller_pk_str,
resource_id,
resource_type_str,
)
})
.collect();
Error Handling Architecture
Error Categories
- Context Errors: Missing or invalid authentication context
- Type Conversion Errors: Invalid ID formats or type mismatches
- Authorization Errors: Access denied or insufficient permissions
- Database Errors: Connection failures or query errors
- Not Found Errors: Requested resources don't exist
Error Propagation Pattern
.map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Database error: {:?}", e).into(),
context.position(),
))
})?
Performance Considerations
Database Connection Management
- Connection Per Operation: Each function creates its own database connection
- Path-Based Isolation: Database paths include circle identifiers for isolation
- Connection Pooling: Relies on underlying database implementation
Authorization Caching
- No Caching: Authorization checks are performed for each operation
- Stateless Design: No session state maintained between calls
- Fresh Validation: Ensures up-to-date permission checking
Bulk Operations Optimization
- Filtered Iteration: List operations filter results after database fetch
- Lazy Evaluation: Authorization checks only performed on accessed items
- Memory Efficiency: Results collected into appropriate wrapper types
Integration Patterns
Macro Usage in DSL Modules
register_authorized_get_by_id_fn!(
module: &mut module,
rhai_fn_name: "get_company",
resource_type_str: "Company",
rhai_return_rust_type: heromodels::models::biz::company::Company
);
Context Setup in Engine
let mut context_map = rhai::Map::new();
context_map.insert("CALLER_ID".into(), caller_pk.into());
context_map.insert("CONTEXT_ID".into(), context_id.into());
context_map.insert("DB_PATH".into(), db_path.into());
engine.set_tag(context_map);
Security Considerations
Authentication Requirements
- Mandatory Context: All operations require valid authentication context
- Public Key Validation: Caller identity verified through cryptographic keys
- Circle Membership: Hierarchical access control through circle membership
Authorization Granularity
- Resource-Level: Individual resource access control
- Operation-Level: Different permissions for read/write/delete operations
- Circle-Level: Organization-based access boundaries
Audit and Logging
- Operation Logging: All database operations include caller identification
- Access Logging: Authorization decisions are logged for audit trails
- Error Logging: Failed authorization attempts are recorded
Extensibility
Adding New Operations
- Create new macro following existing patterns
- Implement authorization logic specific to operation
- Add error handling for operation-specific failures
- Register with DSL modules using macro
Custom Authorization Logic
// Custom authorization can be added within macro implementations
if requires_special_permission {
let has_special_access = check_special_permission(db.clone(), &caller_pk_str);
if !has_special_access {
return Err(/* custom error */);
}
}
This architecture provides a robust, secure foundation for database operations within the Rhai scripting environment while maintaining flexibility for future extensions and customizations.