1use std::collections::{HashMap, HashSet};
7
8use chrono::{DateTime, Utc};
9use serde::{Deserialize, Serialize};
10use torsh_core::error::{Result, TorshError};
11
12#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
14pub enum Permission {
15 ReadMetadata,
17 Download,
19 Publish,
21 Update,
23 Delete,
25 ManageVersions,
27 Yank,
29 Unyank,
31 ManageOwners,
33 ViewStats,
35 ManageSecurity,
37 Custom(String),
39}
40
41#[derive(Debug, Clone, Serialize, Deserialize)]
43pub struct Role {
44 pub name: String,
46 pub description: String,
48 pub permissions: HashSet<Permission>,
50 pub builtin: bool,
52}
53
54#[derive(Debug, Clone, Serialize, Deserialize)]
56pub struct User {
57 pub id: String,
59 pub username: String,
61 pub email: String,
63 pub roles: HashSet<String>,
65 pub active: bool,
67 pub created_at: DateTime<Utc>,
69}
70
71#[derive(Debug, Clone, Serialize, Deserialize)]
73pub struct Organization {
74 pub id: String,
76 pub name: String,
78 pub description: Option<String>,
80 pub members: HashMap<String, OrganizationMembership>,
82 pub created_at: DateTime<Utc>,
84}
85
86#[derive(Debug, Clone, Serialize, Deserialize)]
88pub struct OrganizationMembership {
89 pub user_id: String,
91 pub roles: HashSet<String>,
93 pub joined_at: DateTime<Utc>,
95}
96
97#[derive(Debug, Clone, Serialize, Deserialize)]
99pub struct PackageOwnership {
100 pub package_name: String,
102 pub owners: HashSet<String>,
104 pub user_permissions: HashMap<String, HashSet<Permission>>,
106 pub public_access: AccessLevel,
108}
109
110#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
112pub enum AccessLevel {
113 Public,
115 Restricted,
117 Private,
119}
120
121#[derive(Debug, Clone, Serialize, Deserialize)]
123pub struct AclEntry {
124 pub principal: String,
126 pub permissions: HashSet<Permission>,
128 pub expires_at: Option<DateTime<Utc>>,
130}
131
132pub struct AccessControlManager {
134 roles: HashMap<String, Role>,
136 users: HashMap<String, User>,
138 organizations: HashMap<String, Organization>,
140 package_ownership: HashMap<String, PackageOwnership>,
142}
143
144#[derive(Debug, Clone)]
146pub struct AccessCheckResult {
147 pub granted: bool,
149 pub denial_reason: Option<String>,
151 pub checked_permissions: HashSet<Permission>,
153}
154
155impl Role {
156 pub fn new(name: String, description: String) -> Self {
158 Self {
159 name,
160 description,
161 permissions: HashSet::new(),
162 builtin: false,
163 }
164 }
165
166 pub fn add_permission(&mut self, permission: Permission) {
168 self.permissions.insert(permission);
169 }
170
171 pub fn has_permission(&self, permission: &Permission) -> bool {
173 self.permissions.contains(permission)
174 }
175
176 pub fn admin() -> Self {
178 let mut role = Self {
179 name: "admin".to_string(),
180 description: "Administrator with all permissions".to_string(),
181 permissions: HashSet::new(),
182 builtin: true,
183 };
184
185 role.permissions.insert(Permission::ReadMetadata);
186 role.permissions.insert(Permission::Download);
187 role.permissions.insert(Permission::Publish);
188 role.permissions.insert(Permission::Update);
189 role.permissions.insert(Permission::Delete);
190 role.permissions.insert(Permission::ManageVersions);
191 role.permissions.insert(Permission::Yank);
192 role.permissions.insert(Permission::Unyank);
193 role.permissions.insert(Permission::ManageOwners);
194 role.permissions.insert(Permission::ViewStats);
195 role.permissions.insert(Permission::ManageSecurity);
196
197 role
198 }
199
200 pub fn maintainer() -> Self {
202 let mut role = Self {
203 name: "maintainer".to_string(),
204 description: "Maintainer can publish and update packages".to_string(),
205 permissions: HashSet::new(),
206 builtin: true,
207 };
208
209 role.permissions.insert(Permission::ReadMetadata);
210 role.permissions.insert(Permission::Download);
211 role.permissions.insert(Permission::Publish);
212 role.permissions.insert(Permission::Update);
213 role.permissions.insert(Permission::ManageVersions);
214 role.permissions.insert(Permission::Yank);
215 role.permissions.insert(Permission::Unyank);
216
217 role
218 }
219
220 pub fn contributor() -> Self {
222 let mut role = Self {
223 name: "contributor".to_string(),
224 description: "Contributor can download packages".to_string(),
225 permissions: HashSet::new(),
226 builtin: true,
227 };
228
229 role.permissions.insert(Permission::ReadMetadata);
230 role.permissions.insert(Permission::Download);
231
232 role
233 }
234
235 pub fn viewer() -> Self {
237 let mut role = Self {
238 name: "viewer".to_string(),
239 description: "Viewer can only view metadata".to_string(),
240 permissions: HashSet::new(),
241 builtin: true,
242 };
243
244 role.permissions.insert(Permission::ReadMetadata);
245
246 role
247 }
248}
249
250impl User {
251 pub fn new(id: String, username: String, email: String) -> Self {
253 Self {
254 id,
255 username,
256 email,
257 roles: HashSet::new(),
258 active: true,
259 created_at: Utc::now(),
260 }
261 }
262
263 pub fn add_role(&mut self, role: String) {
265 self.roles.insert(role);
266 }
267
268 pub fn has_role(&self, role: &str) -> bool {
270 self.roles.contains(role)
271 }
272
273 pub fn deactivate(&mut self) {
275 self.active = false;
276 }
277}
278
279impl Organization {
280 pub fn new(id: String, name: String) -> Self {
282 Self {
283 id,
284 name,
285 description: None,
286 members: HashMap::new(),
287 created_at: Utc::now(),
288 }
289 }
290
291 pub fn add_member(&mut self, user_id: String, roles: HashSet<String>) {
293 let membership = OrganizationMembership {
294 user_id: user_id.clone(),
295 roles,
296 joined_at: Utc::now(),
297 };
298 self.members.insert(user_id, membership);
299 }
300
301 pub fn remove_member(&mut self, user_id: &str) {
303 self.members.remove(user_id);
304 }
305
306 pub fn is_member(&self, user_id: &str) -> bool {
308 self.members.contains_key(user_id)
309 }
310
311 pub fn get_member_roles(&self, user_id: &str) -> Option<&HashSet<String>> {
313 self.members.get(user_id).map(|m| &m.roles)
314 }
315}
316
317impl PackageOwnership {
318 pub fn new(package_name: String) -> Self {
320 Self {
321 package_name,
322 owners: HashSet::new(),
323 user_permissions: HashMap::new(),
324 public_access: AccessLevel::Public,
325 }
326 }
327
328 pub fn add_owner(&mut self, owner_id: String) {
330 self.owners.insert(owner_id);
331 }
332
333 pub fn remove_owner(&mut self, owner_id: &str) {
335 self.owners.remove(owner_id);
336 }
337
338 pub fn is_owner(&self, principal_id: &str) -> bool {
340 self.owners.contains(principal_id)
341 }
342
343 pub fn grant_permission(&mut self, user_id: String, permission: Permission) {
345 self.user_permissions
346 .entry(user_id)
347 .or_default()
348 .insert(permission);
349 }
350
351 pub fn revoke_permission(&mut self, user_id: &str, permission: &Permission) {
353 if let Some(perms) = self.user_permissions.get_mut(user_id) {
354 perms.remove(permission);
355 }
356 }
357}
358
359impl Default for AccessControlManager {
360 fn default() -> Self {
361 Self::new()
362 }
363}
364
365impl AccessControlManager {
366 pub fn new() -> Self {
368 let mut manager = Self {
369 roles: HashMap::new(),
370 users: HashMap::new(),
371 organizations: HashMap::new(),
372 package_ownership: HashMap::new(),
373 };
374
375 manager.register_role(Role::admin());
377 manager.register_role(Role::maintainer());
378 manager.register_role(Role::contributor());
379 manager.register_role(Role::viewer());
380
381 manager
382 }
383
384 pub fn register_role(&mut self, role: Role) {
386 self.roles.insert(role.name.clone(), role);
387 }
388
389 pub fn create_user(&mut self, id: String, username: String, email: String) -> Result<User> {
391 if self.users.contains_key(&id) {
392 return Err(TorshError::InvalidArgument(format!(
393 "User already exists: {}",
394 id
395 )));
396 }
397
398 let user = User::new(id.clone(), username, email);
399 self.users.insert(id, user.clone());
400 Ok(user)
401 }
402
403 pub fn get_user(&self, user_id: &str) -> Option<&User> {
405 self.users.get(user_id)
406 }
407
408 pub fn create_organization(&mut self, id: String, name: String) -> Result<Organization> {
410 if self.organizations.contains_key(&id) {
411 return Err(TorshError::InvalidArgument(format!(
412 "Organization already exists: {}",
413 id
414 )));
415 }
416
417 let org = Organization::new(id.clone(), name);
418 self.organizations.insert(id, org.clone());
419 Ok(org)
420 }
421
422 pub fn get_organization(&self, org_id: &str) -> Option<&Organization> {
424 self.organizations.get(org_id)
425 }
426
427 pub fn set_package_ownership(&mut self, ownership: PackageOwnership) {
429 self.package_ownership
430 .insert(ownership.package_name.clone(), ownership);
431 }
432
433 pub fn check_access(
435 &self,
436 user_id: &str,
437 package_name: &str,
438 permission: &Permission,
439 ) -> AccessCheckResult {
440 let user = match self.get_user(user_id) {
442 Some(u) => u,
443 None => {
444 return AccessCheckResult {
445 granted: false,
446 denial_reason: Some("User not found".to_string()),
447 checked_permissions: HashSet::from([permission.clone()]),
448 };
449 }
450 };
451
452 if !user.active {
454 return AccessCheckResult {
455 granted: false,
456 denial_reason: Some("User is not active".to_string()),
457 checked_permissions: HashSet::from([permission.clone()]),
458 };
459 }
460
461 let ownership = match self.package_ownership.get(package_name) {
463 Some(o) => o,
464 None => {
465 return AccessCheckResult {
466 granted: false,
467 denial_reason: Some("Package not found".to_string()),
468 checked_permissions: HashSet::from([permission.clone()]),
469 };
470 }
471 };
472
473 if ownership.is_owner(user_id) {
475 return AccessCheckResult {
476 granted: true,
477 denial_reason: None,
478 checked_permissions: HashSet::from([permission.clone()]),
479 };
480 }
481
482 if let Some(perms) = ownership.user_permissions.get(user_id) {
484 if perms.contains(permission) {
485 return AccessCheckResult {
486 granted: true,
487 denial_reason: None,
488 checked_permissions: HashSet::from([permission.clone()]),
489 };
490 }
491 }
492
493 for role_name in &user.roles {
495 if let Some(role) = self.roles.get(role_name) {
496 if role.has_permission(permission) {
497 return AccessCheckResult {
498 granted: true,
499 denial_reason: None,
500 checked_permissions: HashSet::from([permission.clone()]),
501 };
502 }
503 }
504 }
505
506 if ownership.public_access == AccessLevel::Public
508 && (permission == &Permission::ReadMetadata || permission == &Permission::Download)
509 {
510 return AccessCheckResult {
511 granted: true,
512 denial_reason: None,
513 checked_permissions: HashSet::from([permission.clone()]),
514 };
515 }
516
517 AccessCheckResult {
518 granted: false,
519 denial_reason: Some("Insufficient permissions".to_string()),
520 checked_permissions: HashSet::from([permission.clone()]),
521 }
522 }
523
524 pub fn grant_role(&mut self, user_id: &str, role_name: &str) -> Result<()> {
526 if !self.roles.contains_key(role_name) {
528 return Err(TorshError::InvalidArgument(format!(
529 "Role not found: {}",
530 role_name
531 )));
532 }
533
534 let user = self
536 .users
537 .get_mut(user_id)
538 .ok_or_else(|| TorshError::InvalidArgument(format!("User not found: {}", user_id)))?;
539
540 user.add_role(role_name.to_string());
541 Ok(())
542 }
543
544 pub fn revoke_role(&mut self, user_id: &str, role_name: &str) -> Result<()> {
546 let user = self
547 .users
548 .get_mut(user_id)
549 .ok_or_else(|| TorshError::InvalidArgument(format!("User not found: {}", user_id)))?;
550
551 user.roles.remove(role_name);
552 Ok(())
553 }
554
555 pub fn add_to_organization(
557 &mut self,
558 user_id: &str,
559 org_id: &str,
560 roles: HashSet<String>,
561 ) -> Result<()> {
562 if !self.users.contains_key(user_id) {
564 return Err(TorshError::InvalidArgument(format!(
565 "User not found: {}",
566 user_id
567 )));
568 }
569
570 let org = self.organizations.get_mut(org_id).ok_or_else(|| {
572 TorshError::InvalidArgument(format!("Organization not found: {}", org_id))
573 })?;
574
575 org.add_member(user_id.to_string(), roles);
576 Ok(())
577 }
578}
579
580impl AccessCheckResult {
581 pub fn granted() -> Self {
583 Self {
584 granted: true,
585 denial_reason: None,
586 checked_permissions: HashSet::new(),
587 }
588 }
589
590 pub fn denied(reason: String) -> Self {
592 Self {
593 granted: false,
594 denial_reason: Some(reason),
595 checked_permissions: HashSet::new(),
596 }
597 }
598}
599
600#[cfg(test)]
601mod tests {
602 use super::*;
603
604 #[test]
605 fn test_role_creation() {
606 let mut role = Role::new("test-role".to_string(), "Test role".to_string());
607 assert_eq!(role.name, "test-role");
608
609 role.add_permission(Permission::ReadMetadata);
610 assert!(role.has_permission(&Permission::ReadMetadata));
611 assert!(!role.has_permission(&Permission::Publish));
612 }
613
614 #[test]
615 fn test_builtin_roles() {
616 let admin = Role::admin();
617 assert!(admin.has_permission(&Permission::Delete));
618 assert!(admin.builtin);
619
620 let viewer = Role::viewer();
621 assert!(viewer.has_permission(&Permission::ReadMetadata));
622 assert!(!viewer.has_permission(&Permission::Publish));
623 }
624
625 #[test]
626 fn test_user_creation() {
627 let mut user = User::new(
628 "user1".to_string(),
629 "testuser".to_string(),
630 "test@example.com".to_string(),
631 );
632
633 assert_eq!(user.username, "testuser");
634 assert!(user.active);
635
636 user.add_role("admin".to_string());
637 assert!(user.has_role("admin"));
638
639 user.deactivate();
640 assert!(!user.active);
641 }
642
643 #[test]
644 fn test_organization() {
645 let mut org = Organization::new("org1".to_string(), "Test Org".to_string());
646
647 let roles = HashSet::from(["maintainer".to_string()]);
648 org.add_member("user1".to_string(), roles);
649
650 assert!(org.is_member("user1"));
651 assert!(!org.is_member("user2"));
652
653 org.remove_member("user1");
654 assert!(!org.is_member("user1"));
655 }
656
657 #[test]
658 fn test_package_ownership() {
659 let mut ownership = PackageOwnership::new("test-package".to_string());
660
661 ownership.add_owner("user1".to_string());
662 assert!(ownership.is_owner("user1"));
663 assert!(!ownership.is_owner("user2"));
664
665 ownership.grant_permission("user2".to_string(), Permission::Download);
666 assert!(ownership
667 .user_permissions
668 .get("user2")
669 .unwrap()
670 .contains(&Permission::Download));
671
672 ownership.revoke_permission("user2", &Permission::Download);
673 assert!(!ownership
674 .user_permissions
675 .get("user2")
676 .unwrap()
677 .contains(&Permission::Download));
678 }
679
680 #[test]
681 fn test_access_control_manager() {
682 let mut acl = AccessControlManager::new();
683
684 let user = acl
686 .create_user(
687 "user1".to_string(),
688 "testuser".to_string(),
689 "test@example.com".to_string(),
690 )
691 .unwrap();
692 assert_eq!(user.username, "testuser");
693
694 let org = acl
696 .create_organization("org1".to_string(), "Test Org".to_string())
697 .unwrap();
698 assert_eq!(org.name, "Test Org");
699
700 acl.grant_role("user1", "maintainer").unwrap();
702 assert!(acl.get_user("user1").unwrap().has_role("maintainer"));
703 }
704
705 #[test]
706 fn test_access_check() {
707 let mut acl = AccessControlManager::new();
708
709 acl.create_user(
711 "user1".to_string(),
712 "testuser".to_string(),
713 "test@example.com".to_string(),
714 )
715 .unwrap();
716
717 let mut ownership = PackageOwnership::new("test-package".to_string());
719 ownership.public_access = AccessLevel::Public;
720 acl.set_package_ownership(ownership);
721
722 let result = acl.check_access("user1", "test-package", &Permission::ReadMetadata);
724 assert!(result.granted);
725
726 let result = acl.check_access("user1", "test-package", &Permission::Publish);
728 assert!(!result.granted);
729 }
730
731 #[test]
732 fn test_owner_access() {
733 let mut acl = AccessControlManager::new();
734
735 acl.create_user(
737 "user1".to_string(),
738 "testuser".to_string(),
739 "test@example.com".to_string(),
740 )
741 .unwrap();
742
743 let mut ownership = PackageOwnership::new("test-package".to_string());
745 ownership.add_owner("user1".to_string());
746 acl.set_package_ownership(ownership);
747
748 let result = acl.check_access("user1", "test-package", &Permission::Publish);
750 assert!(result.granted);
751
752 let result = acl.check_access("user1", "test-package", &Permission::Delete);
754 assert!(result.granted);
755 }
756}