diff --git a/contracts/access-control/src/lib.rs b/contracts/access-control/src/lib.rs index f89ad36..93a21eb 100644 --- a/contracts/access-control/src/lib.rs +++ b/contracts/access-control/src/lib.rs @@ -13,6 +13,16 @@ mod test; #[repr(u32)] pub enum ContractError { InvalidDidFormat = 1, + AlreadyInitialized = 2, + EntityAlreadyRegistered = 3, + GrantorNotRegistered = 4, + GranteeNotRegistered = 5, + AccessAlreadyGranted = 6, + NotAuthorizedToRevoke = 7, + AccessPermissionNotFound = 8, + ContractNotInitialized = 9, + OnlyAdminCanDeactivate = 10, + EntityNotFound = 11, } /// -------------------- @@ -73,15 +83,16 @@ impl AccessControl { /// /// # Arguments /// * `admin` - The admin address for the contract - pub fn initialize(env: Env, admin: Address) { + pub fn initialize(env: Env, admin: Address) -> Result<(), ContractError> { if env.storage().persistent().has(&DataKey::Admin) { - panic!("Contract already initialized"); + return Err(ContractError::AlreadyInitialized); } admin.require_auth(); env.storage().persistent().set(&DataKey::Admin, &admin); env.events() .publish((symbol_short!("init"), admin), symbol_short!("success")); + Ok(()) } /// Register a new entity in the system @@ -97,12 +108,12 @@ impl AccessControl { entity_type: EntityType, name: String, metadata: String, - ) { + ) -> Result<(), ContractError> { wallet.require_auth(); let key = DataKey::Entity(wallet.clone()); if env.storage().persistent().has(&key) { - panic!("Entity already registered"); + return Err(ContractError::EntityAlreadyRegistered); } let entity = EntityData { @@ -122,6 +133,7 @@ impl AccessControl { env.events() .publish((symbol_short!("reg_ent"), wallet), symbol_short!("success")); + Ok(()) } /// Grant access permission to an entity for a specific resource @@ -137,19 +149,19 @@ impl AccessControl { grantee: Address, resource_id: String, expires_at: u64, - ) { + ) -> Result<(), ContractError> { grantor.require_auth(); // Verify grantor is a registered entity let grantor_key = DataKey::Entity(grantor.clone()); if !env.storage().persistent().has(&grantor_key) { - panic!("Grantor not registered"); + return Err(ContractError::GrantorNotRegistered); } // Verify grantee is a registered entity let grantee_key = DataKey::Entity(grantee.clone()); if !env.storage().persistent().has(&grantee_key) { - panic!("Grantee not registered"); + return Err(ContractError::GranteeNotRegistered); } let permission = AccessPermission { @@ -178,7 +190,7 @@ impl AccessControl { } } if exists { - panic!("Access already granted for this resource"); + return Err(ContractError::AccessAlreadyGranted); } access_list.push_back(permission); @@ -199,6 +211,7 @@ impl AccessControl { (symbol_short!("grant"), grantee, resource_id), symbol_short!("success"), ); + Ok(()) } /// Revoke access permission from an entity for a specific resource @@ -207,7 +220,7 @@ impl AccessControl { /// * `revoker` - The address revoking access (must be the original grantor or admin) /// * `revokee` - The address losing access /// * `resource_id` - The identifier of the resource - pub fn revoke_access(env: Env, revoker: Address, revokee: Address, resource_id: String) { + pub fn revoke_access(env: Env, revoker: Address, revokee: Address, resource_id: String) -> Result<(), ContractError> { revoker.require_auth(); // Get admin for authorization check @@ -215,7 +228,7 @@ impl AccessControl { .storage() .persistent() .get(&DataKey::Admin) - .expect("Contract not initialized"); + .ok_or(ContractError::ContractNotInitialized)?; // Remove from grantee's access list let access_key = DataKey::AccessList(revokee.clone()); @@ -233,7 +246,7 @@ impl AccessControl { if permission.resource_id == resource_id { // Verify revoker is either the original grantor or admin if permission.granted_by != revoker && revoker != admin { - panic!("Not authorized to revoke this access"); + return Err(ContractError::NotAuthorizedToRevoke); } found = true; // Skip this permission (effectively removing it) @@ -244,7 +257,7 @@ impl AccessControl { } if !found { - panic!("Access permission not found"); + return Err(ContractError::AccessPermissionNotFound); } env.storage() @@ -275,6 +288,7 @@ impl AccessControl { (symbol_short!("revoke"), revokee, resource_id), symbol_short!("success"), ); + Ok(()) } /// Check if an entity has access to a specific resource @@ -331,12 +345,12 @@ impl AccessControl { /// /// # Returns /// The EntityData for the given wallet address - pub fn get_entity(env: Env, wallet: Address) -> EntityData { + pub fn get_entity(env: Env, wallet: Address) -> Result { let key = DataKey::Entity(wallet); env.storage() .persistent() .get(&key) - .expect("Entity not found") + .ok_or(ContractError::EntityNotFound) } /// Get all access permissions for an entity @@ -359,7 +373,7 @@ impl AccessControl { /// # Arguments /// * `wallet` - The wallet address of the entity /// * `metadata` - Updated metadata information - pub fn update_entity(env: Env, wallet: Address, metadata: String) { + pub fn update_entity(env: Env, wallet: Address, metadata: String) -> Result<(), ContractError> { wallet.require_auth(); let key = DataKey::Entity(wallet.clone()); @@ -367,13 +381,14 @@ impl AccessControl { .storage() .persistent() .get(&key) - .expect("Entity not found"); + .ok_or(ContractError::EntityNotFound)?; entity.metadata = metadata; env.storage().persistent().set(&key, &entity); env.events() .publish((symbol_short!("upd_ent"), wallet), symbol_short!("success")); + Ok(()) } /// Deactivate an entity (admin only) @@ -381,17 +396,17 @@ impl AccessControl { /// # Arguments /// * `admin` - The admin address /// * `wallet` - The wallet address of the entity to deactivate - pub fn deactivate_entity(env: Env, admin: Address, wallet: Address) { + pub fn deactivate_entity(env: Env, admin: Address, wallet: Address) -> Result<(), ContractError> { admin.require_auth(); let stored_admin: Address = env .storage() .persistent() .get(&DataKey::Admin) - .expect("Contract not initialized"); + .ok_or(ContractError::ContractNotInitialized)?; if admin != stored_admin { - panic!("Only admin can deactivate entities"); + return Err(ContractError::OnlyAdminCanDeactivate); } let key = DataKey::Entity(wallet.clone()); @@ -399,13 +414,14 @@ impl AccessControl { .storage() .persistent() .get(&key) - .expect("Entity not found"); + .ok_or(ContractError::EntityNotFound)?; entity.active = false; env.storage().persistent().set(&key, &entity); env.events() .publish((symbol_short!("deact"), wallet), symbol_short!("success")); + Ok(()) } /// Register or update a W3C DID for the provided address. diff --git a/contracts/access-control/src/test.rs b/contracts/access-control/src/test.rs index 0b98a69..bbfb356 100644 --- a/contracts/access-control/src/test.rs +++ b/contracts/access-control/src/test.rs @@ -19,7 +19,6 @@ fn test_initialize() { } #[test] -#[should_panic(expected = "Contract already initialized")] fn test_double_initialize() { let env = Env::default(); env.mock_all_auths(); @@ -29,7 +28,9 @@ fn test_double_initialize() { let admin = Address::generate(&env); client.initialize(&admin); - client.initialize(&admin); // Should panic + + let result = client.try_initialize(&admin); + assert_eq!(result, Err(Ok(ContractError::AlreadyInitialized))); } #[test] @@ -56,7 +57,6 @@ fn test_register_entity() { } #[test] -#[should_panic(expected = "Entity already registered")] fn test_duplicate_registration() { let env = Env::default(); env.mock_all_auths(); @@ -72,7 +72,9 @@ fn test_duplicate_registration() { let metadata = String::from_str(&env, "General Hospital"); client.register_entity(&hospital, &EntityType::Hospital, &name, &metadata); - client.register_entity(&hospital, &EntityType::Hospital, &name, &metadata); // Should panic + + let result = client.try_register_entity(&hospital, &EntityType::Hospital, &name, &metadata); + assert_eq!(result, Err(Ok(ContractError::EntityAlreadyRegistered))); } #[test] @@ -273,7 +275,6 @@ fn test_deactivate_entity() { } #[test] -#[should_panic(expected = "Only admin can deactivate entities")] fn test_deactivate_entity_non_admin() { let env = Env::default(); env.mock_all_auths(); @@ -294,8 +295,8 @@ fn test_deactivate_entity_non_admin() { &String::from_str(&env, "metadata"), ); - // Non-admin tries to deactivate - should panic - client.deactivate_entity(&non_admin, &hospital); + let result = client.try_deactivate_entity(&non_admin, &hospital); + assert_eq!(result, Err(Ok(ContractError::OnlyAdminCanDeactivate))); } #[test] @@ -416,3 +417,174 @@ fn test_register_did_update_replaces_value() { let stored = client.get_did(&provider).unwrap(); assert_eq!(stored, did_v2); } + +#[test] +fn test_grant_access_grantor_not_registered() { + let env = Env::default(); + env.mock_all_auths(); + + let contract_id = env.register(AccessControl, ()); + let client = AccessControlClient::new(&env, &contract_id); + + let admin = Address::generate(&env); + client.initialize(&admin); + + let unregistered = Address::generate(&env); + let doctor = Address::generate(&env); + + client.register_entity( + &doctor, + &EntityType::Doctor, + &String::from_str(&env, "Dr. Smith"), + &String::from_str(&env, "metadata"), + ); + + let result = client.try_grant_access( + &unregistered, + &doctor, + &String::from_str(&env, "resource-1"), + &0, + ); + assert_eq!(result, Err(Ok(ContractError::GrantorNotRegistered))); +} + +#[test] +fn test_grant_access_grantee_not_registered() { + let env = Env::default(); + env.mock_all_auths(); + + let contract_id = env.register(AccessControl, ()); + let client = AccessControlClient::new(&env, &contract_id); + + let admin = Address::generate(&env); + client.initialize(&admin); + + let hospital = Address::generate(&env); + let unregistered = Address::generate(&env); + + client.register_entity( + &hospital, + &EntityType::Hospital, + &String::from_str(&env, "City Hospital"), + &String::from_str(&env, "metadata"), + ); + + let result = client.try_grant_access( + &hospital, + &unregistered, + &String::from_str(&env, "resource-1"), + &0, + ); + assert_eq!(result, Err(Ok(ContractError::GranteeNotRegistered))); +} + +#[test] +fn test_grant_access_already_granted() { + let env = Env::default(); + env.mock_all_auths(); + + let contract_id = env.register(AccessControl, ()); + let client = AccessControlClient::new(&env, &contract_id); + + let admin = Address::generate(&env); + client.initialize(&admin); + + let hospital = Address::generate(&env); + let doctor = Address::generate(&env); + + client.register_entity( + &hospital, + &EntityType::Hospital, + &String::from_str(&env, "City Hospital"), + &String::from_str(&env, "metadata"), + ); + client.register_entity( + &doctor, + &EntityType::Doctor, + &String::from_str(&env, "Dr. Smith"), + &String::from_str(&env, "metadata"), + ); + + let resource = String::from_str(&env, "patient-records"); + client.grant_access(&hospital, &doctor, &resource, &0); + + let result = client.try_grant_access(&hospital, &doctor, &resource, &0); + assert_eq!(result, Err(Ok(ContractError::AccessAlreadyGranted))); +} + +#[test] +fn test_revoke_access_permission_not_found() { + let env = Env::default(); + env.mock_all_auths(); + + let contract_id = env.register(AccessControl, ()); + let client = AccessControlClient::new(&env, &contract_id); + + let admin = Address::generate(&env); + client.initialize(&admin); + + let hospital = Address::generate(&env); + let doctor = Address::generate(&env); + + client.register_entity( + &hospital, + &EntityType::Hospital, + &String::from_str(&env, "City Hospital"), + &String::from_str(&env, "metadata"), + ); + client.register_entity( + &doctor, + &EntityType::Doctor, + &String::from_str(&env, "Dr. Smith"), + &String::from_str(&env, "metadata"), + ); + + let result = client.try_revoke_access( + &hospital, + &doctor, + &String::from_str(&env, "nonexistent-resource"), + ); + assert_eq!(result, Err(Ok(ContractError::AccessPermissionNotFound))); +} + +#[test] +fn test_revoke_access_not_authorized() { + let env = Env::default(); + env.mock_all_auths(); + + let contract_id = env.register(AccessControl, ()); + let client = AccessControlClient::new(&env, &contract_id); + + let admin = Address::generate(&env); + client.initialize(&admin); + + let hospital = Address::generate(&env); + let doctor = Address::generate(&env); + let other = Address::generate(&env); + + client.register_entity( + &hospital, + &EntityType::Hospital, + &String::from_str(&env, "City Hospital"), + &String::from_str(&env, "metadata"), + ); + client.register_entity( + &doctor, + &EntityType::Doctor, + &String::from_str(&env, "Dr. Smith"), + &String::from_str(&env, "metadata"), + ); + client.register_entity( + &other, + &EntityType::Doctor, + &String::from_str(&env, "Dr. Other"), + &String::from_str(&env, "metadata"), + ); + + let resource = String::from_str(&env, "patient-records"); + client.grant_access(&hospital, &doctor, &resource, &0); + + // `other` is not the grantor and not the admin + let result = client.try_revoke_access(&other, &doctor, &resource); + assert_eq!(result, Err(Ok(ContractError::NotAuthorizedToRevoke))); +}