Skip to content
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: p/demo/accesscontrol & p/demo/timelock #2307

Open
wants to merge 88 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 68 commits
Commits
Show all changes
88 commits
Select commit Hold shift + click to select a range
9ca3745
accesscontrol v1
kazai777 Jun 3, 2024
0748c45
Merge branch 'gnolang:master' into z01-timelock
kazai777 Jun 5, 2024
1569339
testt
kazai777 Jun 5, 2024
1a04741
minor modification
kazai777 Jun 6, 2024
b439489
add other check for roleData and require for gno.mod
Jun 6, 2024
e7e2a39
downgrade version
Jun 6, 2024
d723750
timelock v1 with testfile
kazai777 Jun 6, 2024
5c2e768
modidy name function in the test file
kazai777 Jun 6, 2024
ac70b14
refactor std.Emit
Jun 6, 2024
0f4968d
add createrole methods and add name of role in the struct
kazai777 Jun 7, 2024
5a30a47
unit test
mous1985 Jun 7, 2024
cc86b90
modification of name fields RoleData
mous1985 Jun 7, 2024
2f7dbcc
rebase
mous1985 Jun 7, 2024
5999935
modify testfile
kazai777 Jun 7, 2024
0528c63
last version timelock
kazai777 Jun 7, 2024
374b4f3
testCreateRole panic :caller does not have the admin role
mous1985 Jun 7, 2024
b1bf45d
fix test and add new test
kazai777 Jun 7, 2024
4414bc9
add doc in accesscontrol package
kazai777 Jun 7, 2024
6746863
add doc in the timelock file
kazai777 Jun 7, 2024
eebd5c8
make tidy
kazai777 Jun 7, 2024
d1930b0
Apply suggestions from code review
mous1985 Jun 8, 2024
5dca52f
Update examples/gno.land/p/demo/timelock/timelock_test.gno
DIGIX666 Jun 8, 2024
1e2a159
Update examples/gno.land/p/demo/timelock/timelock_test.gno
DIGIX666 Jun 8, 2024
a4ae651
Update examples/gno.land/p/demo/timelock/timelock_test.gno
DIGIX666 Jun 8, 2024
7df946a
Update examples/gno.land/p/demo/timelock/timelock_test.gno
DIGIX666 Jun 8, 2024
91a21ea
replace panic by return error
kazai777 Jun 8, 2024
84a6e30
remove GetRoleAdmin methods
kazai777 Jun 8, 2024
36102fc
modify name DONE_TIMESTAMPS by DoneTimestamp
kazai777 Jun 8, 2024
5b04454
Fix and improve access control tests
mous1985 Jun 8, 2024
dadcc5d
fix testfile
mous1985 Jun 8, 2024
4e1b98e
Change prefix of NewTimelockUtil to MustnewTimelockUtil. return Error…
mous1985 Jun 10, 2024
5364f67
Change prefix of NewTimelockUtil to MustnewTimelockUtil. return Error…
mous1985 Jun 10, 2024
9a23760
-replace panics with Error.
mous1985 Jun 10, 2024
7f6e861
add method for remove operatioj
kazai777 Jun 10, 2024
863d1c8
change TimelockUtil to TimeLockUtil
kazai777 Jun 10, 2024
59432e0
add minDelay in the struct
kazai777 Jun 10, 2024
d08fc5c
fix type compatibilty delay
mous1985 Jun 10, 2024
13d7a1c
change method to IsWaiting and IsPending
Jun 10, 2024
fca1601
change method to IsPending and just check state == Pending
Jun 12, 2024
bee7811
change IsWaiting to IsPending
Jun 12, 2024
35279e0
change IsWaiting to IsPending
Jun 12, 2024
8a7d710
comma line:74
mous1985 Jun 12, 2024
0035a82
Merge branch 'master' into z01-timelock
kazai777 Jun 13, 2024
8da6e06
replace ufmt.Errorf to errors.New
kazai777 Jun 14, 2024
dc30291
remove must prefix
kazai777 Jun 14, 2024
2b70215
change errof to panic and add return for errors.New
Jun 14, 2024
3c3bb84
change errof to panic and add return for errors.New
Jun 14, 2024
2ff3f48
correct test and gno.mod
Jun 14, 2024
d72594a
Merge branch 'master' into z01-timelock
kazai777 Jun 14, 2024
2dee17b
remove a comment et add a new line befor a import package
DIGIX666 Jun 27, 2024
30866f5
Renamed AssertOrigCallerIsAdmin to CallerIsAdmin for.
mous1985 Jun 29, 2024
5a87e5b
change the struct of roles
DIGIX666 Jun 29, 2024
c79bf43
add comment and change roledata in timelock
DIGIX666 Jun 29, 2024
d6ee9b5
delete NewRoles and chang file test timelock
DIGIX666 Jun 29, 2024
312730a
refactor: refactoring std.Emit
kazai777 Jun 29, 2024
fc9128e
Merge branch 'master' into z01-timelock
kazai777 Jun 29, 2024
923168f
Merge branch 'master' into z01-timelock
kazai777 Jul 8, 2024
6b4175f
Merge branch 'master' into z01-timelock
kazai777 Jul 9, 2024
1eee065
fix: fmt
kazai777 Jul 9, 2024
8b1e104
Update examples/gno.land/p/demo/accesscontrol/accesscontrol.gno
DIGIX666 Jul 9, 2024
f57e5a5
Update examples/gno.land/p/demo/timelock/timelock.gno
DIGIX666 Jul 9, 2024
d0f317b
change else if
DIGIX666 Jul 9, 2024
e94a0b4
add uassert
DIGIX666 Jul 9, 2024
0de057d
gno mod tidy
DIGIX666 Jul 9, 2024
f5a716f
Update examples/gno.land/p/demo/timelock/timelock.gno
DIGIX666 Jul 9, 2024
d233452
Update examples/gno.land/p/demo/accesscontrol/accesscontrol.gno
DIGIX666 Jul 9, 2024
56ba1f1
deleted parameter adminRole of func CreateRole
DIGIX666 Jul 9, 2024
ae3a3db
Merge branch 'master' into z01-timelock
kazai777 Jul 18, 2024
973cbdc
Merge branch 'gnolang:master' into z01-timelock
mous1985 Aug 20, 2024
af534a0
correction after review
DIGIX666 Aug 25, 2024
ca375b8
change name string method
DIGIX666 Aug 26, 2024
7bee06e
refactor code using ownable and modify RevokeRole, RenounceRole and G…
kazai777 Aug 26, 2024
f905404
Merge branch 'z01-timelock' of github.com:kazai777/gno into z01-timelock
kazai777 Aug 26, 2024
86af787
struct{}{}
kazai777 Aug 26, 2024
7998a78
key events defined as constants
kazai777 Aug 26, 2024
e973d18
refactor all tests and check if the role name is valid in CreateRole …
kazai777 Aug 26, 2024
877536b
fix gno.mod and timelock
kazai777 Aug 26, 2024
1cca09a
fix CI
kazai777 Aug 26, 2024
2f78427
add doc.gno and check role name in NewRole function
kazai777 Aug 26, 2024
555c084
Merge branch 'master' into z01-timelock
kazai777 Aug 26, 2024
26505de
Update timelock_test.gno
kazai777 Aug 26, 2024
1593f79
delete defer and use uasser
DIGIX666 Aug 26, 2024
cb919ec
make tidy
DIGIX666 Aug 26, 2024
97ba66c
Merge branch 'gnolang:master' into z01-timelock
mous1985 Oct 19, 2024
fd49b32
Update examples/gno.land/p/demo/accesscontrol/doc.gno
DIGIX666 Oct 31, 2024
000c615
Merge branch 'master' into z01-timelock
DIGIX666 Oct 31, 2024
3b4c63a
change event and modify the doc
DIGIX666 Nov 1, 2024
0ced75a
Change TimelockUtil to Timelock
DIGIX666 Nov 7, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
155 changes: 155 additions & 0 deletions examples/gno.land/p/demo/accesscontrol/accesscontrol.gno
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we make a case for how this is different / better than p/demo/acl?

I'm not saying it's perfect, just that demo/ should probably contain one preferred ACL implementation. We can decide to move this one to p/<name>/accesscontrol, or that one to p/nt/acl. (cc'ing also @moul for an opinion on what to do here)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Although acl and accesscontrol may seem similar at first glance, accesscontrol stands out due to its ability to implement role hierarchies as well as dynamic permission options

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you give an example? Namely, of where this distinction is useful?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see we're using std.PrevRealm() to determine the owner and generally the "sender". This assumes there's an admin user doing management, and everyone else just following suit.

However, I think an equally possible flow is that of having a realm which has an access control list. In this case, actually, we shouldn't do any checks on PrevRealm(); the realm can just use it unexported. But I suggest you have an option for the ACL to not have a "owner"; in which case the PrevRealm checks are simply not performed. Allows someone else to compose other rules on top as well.

Btw if Roles is meant to be exposed in a realm, then its fields should be unexported.

Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
// import "gno.land/p/demo/accesscontrol"
//
// Create a new role with a specific admin.
// adminAddr := std.Address("admin-address")
// role := accesscontrol.NewRole("ExampleRole", adminRole)
//
// Check if an account has a specific role.
// account := std.Address("user-address")
// hasRole := role.HasRole(account)
//
// Grant a role to a specific account.
// role.GrantRole(account)
//
// Revoke a role from a specific account.
// role.RevokeRole(account)
//
// Renounce a role with caller confirmation.
// role.RenounceRole(std.GetOrigCaller())
//
// Change the admin role for a specific role.
// newAdmin := std.Address("new-admin-address")
// role.SetRoleAdmin(newAdmin)
package accesscontrol

import (
"errors"
kazai777 marked this conversation as resolved.
Show resolved Hide resolved
"std"

"gno.land/p/demo/avl"
)

var ErrUnauthorized = errors.New("unauthorized; caller is not admin")

// Role struct to store role information
type Role struct {
Name string
Holders *avl.Tree // -> std.Address -> bool
kazai777 marked this conversation as resolved.
Show resolved Hide resolved
AdminRole std.Address
kazai777 marked this conversation as resolved.
Show resolved Hide resolved
}

type Roles struct {
AllRoles []*Role
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
AllRoles []*Role
Roles []*Role
UserToRoles avl.Tree // std.Address -> []*Role

A suggestion. You'd then need to add the code to manage UserToRoles, but I think we should support being able to map a user to the roles they have.

(Of course, there should be a related function to modify it.)

Admin std.Address
kazai777 marked this conversation as resolved.
Show resolved Hide resolved
}

// NewRole creates a new instance of Role
func NewRole(name string, adminRole std.Address) *Role {
return &Role{
Name: name,
Holders: avl.NewTree(),
AdminRole: adminRole,
}
}

// Method to check if the caller has the admin role and return an error
kazai777 marked this conversation as resolved.
Show resolved Hide resolved
func (r *Role) CallerIsAdmin() error {
caller := std.PrevRealm().Addr()

if r.AdminRole != caller {
return ErrUnauthorized
}

return nil
}

// Method to assert if the caller has the admin role, panics if not
func (r *Role) AssertCallerIsAdmin() {
if err := r.CallerIsAdmin(); err != nil {
panic(err)
kazai777 marked this conversation as resolved.
Show resolved Hide resolved
}
}

// Method to create a new role within the realm
func (rs *Roles) CreateRole(name string) *Role {
if rs.Admin != std.PrevRealm().Addr() {
panic("accesscontrol: caller does not have the global admin role")
}

role := NewRole(name, rs.Admin)
rs.AllRoles = append(rs.AllRoles, role)

std.Emit(
"RoleCreated",
"roleName", name,
"sender", rs.Admin.String(),
)

return role
}

// Method to check if an account has a specific role
func (r *Role) HasRole(account std.Address) bool {
kazai777 marked this conversation as resolved.
Show resolved Hide resolved
return r.Holders.Has(account.String())
}

// Method to grant a role to an account
func (r *Role) GrantRole(account std.Address) {
r.AssertCallerIsAdmin()
r.Holders.Set(account.String(), true)

std.Emit(
"RoleGranted",
"roleName", r.Name,
"account", account.String(),
"sender", std.PrevRealm().Addr().String(),
)
}

// Method to revoke a role from an account
func (r *Role) RevokeRole(account std.Address) {
r.AssertCallerIsAdmin()
r.Holders.Remove(account.String())

std.Emit(
"RoleRevoked",
"roleName", r.Name,
"account", account.String(),
"sender", std.PrevRealm().Addr().String(),
)
}

// Method to renounce a role with caller confirmation
func (r *Role) RenounceRole(callerConfirmation std.Address) error {
caller := std.PrevRealm().Addr()

if callerConfirmation != caller {
return errors.New("accesscontrol: caller confirmation does not match account")
}

r.Holders.Remove(caller.String())

std.Emit(
"RoleRenounced",
"roleName", r.Name,
kazai777 marked this conversation as resolved.
Show resolved Hide resolved
"account", caller.String(),
"sender", caller.String(),
)

return nil
}

// Method to set the admin role for a specific role
kazai777 marked this conversation as resolved.
Show resolved Hide resolved
func (r *Role) SetRoleAdmin(adminRole std.Address) {
r.AssertCallerIsAdmin()

previousAdminRole := r.AdminRole
r.AdminRole = adminRole

std.Emit(
"RoleSet",
"roleName", r.Name,
"previousAdminRole", previousAdminRole.String(),
"newAdminRole", adminRole.String(),
)
}
228 changes: 228 additions & 0 deletions examples/gno.land/p/demo/accesscontrol/accesscontrol_test.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
package accesscontrol

import (
"std"
"testing"

"gno.land/p/demo/testutils"
"gno.land/p/demo/uassert"
)

// TestAccessControl verifies the access control functionality.
func TestAccessControl(t *testing.T) {
admin := testutils.TestAddress("admin")
user1 := testutils.TestAddress("user1")
user2 := testutils.TestAddress("user2")

// Create new RoleData
roleData := NewRole("admin", admin)

// Check initial admin role
if roleData.AdminRole != admin {
t.Fatalf("expected admin role to be %s, got %s", admin.String(), roleData.AdminRole.String())
}
kazai777 marked this conversation as resolved.
Show resolved Hide resolved
!uassert.Equal(t, roleData.AdminRole, admin)
kazai777 marked this conversation as resolved.
Show resolved Hide resolved

// Grant role to user1
std.TestSetOrigCaller(admin)
roleData.GrantRole(user1)
if !roleData.HasRole(user1) {
kazai777 marked this conversation as resolved.
Show resolved Hide resolved
t.Fatalf("expected user1 to have role")
}

// Check that user2 does not have the role
if roleData.HasRole(user2) {
kazai777 marked this conversation as resolved.
Show resolved Hide resolved
t.Fatalf("expected user2 not to have role")
}

// Revoke role from user1
roleData.RevokeRole(user1)
if roleData.HasRole(user1) {
t.Fatalf("expected user1 not to have role after revocation")
}

// Grant role to user1 again
roleData.GrantRole(user1)

// User1 renounces the role
std.TestSetOrigCaller(user1)
roleData.RenounceRole(user1)
if roleData.HasRole(user1) {
t.Fatalf("expected user1 not to have role after renouncing")
}

// Change admin role to user2
std.TestSetOrigCaller(admin)
roleData.SetRoleAdmin(user2)
if roleData.AdminRole != user2 {
t.Fatalf("expected admin role to be %s, got %s", user2.String(), roleData.AdminRole.String())
}

// User1 (now not admin) tries to grant role to user2, should panic
std.TestSetOrigCaller(user1)
defer func() {
if r := recover(); r == nil {
t.Fatalf("expected panic when non-admin tries to grant role")
}
}()
roleData.GrantRole(user2)
kazai777 marked this conversation as resolved.
Show resolved Hide resolved
}

// TestCreateRole tests the CreateRole method of the RoleData struct
func TestCreateRole(t *testing.T) {
// Simulate the administrator as the current caller
admin := testutils.TestAddress("admin")
std.TestSetOrigCaller(admin)

// Create roles with the administrator
roles := &Roles{Admin: admin}

// Create a new role with a new administrator address
newRoleName := "newRole"
newRole := roles.CreateRole(newRoleName)

// Check that the new role has been created correctly
if newRole.Name != newRoleName {
t.Fatalf("expected new role name to be '%s', got '%s'", newRoleName, newRole.Name)
}
if newRole.AdminRole != admin {
t.Fatalf("expected new role admin role to be %s, got %s", admin.String(), newRole.AdminRole.String())
}

// Simulate newAdmin as the current caller
std.TestSetOrigCaller(admin)

// Explicitly add the role to the new administrator to check functionality
newRole.GrantRole(admin)

// Check if the new role has been added to the holder
if !newRole.HasRole(admin) {
t.Fatalf("expected new role to be added to the holder")
}
}

// TestCallerIsAdmin verifies the CallerIsAdmin method.
func TestCallerIsAdmin(t *testing.T) {
adminRole := testutils.TestAddress("admin-address")
roleData := NewRole("ExampleRole", adminRole)
// Call CallerIsAdmin with admin caller
std.TestSetOrigCaller(adminRole)
defer func() {
kazai777 marked this conversation as resolved.
Show resolved Hide resolved
if r := recover(); r != nil {
t.Fatalf("expected no panic, got %v", r)
}
}()
roleData.AssertCallerIsAdmin()
// Call CallerIsAdmin with non-admin caller
user := testutils.TestAddress("user-address")
std.TestSetOrigCaller(user)
defer func() {
if r := recover(); r == nil {
t.Fatalf("expected panic, got nil")
}
}()
roleData.AssertCallerIsAdmin()
}

// Testing the RevokeRole Method for a Non-Admin
func TestRevokeRoleNonAdmin(t *testing.T) {
admin := testutils.TestAddress("admin")
user1 := testutils.TestAddress("user1")
user2 := testutils.TestAddress("user2")

// Create role data with the administrator
roleData := NewRole("admin", admin)

// Grant role to user1
std.TestSetOrigCaller(admin)
roleData.GrantRole(user1)
if !roleData.HasRole(user1) {
t.Fatalf("expected user1 to have role")
}

// Simulate user2 as the current caller
std.TestSetOrigCaller(user2)

// Attempting to revoke user1's role as user2 (non-admin)
defer func() {
if r := recover(); r == nil {
t.Fatalf("expected panic when non-admin tries to revoke role")
}
}()
roleData.RevokeRole(user1)
}

// Testing the RenounceRole method with Invalid Confirmation
func TestRenounceRole(t *testing.T) {
adminRole := testutils.TestAddress("admin-address")
roleData := NewRole("ExampleRole", adminRole)
account := testutils.TestAddress("user-address")
// Simulate the administrator as the current caller
std.TestSetOrigCaller(adminRole)
// Grant role to the account
roleData.GrantRole(account)
// Simulate the account as the current caller
std.TestSetOrigCaller(account)
// Renounce the role
err := roleData.RenounceRole(account)
kazai777 marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
// Check if the account still has the role
hasRole := roleData.HasRole(account)
if hasRole {
t.Fatalf("expected account not to have role after renouncing")
}
}

// Testing the SetRoleAdmin method with a New Administrator Address
func TestSetRoleAdmin(t *testing.T) {
admin := testutils.TestAddress("admin")
newAdmin := testutils.TestAddress("newAdmin")
user := testutils.TestAddress("user")

// Create role data with the administrator
roleData := NewRole("admin", admin)

// Check that the initial administrator is correct
if roleData.AdminRole != admin {
t.Fatalf("expected initial admin to be %s, got %s", admin.String(), roleData.AdminRole.String())
}

// Simulate admin as current caller
std.TestSetOrigCaller(admin)

// Change administrator
roleData.SetRoleAdmin(newAdmin)

// Check that the new administrator is correct
if roleData.AdminRole != newAdmin {
t.Fatalf("expected new admin to be %s, got %s", newAdmin.String(), roleData.AdminRole.String())
}

// Simulate newAdmin as the current caller
std.TestSetOrigCaller(newAdmin)
defer func() {
if r := recover(); r != nil {
t.Fatalf("expected no panic, got %v", r)
}
}()
roleData.AssertCallerIsAdmin()

// Add a role to a user
roleData.GrantRole(user)
if !roleData.HasRole(user) {
t.Fatalf("expected user to have role")
}

// Simulate initial admin as current caller
std.TestSetOrigCaller(admin)

// Attempting to revoke a user's role by the former administrator should cause panic
defer func() {
if r := recover(); r == nil {
t.Fatalf("expected panic when former admin tries to revoke role")
}
}()
roleData.RevokeRole(user)
}
7 changes: 7 additions & 0 deletions examples/gno.land/p/demo/accesscontrol/gno.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module gno.land/p/demo/accesscontrol

require (
gno.land/p/demo/avl v0.0.0-latest
gno.land/p/demo/testutils v0.0.0-latest
gno.land/p/demo/uassert v0.0.0-latest
)
Loading
Loading