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}