Skip to main content

aura_core/effects/
capability.rs

1//! Capability Token Effects Trait Definitions
2//!
3//! This module defines trait interfaces for capability token operations including
4//! parsing, verification, creation, and management. Capability tokens provide
5//! fine-grained access control using cryptographically verifiable tokens.
6//!
7//! # Effect Classification
8//!
9//! - **Category**: Application Effect
10//! - **Implementation**: `aura-authorization` (Layer 2)
11//! - **Usage**: Capability token management (Biscuit, JWT, custom formats)
12//!
13//! This is an application effect implemented in domain crates by composing
14//! infrastructure effects (crypto, storage) with capability-specific logic.
15//!
16//! ## Security Model
17//!
18//! Capability tokens provide:
19//! - Cryptographic verification of permissions
20//! - Delegation and attenuation of capabilities
21//! - Time-bound and context-bound access control
22//! - Revocation and audit trails
23//!
24//! ## Token Format
25//!
26//! Supports multiple token formats:
27//! - Biscuit tokens (Datalog-based authorization)
28//! - JWT tokens (JSON Web Tokens)
29//! - Custom binary formats
30//! - Macaroon-style tokens
31
32use crate::AuraError;
33use async_trait::async_trait;
34use serde::{Deserialize, Serialize};
35use std::collections::HashMap;
36
37/// Capability token operation error
38pub type CapabilityError = AuraError;
39
40/// Types of capability token formats supported
41#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
42pub enum CapabilityTokenFormat {
43    /// Biscuit authorization tokens
44    Biscuit,
45    /// JSON Web Tokens (JWT)
46    Jwt,
47    /// Custom binary format
48    Binary,
49    /// Macaroon-style tokens
50    Macaroon,
51}
52
53/// Capability token verification level
54#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
55pub enum VerificationLevel {
56    /// Basic signature verification only
57    Basic,
58    /// Signature + expiration check
59    Standard,
60    /// Full verification including revocation checks
61    Full,
62    /// Strict verification with all security checks
63    Strict,
64}
65
66/// Result of capability token verification
67#[derive(Debug, Clone, Serialize, Deserialize)]
68pub struct CapabilityVerificationResult {
69    /// Whether the token is valid
70    pub valid: bool,
71    /// Extracted permissions from the token
72    pub permissions: Vec<String>,
73    /// Token expiration timestamp (if present)
74    pub expires_at: Option<u64>,
75    /// Subject (user/service) the token was issued for
76    pub subject: Option<String>,
77    /// Issuer of the token
78    pub issuer: Option<String>,
79    /// Additional claims/attributes from the token
80    pub claims: HashMap<String, String>,
81    /// Verification errors (if any)
82    pub errors: Vec<String>,
83    /// Verification level used
84    pub verification_level: VerificationLevel,
85}
86
87/// Capability token creation request
88#[derive(Debug, Clone, Serialize, Deserialize)]
89pub struct CapabilityTokenRequest {
90    /// Subject to issue the token for
91    pub subject: String,
92    /// Permissions to grant
93    pub permissions: Vec<String>,
94    /// Token expiration time (timestamp)
95    pub expires_at: Option<u64>,
96    /// Additional claims to include
97    pub claims: HashMap<String, String>,
98    /// Token format to use
99    pub format: CapabilityTokenFormat,
100    /// Context restrictions (optional)
101    pub context: Option<String>,
102}
103
104/// Information about a capability token
105#[derive(Debug, Clone, Serialize, Deserialize)]
106pub struct CapabilityTokenInfo {
107    /// Unique identifier for this token
108    pub token_id: String,
109    /// Token format
110    pub format: CapabilityTokenFormat,
111    /// Subject the token was issued for
112    pub subject: String,
113    /// Issuer of the token
114    pub issuer: String,
115    /// Creation timestamp
116    pub issued_at: u64,
117    /// Expiration timestamp (if applicable)
118    pub expires_at: Option<u64>,
119    /// Current status of the token
120    pub status: TokenStatus,
121    /// Permissions granted by this token
122    pub permissions: Vec<String>,
123}
124
125/// Status of a capability token
126#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
127pub enum TokenStatus {
128    /// Token is active and valid
129    Active,
130    /// Token has expired
131    Expired,
132    /// Token has been revoked
133    Revoked,
134    /// Token is suspended temporarily
135    Suspended,
136    /// Token is pending activation
137    Pending,
138}
139
140/// Configuration for capability token operations
141#[derive(Debug, Clone, Serialize, Deserialize)]
142pub struct CapabilityConfig {
143    /// Default token format to use
144    pub default_format: CapabilityTokenFormat,
145    /// Default expiration time (seconds from now)
146    pub default_expiry_seconds: u64,
147    /// Whether to enforce strict verification by default
148    pub strict_verification: bool,
149    /// Maximum number of permissions per token
150    pub max_permissions_per_token: u32,
151    /// Whether to enable revocation checking
152    pub enable_revocation_checks: bool,
153    /// Clock skew tolerance (seconds)
154    pub clock_skew_tolerance_seconds: u64,
155}
156
157impl Default for CapabilityConfig {
158    fn default() -> Self {
159        Self {
160            default_format: CapabilityTokenFormat::Biscuit,
161            default_expiry_seconds: 3600, // 1 hour
162            strict_verification: true,
163            max_permissions_per_token: 100,
164            enable_revocation_checks: true,
165            clock_skew_tolerance_seconds: 300, // 5 minutes
166        }
167    }
168}
169
170/// Capability token effects interface
171///
172/// This trait defines operations for creating, verifying, and managing capability tokens
173/// that provide cryptographically verifiable access control.
174///
175/// # Implementation Notes
176///
177/// - Production: Interface with real cryptographic libraries (Biscuit, JWT, etc.)
178/// - Testing: Simulate token operations with configurable verification outcomes
179/// - Simulation: Deterministic token generation and verification for testing
180///
181/// # Security Properties
182///
183/// - Cryptographic signature verification
184/// - Time-bound access control with expiration
185/// - Revocation support and checking
186/// - Delegation and attenuation capabilities
187/// - Audit trail for token operations
188///
189/// # Stability: EXPERIMENTAL
190/// This API is under development and may change in future versions.
191#[async_trait]
192pub trait CapabilityEffects: Send + Sync {
193    /// Create a new capability token
194    ///
195    /// Generates a cryptographically signed capability token with the specified
196    /// permissions and constraints.
197    ///
198    /// # Parameters
199    /// - `request`: Token creation parameters including subject, permissions, expiration
200    ///
201    /// # Returns
202    /// The generated token as bytes, or error if creation fails
203    async fn create_capability_token(
204        &self,
205        request: CapabilityTokenRequest,
206    ) -> Result<Vec<u8>, CapabilityError>;
207
208    /// Verify a capability token
209    ///
210    /// Parses and verifies a capability token, checking signature, expiration,
211    /// and other security constraints.
212    ///
213    /// # Parameters
214    /// - `token`: Token bytes to verify
215    /// - `verification_level`: Level of verification to perform
216    /// - `required_permissions`: Optional list of permissions that must be present
217    ///
218    /// # Returns
219    /// Verification result with token information and validation status
220    async fn verify_capability_token(
221        &self,
222        token: &[u8],
223        verification_level: VerificationLevel,
224        required_permissions: Option<&[String]>,
225    ) -> Result<CapabilityVerificationResult, CapabilityError>;
226
227    /// Parse capability token without verification
228    ///
229    /// Extracts information from a token without performing cryptographic verification.
230    /// Useful for debugging or when verification is performed separately.
231    ///
232    /// # Parameters
233    /// - `token`: Token bytes to parse
234    ///
235    /// # Returns
236    /// Token information without verification status
237    async fn parse_capability_token(
238        &self,
239        token: &[u8],
240    ) -> Result<CapabilityTokenInfo, CapabilityError>;
241
242    /// Revoke a capability token
243    ///
244    /// Adds a token to the revocation list, making it invalid for future verification.
245    ///
246    /// # Parameters
247    /// - `token_id`: Unique identifier of the token to revoke
248    /// - `reason`: Reason for revocation (for audit trail)
249    ///
250    /// # Returns
251    /// Success/failure result
252    async fn revoke_capability_token(
253        &self,
254        token_id: &str,
255        reason: &str,
256    ) -> Result<(), CapabilityError>;
257
258    /// Check if a token is revoked
259    ///
260    /// Queries the revocation list to check if a token has been revoked.
261    ///
262    /// # Parameters
263    /// - `token_id`: Token identifier to check
264    ///
265    /// # Returns
266    /// `true` if the token is revoked, `false` otherwise
267    async fn is_token_revoked(&self, token_id: &str) -> Result<bool, CapabilityError>;
268
269    /// Create a delegated token
270    ///
271    /// Creates a new token that is derived from an existing token with
272    /// attenuated (reduced) permissions.
273    ///
274    /// # Parameters
275    /// - `parent_token`: Parent token to delegate from
276    /// - `new_permissions`: Subset of parent permissions for the new token
277    /// - `new_expiry`: Optional new expiration time (must be <= parent expiry)
278    /// - `subject`: Subject for the new token
279    ///
280    /// # Returns
281    /// The delegated token bytes
282    async fn delegate_capability_token(
283        &self,
284        parent_token: &[u8],
285        new_permissions: &[String],
286        new_expiry: Option<u64>,
287        subject: &str,
288    ) -> Result<Vec<u8>, CapabilityError>;
289
290    /// List active tokens for a subject
291    ///
292    /// Returns information about all active tokens issued for a specific subject.
293    ///
294    /// # Parameters
295    /// - `subject`: Subject to list tokens for
296    ///
297    /// # Returns
298    /// List of active token information
299    async fn list_subject_tokens(
300        &self,
301        subject: &str,
302    ) -> Result<Vec<CapabilityTokenInfo>, CapabilityError>;
303
304    /// Validate token permissions
305    ///
306    /// Checks if a verified token contains the required permissions for a specific operation.
307    ///
308    /// # Parameters
309    /// - `verification_result`: Previous verification result
310    /// - `required_permissions`: Permissions needed for the operation
311    /// - `operation_context`: Optional context for the operation
312    ///
313    /// # Returns
314    /// `true` if token has sufficient permissions, `false` otherwise
315    async fn validate_token_permissions(
316        &self,
317        verification_result: &CapabilityVerificationResult,
318        required_permissions: &[String],
319        operation_context: Option<&str>,
320    ) -> Result<bool, CapabilityError>;
321
322    /// Get capability token statistics
323    ///
324    /// Returns statistics about token usage, creation, verification, and revocation.
325    ///
326    /// # Returns
327    /// Token usage statistics
328    async fn get_token_statistics(&self) -> Result<CapabilityStatistics, CapabilityError>;
329
330    /// Configure capability token settings
331    ///
332    /// Updates the configuration for capability token operations.
333    ///
334    /// # Parameters
335    /// - `config`: New configuration to apply
336    ///
337    /// # Returns
338    /// Success/failure result
339    async fn configure_capabilities(&self, config: CapabilityConfig)
340        -> Result<(), CapabilityError>;
341
342    /// Check what token formats are supported
343    ///
344    /// Returns the list of capability token formats supported by this implementation.
345    ///
346    /// # Returns
347    /// List of supported token formats
348    fn get_supported_formats(&self) -> Vec<CapabilityTokenFormat>;
349
350    /// Check if this implementation supports cryptographic verification
351    fn supports_cryptographic_verification(&self) -> bool;
352
353    /// Get implementation capabilities
354    fn get_capability_features(&self) -> Vec<String>;
355}
356
357/// Statistics about capability token usage
358#[derive(Debug, Clone, Serialize, Deserialize)]
359pub struct CapabilityStatistics {
360    /// Total number of tokens created
361    pub total_tokens_created: u64,
362    /// Total number of verification attempts
363    pub total_verifications: u64,
364    /// Number of successful verifications
365    pub successful_verifications: u64,
366    /// Number of failed verifications
367    pub failed_verifications: u64,
368    /// Number of revoked tokens
369    pub revoked_tokens: u64,
370    /// Number of expired tokens
371    pub expired_tokens: u64,
372    /// Average token lifetime (seconds)
373    pub average_token_lifetime_seconds: u64,
374    /// Most common permissions granted
375    pub common_permissions: HashMap<String, u64>,
376    /// Token creation rate (tokens per day)
377    pub creation_rate_per_day: f64,
378    /// Last token created timestamp
379    pub last_token_created_at: Option<u64>,
380    /// Last verification timestamp
381    pub last_verification_at: Option<u64>,
382}
383
384impl Default for CapabilityStatistics {
385    fn default() -> Self {
386        Self {
387            total_tokens_created: 0,
388            total_verifications: 0,
389            successful_verifications: 0,
390            failed_verifications: 0,
391            revoked_tokens: 0,
392            expired_tokens: 0,
393            average_token_lifetime_seconds: 3600,
394            common_permissions: HashMap::new(),
395            creation_rate_per_day: 0.0,
396            last_token_created_at: None,
397            last_verification_at: None,
398        }
399    }
400}
401
402/// Helper functions for common capability token operations
403impl CapabilityTokenRequest {
404    /// Create a standard token request with common settings
405    pub fn standard(subject: &str, permissions: &[String]) -> Self {
406        Self {
407            subject: subject.to_string(),
408            permissions: permissions.to_vec(),
409            expires_at: None, // Use default expiration
410            claims: HashMap::new(),
411            format: CapabilityTokenFormat::Biscuit,
412            context: None,
413        }
414    }
415
416    /// Create a short-lived token request (5 minutes)
417    ///
418    /// Note: This method requires PhysicalTimeEffects to set expiration.
419    /// Use `short_lived_with_time` for production code.
420    pub fn short_lived(subject: &str, permissions: &[String]) -> Self {
421        Self {
422            subject: subject.to_string(),
423            permissions: permissions.to_vec(),
424            expires_at: None, // Must be set separately using PhysicalTimeEffects
425            claims: HashMap::new(),
426            format: CapabilityTokenFormat::Jwt,
427            context: None,
428        }
429    }
430
431    /// Create a short-lived token request with explicit expiration time
432    pub fn short_lived_with_expiry(subject: &str, permissions: &[String], expires_at: u64) -> Self {
433        Self {
434            subject: subject.to_string(),
435            permissions: permissions.to_vec(),
436            expires_at: Some(expires_at),
437            claims: HashMap::new(),
438            format: CapabilityTokenFormat::Jwt,
439            context: None,
440        }
441    }
442
443    /// Create a read-only token request
444    pub fn read_only(subject: &str, resource: &str) -> Self {
445        let permissions = vec![format!("read:{}", resource)];
446        Self::standard(subject, &permissions)
447    }
448
449    /// Add a custom claim to the token request
450    #[must_use]
451    pub fn with_claim(mut self, key: &str, value: &str) -> Self {
452        self.claims.insert(key.to_string(), value.to_string());
453        self
454    }
455
456    /// Set the expiration time for the token
457    #[must_use]
458    pub fn with_expiry(mut self, expires_at: u64) -> Self {
459        self.expires_at = Some(expires_at);
460        self
461    }
462
463    /// Set the context for the token
464    #[must_use]
465    pub fn with_context(mut self, context: &str) -> Self {
466        self.context = Some(context.to_string());
467        self
468    }
469}
470
471impl CapabilityVerificationResult {
472    /// Check if the token is valid (signature verification passed)
473    pub fn is_valid(&self) -> bool {
474        self.valid
475    }
476
477    /// Check if the token is valid and not expired at the given time
478    pub fn is_valid_at(&self, current_time_ms: u64) -> bool {
479        if !self.valid {
480            return false;
481        }
482
483        if let Some(expires_at) = self.expires_at {
484            // Convert seconds to milliseconds for consistent time representation
485            let expires_at_ms = expires_at * 1000;
486            return current_time_ms < expires_at_ms;
487        }
488
489        true
490    }
491
492    /// Check if the token has all required permissions
493    pub fn has_permissions(&self, required: &[String]) -> bool {
494        required.iter().all(|perm| self.permissions.contains(perm))
495    }
496
497    /// Get the remaining validity time in seconds at the given time
498    pub fn remaining_validity_seconds(&self, current_time_ms: u64) -> Option<u64> {
499        self.expires_at.map(|expires| {
500            let current_seconds = current_time_ms / 1000;
501            expires.saturating_sub(current_seconds)
502        })
503    }
504}
505
506impl TokenStatus {
507    /// Check if the token is usable
508    pub fn is_active(&self) -> bool {
509        matches!(self, TokenStatus::Active)
510    }
511
512    /// Check if the token is permanently invalid
513    pub fn is_permanently_invalid(&self) -> bool {
514        matches!(self, TokenStatus::Expired | TokenStatus::Revoked)
515    }
516}
517
518#[cfg(test)]
519mod tests {
520    use super::*;
521
522    #[test]
523    fn test_capability_token_request_creation() {
524        let permissions = vec!["read:data".to_string(), "write:logs".to_string()];
525        let request = CapabilityTokenRequest::standard("user123", &permissions);
526
527        assert_eq!(request.subject, "user123");
528        assert_eq!(request.permissions, permissions);
529        assert_eq!(request.format, CapabilityTokenFormat::Biscuit);
530        assert!(request.claims.is_empty());
531    }
532
533    #[test]
534    fn test_capability_token_request_short_lived() {
535        let permissions = vec!["admin:all".to_string()];
536        let request = CapabilityTokenRequest::short_lived("admin", &permissions);
537
538        assert_eq!(request.subject, "admin");
539        assert_eq!(request.format, CapabilityTokenFormat::Jwt);
540        assert!(request.expires_at.is_none()); // Must be set separately
541    }
542
543    #[test]
544    fn test_capability_token_request_short_lived_with_expiry() {
545        let permissions = vec!["admin:all".to_string()];
546        let expires_at = 1234567890;
547        let request =
548            CapabilityTokenRequest::short_lived_with_expiry("admin", &permissions, expires_at);
549
550        assert_eq!(request.subject, "admin");
551        assert_eq!(request.format, CapabilityTokenFormat::Jwt);
552        assert_eq!(request.expires_at, Some(expires_at));
553    }
554
555    #[test]
556    fn test_capability_token_request_read_only() {
557        let request = CapabilityTokenRequest::read_only("viewer", "database");
558
559        assert_eq!(request.subject, "viewer");
560        assert_eq!(request.permissions, vec!["read:database"]);
561    }
562
563    #[test]
564    fn test_capability_token_request_builder() {
565        let request = CapabilityTokenRequest::standard("user", &["read:data".to_string()])
566            .with_claim("department", "engineering")
567            .with_context("development")
568            .with_expiry(1234567890);
569
570        assert_eq!(
571            request.claims.get("department"),
572            Some(&"engineering".to_string())
573        );
574        assert_eq!(request.context, Some("development".to_string()));
575        assert_eq!(request.expires_at, Some(1234567890));
576    }
577
578    #[test]
579    fn test_verification_result_expiration_times() {
580        let result = CapabilityVerificationResult {
581            valid: true,
582            permissions: vec![],
583            expires_at: Some(1640999400), // 70 minutes after epoch start
584            subject: None,
585            issuer: None,
586            claims: HashMap::new(),
587            errors: vec![],
588            verification_level: VerificationLevel::Standard,
589        };
590
591        // Test remaining time calculation
592        let current_time_ms = 1640995800000; // 1 hour after epoch start
593        assert_eq!(
594            result.remaining_validity_seconds(current_time_ms),
595            Some(3600)
596        ); // 1 hour remaining
597    }
598
599    #[test]
600    fn test_verification_result_validation() {
601        let result = CapabilityVerificationResult {
602            valid: true,
603            permissions: vec!["read:data".to_string(), "write:logs".to_string()],
604            expires_at: Some(9999999999), // Far future in seconds
605            subject: Some("user".to_string()),
606            issuer: Some("auth_service".to_string()),
607            claims: HashMap::new(),
608            errors: vec![],
609            verification_level: VerificationLevel::Standard,
610        };
611
612        assert!(result.is_valid());
613        // Test with current time well before expiration (using milliseconds)
614        let current_time_ms = 1640995200000; // 2022-01-01 in milliseconds
615        assert!(result.is_valid_at(current_time_ms));
616
617        assert!(result.has_permissions(&["read:data".to_string()]));
618        assert!(result.has_permissions(&["read:data".to_string(), "write:logs".to_string()]));
619        assert!(!result.has_permissions(&["admin:all".to_string()]));
620
621        // Test expiration
622        let far_future_ms = 99999999990000; // Beyond expiration
623        assert!(!result.is_valid_at(far_future_ms));
624    }
625
626    #[test]
627    fn test_token_status_checks() {
628        assert!(TokenStatus::Active.is_active());
629        assert!(!TokenStatus::Expired.is_active());
630        assert!(!TokenStatus::Revoked.is_active());
631
632        assert!(TokenStatus::Expired.is_permanently_invalid());
633        assert!(TokenStatus::Revoked.is_permanently_invalid());
634        assert!(!TokenStatus::Active.is_permanently_invalid());
635        assert!(!TokenStatus::Suspended.is_permanently_invalid());
636    }
637
638    #[test]
639    fn test_capability_config_defaults() {
640        let config = CapabilityConfig::default();
641
642        assert_eq!(config.default_format, CapabilityTokenFormat::Biscuit);
643        assert_eq!(config.default_expiry_seconds, 3600);
644        assert!(config.strict_verification);
645        assert!(config.enable_revocation_checks);
646        assert_eq!(config.max_permissions_per_token, 100);
647    }
648
649    #[test]
650    fn test_capability_statistics_defaults() {
651        let stats = CapabilityStatistics::default();
652
653        assert_eq!(stats.total_tokens_created, 0);
654        assert_eq!(stats.total_verifications, 0);
655        assert_eq!(stats.successful_verifications, 0);
656        assert!(stats.common_permissions.is_empty());
657        assert_eq!(stats.creation_rate_per_day, 0.0);
658    }
659}