-
Notifications
You must be signed in to change notification settings - Fork 5
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: use context-based role assignments to implement the permission model #19
base: main
Are you sure you want to change the base?
feat: use context-based role assignments to implement the permission model #19
Conversation
Pull Request Test Coverage Report for Build 13805766124Warning: This coverage report may be inaccurate.This pull request's base commit is no longer the HEAD commit of its target branch. This means it includes changes from outside the original pull request, including, potentially, unrelated coverage changes.
Details
💛 - Coveralls |
…even when user-facing token id is re-generated
feat: add _transferRole method
@Arachnid Given that we only need to transfer two roles to a new registrar (registrar and renew) I felt that it was easier to do that manually whilst keeping the current storage approach in Plus, role transfers only work across a particular context. There's no way to transfer all of a user's roles in all contexts. I suppose if we implement hierarchical contexts of some sort we could perhaps do this. But again, this complicates the access control storage. Happy to discuss this further over calls. |
…rom one account to another
/** | ||
* @dev admin role that controls a given role. | ||
* RoleId -> AdminRoleId | ||
*/ | ||
mapping(uint256 role => uint256 adminRole) private _adminRoles; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we simply say that the upper 128 bits are admin roles and the lower 128 bits are user roles, and thus remove the need to map them explicitly?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's possible that a role can be a role as well as an admin role - e.g registrar can be admin roles for setting the renewer admin role, even though registrars are themselves roles.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think there's in harm in representing those with separate bits, though? If they're typically used together, the inheriting contract can simply grant or revoke both bits together.
/** | ||
* @dev locked roles within a resource stored as a bitmap. | ||
* Resource -> LockedRoleBitmap | ||
*/ | ||
mapping(bytes32 resource => uint256 lockedRoleBitmap) private _lockedRoles; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this necessary? If a role for a given resource has no admins, it's locked implicitly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The issue is that the DEFAULT_ADMIN_ROLE
can always revoke a role, thus I figured we needed an explicit locking mechanism to prevent that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With the admin-bit-for-each-role I suggested above, there is no DEFAULT_ADMIN_ROLE - only individual admins for each role.
* | ||
* May emit a {RoleGranted} event. | ||
*/ | ||
function grantRole(bytes32 resource, uint256 role, address account) public virtual canGrantRole(resource, role) returns (bool) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
function grantRole(bytes32 resource, uint256 role, address account) public virtual canGrantRole(resource, role) returns (bool) { | |
function grantRoles(bytes32 resource, uint256 role, address account) public virtual canGrantRole(resource, role) returns (bool) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This isn't as straightforward since we need to check that the caller has the admin role for the given role - and we don't want to be looping through and checking the caller is an admin for every role passed in.
The alternative is to be able to set an admin for a bitmap of roles, but then that semantically doesn't make any sense.
Hence why granting/revoking one role at a time makes the most sense.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we do the admin-role-per-role I suggested above, this is doable with simple bitmasking. Eg:
uint256 caller_roles = getRoles(resource, msg.sender);
uint256 caller_admin_roles = (caller_roles & 0xffffffffffffffffffffffffffffffff) | (caller_roles >> 128);
role &= caller_admin_roles;
This will take the role
input and mask out any roles the caller doesn't have permission to grant.
* | ||
* May emit a {RoleRevoked} event. | ||
*/ | ||
function revokeRole(bytes32 resource, uint256 role, address account) public virtual canGrantRole(resource, role) returns (bool) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
function revokeRole(bytes32 resource, uint256 role, address account) public virtual canGrantRole(resource, role) returns (bool) { | |
function revokeRoles(bytes32 resource, uint256 role, address account) public virtual canGrantRole(resource, role) returns (bool) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This isn't as straightforward since we need to check that the caller has the admin role for the given role - and we don't want to be looping through and checking the caller is an admin for every role passed in.
The alternative is to be able to set an admin for a bitmap of roles, but then that semantically doesn't make any sense.
Hence why granting/revoking one role at a time makes the most sense.
/** | ||
* @dev Revokes `role` from the calling account. | ||
* | ||
* Roles are often managed via {grantRole} and {revokeRole}: this function's | ||
* purpose is to provide a mechanism for accounts to lose their privileges | ||
* if they are compromised (such as when a trusted device is misplaced). | ||
* | ||
* If the calling account had been revoked `role`, emits a {RoleRevoked} | ||
* event. | ||
* | ||
* Requirements: | ||
* | ||
* - the caller must be `callerConfirmation`. | ||
* | ||
* May emit a {RoleRevoked} event. | ||
* | ||
* Returns `true` if the role was revoked, `false` otherwise. | ||
*/ | ||
function renounceRole(bytes32 resource, uint256 role, address callerConfirmation) public virtual returns (bool) { | ||
if (callerConfirmation != _msgSender()) { | ||
revert EnhancedAccessControlBadConfirmation(); | ||
} | ||
|
||
return _revokeRoles(resource, role, callerConfirmation); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should omit this; if an account is an admin for the role, they can revoke both the role and their own admin status, but having this makes it impossible to 'lock open' a permission.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't really understand what you mean by "lock open" here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Revoke all admin access over a role, without revoking the role itself for everyone - meaning it can still be done, but the set of accounts that can do it can't be changed any longer.
Co-authored-by: Nick Johnson <[email protected]>
Notes: