quorum/permission/contract/AccountManager.sol

315 lines
14 KiB
Solidity

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);
}
}