-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(p/rbac): RBAC Pacakge Implementation (#506)
* rbac implementation * fix doc --------- Co-authored-by: 0xTopaz <60733299+onlyhyde@users.noreply.github.com>
- Loading branch information
Showing
5 changed files
with
505 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
// Package rbac provides a flexible, upgradeable Role-Based Access Control (RBAC) | ||
// system for Gno smart contracts and related applications. It decouples authorization | ||
// logic from fixed addresses, enabling dynamic registration, update, and removal of roles | ||
// and permissions. | ||
// | ||
// ## Overview | ||
// | ||
// The RBAC package encapsulates a manager that maintains an internal registry of roles. | ||
// Each role is defined by a unique name and a set of permissions. A permission is | ||
// represented by a `PermissionChecker` function that validates whether a given caller | ||
// (`std.Address`) satisfies the required access conditions. | ||
// | ||
// Key components of this package include: | ||
// | ||
// 1. **Role**: Represents a role with a name and a collection of permission-checking functions. | ||
// 2. **PermissionChecker**: A function type defined as `func(caller std.Address) error`, | ||
// used to verify access for a given permission. | ||
// 3. **RBAC Manager**: The core type (RBAC) that encapsulates role registration, permission | ||
// assignment, verification, updating, and removal. | ||
// | ||
// ## Key Features | ||
// | ||
// - **Dynamic Role Management**: Roles can be registered, and permissions can be assigned | ||
// or updated at runtime without requiring contract redeployment. | ||
// - **Multiple Permissions per Role**: A single role can have multiple permissions, | ||
// each with its own validation logic. | ||
// - **Declarative Role Definition**: The package supports a Functional Option pattern, | ||
// allowing roles and their permissions to be defined declaratively via functions like | ||
// `DeclareRole` and `WithPermission`. | ||
// - **Encapsulation**: Internal state (roles registry) is encapsulated within the RBAC | ||
// manager, preventing unintended external modifications. | ||
// - **Flexible Validation**: Permission checkers can implement custom logic, supporting | ||
// arbitrary access control policies. | ||
// | ||
// ## Workflow | ||
// | ||
// Typical usage of the RBAC package includes the following steps: | ||
// | ||
// 1. **Initialization**: Create a new RBAC manager using `NewRBAC()`. | ||
// 2. **Role Registration**: Register roles using `RegisterRole` or declaratively with | ||
// `DeclareRole`. | ||
// 3. **Permission Assignment**: Add permissions to roles using `RegisterPermission` or the | ||
// `WithPermission` option during role declaration. | ||
// 4. **Permission Verification**: Validate access by invoking `CheckPermission` with the | ||
// role name, permission name, and the caller's address (std.Address). | ||
// | ||
// ## Example Usage | ||
// | ||
// The following example demonstrates how to use the RBAC package in both traditional and | ||
// declarative styles: | ||
// | ||
// ```gno | ||
// package main | ||
// | ||
// import ( | ||
// "std" | ||
// | ||
// "gno.land/p/gnoswap/rbac" | ||
// "gno.land/p/demo/ufmt" | ||
// ) | ||
// | ||
// func main() { | ||
// // Create a new RBAC manager | ||
// manager := rbac.NewRBAC() | ||
// | ||
// // Define example addresses | ||
// adminAddr := std.Address("admin") | ||
// userAddr := std.Address("user") | ||
// | ||
// // --- Traditional Role Registration --- | ||
// // Register an "admin" role | ||
// if err := manager.RegisterRole("admin"); err != nil { | ||
// panic(err) | ||
// } | ||
// | ||
// // Register an "access" permission for the "admin" role. | ||
// // The checker verifies that the caller matches adminAddr. | ||
// adminChecker := func(caller std.Address) error { | ||
// if caller != adminAddr { | ||
// return ufmt.Errorf("caller %s is not admin", caller) | ||
// } | ||
// return nil | ||
// } | ||
// if err := manager.RegisterPermission("admin", "access", adminChecker); err != nil { | ||
// panic(err) | ||
// } | ||
// | ||
// // --- Declarative Role Registration --- | ||
// // Register an "editor" role with a "modify" permission using the Functional Option pattern. | ||
// editorChecker := func(caller std.Address) error { | ||
// if caller != userAddr { | ||
// return ufmt.Errorf("caller %s is not editor", caller) | ||
// } | ||
// return nil | ||
// } | ||
// if err := manager.DeclareRole("editor", rbac.WithPermission("modify", editorChecker)); err != nil { | ||
// panic(err) | ||
// } | ||
// | ||
// // --- Permission Check --- | ||
// // Check if adminAddr has the "access" permission on the "admin" role. | ||
// if err := manager.CheckPermission("admin", "access", adminAddr); err != nil { | ||
// println("Access denied for admin:", err) | ||
// } else { | ||
// println("Admin access granted") | ||
// } | ||
// } | ||
// | ||
// ``` | ||
// | ||
// ## Error Handling | ||
// | ||
// The package reports errors using the ufmt.Errorf function. Typical errors include: | ||
// | ||
// - Registering a role that already exists. | ||
// - Attempting to register a permission for a non-existent role. | ||
// - Verifying a permission that does not exist on a role. | ||
// - Failing a permission check due to a caller not meeting the required conditions. | ||
// | ||
// ## Limitations and Considerations | ||
// | ||
// - This RBAC implementation does not directly map addresses to roles; instead, it verifies | ||
// the caller against permission-checking functions registered for a role. | ||
// - Address validation relies on the logic provided within each PermissionChecker. Ensure that | ||
// your checkers properly validate `std.Address` values (which follow the Bech32 format). | ||
// - The encapsulated RBAC manager is designed to minimize external mutation, but integrating it | ||
// with other modules may require additional mapping between addresses and roles. | ||
// | ||
// # Notes | ||
// | ||
// - The RBAC system is designed to be upgradeable, enabling contracts to modify permission | ||
// logic without redeploying the entire contract. | ||
// - Both imperative and declarative styles are supported, providing flexibility to developers. | ||
// | ||
// Package rbac is intended for use in Gno smart contracts and other systems requiring dynamic, | ||
// upgradeable access control mechanisms. | ||
package rbac |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
module gno.land/p/gnoswap/rbac |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
package rbac | ||
|
||
import ( | ||
"std" | ||
|
||
"gno.land/p/demo/ufmt" | ||
) | ||
|
||
// RBAC encapsulates and manages toles and their permissions. | ||
type RBAC struct { | ||
// roles maps role names to their respective `Role` objects | ||
roles map[string]*Role | ||
} | ||
|
||
// New creates a new RBAC instance. | ||
func New() *RBAC { | ||
return &RBAC{ | ||
roles: make(map[string]*Role), | ||
} | ||
} | ||
|
||
func (rb *RBAC) hasRole(name string) bool { | ||
_, exists := rb.roles[name] | ||
return exists | ||
} | ||
|
||
// RegisterRole registers a role with the given role name. | ||
// Returns an error if the role already exists. | ||
func (rb *RBAC) RegisterRole(roleName string) error { | ||
if rb.hasRole(roleName) { | ||
return ufmt.Errorf("role %s already exists", roleName) | ||
} | ||
rb.roles[roleName] = NewRole(roleName) | ||
return nil | ||
} | ||
|
||
// RegisterPermission registers a permission name and checker | ||
// for the specific role. | ||
func (rb *RBAC) RegisterPermission( | ||
roleName, permissionName string, | ||
checker PermissionChecker, | ||
) error { | ||
role, exists := rb.roles[roleName] | ||
if !exists { | ||
return ufmt.Errorf("role %s does not exist", roleName) | ||
} | ||
role.AddPermission(permissionName, checker) | ||
return nil | ||
} | ||
|
||
// CheckPermission verifies if the caller has the specific permission. | ||
func (rb *RBAC) CheckPermission( | ||
roleName, permissionName string, | ||
caller std.Address, | ||
) error { | ||
role, exists := rb.roles[roleName] | ||
if !exists { | ||
return ufmt.Errorf("role %s does not exist", roleName) | ||
} | ||
checker, exists := role.permissions[permissionName] | ||
if !exists { | ||
return ufmt.Errorf("permission %s does not exist for role %s", permissionName, roleName) | ||
} | ||
return checker(caller) | ||
} | ||
|
||
// UpdatePermission updates the checker for a specific permission | ||
// in a role. | ||
func (rb *RBAC) UpdatePermission( | ||
roleName, permissionName string, | ||
newChecker PermissionChecker, | ||
) error { | ||
role, exists := rb.roles[roleName] | ||
if !exists { | ||
return ufmt.Errorf("role %s does not exist", roleName) | ||
} | ||
if !role.HasPermission(permissionName) { | ||
return ufmt.Errorf("permission %s does not exist for role %s", permissionName, roleName) | ||
} | ||
role.AddPermission(permissionName, newChecker) | ||
return nil | ||
} | ||
|
||
// RemovePermission removes a permission from a role. | ||
func (rb *RBAC) RemovePermission( | ||
roleName, permissionName string, | ||
) error { | ||
role, exists := rb.roles[roleName] | ||
if !exists { | ||
return ufmt.Errorf("cannot remove permission from non-existent role %s", roleName) | ||
} | ||
if !role.HasPermission(permissionName) { | ||
return ufmt.Errorf("permission %s does not exist for role %s", permissionName, roleName) | ||
} | ||
delete(role.permissions, permissionName) | ||
return nil | ||
} |
Oops, something went wrong.