簡介
基于角色的訪問控制是軟件系統的安全需求,旨在為數百個用戶提供訪問。雖然這種需求通常在企業軟件和操作系統中實現,但對以太坊區塊鏈的處理并不多。
當將供應鏈設計為有向無環圖時,我們意識到需要動態地確定誰可以向圖中的每個節點添加信息。從現實世界的角度來看,如果您擁有一家制造工廠,您可能希望裝配線上的所有操作員都能夠用他們自己的帳戶記錄他們已經組裝了一個零件。
OpenZeppelin是我在Solidity開發中使用的金標準,它有一個roles.sol合同,用于在erc721.sol合同中實現諸如minter和burner等角色。不幸的是,這些實現不允許在運行時創建新角色,如果您想使用單獨的角色控制對每個單獨令牌的訪問,則需要創建新角色。
本文旨在展示如何為以太坊區塊鏈構建基于角色的訪問控制系統。根據我們的要求從頭開始編寫RBAC合同,然后從OpenZeppelin中找到了相同想法的版本,它具有幾乎相同的方法。為了可重用性,我盡可能地重構我的代碼以遵循它們的命名法。
在以下各節中,我將介紹:
1. 我們進入訪問系統的設計要求;
2. 智能合約的實施;
3. 測試案例;
4. 狀態變換法的gas利用;
5. 還有一些完善的想法。
概念設計
我對RBAC系統的想法很簡單。
1. 角色將由數字標識符標識,如Unix中的組。
2. 角色可以動態創建。
3. 每個角色存儲用戶的地址。
4. 每個角色都會有一個關聯的第二個角色,這是唯一允許添加或刪除用戶的角色。
如果您是使用OpenZeppelin中的Roles.sol和RBAC.sol合同,則需要注意Roles.sol僅實現在角色內生效的操作,而在角色外部發生的操作在RBAC.sol或訪問中實現/roles/*Role.sol收縮,包括在創建角色時存儲角色的數據結構。
在我的實現中,我根據我們的用例做了一些決策:
· 角色結構中包含一個描述字符串,結構本身存儲在一個數組中。數組中每個角色結構的位置用作標識符。有一種使用映射來存儲角色,但我發現這里沒有必要。
· 每個角色在實例化時接收我們指定為其管理角色的另一個角色的標識符,并且在實例化之后不能修改該角色。此管理員角色是唯一可以為此角色添加和刪除承載者的角色。
出于安全性和一致性的原因,您可以從角色中刪除承載,但沒有方法可以從系統中完全刪除角色。
pragma solidity ^0.5.0;
/**
* @title RBAC
* @author Alberto Cuesta Canada
* @notice Implements runtime configurable Role Based Access Control.
*/
contract RBAC {
event RoleCreated(uint256 role);
event BearerAdded(address account, uint256 role);
event BearerRemoved(address account, uint256 role);
uint256 constant NO_ROLE = 0;
/**
* @notice A role, which will be used to group users.
* @dev The role id is its position in the roles array.
* @param description A description for the role.
* @param admin The only role that can add or remove bearers from
* this role. To have the role bearers to be also the role admins
* you should pass roles.length as the admin role.
* @param bearers Addresses belonging to this role.
*/
struct Role {
string description;
uint256 admin;
mapping (address =》 bool) bearers;
}
/**
* @notice All roles ever created.
*/
Role[] public roles;
/**
* @notice The contract constructor, empty as of now.
*/
constructor() public {
addRootRole(“NO_ROLE”);
}
/**
* @notice Create a new role that has itself as an admin.
* msg.sender is added as a bearer.
* @param _roleDescription The description of the role created.
* @return The role id.
*/
function addRootRole(string memory _roleDescription)
public
returns(uint256)
{
uint256 role = addRole(_roleDescription, roles.length);
roles[role].bearers[msg.sender] = true;
emit BearerAdded(msg.sender, role);
}
/**
* @notice Create a new role.
* @param _roleDescription The description of the role created.
* @param _admin The role that is allowed to add and remove
* bearers from the role being created.
* @return The role id.
*/
function addRole(string memory _roleDescription, uint256 _admin)
public
returns(uint256)
{
require(_admin 《= roles.length, “Admin role doesn‘t exist.”);
uint256 role = roles.push(
Role({
description: _roleDescription,
admin: _admin
})
) - 1;
emit RoleCreated(role);
return role;
}
/**
* @notice Retrieve the number of roles in the contract.
* @dev The zero position in the roles array is reserved for
* NO_ROLE and doesn’t count towards this total.
*/
function totalRoles()
public
view
returns(uint256)
{
return roles.length - 1;
}
/**
* @notice Verify whether an account is a bearer of a role
* @param _account The account to verify.
* @param _role The role to look into.
* @return Whether the account is a bearer of the role.
*/
function hasRole(address _account, uint256 _role)
public
view
returns(bool)
{
return _role 《 roles.length && roles[_role].bearers[_account];
}
/**
* @notice A method to add a bearer to a role
* @param _account The account to add as a bearer.
* @param _role The role to add the bearer to.
*/
function addBearer(address _account, uint256 _role)
public
{
require(
_role 《 roles.length,
“Role doesn‘t exist.”
);
require(
hasRole(msg.sender, roles[_role].admin),
“User can’t add bearers.”
);
require(
!hasRole(_account, _role),
“Account is bearer of role.”
);
roles[_role].bearers[_account] = true;
emit BearerAdded(_account, _role);
}
/**
* @notice A method to remove a bearer from a role
* @param _account The account to remove as a bearer.
* @param _role The role to remove the bearer from.
*/
function removeBearer(address _account, uint256 _role)
public
{
require(
_role 《 roles.length,
“Role doesn‘t exist.”
);
require(
hasRole(msg.sender, roles[_role].admin),
“User can’t remove bearers.”
);
require(
hasRole(_account, _role),
“Account is not bearer of role.”
);
delete roles[_role].bearers[_account];
emit BearerRemoved(_account, _role);
}
}
測試
我喜歡公開測試智能合約,既展示了操作案例,又能對代碼的可靠性提供了一些信心。
Contract: RBAC
RBAC
? addRootRole creates a role.
? hasRole returns false for non existing roles.
? hasRole returns false for non existing bearerships.
? addRootRole adds msg.sender as bearer.
? addRole doesn’t add msg.sender with admin role.
? addBearer reverts on non existing roles.
? addBearer reverts on non authorized users.
? addBearer reverts if the bearer belongs to the role.
? addBearer adds a bearer to a role.
? removeBearer reverts on non existing roles.
? removeBearer reverts on non authorized users.
? removeBearer reverts if the bearer doesn‘t belong to the role.
? removeBearer removes a bearer from a role.
為了回應之前的反饋,我現在還使用eth-gas-reporter的gas使用報告。
結論
本文描述了一個基于智能合約角色的訪問控制系統的實現,它具有以下屬性:
1. 允許在系統運行時創建新角色。
2. 包括角色管理員的概念,允許添加和刪除角色的成員。
3. 允許輕松確定所有現有角色及其承載。
4. 基于角色的訪問控制實現起來并不一定復雜,但正如本文所示,需要考慮許多權衡和設計決策,這些決策與您的用戶及其允許的操作密切相關 去設計。如果您決定復用RBAC系統的這種實現,我會很高興,但我也鼓勵您尋找并考慮其他選擇。
評論