feat: RBAC authentication contract and role library
This commit is contained in:
61
contracts/examples/RBACExample.sol
Normal file
61
contracts/examples/RBACExample.sol
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
pragma solidity ^0.4.8;
|
||||||
|
|
||||||
|
import '../ownership/rbac/RBAC.sol';
|
||||||
|
|
||||||
|
|
||||||
|
contract RBACExample is RBAC {
|
||||||
|
|
||||||
|
modifier onlyOwnerOrAdvisor()
|
||||||
|
{
|
||||||
|
require(
|
||||||
|
hasRole(msg.sender, "owner") ||
|
||||||
|
hasRole(msg.sender, "advisor")
|
||||||
|
);
|
||||||
|
_;
|
||||||
|
}
|
||||||
|
|
||||||
|
function RBACExample(address[] _advisors)
|
||||||
|
public
|
||||||
|
{
|
||||||
|
addRole(msg.sender, "owner");
|
||||||
|
addRole(msg.sender, "advisor");
|
||||||
|
|
||||||
|
for (uint256 i = 0; i < _advisors.length; i++) {
|
||||||
|
addRole(_advisors[i], "advisor");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onlyOwnersCanDoThis()
|
||||||
|
onlyRole("owner")
|
||||||
|
view
|
||||||
|
external
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
function onlyAdvisorsCanDoThis()
|
||||||
|
onlyRole("advisor")
|
||||||
|
view
|
||||||
|
external
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
function eitherOwnerOrAdvisorCanDoThis()
|
||||||
|
onlyOwnerOrAdvisor
|
||||||
|
view
|
||||||
|
external
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
// owners can remove advisor's role
|
||||||
|
function removeAdvisor(address _addr)
|
||||||
|
onlyRole("owner")
|
||||||
|
public
|
||||||
|
{
|
||||||
|
// revert if the user isn't an advisor
|
||||||
|
// (perhaps you want to soft-fail here instead?)
|
||||||
|
checkRole(_addr, "advisor");
|
||||||
|
|
||||||
|
// remove the advisor's role
|
||||||
|
removeRole(_addr, "advisor");
|
||||||
|
}
|
||||||
|
}
|
||||||
101
contracts/ownership/rbac/RBAC.sol
Normal file
101
contracts/ownership/rbac/RBAC.sol
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
pragma solidity ^0.4.18;
|
||||||
|
|
||||||
|
import './Roles.sol';
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @title RBAC (Role-Based Access Control)
|
||||||
|
* @author Matt Condon (@Shrugs)
|
||||||
|
* @dev Stores and provides setters and getters for roles and addresses.
|
||||||
|
* Supports unlimited numbers of roles and addresses.
|
||||||
|
* See //contracts/examples/RBACExample.sol for an example of usage.
|
||||||
|
* This RBAC method uses strings to key roles. It may be beneficial
|
||||||
|
* for you to write your own implementation of this interface using Enums or similar.
|
||||||
|
*/
|
||||||
|
contract RBAC {
|
||||||
|
using Roles for Roles.Role;
|
||||||
|
|
||||||
|
mapping (string => Roles.Role) internal roles;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev add a role to an address
|
||||||
|
* @param addr address
|
||||||
|
* @param roleName the name of the role
|
||||||
|
*/
|
||||||
|
function addRole(address addr, string roleName)
|
||||||
|
internal
|
||||||
|
{
|
||||||
|
roles[roleName].add(addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev remove a role from an address
|
||||||
|
* @param addr address
|
||||||
|
* @param roleName the name of the role
|
||||||
|
*/
|
||||||
|
function removeRole(address addr, string roleName)
|
||||||
|
internal
|
||||||
|
{
|
||||||
|
roles[roleName].remove(addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev reverts if addr does not have role
|
||||||
|
* @param addr address
|
||||||
|
* @param roleName the name of the role
|
||||||
|
* // reverts
|
||||||
|
*/
|
||||||
|
function checkRole(address addr, string roleName)
|
||||||
|
view
|
||||||
|
internal
|
||||||
|
{
|
||||||
|
roles[roleName].check(addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev determine if addr has role
|
||||||
|
* @param addr address
|
||||||
|
* @param roleName the name of the role
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
function hasRole(address addr, string roleName)
|
||||||
|
view
|
||||||
|
internal
|
||||||
|
returns (bool)
|
||||||
|
{
|
||||||
|
return roles[roleName].has(addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev modifier to scope access to a single role (uses msg.sender as addr)
|
||||||
|
* @param roleName the name of the role
|
||||||
|
* // reverts
|
||||||
|
*/
|
||||||
|
modifier onlyRole(string roleName)
|
||||||
|
{
|
||||||
|
checkRole(msg.sender, roleName);
|
||||||
|
_;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev modifier to scope access to a set of roles (uses msg.sender as addr)
|
||||||
|
* @param roleNames the names of the roles to scope access to
|
||||||
|
* // reverts
|
||||||
|
*
|
||||||
|
* @TODO - when solidity supports dynamic arrays as arguments, provide this
|
||||||
|
* see: https://github.com/ethereum/solidity/issues/2467
|
||||||
|
*/
|
||||||
|
// modifier onlyRoles(string[] roleNames) {
|
||||||
|
// bool hasAnyRole = false;
|
||||||
|
// for (uint8 i = 0; i < roleNames.length; i++) {
|
||||||
|
// if (hasRole(msg.sender, roleNames[i])) {
|
||||||
|
// hasAnyRole = true;
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// require(hasAnyRole);
|
||||||
|
|
||||||
|
// _;
|
||||||
|
// }
|
||||||
|
}
|
||||||
55
contracts/ownership/rbac/Roles.sol
Normal file
55
contracts/ownership/rbac/Roles.sol
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
pragma solidity ^0.4.18;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @title Roles
|
||||||
|
* @author Francisco Giordano (@frangio)
|
||||||
|
* @dev Library for managing addresses assigned to a Role.
|
||||||
|
* See RBAC.sol for example usage.
|
||||||
|
*/
|
||||||
|
library Roles {
|
||||||
|
struct Role {
|
||||||
|
mapping (address => bool) bearer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev give an address access to this role
|
||||||
|
*/
|
||||||
|
function add(Role storage role, address addr)
|
||||||
|
internal
|
||||||
|
{
|
||||||
|
role.bearer[addr] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev remove an address' access to this role
|
||||||
|
*/
|
||||||
|
function remove(Role storage role, address addr)
|
||||||
|
internal
|
||||||
|
{
|
||||||
|
role.bearer[addr] = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev check if an address has this role
|
||||||
|
* // reverts
|
||||||
|
*/
|
||||||
|
function check(Role storage role, address addr)
|
||||||
|
view
|
||||||
|
internal
|
||||||
|
{
|
||||||
|
require(has(role, addr));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev check if an address has this role
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
function has(Role storage role, address addr)
|
||||||
|
view
|
||||||
|
internal
|
||||||
|
returns (bool)
|
||||||
|
{
|
||||||
|
return role.bearer[addr];
|
||||||
|
}
|
||||||
|
}
|
||||||
77
test/RBAC.js
Normal file
77
test/RBAC.js
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
const RBACMock = artifacts.require('./helpers/RBACMock.sol')
|
||||||
|
|
||||||
|
import expectThrow from './helpers/expectThrow'
|
||||||
|
|
||||||
|
require('chai')
|
||||||
|
.use(require('chai-as-promised'))
|
||||||
|
.should()
|
||||||
|
|
||||||
|
contract('RBAC', function(accounts) {
|
||||||
|
let mock
|
||||||
|
|
||||||
|
const [
|
||||||
|
owner,
|
||||||
|
anyone,
|
||||||
|
...advisors
|
||||||
|
] = accounts
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
mock = await RBACMock.new(advisors, { from: owner })
|
||||||
|
})
|
||||||
|
|
||||||
|
context('in normal conditions', () => {
|
||||||
|
it('allows owner to call #onlyOwnersCanDoThis', async () => {
|
||||||
|
await mock.onlyOwnersCanDoThis({ from: owner })
|
||||||
|
.should.be.fulfilled
|
||||||
|
})
|
||||||
|
it('allows owner to call #onlyAdvisorsCanDoThis', async () => {
|
||||||
|
await mock.onlyAdvisorsCanDoThis({ from: owner })
|
||||||
|
.should.be.fulfilled
|
||||||
|
})
|
||||||
|
it('allows advisors to call #onlyAdvisorsCanDoThis', async () => {
|
||||||
|
await mock.onlyAdvisorsCanDoThis({ from: advisors[0] })
|
||||||
|
.should.be.fulfilled
|
||||||
|
})
|
||||||
|
it('allows owner to call #eitherOwnerOrAdvisorCanDoThis', async () => {
|
||||||
|
await mock.eitherOwnerOrAdvisorCanDoThis({ from: owner })
|
||||||
|
.should.be.fulfilled
|
||||||
|
})
|
||||||
|
it('allows advisors to call #eitherOwnerOrAdvisorCanDoThis', async () => {
|
||||||
|
await mock.eitherOwnerOrAdvisorCanDoThis({ from: advisors[0] })
|
||||||
|
.should.be.fulfilled
|
||||||
|
})
|
||||||
|
it('does not allow owners to call #nobodyCanDoThis', async () => {
|
||||||
|
expectThrow(
|
||||||
|
mock.nobodyCanDoThis({ from: owner })
|
||||||
|
)
|
||||||
|
})
|
||||||
|
it('does not allow advisors to call #nobodyCanDoThis', async () => {
|
||||||
|
expectThrow(
|
||||||
|
mock.nobodyCanDoThis({ from: advisors[0] })
|
||||||
|
)
|
||||||
|
})
|
||||||
|
it('does not allow anyone to call #nobodyCanDoThis', async () => {
|
||||||
|
expectThrow(
|
||||||
|
mock.nobodyCanDoThis({ from: anyone })
|
||||||
|
)
|
||||||
|
})
|
||||||
|
it('allows an owner to remove an advisor\'s role', async () => {
|
||||||
|
await mock.removeAdvisor(advisors[0], { from: owner })
|
||||||
|
.should.be.fulfilled
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
context('in adversarial conditions', () => {
|
||||||
|
it('does not allow an advisor to remove another advisor', async () => {
|
||||||
|
expectThrow(
|
||||||
|
mock.removeAdvisor(advisors[1], { from: advisors[0] })
|
||||||
|
)
|
||||||
|
})
|
||||||
|
it('does not allow "anyone" to remove an advisor', async () => {
|
||||||
|
expectThrow(
|
||||||
|
mock.removeAdvisor(advisors[0], { from: anyone })
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
||||||
68
test/helpers/RBACMock.sol
Normal file
68
test/helpers/RBACMock.sol
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
pragma solidity ^0.4.8;
|
||||||
|
|
||||||
|
import '../../contracts/ownership/rbac/RBAC.sol';
|
||||||
|
|
||||||
|
|
||||||
|
contract RBACMock is RBAC {
|
||||||
|
|
||||||
|
modifier onlyOwnerOrAdvisor()
|
||||||
|
{
|
||||||
|
require(
|
||||||
|
hasRole(msg.sender, "owner") ||
|
||||||
|
hasRole(msg.sender, "advisor")
|
||||||
|
);
|
||||||
|
_;
|
||||||
|
}
|
||||||
|
|
||||||
|
function RBACMock(address[] _advisors)
|
||||||
|
public
|
||||||
|
{
|
||||||
|
addRole(msg.sender, "owner");
|
||||||
|
addRole(msg.sender, "advisor");
|
||||||
|
|
||||||
|
for (uint256 i = 0; i < _advisors.length; i++) {
|
||||||
|
addRole(_advisors[i], "advisor");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onlyOwnersCanDoThis()
|
||||||
|
onlyRole("owner")
|
||||||
|
view
|
||||||
|
external
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
function onlyAdvisorsCanDoThis()
|
||||||
|
onlyRole("advisor")
|
||||||
|
view
|
||||||
|
external
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
function eitherOwnerOrAdvisorCanDoThis()
|
||||||
|
onlyOwnerOrAdvisor
|
||||||
|
view
|
||||||
|
external
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
function nobodyCanDoThis()
|
||||||
|
onlyRole("unknown")
|
||||||
|
view
|
||||||
|
external
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
// owners can remove advisor's role
|
||||||
|
function removeAdvisor(address _addr)
|
||||||
|
onlyRole("owner")
|
||||||
|
public
|
||||||
|
{
|
||||||
|
// revert if the user isn't an advisor
|
||||||
|
// (perhaps you want to soft-fail here instead?)
|
||||||
|
checkRole(_addr, "advisor");
|
||||||
|
|
||||||
|
// remove the advisor's role
|
||||||
|
removeRole(_addr, "advisor");
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user