pragma solidity ^0.5.3; import "./PermissionsUpgradable.sol"; /// @title Account manager contract /// @notice This contract holds implementation logic for all account management /// @notice functionality. This can be called only by the implementation /// @notice contract only. there are few view functions exposed as public and /// @notice can be called directly. these are invoked by quorum for populating /// @notice permissions data in cache contract AccountManager { PermissionsUpgradable private permUpgradable; // enum AccountStatus {0-NotInList, 1-PendingApproval, 2-Active, 3-Inactive, // 4-Suspended, 5-Blacklisted, 6-Revoked} struct AccountAccessDetails { address account; string orgId; string role; uint status; bool orgAdmin; } AccountAccessDetails[] private accountAccessList; mapping(address => uint) private accountIndex; uint private numAccounts; string private adminRole; string private orgAdminRole; mapping(bytes32 => address) private orgAdminIndex; // account permission events event AccountAccessModified(address _account, string _orgId, string _roleId, bool _orgAdmin, uint _status); event AccountAccessRevoked(address _account, string _orgId, string _roleId, bool _orgAdmin); event AccountStatusChanged(address _account, string _orgId, uint _status); /// @notice confirms that the caller is the address of implementation /// @notice contract modifier onlyImplementation { require(msg.sender == permUpgradable.getPermImpl(), "invalid caller"); _; } /// checks if the account is exists and belongs to the org id passed /// @param _orgId - org id /// @param _account - account id modifier accountExists(string memory _orgId, address _account) { require((accountIndex[_account]) != 0, "account does not exists"); require(keccak256(abi.encode(accountAccessList[_getAccountIndex(_account)].orgId)) == keccak256(abi.encode(_orgId)), "account in different org"); _; } /// @notice constructor. sets the permissions upgradable address constructor (address _permUpgradable) public { permUpgradable = PermissionsUpgradable(_permUpgradable); } /// @notice returns the account details for a given account /// @param _account account id /// @return account id /// @return org id of the account /// @return role linked to the account /// @return status of the account /// @return bool indicating if the account is an org admin function getAccountDetails(address _account) external view returns (address, string memory, string memory, uint, bool){ if (accountIndex[_account] == 0) { return (_account, "NONE", "", 0, false); } uint aIndex = _getAccountIndex(_account); return (accountAccessList[aIndex].account, accountAccessList[aIndex].orgId, accountAccessList[aIndex].role, accountAccessList[aIndex].status, accountAccessList[aIndex].orgAdmin); } /// @notice returns the account details a given account index /// @param _aIndex account index /// @return account id /// @return org id of the account /// @return role linked to the account /// @return status of the account /// @return bool indicating if the account is an org admin function getAccountDetailsFromIndex(uint _aIndex) external view returns (address, string memory, string memory, uint, bool) { return (accountAccessList[_aIndex].account, accountAccessList[_aIndex].orgId, accountAccessList[_aIndex].role, accountAccessList[_aIndex].status, accountAccessList[_aIndex].orgAdmin); } /// @notice returns the total number of accounts /// @return total number accounts function getNumberOfAccounts() external view returns (uint) { return accountAccessList.length; } /// @notice this is called at the time of network initialization to set /// @notice the default values of network admin and org admin roles function setDefaults(string calldata _nwAdminRole, string calldata _oAdminRole) external onlyImplementation { adminRole = _nwAdminRole; orgAdminRole = _oAdminRole; } /// @notice this function is called to assign the org admin or network /// @notice admin roles only to the passed account /// @param _account - account id /// @param _orgId - org to which it belongs /// @param _roleId - role id to be assigned /// @param _status - account status to be assigned function assignAdminRole(address _account, string calldata _orgId, string calldata _roleId, uint _status) external onlyImplementation { require(((keccak256(abi.encode(_roleId)) == keccak256(abi.encode(orgAdminRole))) || (keccak256(abi.encode(_roleId)) == keccak256(abi.encode(adminRole)))), "can be called to assign admin roles only"); _setAccountRole(_account, _orgId, _roleId, _status, true); } /// @notice this function is called to assign the any role to the passed /// @notice account. /// @param _account - account id /// @param _orgId - org to which it belongs /// @param _roleId - role id to be assigned /// @param _adminRole - indicates of the role is an admin role function assignAccountRole(address _account, string calldata _orgId, string calldata _roleId, bool _adminRole) external onlyImplementation { require(((keccak256(abi.encode(_roleId)) != keccak256(abi.encode(adminRole))) && (keccak256(abi.encode(abi.encode(_roleId))) != keccak256(abi.encode(orgAdminRole)))), "cannot be called fro assigning org admin and network admin roles"); _setAccountRole(_account, _orgId, _roleId, 2, _adminRole); } /// @notice this function removes existing admin account. will be called at /// @notice the time of adding a new account as org admin account. at org /// @notice level there can be one org admin account only /// @param _orgId - org id /// @return bool to indicate if voter update is required or not /// @return _adminRole - indicates of the role is an admin role function removeExistingAdmin(string calldata _orgId) external onlyImplementation returns (bool voterUpdate, address account) { // change the status of existing org admin to revoked if (orgAdminExists(_orgId)) { uint id = _getAccountIndex(orgAdminIndex[keccak256(abi.encode(_orgId))]); accountAccessList[id].status = 6; accountAccessList[id].orgAdmin = false; emit AccountAccessModified(accountAccessList[id].account, accountAccessList[id].orgId, accountAccessList[id].role, accountAccessList[id].orgAdmin, accountAccessList[id].status); return ((keccak256(abi.encode(accountAccessList[id].role)) == keccak256(abi.encode(adminRole))), accountAccessList[id].account); } return (false, address(0)); } /// @notice function to add an account as network admin or org admin. /// @param _orgId - org id /// @param _account - account id /// @return bool to indicate if voter update is required or not function addNewAdmin(string calldata _orgId, address _account) external onlyImplementation returns (bool voterUpdate) { // check of the account role is org admin role and status is pending // approval. if yes update the status to approved string memory role = getAccountRole(_account); uint status = _getAccountStatus(_account); uint id = _getAccountIndex(_account); if ((keccak256(abi.encode(role)) == keccak256(abi.encode(orgAdminRole))) && (status == 1)) { orgAdminIndex[keccak256(abi.encode(_orgId))] = _account; } accountAccessList[id].status = 2; accountAccessList[id].orgAdmin = true; emit AccountAccessModified(_account, accountAccessList[id].orgId, accountAccessList[id].role, accountAccessList[id].orgAdmin, accountAccessList[id].status); return (keccak256(abi.encode(accountAccessList[id].role)) == keccak256(abi.encode(adminRole))); } /// @notice updates the account status to the passed status value /// @param _orgId - org id /// @param _account - account id /// @param _action - new status of the account function updateAccountStatus(string calldata _orgId, address _account, uint _action) external onlyImplementation accountExists(_orgId, _account) { // operations that can be done 1-Suspend account, 2-Unsuspend Account, 3-Blacklist account require((_action == 1 || _action == 2 || _action == 3), "invalid status change request"); // check if the account is org admin. if yes then do not allow any status change require(checkOrgAdmin(_account, _orgId, "") != true, "status change not possible for org admin accounts"); uint newStatus; if (_action == 1) { // for suspending an account current status should be active require(accountAccessList[_getAccountIndex(_account)].status == 2, "account is not in active status. operation cannot be done"); newStatus = 4; } else if (_action == 2) { // for reactivating a suspended account, current status should be suspended require(accountAccessList[_getAccountIndex(_account)].status == 4, "account is not in suspended status. operation cannot be done"); newStatus = 2; } else if (_action == 3) { require(accountAccessList[_getAccountIndex(_account)].status != 5, "account is already blacklisted. operation cannot be done"); newStatus = 5; } accountAccessList[_getAccountIndex(_account)].status = newStatus; emit AccountStatusChanged(_account, _orgId, newStatus); } /// @notice checks if the passed account exists and if exists does it /// @notice belong to the passed organization. /// @param _account - account id /// @param _orgId - org id /// @return bool true if the account does not exists or exists and belongs /// @return passed org function validateAccount(address _account, string calldata _orgId) external view returns (bool){ if (accountIndex[_account] == 0) { return true; } uint256 id = _getAccountIndex(_account); return (keccak256(abi.encode(accountAccessList[id].orgId)) == keccak256(abi.encode(_orgId))); } /// @notice checks if org admin account exists for the passed org id /// @param _orgId - org id /// @return true if the org admin account exists and is approved function orgAdminExists(string memory _orgId) public view returns (bool) { if (orgAdminIndex[keccak256(abi.encode(_orgId))] != address(0)) { address adminAcct = orgAdminIndex[keccak256(abi.encode(_orgId))]; return _getAccountStatus(adminAcct) == 2; } return false; } /// @notice returns the role id linked to the passed account /// @param _account account id /// @return role id function getAccountRole(address _account) public view returns (string memory) { if (accountIndex[_account] == 0) { return "NONE"; } uint256 acctIndex = _getAccountIndex(_account); if (accountAccessList[acctIndex].status != 0) { return accountAccessList[acctIndex].role; } else { return "NONE"; } } /// @notice checks if the account is a org admin for the passed org or /// @notice for the ultimate parent organization /// @param _account account id /// @param _orgId org id /// @param _ultParent master org id or function checkOrgAdmin(address _account, string memory _orgId, string memory _ultParent) public view returns (bool) { // check if the account role is network admin. If yes return success if (keccak256(abi.encode(getAccountRole(_account))) == keccak256(abi.encode(adminRole))) { // check of the orgid is network admin org. then return true uint256 id = _getAccountIndex(_account); return ((keccak256(abi.encode(accountAccessList[id].orgId)) == keccak256(abi.encode(_orgId))) || (keccak256(abi.encode(accountAccessList[id].orgId)) == keccak256(abi.encode(_ultParent)))); } return ((orgAdminIndex[keccak256(abi.encode(_orgId))] == _account) || (orgAdminIndex[keccak256(abi.encode(_ultParent))] == _account)); } /// @notice returns the index for a given account id /// @param _account account id /// @return account index function _getAccountIndex(address _account) internal view returns (uint256) { return accountIndex[_account] - 1; } /// @notice returns the account status for a given account /// @param _account account id /// @return account status function _getAccountStatus(address _account) internal view returns (uint256) { if (accountIndex[_account] == 0) { return 0; } uint256 aIndex = _getAccountIndex(_account); return (accountAccessList[aIndex].status); } /// @notice sets the account role to the passed role id and sets the status /// @param _account account id /// @param _orgId org id /// @param _status status to be set /// @param _oAdmin bool to indicate if account is org admin function _setAccountRole(address _account, string memory _orgId, string memory _roleId, uint256 _status, bool _oAdmin) internal onlyImplementation { // Check if account already exists uint256 aIndex = _getAccountIndex(_account); if (accountIndex[_account] != 0) { accountAccessList[aIndex].role = _roleId; accountAccessList[aIndex].status = _status; accountAccessList[aIndex].orgAdmin = _oAdmin; } else { numAccounts ++; accountIndex[_account] = numAccounts; accountAccessList.push(AccountAccessDetails(_account, _orgId, _roleId, _status, _oAdmin)); } emit AccountAccessModified(_account, _orgId, _roleId, _oAdmin, _status); } }