diff --git a/controls/permission/AccountManager.sol b/controls/permission/AccountManager.sol new file mode 100644 index 000000000..0e79ca676 --- /dev/null +++ b/controls/permission/AccountManager.sol @@ -0,0 +1,173 @@ +pragma solidity ^0.5.3; + +contract AccountManager { +// enum AccountStatus {0-NotInList, 1-PendingApproval, 2-Active, 3-Inactive} + struct AccountAccessDetails { + address acctId; + string orgId; + string role; + uint status; + bool orgAdmin; + } + + AccountAccessDetails[] private acctAccessList; + mapping(address => uint) private accountIndex; + uint private numberOfAccts; + + string private adminRole; + string private orgAdminRole; + + mapping(bytes32 => bool) private orgAdminIndex; + + // account permission events + event AccountAccessModified(address _address, string _roleId); + event AccountAccessRevoked(address _address, string _roleId); + + // Get account details given index + + function orgAdminExists(string memory _orgId) public view returns (bool) + { + return orgAdminIndex[keccak256(abi.encodePacked(_orgId))]; + + } + + function getAccountStatus(address _acct) internal view returns (uint) + { + if (accountIndex[_acct] == 0) { + return 0; + } + uint aIndex = getAcctIndex(_acct); + return (acctAccessList[aIndex].status); + } + + function getAccountDetails(address _acct) external view returns (address, string memory, string memory, uint, bool) + { + if (accountIndex[_acct] == 0) { + return (_acct, "NONE", "", 0, false); + } + uint aIndex = getAcctIndex(_acct); + return (acctAccessList[aIndex].acctId, acctAccessList[aIndex].orgId, acctAccessList[aIndex].role, acctAccessList[aIndex].status, acctAccessList[aIndex].orgAdmin); + } + + // Get number of accounts + function getNumberOfAccounts() external view returns (uint) + { + return acctAccessList.length; + } + + function setDefaults(string calldata _nwAdminRole, string calldata _oAdminRole) external + { + adminRole = _nwAdminRole; + orgAdminRole = _oAdminRole; + } + + function setAccountRole(address _address, string memory _orgId, string memory _roleId, uint _status, bool _oAdmin) internal + { + // Check if account already exists + uint aIndex = getAcctIndex(_address); + if (accountIndex[_address] != 0) { + acctAccessList[aIndex].role = _roleId; + acctAccessList[aIndex].status = _status; + acctAccessList[aIndex].orgAdmin = _oAdmin; + } + else { + numberOfAccts ++; + accountIndex[_address] = numberOfAccts; + acctAccessList.push(AccountAccessDetails(_address, _orgId, _roleId, _status, _oAdmin)); + } + if (_oAdmin) { + orgAdminIndex[keccak256(abi.encodePacked(_orgId))] = true; + } + emit AccountAccessModified(_address, _roleId); + } + + function addNWAdminAccount(address _address, string calldata _orgId) external + { + setAccountRole(_address, _orgId, adminRole, 2, true); + } + + function assignAccountRole(address _address, string calldata _orgId, string calldata _roleId) external + { + bool oAdminRole = false; + uint status = 2; + // if the role id is ORGADMIN then check if already an orgadmin exists + if ((keccak256(abi.encodePacked(_roleId)) == keccak256(abi.encodePacked(orgAdminRole))) || + (keccak256(abi.encodePacked(_roleId)) == keccak256(abi.encodePacked(adminRole)))) { + if (orgAdminIndex[keccak256(abi.encodePacked(_orgId))]) { + return; + } + else { + oAdminRole = true; + status = 1; + } + } + setAccountRole(_address, _orgId, _roleId, status, oAdminRole); + } + + function approveOrgAdminAccount(address _address) external + { + // check of the account role is ORGADMIN and status is pending approval + // if yes update the status to approved + string memory role = getAccountRole(_address); + uint status = getAccountStatus(_address); + + if ((keccak256(abi.encodePacked(role)) == keccak256(abi.encodePacked(orgAdminRole))) && + (status == 1)) { + uint aIndex = getAcctIndex(_address); + acctAccessList[aIndex].status = 2; + emit AccountAccessModified(_address, acctAccessList[aIndex].role); + } + + } + + function revokeAccountRole(address _address) external + { + // Check if account already exists + uint aIndex = getAcctIndex(_address); + if (accountIndex[_address] != 0) { + acctAccessList[aIndex].status = 3; + emit AccountAccessRevoked(_address, acctAccessList[aIndex].role); + } + } + + function getAccountRole(address _acct) public view returns (string memory) + { + if (accountIndex[_acct] == 0) { + return "NONE"; + } + uint acctIndex = getAcctIndex(_acct); + if (acctAccessList[acctIndex].status != 0) { + return acctAccessList[acctIndex].role; + } + else { + return "NONE"; + } + } + + function checkOrgAdmin(address _acct, string calldata _orgId) external view returns (bool) + { + if (accountIndex[_acct] == 0) { + return false; + } + uint acctIndex = getAcctIndex(_acct); + return ((acctAccessList[acctIndex].orgAdmin) && + (keccak256(abi.encodePacked(acctAccessList[acctIndex].orgId)) == keccak256(abi.encodePacked(_orgId)))); + } + + // this function checks if account access can be modified. Account access can be modified for a new account + // or if the call is from the orgadmin of the same org. + function valAcctAccessChange(address _acct, string calldata _orgId) external view returns (bool) + { + if (accountIndex[_acct] == 0) { + return true; + } + uint acctIndex = getAcctIndex(_acct); + return ((keccak256(abi.encodePacked(acctAccessList[acctIndex].orgId)) == keccak256(abi.encodePacked(_orgId)))); + } + // Returns the account index based on account id + function getAcctIndex(address _acct) internal view returns (uint) + { + return accountIndex[_acct] - 1; + } + +} diff --git a/controls/permission/NodeManager.sol b/controls/permission/NodeManager.sol new file mode 100644 index 000000000..000d51474 --- /dev/null +++ b/controls/permission/NodeManager.sol @@ -0,0 +1,197 @@ +pragma solidity ^0.5.3; + +contract NodeManager { + address[] initialAcctList; + // enum and struct declaration + // changing node status to integer (0-NotInList, 1- PendingApproval, 2-Approved, + // PendingDeactivation, Deactivated, PendingActivation, PendingBlacklisting, Blacklisted) +// enum NodeStatus {NotInList, PendingApproval, Approved, PendingDeactivation, Deactivated, PendingActivation, PendingBlacklisting, Blacklisted} + struct NodeDetails { + string enodeId; //e.g. 127.0.0.1:20005 + string orgId; + uint status; + } + // use an array to store node details + // if we want to list all node one day, mapping is not capable + NodeDetails[] private nodeList; + // use a mapping of enodeid to array index to track node + mapping(bytes32 => uint) private nodeIdToIndex; + // keep track of node number + uint private numberOfNodes; + + + // node permission events for new node propose + event NodeProposed(string _enodeId); + event NodeApproved(string _enodeId); + + // node permission events for node decativation + event NodePendingDeactivation (string _enodeId); + event NodeDeactivated(string _enodeId); + + // node permission events for node activation + event NodePendingActivation(string _enodeId); + event NodeActivated(string _enodeId); + + // node permission events for node blacklist + event NodePendingBlacklist(string _enodeId); + event NodeBlacklisted(string); + + // Checks if the given enode exists + modifier enodeInList(string memory _enodeId) + { + require(nodeIdToIndex[keccak256(abi.encodePacked(_enodeId))] != 0, "Enode is not in the list"); + _; + } + + // Checks if the given enode does not exists + modifier enodeNotInList(string memory _enodeId) + { + require(nodeIdToIndex[keccak256(abi.encodePacked(_enodeId))] == 0, "Enode is in the list"); + _; + } + + // Get node details given enode Id + function getNodeDetails(string memory enodeId) public view returns (string memory _enodeId, uint _nodeStatus) + { + uint nodeIndex = getNodeIndex(enodeId); + return (nodeList[nodeIndex].enodeId, nodeList[nodeIndex].status); + } + // Get node details given index + function getNodeDetailsFromIndex(uint nodeIndex) public view returns (string memory _enodeId, uint _nodeStatus) + { + return (nodeList[nodeIndex].enodeId, nodeList[nodeIndex].status); + } + // Get number of nodes + function getNumberOfNodes() public view returns (uint) + { + return numberOfNodes; + } + + // Get node status by enode id + function getNodeStatus(string memory _enodeId) public view returns (uint) + { + if (nodeIdToIndex[keccak256(abi.encodePacked(_enodeId))] == 0){ + return 0; + } + return nodeList[getNodeIndex(_enodeId)].status; + } + + function addNode(string calldata _enodeId, string calldata _orgId) external enodeNotInList(_enodeId){ + numberOfNodes++; + nodeIdToIndex[keccak256(abi.encodePacked(_enodeId))] = numberOfNodes; + nodeList.push(NodeDetails(_enodeId, _orgId, 1)); + emit NodeProposed(_enodeId); + } + + function addOrgNode(string calldata _enodeId, string calldata _orgId) external enodeNotInList(_enodeId){ + numberOfNodes++; + nodeIdToIndex[keccak256(abi.encodePacked(_enodeId))] = numberOfNodes; + nodeList.push(NodeDetails(_enodeId, _orgId, 2)); + emit NodeApproved(_enodeId); + } + + // Adds a node to the nodeList mapping and emits node added event if successfully and node exists event of node is already present + function approveNode(string calldata _enodeId) external + { + require(getNodeStatus(_enodeId) == 1, "Node need to be in PendingApproval status"); + uint nodeIndex = getNodeIndex(_enodeId); + // vote node + nodeList[nodeIndex].status = 2; + emit NodeApproved(nodeList[nodeIndex].enodeId); + } + +// // Propose a node for deactivation from network +// function proposeDeactivation(string calldata _enodeId) external enodeInList(_enodeId) +// { +// require(getNodeStatus(_enodeId) == NodeStatus.Approved, "Node need to be in Approved status"); +// uint nodeIndex = getNodeIndex(_enodeId); +// nodeList[nodeIndex].status = NodeStatus.PendingDeactivation; +// emit NodePendingDeactivation(_enodeId); +// +// } +// +// //deactivates a given Enode and emits the decativation event +// function deactivateNode(string calldata _enodeId) external +// { +// require(getNodeStatus(_enodeId) == NodeStatus.PendingDeactivation, "Node need to be in PendingDeactivation status"); +// uint nodeIndex = getNodeIndex(_enodeId); +// nodeList[nodeIndex].status = NodeStatus.Deactivated; +// emit NodeDeactivated(nodeList[nodeIndex].enodeId); +// +// } +// +// // Propose node for blacklisting +// function proposeNodeActivation(string calldata _enodeId) external +// { +// require(getNodeStatus(_enodeId) == NodeStatus.Deactivated, "Node need to be in Deactivated status"); +// uint nodeIndex = getNodeIndex(_enodeId); +// nodeList[nodeIndex].status = NodeStatus.PendingActivation; +// // emit event +// emit NodePendingActivation(_enodeId); +// } + +// //deactivates a given Enode and emits the decativation event +// function activateNode(string calldata _enodeId) external +// { +// require(getNodeStatus(_enodeId) == NodeStatus.PendingActivation, "Node need to be in PendingActivation status"); +// uint nodeIndex = getNodeIndex(_enodeId); +// require(voteStatus[nodeIndex][msg.sender] == false, "Node can not double vote"); +// // vote node +// updateVoteStatus(nodeIndex); +// // emit event +// // check if node vote reach majority +// if (checkEnoughVotes(nodeIndex)) { +// nodeList[nodeIndex].status = NodeStatus.Approved; +// emit NodeActivated(nodeList[nodeIndex].enodeId, nodeList[nodeIndex].ipAddrPort, nodeList[nodeIndex].discPort, nodeList[nodeIndex].raftPort); +// } +// } +// +// // Propose node for blacklisting +// function proposeNodeBlacklisting(string calldata _enodeId, string calldata _ipAddrPort, string calldata _discPort, string calldata _raftPort) external +// { +// if (checkVotingAccountExist()) { +// uint nodeIndex = getNodeIndex(_enodeId); +// // check if node is in the nodeList +// if (nodeIdToIndex[keccak256(abi.encodePacked(_enodeId))] != 0) { +// // no matter what status the node is in, vote will reset and node status change to PendingBlacklisting +// nodeList[nodeIndex].status = NodeStatus.PendingBlacklisting; +// nodeIndex = getNodeIndex(_enodeId); +// } else { +// // increment node number, add node to the list +// numberOfNodes++; +// nodeIdToIndex[keccak256(abi.encodePacked(_enodeId))] = numberOfNodes; +// nodeList.push(NodeDetails(_enodeId, _ipAddrPort, _discPort, _raftPort, NodeStatus.PendingBlacklisting)); +// nodeIndex = numberOfNodes; +// } +// // add voting status, numberOfNodes is the index of current proposed node +// initNodeVoteStatus(nodeIndex); +// // emit event +// emit NodePendingBlacklist(_enodeId); +// } +// } +// +// //Approve node blacklisting +// function blacklistNode(string calldata _enodeId) external +// { +// require(getNodeStatus(_enodeId) == NodeStatus.PendingBlacklisting, "Node need to be in PendingBlacklisting status"); +// uint nodeIndex = getNodeIndex(_enodeId); +// require(voteStatus[nodeIndex][msg.sender] == false, "Node can not double vote"); +// // vote node +// voteStatus[nodeIndex][msg.sender] = true; +// voteCount[nodeIndex]++; +// // emit event +// // check if node vote reach majority +// if (checkEnoughVotes(nodeIndex)) { +// nodeList[nodeIndex].status = NodeStatus.Blacklisted; +// emit NodeBlacklisted(nodeList[nodeIndex].enodeId, nodeList[nodeIndex].ipAddrPort, nodeList[nodeIndex].discPort, nodeList[nodeIndex].raftPort); +// } +// } + + /* private functions */ + function getNodeIndex(string memory _enodeId) internal view returns (uint) + { + return nodeIdToIndex[keccak256(abi.encodePacked(_enodeId))] - 1; + } + + +} diff --git a/controls/permission/OrgManager.sol b/controls/permission/OrgManager.sol new file mode 100644 index 000000000..3913a8c41 --- /dev/null +++ b/controls/permission/OrgManager.sol @@ -0,0 +1,168 @@ +pragma solidity ^0.5.3; + +import "./PermissionsUpgradable.sol"; + +contract OrgManager { + string private adminOrgId; + PermissionsImplUpgradeable private permUpgradable; + // checks if first time network boot up has happened or not + bool private networkBoot = false; + // enum OrgStatus {0- NotInList, 1- Proposed, 2- Approved, 3- PendingSuspension, 4- Suspended, 5- RevokeSuspension} + struct OrgDetails { + string orgId; + uint status; + } + OrgDetails [] private orgList; + mapping(bytes32 => uint) private OrgIndex; + uint private orgNum = 0; + + // events related to Master Org add + event OrgApproved(string _orgId); + event OrgPendingApproval(string _orgId, uint _type); + event OrgSuspended(string _orgId); + event OrgSuspensionRevoked(string _orgId); + + event Dummy(string _msg); + + modifier onlyImpl + { + require(msg.sender == permUpgradable.getPermImpl()); + _; + } + + modifier orgNotExists(string memory _orgId) { + require(checkOrgExists(_orgId) == false, "Org already exists"); + _; + } + + modifier orgExists(string memory _orgId) { + require(checkOrgExists(_orgId) == true, "Org does not exists"); + _; + } + + function setUpgradable (address _permUpgradable) external { + permUpgradable = PermissionsImplUpgradeable(_permUpgradable); + } + + function addAdminOrg(string calldata _orgId) external + onlyImpl + { + orgNum++; + OrgIndex[keccak256(abi.encodePacked(_orgId))] = orgNum; + uint id = orgList.length++; + orgList[id].orgId = _orgId; + orgList[id].status = 2; + emit OrgApproved(_orgId); + } + + // Org related functions + // returns the org index for the org list + function getOrgIndex(string memory _orgId) public view returns (uint) + { + return OrgIndex[keccak256(abi.encodePacked(_orgId))] - 1; + } + + function getOrgStatus(string memory _orgId) public view returns (uint) + { + return orgList[OrgIndex[keccak256(abi.encodePacked(_orgId))]].status; + } + + // function for adding a new master org + function addOrg(string calldata _orgId) external + onlyImpl + orgNotExists(_orgId) + { + orgNum++; + OrgIndex[keccak256(abi.encodePacked(_orgId))] = orgNum; + uint id = orgList.length++; + orgList[id].orgId = _orgId; + orgList[id].status = 1; + emit OrgPendingApproval(_orgId, 1); + } + + function updateOrg(string calldata _orgId, uint _status) external + onlyImpl + orgExists(_orgId) + { + if (_status == 3) { + suspendOrg(_orgId); + } + else { + revokeOrgSuspension(_orgId); + } + } + + function approveOrgStatusUpdate(string calldata _orgId, uint _status) external + onlyImpl + orgExists(_orgId) + { + if (_status == 3) { + approveOrgSuspension(_orgId); + } + else { + approveOrgRevokeSuspension(_orgId); + } + } + + + // function for adding a new master org + function suspendOrg(string memory _orgId) internal + { + require(checkOrgStatus(_orgId, 2) == true, "Org not in approved state"); + uint id = getOrgIndex(_orgId); + orgList[id].status = 3; + emit OrgPendingApproval(_orgId, 3); + } + + function revokeOrgSuspension(string memory _orgId) internal + + { + require(checkOrgStatus(_orgId, 4) == true, "Org not in suspended state"); + uint id = getOrgIndex(_orgId); + orgList[id].status = 5; + emit OrgPendingApproval(_orgId, 5); + } + + function approveOrg(string calldata _orgId) external + onlyImpl + { + require(checkOrgStatus(_orgId, 1) == true, "Nothing to approve"); + uint id = getOrgIndex(_orgId); + orgList[id].status = 2; + emit OrgApproved(_orgId); + } + + function approveOrgSuspension(string memory _orgId) internal + + { + require(checkOrgStatus(_orgId, 3) == true, "Nothing to approve"); + uint id = getOrgIndex(_orgId); + orgList[id].status = 4; + emit OrgSuspended(_orgId); + } + + function approveOrgRevokeSuspension(string memory _orgId) internal + { + require(checkOrgStatus(_orgId, 5) == true, "Nothing to approve"); + uint id = getOrgIndex(_orgId); + orgList[id].status = 2; + emit OrgSuspensionRevoked(_orgId); + } + + function checkOrgStatus(string memory _orgId, uint _orgStatus) public view returns (bool){ + uint id = getOrgIndex(_orgId); + return ((OrgIndex[keccak256(abi.encodePacked(_orgId))] != 0) && orgList[id].status == _orgStatus); + } + + // function to check if morg exists + function checkOrgExists(string memory _orgId) public view returns (bool) + { + return (!(OrgIndex[keccak256(abi.encodePacked(_orgId))] == 0)); + } + + // returns org and master org details based on org index + function getOrgInfo(uint _orgIndex) external view returns (string memory, uint) + { + return (orgList[_orgIndex].orgId, orgList[_orgIndex].status); + } +} diff --git a/controls/permission/PermissionsImplementation.sol b/controls/permission/PermissionsImplementation.sol new file mode 100644 index 000000000..4fcfa3a83 --- /dev/null +++ b/controls/permission/PermissionsImplementation.sol @@ -0,0 +1,325 @@ +pragma solidity ^0.5.3; + +import "./RoleManager.sol"; +import "./AccountManager.sol"; +import "./VoterManager.sol"; +import "./NodeManager.sol"; +import "./OrgManager.sol"; + +contract PermissionsImplementation { + AccountManager private accounts; + RoleManager private roles; + VoterManager private voter; + NodeManager private nodes; + OrgManager private org; + + string private adminOrg; + string private adminRole; + string private orgAdminRole; + + uint private fullAccess = 3; + + // checks if first time network boot up has happened or not + bool private networkBoot = false; + + // Checks if the given network boot up is pending exists + modifier networkBootUpPending() + { + require(networkBoot == false, "Network boot up completed"); + _; + } + + // Checks if the given network boot up is pending exists + modifier networkBootUpDone() + { + require(networkBoot == true, "Network boot not complete"); + _; + } + + modifier networkAdmin(address _account) { + require(isNetworkAdmin(_account) == true, "Not an network admin"); + _; + } + + modifier orgAdmin(address _account, string memory _orgId) { + require(isOrgAdmin(_account, _orgId) == true, "Not an org admin"); + _; + } + + modifier orgNotExists(string memory _orgId) { + require(org.checkOrgExists(_orgId) == false, "Org already exists"); + _; + } + + modifier orgExists(string memory _orgId) { + require(org.checkOrgExists(_orgId) == true, "Org does not exists"); + _; + } + + modifier orgApproved(string memory _orgId) { + require(org.checkOrgStatus(_orgId, 2) == true, "Org not approved"); + _; + } + + + function setPolicy(string calldata _nwAdminOrg, string calldata _nwAdminRole, string calldata _oAdminRole) external + networkBootUpPending() + { + adminOrg = _nwAdminOrg; + adminRole = _nwAdminRole; + orgAdminRole = _oAdminRole; + } + + function init(address _orgManager, address _rolesManager, address _acctManager, address _voterManager, address _nodeManager) external + networkBootUpPending() + { + org = OrgManager(_orgManager); + roles = RoleManager(_rolesManager); + accounts = AccountManager(_acctManager); + voter = VoterManager(_voterManager); + nodes = NodeManager(_nodeManager); + + org.addAdminOrg(adminOrg); + roles.addRole(adminRole, adminOrg, fullAccess, true); + accounts.setDefaults(adminRole, orgAdminRole); + } + + function addAdminNodes(string calldata _enodeId) external + networkBootUpPending() + { + nodes.addNode(_enodeId, adminOrg); + nodes.approveNode(_enodeId); + } + + function addAdminAccounts(address _acct) external + networkBootUpPending() + { + // add the account as a voter for the admin org + voter.addVoter(adminOrg, _acct); + // add the account as an account with full access into the admin org + accounts.addNWAdminAccount(_acct, adminOrg); + } + + // update the network boot status as true + function updateNetworkBootStatus() external + networkBootUpPending() + returns (bool) + { + networkBoot = true; + return networkBoot; + } + + // // Get network boot status + function getNetworkBootStatus() external view returns (bool) + { + return networkBoot; + } + + // function for adding a new master org + function addOrg(string calldata _orgId, string calldata _enodeId) external + networkBootUpDone() + orgNotExists(_orgId) + networkAdmin(msg.sender) + { + org.addOrg(_orgId); + // add the node to permissioned node list + nodes.addNode(_enodeId, _orgId); + // org add has to be approved by network admin org. create an item for approval + voter.addVotingItem(adminOrg, _orgId, _enodeId, address(0), 1); + } + + function approveOrg(string calldata _orgId, string calldata _enodeId) external + networkBootUpDone() + networkAdmin(msg.sender) + { + require(org.checkOrgStatus(_orgId, 1) == true, "Nothing to approve"); + if ((voter.processVote(adminOrg, msg.sender, 1))) { + org.approveOrg(_orgId); + nodes.approveNode(_enodeId); + } + } + + function updateOrgStatus(string calldata _orgId, uint _status) external + networkBootUpDone() + orgExists(_orgId) + networkAdmin(msg.sender) + { + require ((_status == 3 || _status == 5), "Operation not allowed"); + uint reqStatus; + uint pendingOp; + if (_status == 3) { + reqStatus = 2; + pendingOp = 2; + } + else if (_status == 5) { + reqStatus = 4; + pendingOp = 3; + } + require(org.checkOrgStatus(_orgId, reqStatus) == true, "Operation not allowed"); + org.updateOrg(_orgId, _status); + voter.addVotingItem(adminOrg, _orgId, "", address(0), pendingOp); + } + + function approveOrgStatus(string calldata _orgId, uint _status) external + networkBootUpDone() + orgExists(_orgId) + networkAdmin(msg.sender) + { + require ((_status == 3 || _status == 5), "Operation not allowed"); + uint pendingOp; + if (_status == 3) { + pendingOp = 2; + } + else if (_status == 5) { + pendingOp = 3; + } + require(org.checkOrgStatus(_orgId, _status) == true, "Operation not allowed"); + if ((voter.processVote(adminOrg, msg.sender, pendingOp))) { + org.approveOrgStatusUpdate(_orgId, _status); + } + } + // returns org and master org details based on org index + function getOrgInfo(uint _orgIndex) external view returns (string memory, uint) + { + return org.getOrgInfo(_orgIndex); + } + + // Role related functions + function addNewRole(string calldata _roleId, string calldata _orgId, uint _access, bool _voter) external + orgApproved(_orgId) + orgAdmin(msg.sender, _orgId) + { + //add new roles can be created by org admins only + roles.addRole(_roleId, _orgId, _access, _voter); + } + + function removeRole(string calldata _roleId, string calldata _orgId) external + orgApproved(_orgId) + orgAdmin(msg.sender, _orgId) + { + roles.removeRole(_roleId, _orgId); + } + + function getRoleDetails(string calldata _roleId, string calldata _orgId) external view returns (string memory, string memory, uint, bool, bool) + { + return roles.getRoleDetails(_roleId, _orgId); + + } + + // Org voter related functions + function getNumberOfVoters(string calldata _orgId) external view returns (uint){ + + return voter.getNumberOfValidVoters(_orgId); + } + + function checkIfVoterExists(string calldata _orgId, address _acct) external view returns (bool) + { + return voter.checkIfVoterExists(_orgId, _acct); + } + + function getVoteCount(string calldata _orgId) external view returns (uint, uint) + { + return voter.getVoteCount(_orgId); + } + + function getPendingOp(string calldata _orgId) external view returns (string memory, string memory, address, uint) + { + return voter.getPendingOpDetails(_orgId); + } + + function assignOrgAdminAccount(string calldata _orgId, address _account) external + networkBootUpDone() + networkAdmin(msg.sender) + orgExists(_orgId) + { + // check if orgAdmin already exists if yes then op cannot be performed + require(accounts.orgAdminExists(_orgId) != true, "org admin exists"); + // assign the account org admin role and propose voting + accounts.assignAccountRole(_account, _orgId, orgAdminRole); + //add voting item + voter.addVotingItem(adminOrg, _orgId, "", _account, 4); + } + + function approveOrgAdminAccount(address _account) external + networkBootUpDone() + networkAdmin(msg.sender) + { + require(isNetworkAdmin(msg.sender) == true, "can be called from network admin only"); + if ((voter.processVote(adminOrg, msg.sender, 4))) { + accounts.approveOrgAdminAccount(_account); + } + } + + + function assignAccountRole(address _acct, string memory _orgId, string memory _roleId) public + networkBootUpDone() + orgApproved(_orgId) + orgAdmin(msg.sender, _orgId) + { + // check if the account is part of another org. If yes then op cannot be done + require(validateAccount(_acct, _orgId) == true, "Operation cannot be performed"); + // check if role is existing for the org. if yes the op can be done + require(roles.roleExists(_roleId, _orgId) == true, "role does not exists"); + bool newRoleVoter = roles.isVoterRole(_roleId, _orgId); + // check the role of the account. if the current role is voter and new role is also voter + // voterlist change is not required. else voter list needs to be changed + string memory acctRole = accounts.getAccountRole(_acct); + if (keccak256(abi.encodePacked(acctRole)) == keccak256(abi.encodePacked("NONE"))) { + //new account + if (newRoleVoter) { + // add to voter list + voter.addVoter(_orgId, _acct); + } + } + else { + bool currRoleVoter = roles.isVoterRole(acctRole, _orgId); + if (!(currRoleVoter && newRoleVoter)) { + if (newRoleVoter) { + // add to voter list + voter.addVoter(_orgId, _acct); + } + else { + // delete from voter list + voter.deleteVoter(_orgId, _acct); + } + } + } + accounts.assignAccountRole(_acct, _orgId, _roleId); + } + + function addNode(string calldata _orgId, string calldata _enodeId) external + networkBootUpDone() + orgApproved(_orgId) + orgAdmin(msg.sender, _orgId) + { + // check that the node is not part of another org + require(getNodeStatus(_enodeId) == 0, "Node present already"); + nodes.addOrgNode(_enodeId, _orgId); + } + + function getNodeStatus(string memory _enodeId) public view returns (uint) + { + return (nodes.getNodeStatus(_enodeId)); + } + + function isNetworkAdmin(address _account) public view returns (bool) + { + return (keccak256(abi.encodePacked(accounts.getAccountRole(_account))) == keccak256(abi.encodePacked(adminRole))); + } + + function isOrgAdmin(address _account, string memory _orgId) public view returns (bool) + { + return (accounts.checkOrgAdmin(_account, _orgId)); + } + + function validateAccount(address _account, string memory _orgId) public view returns (bool) + { + return (accounts.valAcctAccessChange(_account, _orgId)); + } + + function getAccountDetails(address _acct) external view returns (address, string memory, string memory, uint, bool) + { + return accounts.getAccountDetails(_acct); + } + +} \ No newline at end of file diff --git a/controls/permission/PermissionsInterface.sol b/controls/permission/PermissionsInterface.sol new file mode 100644 index 000000000..79631a0cd --- /dev/null +++ b/controls/permission/PermissionsInterface.sol @@ -0,0 +1,174 @@ +pragma solidity ^0.5.3; + +import "./PermissionsImplementation.sol"; + +contract PermissionsInterface { + PermissionsImplementation permImplementation; + address private permImplUpgradeable; + + constructor(address _permImplUpgradeable) public { + permImplUpgradeable = _permImplUpgradeable; + } + + modifier onlyUpgradeable { + require(msg.sender == permImplUpgradeable); + _; + } + + function setPermImplementation(address _permImplementation) public + onlyUpgradeable + { + permImplementation = PermissionsImplementation(_permImplementation); + } + + function getPermissionsImpl() public view returns(address) + { + return address(permImplementation); + } + + function setPolicy(string calldata _nwAdminOrg, string calldata _nwAdminRole, string calldata _oAdminRole) external + { + permImplementation.setPolicy(_nwAdminOrg, _nwAdminRole, _oAdminRole); + } + + function init(address _orgManager, address _rolesManager, address _acctManager, address _voterManager, address _nodeManager) external + { + permImplementation.init(_orgManager, _rolesManager, _acctManager, _voterManager, _nodeManager); + } + + function addAdminNodes(string calldata _enodeId) external + { + permImplementation.addAdminNodes(_enodeId); + } + + function addAdminAccounts(address _acct) external + { + permImplementation.addAdminAccounts(_acct); + } + + // update the network boot status as true + function updateNetworkBootStatus() external + returns (bool) + { + permImplementation.updateNetworkBootStatus(); + } + + // // Get network boot status + function getNetworkBootStatus() external view returns (bool) + { + return permImplementation.getNetworkBootStatus(); + } + + // function for adding a new master org + function addOrg(string calldata _orgId, string calldata _enodeId) external + { + permImplementation.addOrg(_orgId, _enodeId); + } + + function approveOrg(string calldata _orgId, string calldata _enodeId) external + { + permImplementation.approveOrg(_orgId, _enodeId); + } + + function updateOrgStatus(string calldata _orgId, uint _status) external + { + permImplementation.updateOrgStatus(_orgId, _status); + } + + function approveOrgStatus(string calldata _orgId, uint _status) external + { + permImplementation.approveOrgStatus(_orgId, _status); + } + // returns org and master org details based on org index + function getOrgInfo(uint _orgIndex) external view returns (string memory, uint) + { + return permImplementation.getOrgInfo(_orgIndex); + } + + // Role related functions + function addNewRole(string calldata _roleId, string calldata _orgId, uint _access, bool _voter) external + { + permImplementation.addNewRole(_roleId, _orgId, _access, _voter); + } + + function removeRole(string calldata _roleId, string calldata _orgId) external + { + permImplementation.removeRole(_roleId, _orgId); + } + + function getRoleDetails(string calldata _roleId, string calldata _orgId) external view returns (string memory, string memory, uint, bool, bool) + { + return permImplementation.getRoleDetails(_roleId, _orgId); + } + + // Org voter related functions + function getNumberOfVoters(string calldata _orgId) external view returns (uint) + { + return permImplementation.getNumberOfVoters(_orgId); + } + + function checkIfVoterExists(string calldata _orgId, address _acct) external view returns (bool) + { + return permImplementation.checkIfVoterExists(_orgId, _acct); + } + + function getVoteCount(string calldata _orgId) external view returns (uint, uint) + { + return permImplementation.getVoteCount(_orgId); + } + + function getPendingOp(string calldata _orgId) external view returns (string memory, string memory, address, uint) + { + return permImplementation.getPendingOp(_orgId); + } + + function assignOrgAdminAccount(string calldata _orgId, address _account) external + { + permImplementation.assignOrgAdminAccount(_orgId, _account); + + } + + function approveOrgAdminAccount(address _account) external + { + permImplementation.approveOrgAdminAccount(_account); + + } + + function assignAccountRole(address _acct, string memory _orgId, string memory _roleId) public + { + permImplementation.assignAccountRole(_acct, _orgId, _roleId); + + } + + function addNode(string calldata _orgId, string calldata _enodeId) external + { + permImplementation.addNode(_orgId, _enodeId); + + } + + function getNodeStatus(string memory _enodeId) public view returns (uint) + { + return permImplementation.getNodeStatus(_enodeId); + } + + function isNetworkAdmin(address _account) public view returns (bool) + { + return permImplementation.isNetworkAdmin(_account); + } + + function isOrgAdmin(address _account, string memory _orgId) public view returns (bool) + { + return permImplementation.isOrgAdmin(_account, _orgId); + } + + function validateAccount(address _account, string memory _orgId) public view returns (bool) + { + return permImplementation.validateAccount(_account, _orgId); + } + + function getAccountDetails(address _acct) external view returns (address, string memory, string memory, uint, bool) + { + return permImplementation.getAccountDetails(_acct); + } + +} \ No newline at end of file diff --git a/controls/permission/PermissionsUpgradable.sol b/controls/permission/PermissionsUpgradable.sol new file mode 100644 index 000000000..8184627c2 --- /dev/null +++ b/controls/permission/PermissionsUpgradable.sol @@ -0,0 +1,43 @@ +pragma solidity ^0.5.3; + +import "./PermissionsInterface.sol"; + +contract PermissionsImplUpgradeable { + + address private custodian; + address private permImpl; + // store the instances in the contract because upgradeable will setCoinImpl for them + PermissionsInterface private permInterface; + + constructor (address _custodian, address _permInterface, address _permImpl) public { + custodian = _custodian; + permImpl = _permImpl; + permInterface = PermissionsInterface(_permInterface); + setImpl(_permImpl); + } + + modifier onlyCustodian { + require(msg.sender == custodian); + _; + } + + // custodian can potentially become a contract + // implementation change and custodian change are sending from custodian + function confirmImplChange(address _proposedImpl) public onlyCustodian { + permImpl = _proposedImpl; + setImpl(permImpl); + } + + function getCustodian() public view returns(address) { + return custodian; + } + + function getPermImpl() public view returns(address) { + return permImpl; + } + + function setImpl(address _permImpl) private { + permInterface.setPermImplementation(_permImpl); + } + +} \ No newline at end of file diff --git a/controls/permission/RoleManager.sol b/controls/permission/RoleManager.sol new file mode 100644 index 000000000..89bdde84c --- /dev/null +++ b/controls/permission/RoleManager.sol @@ -0,0 +1,80 @@ +pragma solidity ^0.5.3; + +contract RoleManager { + struct RoleDetails { + string roleId; + string orgId; + uint baseAccess; + bool isVoter; + bool active; + } + + RoleDetails[] private roleList; + mapping(bytes32 => uint) private roleIndex; + uint private numberOfRoles; + + event RoleCreated(string _roleId, string _orgId); + event RoleRevoked(string _roleId, string _orgId); + + function roleExists(string memory _roleId, string memory _orgId) public view returns(bool) + { + return (roleIndex[keccak256(abi.encodePacked(_roleId, _orgId))] != 0); + } + + function getRoleDetails(string calldata _roleId, string calldata _orgId) external view returns (string memory roleId, string memory orgId, uint accessType, bool voter, bool active) + { + if (!(roleExists(_roleId, _orgId))){ + return (_roleId, "", 0, false, false); + } + uint rIndex = getRoleIndex(_roleId, _orgId); + return (roleList[rIndex].roleId, roleList[rIndex].orgId, roleList[rIndex].baseAccess, roleList[rIndex].isVoter, roleList[rIndex].active); + } + + // Get number of Role + function getNumberOfRoles() external view returns (uint) + { + return roleList.length; + } + + function addRole(string memory _roleId, string memory _orgId, uint _baseAccess, bool _voter) public + { + // Check if account already exists + if (roleIndex[keccak256(abi.encodePacked(_roleId, _orgId))] == 0) { + numberOfRoles ++; + roleIndex[keccak256(abi.encodePacked(_roleId, _orgId))] = numberOfRoles; + roleList.push(RoleDetails(_roleId, _orgId, _baseAccess, _voter, true)); + emit RoleCreated(_roleId, _orgId); + } + } + + function removeRole(string calldata _roleId, string calldata _orgId) external{ + if (roleIndex[keccak256(abi.encodePacked(_roleId, _orgId))] != 0) { + uint rIndex = getRoleIndex(_roleId, _orgId); + roleList[rIndex].active = false; + emit RoleRevoked(_roleId, _orgId); + } + } + // Returns the account index based on account id + function getRoleIndex(string memory _roleId, string memory _orgId) internal view returns (uint) + { + return roleIndex[keccak256(abi.encodePacked(_roleId, _orgId))] - 1; + } + + + function isFullAccessRole(string calldata _roleId, string calldata _orgId) external view returns (bool){ + if (!(roleExists(_roleId, _orgId))){ + return false; + } + uint rIndex = getRoleIndex(_roleId, _orgId); + return (roleList[rIndex].active && roleList[rIndex].baseAccess == 3); + } + + function isVoterRole(string calldata _roleId, string calldata _orgId) external view returns (bool){ + if (!(roleExists(_roleId, _orgId))){ + return false; + } + uint rIndex = getRoleIndex(_roleId, _orgId); + return (roleList[rIndex].active && roleList[rIndex].isVoter); + } + +} diff --git a/controls/permission/VoterManager.sol b/controls/permission/VoterManager.sol new file mode 100644 index 000000000..57970dd7d --- /dev/null +++ b/controls/permission/VoterManager.sol @@ -0,0 +1,195 @@ +pragma solidity ^0.5.3; + +contract VoterManager { +// enum PendingOpType {0-None, 1-OrgAdd, 2-OrgSuspension, 3-OrgRevokeSuspension, 4-AddOrgAdmin} + struct PendingOpDetails { + string orgId; + string enodeId; + address account; + uint opType; + } + + struct VoterDetails { + address vAccount; + bool active; + } + + struct OrgVoterDetails { + string orgId; + uint voterCount; + uint validVoterCount; + uint voteCount; + PendingOpDetails pendingOp; + VoterDetails [] voterList; + mapping(address => uint) voterIndex; + mapping(uint => mapping(address => bool)) votingStatus; + } + + OrgVoterDetails [] private orgVoterList; + mapping(bytes32 => uint) private VoterOrgIndex; + uint private orgNum = 0; + + // events related to managing voting accounts for the org + event VoterAdded(string _orgId, address _address); + event VoterDeleted(string _orgId, address _address); + + event VotingItemAdded(string _orgId); + event VoteProcessed(string _orgId); + + event Dummy(string _msg); + + modifier voterExists(string memory _orgId, address _address) { + require(checkIfVoterExists(_orgId, _address) == true, "must be a voter"); + _; + } + + // returns the voter index + function getVoterIndex(string memory _orgId, address _vAccount) internal view returns (uint) + { + uint orgIndex = getVoterOrgIndex(_orgId); + return orgVoterList[orgIndex].voterIndex[_vAccount] - 1; + } + + // returns the master org index for the org from voter list + function getVoterOrgIndex(string memory _orgId) internal view returns (uint) + { + return VoterOrgIndex[keccak256(abi.encodePacked(_orgId))] - 1; + } + + // checks if the org has any voter accounts set up or not + function checkIfVoterExists(string memory _orgId, address _address) public view returns (bool){ + uint orgIndex = getVoterOrgIndex(_orgId); + if (orgVoterList[orgIndex].voterIndex[_address] == 0) { + return false; + } + uint voterIndex = getVoterIndex(_orgId, _address); + return orgVoterList[orgIndex].voterList[voterIndex].active; + } + + // Get number of total voters + function getNumberOfVoters(string calldata _orgId) external view returns (uint) + { + return orgVoterList[getVoterOrgIndex(_orgId)].voterCount; + } + + // Get number of valid voters + function getNumberOfValidVoters(string calldata _orgId) external view returns (uint) + { + return orgVoterList[getVoterOrgIndex(_orgId)].validVoterCount; + } + + // checks if the voting accounts exists for the org + function checkVotingAccountExists(string calldata _orgId) external view returns (bool) + { + uint orgIndex = getVoterOrgIndex(_orgId); + return (orgVoterList[orgIndex].validVoterCount > 0); + } + + // function for adding a voter account to a master org + function addVoter(string calldata _orgId, address _address) external + { + // check if the org exists + if (VoterOrgIndex[keccak256(abi.encodePacked(_orgId))] == 0) { + orgNum++; + VoterOrgIndex[keccak256(abi.encodePacked(_orgId))] = orgNum; + uint id = orgVoterList.length++; + orgVoterList[id].orgId = _orgId; + orgVoterList[id].voterCount = 1; + orgVoterList[id].validVoterCount = 1; + orgVoterList[id].voteCount = 0; + orgVoterList[id].pendingOp.orgId = ""; + orgVoterList[id].pendingOp.enodeId = ""; + orgVoterList[id].pendingOp.account = address(0); + orgVoterList[id].pendingOp.opType = 0; + orgVoterList[id].voterIndex[_address] = orgVoterList[id].voterCount; + orgVoterList[id].voterList.push(VoterDetails(_address, true)); + } + else { + uint id = getVoterOrgIndex(_orgId); + // check of the voter already present in the list + if (orgVoterList[id].voterIndex[_address] == 0) { + orgVoterList[id].voterCount++; + orgVoterList[id].voterIndex[_address] = orgVoterList[id].voterCount; + orgVoterList[id].voterList.push(VoterDetails(_address, true)); + orgVoterList[id].validVoterCount++; + } + else { + uint vid = getVoterIndex(_orgId, _address); + require(orgVoterList[id].voterList[vid].active != true, "already a voter"); + orgVoterList[id].voterList[vid].active = true; + orgVoterList[id].validVoterCount++; + } + + } + emit VoterAdded(_orgId, _address); + } + + // function for deleting a voter account to a master org + function deleteVoter(string calldata _orgId, address _address) external voterExists(_orgId, _address) + { + uint id = getVoterOrgIndex(_orgId); + uint vId = getVoterIndex(_orgId, _address); + orgVoterList[id].validVoterCount --; + orgVoterList[id].voterList[vId].active = false; + emit VoterDeleted(_orgId, _address); + } + + // function for adding an item into voting queue of the org + function addVotingItem(string calldata _authOrg, string calldata _orgId, string calldata _enodeId, address _account, uint _pendingOp) external + { + // check if anything is pending approval for the org. If yes another item cannot be added + require((checkPendingOp(_authOrg, 0)), "Items pending approval. New item cannot be added"); + uint id = getVoterOrgIndex(_authOrg); + orgVoterList[id].pendingOp.orgId = _orgId; + orgVoterList[id].pendingOp.enodeId = _enodeId; + orgVoterList[id].pendingOp.account = _account; + orgVoterList[id].pendingOp.opType = _pendingOp; + // init vote status + for (uint i = 0; i < orgVoterList[id].voterList.length; i++) { + if (orgVoterList[id].voterList[i].active) { + orgVoterList[id].votingStatus[id][orgVoterList[id].voterList[i].vAccount] = false; + } + } + // set vote count to zero + orgVoterList[id].voteCount = 0; + emit VotingItemAdded(_authOrg); + + } + + // process vote and update status + function processVote(string calldata _authOrg, address _vAccount, uint _pendingOp) external voterExists(_authOrg, _vAccount) returns (bool) { + + // check something is pending approval + require(checkPendingOp(_authOrg, _pendingOp) == true, "nothing to approve"); + uint id = getVoterOrgIndex(_authOrg); + // check if vote already processed + require(orgVoterList[id].votingStatus[id][_vAccount] != true, "cannot double vote"); + orgVoterList[id].voteCount++; + orgVoterList[id].votingStatus[id][_vAccount] = true; + emit VoteProcessed(_authOrg); + if (orgVoterList[id].voteCount > orgVoterList[id].validVoterCount / 2) { + // majority achieved, clean up pending op + orgVoterList[id].pendingOp.orgId = ""; + orgVoterList[id].pendingOp.enodeId = ""; + orgVoterList[id].pendingOp.account = address(0); + orgVoterList[id].pendingOp.opType = 0; + return true; + } + return false; + } + + function checkPendingOp(string memory _orgId, uint _pendingOp) internal view returns (bool){ + return (orgVoterList[getVoterOrgIndex(_orgId)].pendingOp.opType == _pendingOp); + } + + function getVoteCount(string calldata _orgId) external view returns (uint, uint) { + uint orgIndex = getVoterOrgIndex(_orgId); + return (orgVoterList[orgIndex].voteCount, orgVoterList[orgIndex].validVoterCount); + } + + function getPendingOpDetails(string calldata _orgId) external view returns (string memory, string memory, address, uint){ + uint orgIndex = getVoterOrgIndex(_orgId); + return (orgVoterList[orgIndex].pendingOp.orgId, orgVoterList[orgIndex].pendingOp.enodeId, orgVoterList[orgIndex].pendingOp.account, orgVoterList[orgIndex].pendingOp.opType); + } + +}