Skip to main content

crawlkit_engine/
enterprise.rs

1use std::collections::HashMap;
2use std::sync::Arc;
3
4use parking_lot::RwLock;
5use serde::{Deserialize, Serialize};
6
7/// Tenant configuration.
8#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct Tenant {
10    /// Tenant ID.
11    pub id: String,
12    /// Tenant name.
13    pub name: String,
14    /// Tenant plan (free, pro, enterprise).
15    pub plan: TenantPlan,
16    /// Maximum crawls per month.
17    pub max_crawls: u64,
18    /// Maximum pages per crawl.
19    pub max_pages: usize,
20    /// Maximum concurrent crawls.
21    pub max_concurrent: usize,
22    /// API rate limit (requests per minute).
23    pub rate_limit: u32,
24    /// Features enabled for this tenant.
25    pub features: Vec<String>,
26}
27
28/// Tenant plan.
29#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
30pub enum TenantPlan {
31    Free,
32    Pro,
33    Enterprise,
34}
35
36/// Tenant manager for multi-tenant support.
37pub struct TenantManager {
38    tenants: Arc<RwLock<HashMap<String, Tenant>>>,
39}
40
41impl TenantManager {
42    /// Create new tenant manager.
43    #[must_use]
44    pub fn new() -> Self {
45        Self {
46            tenants: Arc::new(RwLock::new(HashMap::new())),
47        }
48    }
49
50    /// Add a tenant.
51    pub fn add_tenant(&self, tenant: Tenant) {
52        let mut tenants = self.tenants.write();
53        tenants.insert(tenant.id.clone(), tenant);
54    }
55
56    /// Get a tenant by ID.
57    #[must_use]
58    pub fn get_tenant(&self, id: &str) -> Option<Tenant> {
59        self.tenants.read().get(id).cloned()
60    }
61
62    /// List all tenants.
63    #[must_use]
64    pub fn list_tenants(&self) -> Vec<Tenant> {
65        self.tenants.read().values().cloned().collect()
66    }
67
68    /// Remove a tenant.
69    pub fn remove_tenant(&self, id: &str) -> bool {
70        self.tenants.write().remove(id).is_some()
71    }
72
73    /// Get tenant count.
74    #[must_use]
75    pub fn count(&self) -> usize {
76        self.tenants.read().len()
77    }
78}
79
80impl Default for TenantManager {
81    fn default() -> Self {
82        Self::new()
83    }
84}
85
86/// Role definitions for RBAC.
87#[derive(Debug, Clone, Serialize, Deserialize)]
88pub struct Role {
89    /// Role ID.
90    pub id: String,
91    /// Role name.
92    pub name: String,
93    /// Permissions granted by this role.
94    pub permissions: Vec<String>,
95}
96
97/// Permission definitions.
98#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
99pub enum Permission {
100    /// Create crawls.
101    CrawlCreate,
102    /// Read crawl results.
103    CrawlRead,
104    /// Delete crawls.
105    CrawlDelete,
106    /// Manage API keys.
107    ApiKeyManage,
108    /// View analytics.
109    AnalyticsView,
110    /// Manage users.
111    UserManage,
112    /// Manage tenants.
113    TenantManage,
114    /// View audit logs.
115    AuditView,
116    /// Manage billing.
117    BillingManage,
118}
119
120impl Permission {
121    /// Get permission string representation.
122    #[must_use]
123    pub fn as_str(&self) -> &'static str {
124        match self {
125            Permission::CrawlCreate => "crawl:create",
126            Permission::CrawlRead => "crawl:read",
127            Permission::CrawlDelete => "crawl:delete",
128            Permission::ApiKeyManage => "apikey:manage",
129            Permission::AnalyticsView => "analytics:view",
130            Permission::UserManage => "user:manage",
131            Permission::TenantManage => "tenant:manage",
132            Permission::AuditView => "audit:view",
133            Permission::BillingManage => "billing:manage",
134        }
135    }
136
137    /// Parse a permission from its string representation.
138    ///
139    /// Returns `None` if the string does not correspond to a known permission.
140    pub fn parse(s: &str) -> Option<Self> {
141        match s {
142            "crawl:create" => Some(Permission::CrawlCreate),
143            "crawl:read" => Some(Permission::CrawlRead),
144            "crawl:delete" => Some(Permission::CrawlDelete),
145            "apikey:manage" => Some(Permission::ApiKeyManage),
146            "analytics:view" => Some(Permission::AnalyticsView),
147            "user:manage" => Some(Permission::UserManage),
148            "tenant:manage" => Some(Permission::TenantManage),
149            "audit:view" => Some(Permission::AuditView),
150            "billing:manage" => Some(Permission::BillingManage),
151            _ => None,
152        }
153    }
154}
155
156/// User with RBAC support.
157#[derive(Debug, Clone, Serialize, Deserialize)]
158pub struct User {
159    /// User ID.
160    pub id: String,
161    /// User email.
162    pub email: String,
163    /// User display name.
164    pub name: String,
165    /// Tenant ID.
166    pub tenant_id: String,
167    /// User roles.
168    pub roles: Vec<String>,
169    /// Whether user is active.
170    pub active: bool,
171}
172
173/// RBAC manager for role-based access control.
174pub struct RbacManager {
175    roles: Arc<RwLock<HashMap<String, Role>>>,
176    users: Arc<RwLock<HashMap<String, User>>>,
177}
178
179impl RbacManager {
180    /// Create new RBAC manager.
181    #[must_use]
182    pub fn new() -> Self {
183        let mut roles = HashMap::new();
184
185        // Default roles
186        roles.insert(
187            "admin".to_string(),
188            Role {
189                id: "admin".to_string(),
190                name: "Administrator".to_string(),
191                permissions: Permission::iter().map(|p| p.as_str().to_string()).collect(),
192            },
193        );
194
195        roles.insert(
196            "user".to_string(),
197            Role {
198                id: "user".to_string(),
199                name: "User".to_string(),
200                permissions: vec![
201                    Permission::CrawlCreate.as_str().to_string(),
202                    Permission::CrawlRead.as_str().to_string(),
203                    Permission::AnalyticsView.as_str().to_string(),
204                ],
205            },
206        );
207
208        roles.insert(
209            "viewer".to_string(),
210            Role {
211                id: "viewer".to_string(),
212                name: "Viewer".to_string(),
213                permissions: vec![
214                    Permission::CrawlRead.as_str().to_string(),
215                    Permission::AnalyticsView.as_str().to_string(),
216                ],
217            },
218        );
219
220        Self {
221            roles: Arc::new(RwLock::new(roles)),
222            users: Arc::new(RwLock::new(HashMap::new())),
223        }
224    }
225
226    /// Add a role.
227    pub fn add_role(&self, role: Role) {
228        let mut roles = self.roles.write();
229        roles.insert(role.id.clone(), role);
230    }
231
232    /// Get a role by ID.
233    #[must_use]
234    pub fn get_role(&self, id: &str) -> Option<Role> {
235        self.roles.read().get(id).cloned()
236    }
237
238    /// Add a user.
239    pub fn add_user(&self, user: User) {
240        let mut users = self.users.write();
241        users.insert(user.id.clone(), user);
242    }
243
244    /// Get a user by ID.
245    #[must_use]
246    pub fn get_user(&self, id: &str) -> Option<User> {
247        self.users.read().get(id).cloned()
248    }
249
250    /// Check if user has permission.
251    #[must_use]
252    pub fn has_permission(&self, user_id: &str, permission: &Permission) -> bool {
253        let users = self.users.read();
254        let roles = self.roles.read();
255
256        if let Some(user) = users.get(user_id) {
257            if !user.active {
258                return false;
259            }
260
261            for role_id in &user.roles {
262                if let Some(role) = roles.get(role_id) {
263                    if role.permissions.contains(&permission.as_str().to_string()) {
264                        return true;
265                    }
266                }
267            }
268        }
269
270        false
271    }
272
273    /// Get all permissions for a user.
274    #[must_use]
275    pub fn get_permissions(&self, user_id: &str) -> Vec<String> {
276        let users = self.users.read();
277        let roles = self.roles.read();
278        let mut permissions = Vec::new();
279
280        if let Some(user) = users.get(user_id) {
281            if !user.active {
282                return permissions;
283            }
284
285            for role_id in &user.roles {
286                if let Some(role) = roles.get(role_id) {
287                    permissions.extend(role.permissions.clone());
288                }
289            }
290        }
291
292        permissions.sort();
293        permissions.dedup();
294        permissions
295    }
296}
297
298impl Default for RbacManager {
299    fn default() -> Self {
300        Self::new()
301    }
302}
303
304/// SSO configuration.
305#[derive(Debug, Clone, Serialize, Deserialize)]
306pub struct SsoConfig {
307    /// SSO provider (saml, oidc).
308    pub provider: SsoProvider,
309    /// Provider URL.
310    pub provider_url: String,
311    /// Client ID.
312    pub client_id: String,
313    /// Client secret.
314    pub client_secret: String,
315    /// Callback URL.
316    pub callback_url: String,
317    /// Enabled SSO domains.
318    pub enabled_domains: Vec<String>,
319}
320
321/// SSO provider type.
322#[derive(Debug, Clone, Serialize, Deserialize)]
323pub enum SsoProvider {
324    Saml,
325    Oidc,
326}
327
328/// SSO manager for enterprise authentication.
329pub struct SsoManager {
330    configs: Arc<RwLock<Vec<SsoConfig>>>,
331}
332
333impl SsoManager {
334    /// Create new SSO manager.
335    #[must_use]
336    pub fn new() -> Self {
337        Self {
338            configs: Arc::new(RwLock::new(Vec::new())),
339        }
340    }
341
342    /// Add SSO configuration.
343    pub fn add_config(&self, config: SsoConfig) {
344        let mut configs = self.configs.write();
345        configs.push(config);
346    }
347
348    /// Get SSO config for a domain.
349    #[must_use]
350    pub fn get_config_for_domain(&self, domain: &str) -> Option<SsoConfig> {
351        self.configs
352            .read()
353            .iter()
354            .find(|c| c.enabled_domains.contains(&domain.to_string()))
355            .cloned()
356    }
357
358    /// List all SSO configs.
359    #[must_use]
360    pub fn list_configs(&self) -> Vec<SsoConfig> {
361        self.configs.read().clone()
362    }
363}
364
365impl Default for SsoManager {
366    fn default() -> Self {
367        Self::new()
368    }
369}
370
371/// SLA monitoring.
372#[derive(Debug, Clone, Serialize, Deserialize)]
373pub struct SlaConfig {
374    /// Uptime target (percentage).
375    pub uptime_target: f64,
376    /// Response time target (ms).
377    pub response_time_target: u64,
378    /// Error rate target (percentage).
379    pub error_rate_target: f64,
380    /// Alert thresholds.
381    pub alert_thresholds: SlaThresholds,
382}
383
384/// SLA alert thresholds.
385#[derive(Debug, Clone, Serialize, Deserialize)]
386pub struct SlaThresholds {
387    /// Warning threshold (percentage of target).
388    pub warning: f64,
389    /// Critical threshold (percentage of target).
390    pub critical: f64,
391}
392
393impl Default for SlaConfig {
394    fn default() -> Self {
395        Self {
396            uptime_target: 99.9,
397            response_time_target: 500,
398            error_rate_target: 1.0,
399            alert_thresholds: SlaThresholds {
400                warning: 0.95,
401                critical: 0.90,
402            },
403        }
404    }
405}
406
407/// SLA monitor for tracking compliance.
408pub struct SlaMonitor {
409    config: SlaConfig,
410    metrics: Arc<RwLock<SlaMetrics>>,
411}
412
413/// Current SLA metrics.
414#[derive(Debug, Clone, Default, Serialize, Deserialize)]
415pub struct SlaMetrics {
416    /// Total requests.
417    pub total_requests: u64,
418    /// Successful requests.
419    pub successful_requests: u64,
420    /// Failed requests.
421    pub failed_requests: u64,
422    /// Total response time (ms).
423    pub total_response_time: u64,
424    /// Uptime percentage.
425    pub uptime: f64,
426    /// Average response time (ms).
427    pub avg_response_time: f64,
428    /// Error rate (percentage).
429    pub error_rate: f64,
430}
431
432impl SlaMonitor {
433    /// Create new SLA monitor.
434    #[must_use]
435    pub fn new(config: SlaConfig) -> Self {
436        Self {
437            config,
438            metrics: Arc::new(RwLock::new(SlaMetrics::default())),
439        }
440    }
441
442    /// Create with default config.
443    #[must_use]
444    pub fn with_default_config() -> Self {
445        Self::new(SlaConfig::default())
446    }
447
448    /// Record a successful request.
449    pub fn record_success(&self, response_time_ms: u64) {
450        let mut metrics = self.metrics.write();
451        metrics.total_requests += 1;
452        metrics.successful_requests += 1;
453        metrics.total_response_time += response_time_ms;
454        self.update_metrics(&mut metrics);
455    }
456
457    /// Record a failed request.
458    pub fn record_failure(&self) {
459        let mut metrics = self.metrics.write();
460        metrics.total_requests += 1;
461        metrics.failed_requests += 1;
462        self.update_metrics(&mut metrics);
463    }
464
465    /// Update calculated metrics.
466    fn update_metrics(&self, metrics: &mut SlaMetrics) {
467        if metrics.total_requests > 0 {
468            metrics.error_rate =
469                (metrics.failed_requests as f64 / metrics.total_requests as f64) * 100.0;
470            metrics.avg_response_time =
471                metrics.total_response_time as f64 / metrics.total_requests as f64;
472        }
473    }
474
475    /// Get current metrics.
476    #[must_use]
477    pub fn metrics(&self) -> SlaMetrics {
478        self.metrics.read().clone()
479    }
480
481    /// Check if SLA is met.
482    #[must_use]
483    pub fn is_sla_met(&self) -> bool {
484        let metrics = self.metrics.read();
485        metrics.error_rate <= self.config.error_rate_target
486            && metrics.avg_response_time <= self.config.response_time_target as f64
487    }
488
489    /// Get SLA status.
490    #[must_use]
491    pub fn status(&self) -> SlaStatus {
492        let metrics = self.metrics.read();
493        let error_rate_ratio = metrics.error_rate / self.config.error_rate_target;
494        let response_time_ratio =
495            metrics.avg_response_time / self.config.response_time_target as f64;
496
497        let worst_ratio = error_rate_ratio.max(response_time_ratio);
498
499        if worst_ratio <= self.config.alert_thresholds.critical {
500            SlaStatus::Critical
501        } else if worst_ratio <= self.config.alert_thresholds.warning {
502            SlaStatus::Warning
503        } else {
504            SlaStatus::Healthy
505        }
506    }
507
508    /// Get configuration.
509    #[must_use]
510    pub fn config(&self) -> &SlaConfig {
511        &self.config
512    }
513}
514
515impl Default for SlaMonitor {
516    fn default() -> Self {
517        Self::with_default_config()
518    }
519}
520
521/// SLA status.
522#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
523pub enum SlaStatus {
524    Healthy,
525    Warning,
526    Critical,
527}
528
529/// Helper trait for iterating permissions.
530trait PermissionIter {
531    fn iter() -> Box<dyn Iterator<Item = Permission>>;
532}
533
534impl PermissionIter for Permission {
535    fn iter() -> Box<dyn Iterator<Item = Permission>> {
536        Box::new(
537            vec![
538                Permission::CrawlCreate,
539                Permission::CrawlRead,
540                Permission::CrawlDelete,
541                Permission::ApiKeyManage,
542                Permission::AnalyticsView,
543                Permission::UserManage,
544                Permission::TenantManage,
545                Permission::AuditView,
546                Permission::BillingManage,
547            ]
548            .into_iter(),
549        )
550    }
551}
552
553// ---------------------------------------------------------------------------
554// Tests
555// ---------------------------------------------------------------------------
556
557#[cfg(test)]
558mod tests {
559    use super::*;
560
561    #[test]
562    fn test_tenant_manager() {
563        let manager = TenantManager::new();
564
565        let tenant = Tenant {
566            id: "tenant1".to_string(),
567            name: "Acme Corp".to_string(),
568            plan: TenantPlan::Enterprise,
569            max_crawls: 1000,
570            max_pages: 10000,
571            max_concurrent: 10,
572            rate_limit: 60,
573            features: vec!["backlinks".to_string(), "rum".to_string()],
574        };
575
576        manager.add_tenant(tenant);
577        assert_eq!(manager.count(), 1);
578        assert!(manager.get_tenant("tenant1").is_some());
579    }
580
581    #[test]
582    fn test_rbac_manager() {
583        let manager = RbacManager::new();
584
585        let user = User {
586            id: "user1".to_string(),
587            email: "test@example.com".to_string(),
588            name: "Test User".to_string(),
589            tenant_id: "tenant1".to_string(),
590            roles: vec!["admin".to_string()],
591            active: true,
592        };
593
594        manager.add_user(user);
595
596        // Admin has all permissions
597        assert!(manager.has_permission("user1", &Permission::CrawlCreate));
598        assert!(manager.has_permission("user1", &Permission::TenantManage));
599        assert!(manager.has_permission("user1", &Permission::BillingManage));
600
601        // Test non-existent user
602        assert!(!manager.has_permission("nonexistent", &Permission::CrawlCreate));
603    }
604
605    #[test]
606    fn test_sla_monitor() {
607        let monitor = SlaMonitor::with_default_config();
608
609        // Record some successful requests
610        for _ in 0..90 {
611            monitor.record_success(100);
612        }
613
614        // Record some failures
615        for _ in 0..10 {
616            monitor.record_failure();
617        }
618
619        let metrics = monitor.metrics();
620        assert_eq!(metrics.total_requests, 100);
621        assert_eq!(metrics.successful_requests, 90);
622        assert_eq!(metrics.failed_requests, 10);
623        assert!((metrics.error_rate - 10.0).abs() < 0.01);
624    }
625}