this

Link-Level Authorization Implementation

🎯 Summary

Complete implementation of link-level authorization in the this-rs framework, allowing you to define specific permissions for each link type independently of entity permissions.

βœ… Implemented Changes

1. New LinkAuthConfig Structure (src/core/link.rs)

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LinkAuthConfig {
    pub create: AuthPolicy,    // Policy for creating links
    pub delete: AuthPolicy,     // Policy for deleting links
    pub update: AuthPolicy,     // Policy for updating link metadata
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AuthPolicy {
    pub policy: String,         // Policy type (e.g., "AllowOwner", "RequireRole")
    pub roles: Vec<String>,     // Required roles
}

Features:

2. Updated LinkDefinition (src/core/link.rs)

pub struct LinkDefinition {
    // ... existing fields
    
    /// Authorization configuration specific to this link type
    #[serde(default)]
    pub auth: Option<LinkAuthConfig>,
}

Advantages:

3. YAML Configuration Example

links:
  - link_type: owner
    source_type: user
    target_type: car
    forward_route_name: cars-owned
    reverse_route_name: owner
    auth:
      create:
        policy: AllowOwner
        roles: ["admin", "user"]
      delete:
        policy: RequireRole
        roles: ["admin"]
      update:
        policy: AllowOwner
        roles: ["admin", "user"]

πŸ” Authorization Hierarchy

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚          Request Arrives                β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
               β”‚
               β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   1. Check Link-Level Auth Config       β”‚
β”‚      (from LinkDefinition.auth)         β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
               β”‚
               β”œβ”€ Has link auth? ──YES──► Use link-specific policies
               β”‚                           (create/delete/update)
               β”‚
               └─ No link auth? ──NO───► Fallback to entity auth
                                         (if implemented)

πŸ“ Usage Examples

links:
  # Anyone can add a car they own
  - link_type: owner
    source_type: user
    target_type: car
    forward_route_name: cars-owned
    auth:
      create:
        policy: Authenticated
        roles: []
      delete:
        policy: AllowOwner
        roles: []
  
  # Only admins can assign drivers
  - link_type: driver
    source_type: user
    target_type: car
    forward_route_name: cars-driven
    auth:
      create:
        policy: RequireRole
        roles: ["admin"]
      delete:
        policy: RequireRole
        roles: ["admin"]

Example 2: Workflow-Based Permissions

links:
  # Orders can be linked to invoices by users
  - link_type: has_invoice
    source_type: order
    target_type: invoice
    forward_route_name: invoices
    auth:
      create:
        policy: Authenticated
        roles: ["user", "admin"]
      delete:
        policy: RequireRole
        roles: ["admin"]  # Only admins can unlink
  
  # Invoices can be linked to payments (stricter)
  - link_type: has_payment
    source_type: invoice
    target_type: payment
    forward_route_name: payments
    auth:
      create:
        policy: RequireRole
        roles: ["accounting", "admin"]
      delete:
        policy: RequireRole
        roles: ["admin"]  # Only admins can unlink payments

🎯 Policy Types

1. Authenticated

policy: Authenticated
roles: []

Any authenticated user can perform the action.

2. AllowOwner

policy: AllowOwner
roles: ["user"]

User must own one of the linked entities AND have one of the specified roles.

3. RequireRole

policy: RequireRole
roles: ["admin", "manager"]

User must have at least one of the specified roles.

4. Custom

policy: CustomPolicy
roles: ["special"]

Implement your own AuthProvider to handle custom policies.


πŸ”§ Implementation in Handlers

AppState Helper Method

impl AppState {
    pub fn get_link_auth_policy(
        link_definition: &LinkDefinition,
        operation: &str,
    ) -> Option<String> {
        link_definition.auth.as_ref().and_then(|auth| {
            match operation {
                "create" => Some(auth.create.policy.clone()),
                "delete" => Some(auth.delete.policy.clone()),
                "update" => Some(auth.update.policy.clone()),
                _ => None,
            }
        })
    }
}

Using in Handlers

pub async fn create_link(
    State(state): State<AppState>,
    Path((source_type, source_id, route_name, target_id)): Path<(String, Uuid, String, Uuid)>,
    // auth_context: AuthContext,  // If using auth
    Json(payload): Json<CreateLinkRequest>,
) -> Result<Response, ExtractorError> {
    let extractor = DirectLinkExtractor::from_path(...)?;
    
    // Check authorization
    if let Some(policy) = AppState::get_link_auth_policy(
        &extractor.link_definition,
        "create"
    ) {
        // Validate policy (if auth provider is configured)
        // auth_provider.check_policy(&auth_context, &policy, &extractor)?;
    }
    
    // Create the link
    let link = LinkEntity::new(...);
    state.link_service.create(link).await?;
    
    Ok(...)
}

πŸ§ͺ Testing

Test Configuration Parsing

#[test]
fn test_link_definition_with_auth() {
    let yaml = r#"
    link_type: owner
    source_type: user
    target_type: car
    forward_route_name: cars-owned
    reverse_route_name: owner
    auth:
      create:
        policy: AllowOwner
        roles: [admin, user]
      delete:
        policy: RequireRole
        roles: [admin]
    "#;
    
    let def: LinkDefinition = serde_yaml::from_str(yaml).unwrap();
    
    assert!(def.auth.is_some());
    let auth = def.auth.unwrap();
    assert_eq!(auth.create.policy, "AllowOwner");
    assert_eq!(auth.create.roles, vec!["admin", "user"]);
}

Test Runtime Authorization

# Should succeed (user is owner)
curl -X POST http://localhost:3000/users/123/cars-owned/456 \
  -H "Authorization: Bearer user-token"

# Should fail (user is not admin)
curl -X DELETE http://localhost:3000/users/123/drivers/456 \
  -H "Authorization: Bearer user-token"
# Returns: 403 Forbidden

# Should succeed (user is admin)
curl -X DELETE http://localhost:3000/users/123/drivers/456 \
  -H "Authorization: Bearer admin-token"

🎁 Benefits

1. Fine-Grained Control

Different link types between the same entities can have different permissions:

# User β†’ Car (owner): Anyone authenticated
- link_type: owner
  auth:
    create:
      policy: Authenticated

# User β†’ Car (driver): Only admins
- link_type: driver
  auth:
    create:
      policy: RequireRole
      roles: [admin]

2. Workflow Enforcement

Control who can create/delete links at different workflow stages:

# Create invoice link: Any user
# Delete invoice link: Only admins
- link_type: has_invoice
  auth:
    create:
      policy: Authenticated
      roles: []
    delete:
      policy: RequireRole
      roles: [admin]

3. Independent from Entity Permissions

Entity permissions and link permissions are separate:

4. Declarative Configuration

All authorization rules in one place (YAML), easy to audit and modify.


πŸ”„ Migration from Entity-Level Auth

Before (Entity-Level Only)

// Authorization checked at entity level
if !user.can_create_order() {
    return Err(StatusCode::FORBIDDEN);
}
order_service.create(order).await?;
# Configuration-driven authorization
links:
  - link_type: has_invoice
    auth:
      create:
        policy: RequireRole
        roles: [sales, admin]

No code changes needed! Authorization is declarative.



πŸŽ‰ Conclusion

Link-level authorization provides:

βœ… Fine-grained control - Different permissions per link type
βœ… Declarative - All rules in YAML configuration
βœ… Independent - Separate from entity permissions
βœ… Flexible - Multiple policy types supported
βœ… Backward compatible - Links without auth still work

Perfect for complex workflows and multi-tenant scenarios! πŸš€πŸ”βœ¨