permissions: documentation update and bug fixes

This commit is contained in:
vsmk98 2019-05-08 18:01:30 +08:00
parent 2716937f2e
commit be2331b226
16 changed files with 486 additions and 305 deletions

View File

@ -146,6 +146,9 @@ contract OrgManager {
returns (uint)
{
require((_status == 3 || _status == 5), "Operation not allowed");
uint id = getOrgIndex(_orgId);
require(orgList[id].level == 1, "not a master org. operation not allowed");
uint reqStatus;
uint pendingOp;
if (_status == 3) {

View File

@ -75,7 +75,7 @@ contract PermissionsInterface {
// function for adding a new master org
function addSubOrg(string calldata _pOrg, string calldata _orgId, string calldata _enodeId, address _account) external
{
{
permImplementation.addSubOrg(_pOrg, _orgId, _enodeId, _account, msg.sender);
}

View File

@ -32,7 +32,16 @@ contract RoleManager {
function roleExists(string memory _roleId, string memory _orgId, string memory _ultParent) public view returns (bool)
{
return ((roleIndex[keccak256(abi.encodePacked(_roleId, _orgId))] != 0) || (roleIndex[keccak256(abi.encodePacked(_roleId, _ultParent))] != 0));
uint id;
if (roleIndex[keccak256(abi.encodePacked(_roleId, _orgId))] != 0) {
id = getRoleIndex(_roleId, _orgId);
return roleList[id].active;
}
else if (roleIndex[keccak256(abi.encodePacked(_roleId, _ultParent))] != 0) {
id = getRoleIndex(_roleId, _ultParent);
return roleList[id].active;
}
return false;
}
function getRoleDetails(string calldata _roleId, string calldata _orgId) external view returns (string memory roleId, string memory orgId, uint accessType, bool voter, bool active)

View File

@ -116,8 +116,6 @@ var (
ErrInvalidNode = ExecStatus{false, "Invalid enode id"}
ErrInvalidAccount = ExecStatus{false, "Invalid account id"}
ErrPermissionDisabled = ExecStatus{false, "Permissions control not enabled"}
ErrAccountAccess = ExecStatus{false, "Account does not have sufficient access for operation"}
ErrVoterAccountAccess = ExecStatus{false, "Voter account does not have sufficient access"}
ErrOrgExists = ExecStatus{false, "Org already exists"}
ErrPendingApprovals = ExecStatus{false, "Pending approvals for the organization. Approve first"}
ErrNothingToApprove = ExecStatus{false, "Nothing to approve"}
@ -143,7 +141,9 @@ var (
ErrInactiveRole = ExecStatus{false, "Role is already inactive"}
ErrInvalidRole = ExecStatus{false, "Invalid role"}
ErrInvalidInput = ExecStatus{false, "Invalid input"}
ExecSuccess = ExecStatus{true, "Action completed successfully"}
ErrNotMasterOrg = ExecStatus{false, "Org is not a master org"}
ExecSuccess = ExecStatus{true, "Action completed successfully"}
)
// NewQuorumControlsAPI creates a new QuorumControlsAPI to access quorum services
@ -285,13 +285,18 @@ func (s *QuorumControlsAPI) isOrgAdmin(account common.Address, orgId string) (Ex
func (s *QuorumControlsAPI) validateOrg(orgId, pOrgId string) (ExecStatus, error) {
// validate Parent org id
if pOrgId != "" && types.OrgInfoMap.GetOrg(pOrgId) == nil {
return ErrInvalidParentOrg, errors.New("invalid parent org")
} else {
if pOrgId != "" {
if types.OrgInfoMap.GetOrg(pOrgId) == nil {
return ErrInvalidParentOrg, errors.New("invalid parent org")
}
locOrgId := pOrgId + "." + orgId
if types.OrgInfoMap.GetOrg(locOrgId) != nil {
return ErrOrgExists, errors.New("org exists")
}
} else {
if types.OrgInfoMap.GetOrg(orgId) != nil {
return ErrOrgExists, errors.New("org exists")
}
}
return ExecSuccess, nil
}
@ -306,9 +311,17 @@ func (s *QuorumControlsAPI) checkPendingOp(orgId string, pinterf *pbind.PermInte
return err == nil && op.Int64() != 0
}
func (s *QuorumControlsAPI) checkOrgStatus(orgId string, op uint8) bool {
func (s *QuorumControlsAPI) checkOrgStatus(orgId string, op uint8) (ExecStatus, error) {
org := types.OrgInfoMap.GetOrg(orgId)
return (op == 3 && org.Status == types.OrgApproved) || (op == 5 && org.Status == types.OrgSuspended)
// check if its a master org. operation is allowed only if its a master org
if org.Level.Cmp(big.NewInt(1)) != 0 {
return ErrNotMasterOrg, errors.New("Org not a master org")
}
if !((op == 3 && org.Status == types.OrgApproved) || (op == 5 && org.Status == types.OrgSuspended)) {
return ErrOpNotAllowed, errors.New("operation not allowed for current status")
}
return ExecSuccess, nil
}
func (s *QuorumControlsAPI) valNodeStatusChange(orgId, url string, op int64) (ExecStatus, error) {
@ -348,6 +361,16 @@ func (s *QuorumControlsAPI) valNodeStatusChange(orgId, url string, op int64) (Ex
return ExecSuccess, nil
}
func (s *QuorumControlsAPI) validateRole(orgId, roleId string) bool {
var r *types.RoleInfo
r = types.RoleInfoMap.GetRole(orgId, roleId)
if r == nil {
r = types.RoleInfoMap.GetRole(types.OrgInfoMap.GetOrg(orgId).UltimateParent, roleId)
}
return r != nil && r.Active
}
func (s *QuorumControlsAPI) valAccountStatusChange(orgId string, account common.Address, op int64) (ExecStatus, error) {
// validates if the enode is linked the passed organization
ac := types.AcctInfoMap.GetAccount(account)
@ -390,24 +413,6 @@ func (s *QuorumControlsAPI) checkOrgAdminExists(orgId, roleId string, account co
return ErrAccountOrgAdmin, errors.New("account already org admin for the org")
}
}
//if ac == nil {
// orgAcctList := types.AcctInfoMap.GetAcctListOrg(orgId)
// if len(orgAcctList) > 0 {
// for _, a := range orgAcctList {
// if a.IsOrgAdmin == true && a.Status == types.AcctActive {
// return ErrOrgAdminExists, errors.New("org admin exists for the org")
// }
// }
// }
//} else {
// if ac.OrgId != orgId {
// return ErrAccountInUse, errors.New("account part of another org")
// }
// if ac.IsOrgAdmin == true {
// return ErrAccountOrgAdmin, errors.New("account already org admin for the org")
// }
//}
return ExecSuccess, nil
}
@ -565,16 +570,13 @@ func (s *QuorumControlsAPI) executePermAction(action PermAction, args txArgs) Ex
if !s.isNetworkAdmin(args.txa.From) {
return ErrNotNetworkAdmin
}
// check if status update can be performed. Org should be approved for suspension
if !s.checkOrgStatus(args.orgId, args.status) {
return ErrOpNotAllowed
}
if args.status != 3 && args.status != 5 {
return ErrOpNotAllowed
}
// check if status update can be performed. Org should be approved for suspension
if execStatus, er := s.checkOrgStatus(args.orgId, args.status); er != nil {
return execStatus
}
// and in suspended state for suspension revoke
tx, err = pinterf.UpdateOrgStatus(args.orgId, big.NewInt(int64(args.status)))
@ -723,12 +725,9 @@ func (s *QuorumControlsAPI) executePermAction(action PermAction, args txArgs) Ex
return execStatus
}
// check if the role is part of the org
if types.RoleInfoMap.GetRole(args.orgId, args.roleId) == nil {
// check if the role is existing at master org level
if types.RoleInfoMap.GetRole(types.OrgInfoMap.GetOrg(args.orgId).UltimateParent, args.roleId) == nil {
return ErrRoleDoesNotExist
}
// check if the role is valid
if !s.validateRole(args.orgId, args.roleId) {
return ErrInvalidRole
}
// check if the account is part of another org
@ -741,6 +740,10 @@ func (s *QuorumControlsAPI) executePermAction(action PermAction, args txArgs) Ex
tx, err = pinterf.AssignAccountRole(args.acctId, args.orgId, args.roleId)
case UpdateAccountStatus:
// check if the caller is org admin
if execStatus, er := s.isOrgAdmin(args.txa.From, args.orgId); er != nil {
return execStatus
}
// validation status change is with in allowed set
if execStatus, er := s.valAccountStatusChange(args.orgId, args.acctId, int64(args.status)); er != nil {
return execStatus

View File

@ -335,7 +335,8 @@ func (a *AcctCache) GetAcctListRole(orgId, roleId string) []AccountInfo {
for _, k := range a.c.Keys() {
v, _ := a.c.Get(k)
vp := v.(*AccountInfo)
if vp.OrgId == orgId && vp.RoleId == roleId {
if vp.RoleId == roleId && (vp.OrgId == orgId || OrgInfoMap.GetOrg(vp.OrgId).UltimateParent == orgId) {
alist = append(alist, *vp)
}
}
@ -374,14 +375,14 @@ func (o *RoleCache) GetRoleList() []RoleInfo {
// default access
func GetAcctAccess(acctId common.Address) AccessType {
if a := AcctInfoMap.GetAccount(acctId); a != nil && a.Status == AcctActive {
if a.RoleId == networkAdminRole || a.RoleId == orgAdminRole {
if (a.RoleId == networkAdminRole || a.RoleId == orgAdminRole) && a.Status == AcctActive {
return FullAccess
}
if o := OrgInfoMap.GetOrg(a.OrgId); o != nil && o.Status == OrgApproved {
if r := RoleInfoMap.GetRole(a.OrgId, a.RoleId); r != nil {
if r := RoleInfoMap.GetRole(a.OrgId, a.RoleId); r != nil && r.Active {
return r.Access
}
if r := RoleInfoMap.GetRole(o.UltimateParent, a.RoleId); r != nil {
if r := RoleInfoMap.GetRole(o.UltimateParent, a.RoleId); r != nil && r.Active {
return r.Access
}
}

View File

@ -8,5 +8,4 @@
* [Privacy](./privacy.md) - Sending private transactions [privacy video](https://vimeo.com/user5833792/review/210456729/8f70cfaaa5)
* [Running](./running.md) - Detailed instructions for running Quorum nodes (see also [Constellation](https://github.com/jpmorganchase/constellation), [Tessera](https://github.com/jpmorganchase/tessera))
* [API](./api.md) - new privacy API
* [Node Permissioing & Account Access Control](./permissions.md) - Overview of node permissioning and account access control features
* [Organization level transaction manager key management](./orgkey.md) - Overview of transaction manager key management at organizational level for private transactions
* [Permissions ](permissions/overview.md) - Overview of the Quorum permissions model

Binary file not shown.

After

Width:  |  Height:  |  Size: 797 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 340 KiB

View File

@ -1,258 +0,0 @@
# Overview
Currently when Quorum geth is brought up in `--permissioned` mode, for establishing p2p connectivity the nodes check `permissioned-nodes.json` file. If the enode id of the node requesting connection is present in this file, the p2p connection is established else it is declined. The `permissioned-nodes.json` file is updated procedurally whenever a new node joins the network. Node permissioning feature will allow the existing nodes to propose a new node to join the network and once majority voting is done on the proposal, it will update the `permissioned-nodes.json` automatically. Further the existing nodes can propose any node for deactivation, blacklisting and activating a node back from a deactivated status.
Account permissioning feature introduces controls at account level. This will control the type of activities that a particular account can perform in the network.
It should be noted that both the above features will be available when Quorum geth is brought in `--permissioned` mode with the set up as described in the next section.
## Set up
Node permissioning and account access control is managed by a smart contract [PermissionsInterface.sol](../controls/permission/PermissionsInterface.sol). The precompiled byte code of the smart contract is deployed at address `0x000000000000000000020` in network boot-up process. The binding of the precompiled byte code with the address is in `genesis.json`. The initial set of account that will have full access when the network is up, should given as a part of `genesis.json` as shown below:
```
{
"alloc": {
"0x0000000000000000000000000000000000000020": {
"code": "<<compiled contract bytecode>>"
"storage": {
"0x0000000000000000000000000000000000000000000000000000000000000000" : "0x0000000000000000000000000000000000000003",
"0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563" : "0xcA843569e3427144cEad5e4d5999a3D0cCF92B8e",
"0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e564" : "0xed9d02e382b34818e88b88a309c7fe71e65f419d",
"0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e565" : "0x0fbdc686b912d7722dc86510934589e0aaf3b55a"
},
"balance": "1000000000000000000000000000"
},
```
In the above set up, accounts `"0xcA843569e3427144cEad5e4d5999a3D0cCF92B8e", "0xed9d02e382b34818e88b88a309c7fe71e65f419d", "0x0fbdc686b912d7722dc86510934589e0aaf3b55a"` will have full access when the network boot is completed. The default access for any other account in the network will be `ReadOnly`
If the network is brought up with permissions control byte code and no accounts are given as a part of storage, then geth start up will fail with error `Permissioned network being brought up with zero accounts having full access. Add permissioned full access accounts in genesis.json and bring up the network`
Further, if the initial set of network nodes are brought up with `--permissioned` mode and a new approved node is joining the network without `--permissioned` flag in the `geth` start command, system will not allow the new `geth` node to come and a fatal error `Joining a permissioned network in non-permissioned mode. Bring up geth with --permissioned."` will be thrown.
## Node Permissioning
In a permissioned network any node can be in one of the following status:
* Proposed - The node has been proposed to join the network and pending majority voting to be marked as `Approved`
* Approved - The node is approved and is part of the network
* PendingDeactivation - The node has been proposed for deactivation from network and is pending majority approval
* Deactivated - The node has been deactivated from the network. Any node in this status will be disconnected from the network and block sync with this node will not happen
* PendingActivation - A deactivated node has been proposed for activation and is pending majority approval. Once approved the node will move to `Approved` status
* PendingBlacklisting - The node has been proposed for blacklisting and is pending majority approval
* Blacklisted - The node has been blacklisted. If the node was an active node on the network, the node will be disconnected from the network and block sync will stop
It should be noted that deactivation is temporary in nature and hence the node can join back the network at a later point in time. However blacklisting is permanent in nature. A blacklisted node will never be able to join back the network. Further blacklisting can be proposed for a node which is in the network or a new node which is currently not part of the network.
When the network is started for the first time, all the nodes present in `static-nodes.json` file are added to the permissioned nodes list maintained in the smart contract. Once the initial network is up, these nodes can then propose and approve new nodes to join the network.
### Node Permission APIs
#### quorumNodeMgmt.permissionNodeList
* Input: None
* Output: Returns the list of all enodes and their status
* Example:
```
> quorumNodeMgmt.permissionNodeList
[{
enodeId: "enode://ac6b1096ca56b9f6d004b779ae3728bf83f8e22453404cc3cef16a3d9b96608bc67c4b30db88e0a5a6c6390213f7acbe1153ff6d23ce57380104288ae19373ef@127.0.0.1:21000?discport=0&raftport=50401",
status: "Approved"
}, {
enodeId: "enode://0ba6b9f606a43a95edc6247cdb1c1e105145817be7bcafd6b2c0ba15d58145f0dc1a194f70ba73cd6f4cdd6864edc7687f311254c7555cc32e4d45aeb1b80416@127.0.0.1:21001?discport=0&raftport=50402",
status: "Approved"
}, {
enodeId: "enode://579f786d4e2830bbcc02815a27e8a9bacccc9605df4dc6f20bcc1a6eb391e7225fff7cb83e5b4ecd1f3a94d8b733803f2f66b7e871961e7b029e22c155c3a778@127.0.0.1:21002?discport=0&raftport=50403",
status: "Approved"
}, {
enodeId: "enode://3d9ca5956b38557aba991e31cf510d4df641dce9cc26bfeb7de082f0c07abb6ede3a58410c8f249dabeecee4ad3979929ac4c7c496ad20b8cfdd061b7401b4f5@127.0.0.1:21003?discport=0&raftport=50404",
status: "Approved"
}]
```
#### quorumNodeMgmt.addVoter
Before a new node can be proposed to the network, the network should have valid voters. This api allows an account to be added as voter to the network. Only an account with full access can add another account as voter. Further the account being added as voter account should have at least transact permission.
* Input: Account to be added as voter, transaction object
* Output: Status of the operation
* Example:
```
> quorumNodeMgmt.addVoter("0x0fBDc686b912d7722dc86510934589E0AAf3b55A", {from: eth.accounts[0]})
{
msg: "Action completed successfully",
status: true
}
```
#### quorumNodeMgmt.removeVoter
Allows a voter account to be removed from the network. Only an account with `FullAccess` can perform this activity.
* Input: Account to be removed, transaction object
* Output: Status of the operation
* Example:
```
> quorumNodeMgmt.removeVoter("0x0fBDc686b912d7722dc86510934589E0AAf3b55A", {from: eth.accounts[0]})
{
msg: "Action completed successfully",
status: true
}
```
#### quorumNodeMgmt.voterList
* Input: None
* Output: List of all voters on the network
* Example:
```
> quorumNodeMgmt.voterList
["0xed9d02e382b34818e88B88a309c7fe71E65f419d", "0x0fBDc686b912d7722dc86510934589E0AAf3b55A"]
```
#### quorumNodeMgmt.proposeNode
Allows a new enode to be proposed to the network. This operation will be allowed only if at least one voter account is present in the network.
* Input: enode to be proposed, transaction object
* Output: Status of the operation
* Example:
```
> quorumNodeMgmt.proposeNode("enode://3701f007bfa4cb26512d7df18e6bbd202e8484a6e11d387af6e482b525fa25542d46ff9c99db87bd419b980c24a086117a397f6d8f88e74351b41693880ea0cb@127.0.0.1:21004?discport=0&raftport=50405", {from: eth.accounts[0]})
{
msg: "Action completed successfully",
status: true
}
```
#### quorumNodeMgmt.approveNode
API for approving a proposed node. The node gets approved once majority votes from the voter accounts is received.
* Input: enode to be approved, transaction object
* Output: Status of the operation
* Example:
```
> quorumNodeMgmt.approveNode("enode://3701f007bfa4cb26512d7df18e6bbd202e8484a6e11d387af6e482b525fa25542d46ff9c99db87bd419b980c24a086117a397f6d8f88e74351b41693880ea0cb@127.0.0.1:21004?discport=0&raftport=50405", {from: eth.accounts[0]})
{
msg: "Action completed successfully",
status: true
}
```
#### quorumNodeMgmt.proposeNodeDeactivation
API for proposing a node for deactivation. The node must be `Approved` state and there should be at least one voter account present at network.
* Input: enode to be deactivated, transaction object
* Output: Status of the operation
* Example:
```
> quorumNodeMgmt.proposeNodeDeactivation("enode://3701f007bfa4cb26512d7df18e6bbd202e8484a6e11d387af6e482b525fa25542d46ff9c99db87bd419b980c24a086117a397f6d8f88e74351b41693880ea0cb@127.0.0.1:21004?discport=0&raftport=50405", {from: eth.accounts[0]})
{
msg: "Action completed successfully",
status: true
}
```
#### quorumNodeMgmt.approveNodeDeactivation
API for approving node for deactivation. The node gets deactivated once majority votes from the voter accounts is received
* Input: enode to be deactivated, transaction object
* Output: Status of the operation
* Example:
```
> quorumNodeMgmt.approveNodeDeactivation("enode://3701f007bfa4cb26512d7df18e6bbd202e8484a6e11d387af6e482b525fa25542d46ff9c99db87bd419b980c24a086117a397f6d8f88e74351b41693880ea0cb@127.0.0.1:21004?discport=0&raftport=50405", {from: eth.accounts[0]})
{
msg: "Action completed successfully",
status: true
}
```
#### quorumNodeMgmt.proposeNodeActivation
API for proposing activation of a deactivated node. The node must be in `Deactivated` state and there should be at least one voter account present at network.
* Input: enode to be activated, transaction object
* Output: Status of the operation
* Example:
```
> quorumNodeMgmt.proposeNodeActivation("enode://3701f007bfa4cb26512d7df18e6bbd202e8484a6e11d387af6e482b525fa25542d46ff9c99db87bd419b980c24a086117a397f6d8f88e74351b41693880ea0cb@127.0.0.1:21004?discport=0&raftport=50405", {from: eth.accounts[0]})
{
msg: "Action completed successfully",
status: true
}
```
#### quorumNodeMgmt.approveNodeActivation
API for approval of activating a deactivated node. The node gets activated once majority votes from the voter accounts is received
* Input: enode to be activated, transaction object
* Output: Status of the operation
* Example:
```
> quorumNodeMgmt.approveNodeActivation("enode://3701f007bfa4cb26512d7df18e6bbd202e8484a6e11d387af6e482b525fa25542d46ff9c99db87bd419b980c24a086117a397f6d8f88e74351b41693880ea0cb@127.0.0.1:21004?discport=0&raftport=50405", {from: eth.accounts[0]})
{
msg: "Action completed successfully",
status: true
}
```
#### quorumNodeMgmt.proposeNodeBlacklisting
API for blacklisting a node from the network. Any node (irrespective of node status or a node which is not part of network) can be proposed for blacklisting. Blacklisting takes precedence on any other proposal.
* Input: enode to be blacklisted, transaction object
* Output: Status of the operation
* Example:
```
> quorumNodeMgmt.proposeNodeBlacklisting("enode://3701f007bfa4cb26512d7df18e6bbd202e8484a6e11d387af6e482b525fa25542d46ff9c99db87bd419b980c24a086117a397f6d8f88e74351b41693880ea0cb@127.0.0.1:21004?discport=0&raftport=50405", {from: eth.accounts[0]})
{
msg: "Action completed successfully",
status: true
}
```
#### quorumNodeMgmt.approveNodeBlacklisting
API for approving node blacklisting. The node is blacklisted once majority votes from the voter accounts. Once the node is blacklisted, it cannot rejoin the network.
* Input: enode to be blacklisted, transaction object
* Output: Status of the operation
* Example:
```
> quorumNodeMgmt.approveNodeBlacklisting("enode://3701f007bfa4cb26512d7df18e6bbd202e8484a6e11d387af6e482b525fa25542d46ff9c99db87bd419b980c24a086117a397f6d8f88e74351b41693880ea0cb@127.0.0.1:21004?discport=0&raftport=50405", {from: eth.accounts[0]})
{
msg: "Action completed successfully",
status: true
}
```
## Account Access Control
The following account access types are being introduced as a part of this feature:
* ReadOnly: Accounts with this access will be able to perform only read activities and will not be able to deploy contracts or transactions. By default any account which is not permissioned will have a read only access.
* Transact: Accounts with transact access will be able to commit transactions but will not be able to deploy contracts
* Contract deploy: Accounts with this access will be able to deploy contracts and commit transactions
* Full access: Similar to "Contract deploy" access, accounts with this access will be able to deploy contracts and perform transactions. Further only an account with Full access can add voters to the network.
### Account Access APIs
#### quorumAcctMgmt.permissionAccountList
* Input: None
* Output: Returns the list of all permissioned accounts with account access for each
* Example:
```
> quorumAcctMgmt.permissionAccountList
[{
access: "FullAccess",
address: "0xcA843569e3427144cEad5e4d5999a3D0cCF92B8e"
}, {
access: "Transact",
address: "0xed9d02e382b34818e88B88a309c7fe71E65f419d"
}, {
access: "ContractDeploy",
address: "0x0fBDc686b912d7722dc86510934589E0AAf3b55A"
}, {
access: "ReadOnly",
address: "0x9186eb3d20Cbd1F5f992a950d808C4495153ABd5"
}
```
#### quorumAcctMgmt.setAccountAccess
* Input: Account, access type for the account and transaction object
* Output: Status of the operation
* Example:
```
> quorumAcctMgmt.setAccountAccess("0x9186eb3d20cbd1f5f992a950d808c4495153abd5", 2, {from: eth.accounts[0]})
{
msg: "Action completed successfully",
status: true
}
```
### General validations for account access
The table below indicates the numeric value for each account access type.
| AccessType | Value |
| :-------------: | :-------------: |
| ReadOnly | 0 |
| Transact | 1 |
| Contract deploy | 2 |
| Full access | 3 |
While setting the account access, system checks if the account which is setting the access has sufficient privileges to perform the activity.
* Accounts with `FullAccess` can grant any access type ( FullAccess, Transact, ContractDeploy or ReadOnly) to any other account
* Accounts with `ContractDeploy` can grant only `Transact`, `ContractDeploy` or `ReadOnly` access to other accounts
* Accounts with `Transact` access grant only `Transact` or `ReadOnly` access to other accounts
* Accounts with `ReadOnly` access cannot grant any access
If an account having lower privileges tries to assign a higher privilege to an account, system will not allow the operation and will throw the an error as shown below:
```
> quorumAcctMgmt.setAccountAccess("0xAE9bc6cD5145e67FbD1887A5145271fd182F0eE7", "0", {from: eth.accounts[0]})
{
msg: "Account does not have sufficient access for operation",
status: false
}
```

View File

@ -0,0 +1,13 @@
# Smart Contract design for permissions
The permissions model is completely built on smart contracts. The smart contract design is as below:
![contract design](images/ContractDesign.png)
The permissions smart contract design follows the Proxy-Implementation-Storage pattern which allows the implementation logic to change without changing the storage or interface layer. Brief description of the smart contracts is as below:
* `PermissionsUpgradable.sol`: This contract stores the address of current implementation contract and is owned by a custodian(an Ethereum account). Only custodian is allowed to change the implementation contract address.
* `PermissionsInterface.sol`: This is the interface contract and holds the interfaces for permissions related actions. This does not have any business logic and it forwards requests to current implementation contract
* `PermissionsImplementation.sol`: This contract has the business logic for the permissions actions. This contract can receive requests only from a valid interface as defined in `PermissionsUpgradable.sol` and interact with all the storage contracts for respective actions.
* `OrgManager.sol`: This contract stores data for organizations and sub organizations. This contract can receive request from valid implementation contract as defined in `PermissionsUpgrdable.sol`
* `AccountManager.sol`: This contract receives requests from valid implementation contract as defined in `PermissionsUpgrdable.sol`. This contracts stores the data of all accounts, their linkage to organization and various roles. This contracts also stores the status of an account. The account can be in any of the following states - `PendingApproval`, `Active`, `Suspended`, `Blacklisted`, `Revoked`
* `NodeManager.sol`: This contract receives requests from valid implementation contract as defined in `PermissionsUpgrdable.sol`. This contracts stores the data of a node, its linkage to a organization or sub organization and status of the node. The node status can be any one of the following states - `PendingApproval`, `Active`, `Deactivated`, `Blacklisted`
* `RoleManager.sol`: This contract receives requests from valid implementation contract as defined in `PermissionsUpgrdable.sol`. This contract stores data for various roles and the organization to which it is linked. At access at role level can be any one of the following: `Readonly` which allows only read operations, `Transact` which allows value transfer but no contract deployment access, `ContractDeploy` which allows both value transfer and contract deployment access and `FullAccess` which allows additional network level accesses in addition to value transfer and contract deployment. If a role is revoked all accounts which are linked to the role lose all access rights.
* `VoterManager.sol`: This contract receives requests from valid implementation contract as defined in `PermissionsUpgrdable.sol`. This contract stores the data of valid voters at network level which can approve identified activities e.g. adding a new organization to the network. Any account which is linked to a predefined network admin role will be marked as a voter. Whenever a network level activity which requires voting is performed, a voting item is added to this contract and each voter account can vote for the activity. The activity is marked as `Approved` upon majority voting.

Binary file not shown.

After

Width:  |  Height:  |  Size: 797 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 340 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 144 KiB

View File

@ -0,0 +1,16 @@
# Introduction
The current permission model with in Quorum is limited to node level permissions only and allows a set of nodes which are part of `permissioned-nodes.json` to join the network. The model has been enhanced to cater for enterprise level needs to have **smart contract based permissions model** which allows flexibility to manage nodes, accounts and account level access controls. The overview of the model is as depicted below:
![permissions mode](images/PermissionsModel.png)
### Key Definitions
* Network - A set of organizations
* Organization - A set of Ethereum accounts, nodes having varied permissions to interact with the network
* Sub Organization - Further sub grouping with in the Organization as per business need
* Account - An Ethereum account
* Voter - An account capable of voting for a certain action
* Role - A named job function in organization
* Node - A `geth` node which is part of the network and belongs to an organization or sub organization
As depicted above, in the enhanced permissions model, the network comprises of group of organization. The network admin accounts defined at network level can propose and approve new organizations to join the network and assign an account as the organization administration account. The organization admin account can in turn creates roles, sub organizations, assign roles to its accounts and add any other node which is part of the organization. The sub organizations in turn can have its own set of roles accounts and sub organizations. The organization administration account manages and controls all activities at the organization level. The organization administrator can create an admin role and assign the same to a different account to manage the administration of a sub organization. The access rights of an account are derived based on the role assigned to it. The account will be able to transact via any node linked to the sub org or at overall organizations level.
A sample network view is as depicted below:
![sample mode](images/sampleNetwork.png)

View File

@ -0,0 +1,355 @@
# Permission APIs
#### quorumPermission.orgList
* Input: None
* Output: Returns the list of all organizations and their status
* Example:
```
> quorumPermission.orgList
[{
fullOrgId: "INITORG",
level: 1,
orgId: "INITORG",
parentOrgId: "",
status: 2,
subOrgList: null,
ultimateParent: "INITORG"
}]
```
#### quorumPermission.acctList
* Input: None
* Output: Returns the list of all accounts across organizations
* Example:
```
> quorumPermission.acctList
[{
acctId: "0xed9d02e382b34818e88b88a309c7fe71e65f419d",
isOrgAdmin: true,
orgId: "INITORG",
roleId: "NWADMIN",
status: 2
}, {
acctId: "0xca843569e3427144cead5e4d5999a3d0ccf92b8e",
isOrgAdmin: true,
orgId: "INITORG",
roleId: "NWADMIN",
status: 2
}]
```
#### quorumPermission.nodeList
* Input: None
* Output: Returns the list of all nodes across organizations
* Example:
```
> quorumPermission.nodeList
[{
orgId: "INITORG",
status: 2,
url: "enode://72c0572f7a2492cffb5efc3463ef350c68a0446402a123dacec9db5c378789205b525b3f5f623f7548379ab0e5957110bffcf43a6115e450890f97a9f65a681a@127.0.0.1:21000?discport=0"
}, {
orgId: "INITORG",
status: 2,
url: "enode://7a1e3b5c6ad614086a4e5fb55b6fe0a7cf7a7ac92ac3a60e6033de29df14148e7a6a7b4461eb70639df9aa379bd77487937bea0a8da862142b12d326c7285742@127.0.0.1:21001?discport=0"
}, {
orgId: "INITORG",
status: 2,
url: "enode://5085e86db5324ca4a55aeccfbb35befb412def36e6bc74f166102796ac3c8af3cc83a5dec9c32e6fd6d359b779dba9a911da8f3e722cb11eb4e10694c59fd4a1@127.0.0.1:21002?discport=0"
}, {
orgId: "INITORG",
status: 2,
url: "enode://28a4afcf56ee5e435c65b9581fc36896cc684695fa1db83c9568de4353dc6664b5cab09694d9427e9cf26a5cd2ac2fb45a63b43bb24e46ee121f21beb3a7865e@127.0.0.1:21003?discport=0"
}]
```
#### quorumPermission.roleList
* Input: None
* Output: Returns the list of all roles across organizations and their details
* Example:
```
> quorumPermission.roleList
[{
access: 3,
active: true,
isAdmin: true,
isVoter: true,
orgId: "INITORG",
roleId: "NWADMIN"
}]
```
#### quorumPermission.getOrgDetails
This returns the list of accounts, nodes, roles, sub organizations linked to an organization
* Input: idrganization or sub organization id
* Output: list of all accounts, roles, nodes and sub orgs
* Example:
```
> quorumPermission.getOrgDetails("INITORG")
{
acctList: [{
acctId: "0xed9d02e382b34818e88b88a309c7fe71e65f419d",
isOrgAdmin: true,
orgId: "INITORG",
roleId: "NWADMIN",
status: 2
}, {
acctId: "0xca843569e3427144cead5e4d5999a3d0ccf92b8e",
isOrgAdmin: true,
orgId: "INITORG",
roleId: "NWADMIN",
status: 2
}],
nodeList: [{
orgId: "INITORG",
status: 2,
url: "enode://72c0572f7a2492cffb5efc3463ef350c68a0446402a123dacec9db5c378789205b525b3f5f623f7548379ab0e5957110bffcf43a6115e450890f97a9f65a681a@127.0.0.1:21000?discport=0"
}, {
orgId: "INITORG",
status: 2,
url: "enode://7a1e3b5c6ad614086a4e5fb55b6fe0a7cf7a7ac92ac3a60e6033de29df14148e7a6a7b4461eb70639df9aa379bd77487937bea0a8da862142b12d326c7285742@127.0.0.1:21001?discport=0"
}, {
orgId: "INITORG",
status: 2,
url: "enode://5085e86db5324ca4a55aeccfbb35befb412def36e6bc74f166102796ac3c8af3cc83a5dec9c32e6fd6d359b779dba9a911da8f3e722cb11eb4e10694c59fd4a1@127.0.0.1:21002?discport=0"
}, {
orgId: "INITORG",
status: 2,
url: "enode://28a4afcf56ee5e435c65b9581fc36896cc684695fa1db83c9568de4353dc6664b5cab09694d9427e9cf26a5cd2ac2fb45a63b43bb24e46ee121f21beb3a7865e@127.0.0.1:21003?discport=0"
}],
roleList: [{
access: 3,
active: true,
isAdmin: true,
isVoter: true,
orgId: "INITORG",
roleId: "NWADMIN"
}],
subOrgList: null
}
```
#### quorumPermission.addOrg
This api can be executed by a network admin account only for proposing a new organization into the network
* Input: Unique organization id, enode id, account id
* Output: Status of the operation
* Example:
```
> quorumPermission.addOrg("ABC", "enode://3d9ca5956b38557aba991e31cf510d4df641dce9cc26bfeb7de082f0c07abb6ede3a58410c8f249dabeecee4ad3979929ac4c7c496ad20b8cfdd061b7401b4f5@127.0.0.1:21003?discport=0&raftport=50404", "0x0638e1574728b6d862dd5d3a3e0942c3be47d996", {from: eth.accounts[0]})
{
msg: "Action completed successfully",
status: true
}
```
If there any pending items for approval, proposal of any new organization will fail. Also the enode id and accounts can be linked to one organization only.
```
> quorumPermission.addOrg("ABC", "enode://3d9ca5956b38557aba991e31cf510d4df641dce9cc26bfeb7de082f0c07abb6ede3a58410c8f249dabeecee4ad3979929ac4c7c496ad20b8cfdd061b7401b4f5@127.0.0.1:21003?discport=0&raftport=50404", "0x0638e1574728b6d862dd5d3a3e0942c3be47d996", {from: eth.accounts[0]})
{
msg: "Pending approvals for the organization. Approve first",
status: false
}
> quorumPermission.addOrg("XYZ", "enode://3d9ca5956b38557aba991e31cf510d4df641dce9cc26bfeb7de082f0c07abb6ede3a58410c8f249dabeecee4ad3979929ac4c7c496ad20b8cfdd061b7401b4f5@127.0.0.1:21003?discport=0&raftport=50404", "0x0638e1574728b6d862dd5d3a3e0942c3be47d996", {from: eth.accounts[0]})
{
msg: "EnodeId already part of network.",
status: false
}
> quorumPermission.addOrg("XYZ", "enode://de9c2d5937e599930832cecc1df8cc90b50839bdf635c1a4e68e1dab2d001cd4a11c626e155078cc65958a72e2d72c1342a28909775edd99cc39470172cce0ac@127.0.0.1:21004?discport=0", "0x0638e1574728b6d862dd5d3a3e0942c3be47d996", {from: eth.accounts[0]})
{
msg: "Account already in use in another organization",
status: false
}
```
#### quorumPermission.approveOrg
This api can be executed by a network admin account only for approving a proposed organization into the network
* Input: Unique organization id, enode id, account id
* Output: Status of the operation
* Example:
```
quorumPermission.approveOrg("ABC", "enode://3d9ca5956b38557aba991e31cf510d4df641dce9cc26bfeb7de082f0c07abb6ede3a58410c8f249dabeecee4ad3979929ac4c7c496ad20b8cfdd061b7401b4f5@127.0.0.1:21003?discport=0&raftport=50404", "0x0638e1574728b6d862dd5d3a3e0942c3be47d996", {from: eth.accounts[0]})
{
msg: "Action completed successfully",
status: true
}
```
#### quorumPermission.updateOrgStatus
This api can be executed by a network admin account only for temporarily suspending an organization and re-enabling a suspended organization. This activity can be performed for master organization only and requires majority approval from network admins.
* Input: organization id, action (3 for suspending the organization and 5 for re-enabling the suspended organization)
* Output: Status of the operation
* Example:
```$xslt
> quorumPermission.updateOrgStatus("ABC.SUB1", 3, {from:eth.accounts[0]})
{
msg: "Action completed successfully",
status: true
}
```
#### quorumPermission.approveOrgStatus
This api can be executed by a network admin account only for approving the org status change proposal. Once majority approval is received from network admins, the org status is updated.
* Input: organization id, action (3 for suspending the organization and 5 for re-enabling the suspended organization)
* Output: Status of the operation
* Example:
```$xslt
> quorumPermission.approveOrgStatus("ABC", 3, {from: eth.accounts[0]})
{
msg: "Action completed successfully",
status: true
}
```
When a org is in suspended status, no transactions or contract deploy activities are allowed from any nodes linked to the org and sub organizations under it. Similarly no transactions will be allowed from any accounts linked to the organization
#### quorumPermission.addSubOrg
This api can be executed by a organization admin account to create a sub organization under under the master org.
* Input: parent org id, Sub organization id, enode id ( not mandatory and can be null), account id (not mandatory and can be 0x0)
* Output: Status of the operation
* Example:
```
> quorumPermission.addSubOrg("ABC", "SUB1", "", "0x0000000000000000000000000000000000000000", {from: eth.accounts[0]})
{
msg: "Action completed successfully",
status: true
}
```
For adding a sub organization at next level the parent org id should have the entire org hierarchy upto the immediate parent. e.g.
```
> quorumPermission.addSubOrg("ABC.SUB1", "SUB2","", "0x0000000000000000000000000000000000000000", {from: eth.accounts[0]})
{
msg: "Action completed successfully",
status: true
}
> quorumPermission.addSubOrg("ABC.SUB1.SUB2", "SUB3","", "0x0000000000000000000000000000000000000000", {from: eth.accounts[0]})
{
msg: "Action completed successfully",
status: true
}
```
#### quorumPermission.addNewRole
This api can be executed by organization admin account to create a new role for the organization
* Input: org or sub org id, role id, account access(can be 0 - ReadOnly, 1 - Transact, 2 - ContractDeploy, 3 - FullAccess), isVoter, isAdminRole
* Output: Status of the operation
* Example:
```
> quorumPermission.addNewRole("ABC", "TRANSACT", 1, false, false,{from: eth.accounts[0]})
{
msg: "Action completed successfully",
status: true
}
> quorumPermission.addNewRole("ABC.SUB1.SUB2.SUB3", "TRANSACT", 1, false, false,{from: eth.accounts[0]})
{
msg: "Action completed successfully",
status: true
}
```
#### quorumPermission.removeRole
This api can be executed by organization admin account to create a new role for the organization
* Input: org or sub org id, role id
* Output: Status of the operation
* Example:
```
> quorumPermission.removeRole("ABC.SUB1.SUB2.SUB3", "TRANSACT", {from: eth.accounts[1]})
{
msg: "Action completed successfully",
status: true
}
```
#### quorumPermission.assignAccountRole
This api can be executed by organization admin account to assign a role to an account
* Input: Account id, org or sub org id, role to be assigned
* Output: Status of the operation
* Example:
```
> quorumPermission.assignAccountRole("0xf017976fdf1521de2e108e63b423380307f501f8", "ABC", "TRANSACT", {from: eth.accounts[1]})
{
msg: "Action completed successfully",
status: true
}
```
The account can be linked to a organization or sub organization only and cannot belong to multiple organizations or sub organizations
```$xslt
> quorumPermission.assignAccountRole("0xf017976fdf1521de2e108e63b423380307f501f8", "ABC.SUB1", "TRANSACT", {from: eth.accounts[1]})
{
msg: "Account already in use in another organization",
status: false
}
```
#### quorumPermission.updateAccountStatus
This api can be executed by organization admin account to update the account status
* Input: org or sub org id, Account id, action (1 for suspending the account, 2 for activating a suspended account, 3 for blacklisting the account)
* Output: Status of the operation
* Example:
```
> quorumPermission.updateAccountStatus("ABC", "0xf017976fdf1521de2e108e63b423380307f501f8", 1, {from: eth.accounts[1]})
{
msg: "Action completed successfully",
status: true
}
```
Once a account is blacklisted no further action is allowed on it
#### quorumPermission.assignAdminRole
This api can be executed by network admin to add a new account as network admin or change the org admin account for a organization
* Input: org id to which the account belongs, account id, role id (it can be either org admin role or network admin role)
* Output: Status of the operation
* Example:
```
> quorumPermission.assignAdminRole("ABC", "0xf017976fdf1521de2e108e63b423380307f501f8", "NWADMIN", {from: eth.accounts[0]})
{
msg: "Action completed successfully",
status: true
}
```
#### quorumPermission.approveAdminRole
This api can be executed by network admin to add approve the org admin or network admin role assignment to an account. The role is approved once majority approvals is received
* Input: org id to which the account belongs, account id
* Output: Status of the operation
* Example:
```
> quorumPermission.approveAdminRole("ABC", "0xf017976fdf1521de2e108e63b423380307f501f8", {from: eth.accounts[0]})
{
msg: "Action completed successfully",
status: true
}
```
#### quorumPermission.addNode
This api can be executed by organization admin account to add a node to the org or sub org
* Input: org or sub org id, enode id
* Output: Status of the operation
* Example:
```
> quorumPermission.addNode("ABC.SUB1.SUB2.SUB3", "enode://239c1f044a2b03b6c4713109af036b775c5418fe4ca63b04b1ce00124af00ddab7cc088fc46020cdc783b6207efe624551be4c06a994993d8d70f684688fb7cf@127.0.0.1:21006?discport=0&raftport=50407", {from: eth.accounts[1]})
{
msg: "Action completed successfully",
status: true
}
```
A node cannot be part of multiple organizations.
#### quorumPermission.updateNodeStatus
This api can be executed by organization admin account to update the status of a node
* Input: org or sub org id, enode id, action (3 for deactivating the node, 4 for activating a deactivated node and 5 for blacklisting a node)
* Output: Status of the operation
* Example:
```
> quorumPermission.updateNodeStatus("ABC.SUB1.SUB2.SUB3", "enode://239c1f044a2b03b6c4713109af036b775c5418fe4ca63b04b1ce00124af00ddab7cc088fc46020cdc783b6207efe624551be4c06a994993d8d70f684688fb7cf@127.0.0.1:21006?discport=0&raftport=50407",3, {from: eth.accounts[1]})
{
msg: "Action completed successfully",
status: true
}
```
Once a node is blacklisted no further action is possible on the same.
#### Account access types
The table below indicates the numeric value for each account access type.
| AccessType | Value |
| :-------------: | :-------------: |
| ReadOnly | 0 |
| Transact | 1 |
| Contract deploy | 2 |
| Full access | 3 |
While setting the account access, system checks if the account which is setting the access has sufficient privileges to perform the activity.
* Accounts with `FullAccess` can grant any access type ( FullAccess, Transact, ContractDeploy or ReadOnly) to any other account
* Accounts with `ContractDeploy` can grant only `Transact`, `ContractDeploy` or `ReadOnly` access to other accounts
* Accounts with `Transact` access grant only `Transact` or `ReadOnly` access to other accounts
* Accounts with `ReadOnly` access cannot grant any access

40
docs/permissions/setup.md Normal file
View File

@ -0,0 +1,40 @@
# Set up
The steps to enable new permissions model are as described below:
* For a new network, bring up the initial set of nodes which will be part of the network
* Deploy the `PermissionsUpgradable.sol` in the network. The deployment of this contract will require a custodian account to be given as a part of deployment.
* Deploy the rest of the contracts. All the other contracts will require the address of `PermissionsUpgradable.sol` contract as a part of deployment.
* Once all the contracts are deployed create a file `permission-config.json` which will have the following construct:
```$xslt
{
"upgrdableAddress": "0x1932c48b2bf8102ba33b4a6b545c32236e342f34",
"interfaceAddress": "0x4d3bfd7821e237ffe84209d8e638f9f309865b87",
"impladdress": "0xfe0602d820f42800e3ef3f89e1c39cd15f78d283",
"nodeMgraddress": "0x8a5e2a6343108babed07899510fb42297938d41f",
"accountMgraddress": "0x9d13c6d3afe1721beef56b55d303b09e021e27ab",
"roleMgraddress": "0x1349f3e1b8d71effb47b840594ff27da7e603d17",
"voterMgraddress": "0xd9d64b7dc034fafdba5dc2902875a67b5d586420",
"orgMgraddress" : "0x938781b9796aea6376e40ca158f67fa89d5d8a18",
"nwAdminOrg": "INITORG",
"nwAdminRole" : "NWADMIN",
"orgAdminRole" : "ORGADMIN",
"accounts":["0xed9d02e382b34818e88b88a309c7fe71e65f419d", "0xca843569e3427144cead5e4d5999a3d0ccf92b8e"],
"subOrgBreadth" : "3",
"subOrgDepth" : "4"
}
```
> * `upgrdableAddress` is the address of deployed contract `PermissionsUpgradable.sol`
> * `interfaceAddress` is the address of deployed contract `PermissionsInterface.sol`
> * `impladdress` is the address of deployed contract `PermissionsImplementation.sol`
> * `nodeMgraddress` is the address of deployed contract `NodeManager.sol`
> * `accountMgraddress` is the address of deployed contract `AccountManager.sol`
> * `roleMgraddress` is the address of deployed contract `RoleManager.sol`
> * `voterMgraddress` is the address of deployed contract `VoterManager.sol`
> * `orgMgraddress` is the address of deployed contract `OrgManager.sol`
> * `nwAdminOrg` is the name of initial organization that will be created as a part of network boot up with new permissions model. This organization will own all the initial nodes which come at the time of network boot up and accounts which will be the network admin account
> * `nwAdminRole` is role id for which will have full access and will be network admin
> * `accounts` holds the initial list of accounts which will be linked to the network admin organization and will be assigned the network admin role. These accounts will have complete control on the network and can propose and approve new organizations into the network
> * `subOrgBreadth` indicates the number of sub organizations that any org can have
> * `subOrgDepth` indicates the maximum depth sub org hierarchy allowed in the network
* Bring down the all `geth` nodes in network and copy `permission-config.json` into the data directory of each of the node
* Bring up all `geth` nodes in `--permissioned` mode for new permissions model to take effect