rhailib/src/macros/docs/ARCHITECTURE.md
2025-06-27 12:09:50 +03:00

303 lines
8.5 KiB
Markdown

# 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:
```mermaid
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 user
- **`CONTEXT_ID`**: Identifies the target context (the circle)
- **`DB_PATH`**: Specifies the database location
### Authorization Levels
1. **Owner Access**: Direct access when `CALLER_ID == CONTEXT_ID`
2. **Circle Member Access**: Verified through `is_circle_member()` function
3. **Resource-Specific Access**: Granular permissions via `can_access_resource()`
### Access Control Flow
```mermaid
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`)
```rust
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:
```rust
move |context: rhai::NativeCallContext, /* parameters */| -> Result<ReturnType, Box<EvalAltResult>>
```
### Context Extraction Pattern
```rust
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
```rust
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
```rust
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
```rust
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)
```rust
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
1. **Context Errors**: Missing or invalid authentication context
2. **Type Conversion Errors**: Invalid ID formats or type mismatches
3. **Authorization Errors**: Access denied or insufficient permissions
4. **Database Errors**: Connection failures or query errors
5. **Not Found Errors**: Requested resources don't exist
### Error Propagation Pattern
```rust
.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
```rust
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
```rust
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
1. Create new macro following existing patterns
2. Implement authorization logic specific to operation
3. Add error handling for operation-specific failures
4. Register with DSL modules using macro
### Custom Authorization Logic
```rust
// 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.