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 105 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 84 commits
Commits
Show all changes
105 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
c5f5604
Merge branch 'master' into z01-timelock
DIGIX666 Nov 12, 2024
7dd9256
change HasRole to HasAccount
DIGIX666 Nov 12, 2024
ce643c4
call admin before
DIGIX666 Nov 12, 2024
8fe3820
add UserToRoles in Roles struct
DIGIX666 Nov 13, 2024
13f8de4
Merge branch 'master' into z01-timelock
DIGIX666 Nov 14, 2024
de86c45
try fix CI codecov
DIGIX666 Nov 14, 2024
3f99de7
reset the last code
DIGIX666 Nov 14, 2024
a667cd9
Merge branch 'master' into z01-timelock
DIGIX666 Nov 14, 2024
f3b01fd
Merge branch 'master' into z01-timelock
thehowl Feb 20, 2025
982b1c7
add pull_request_target for codecov
DIGIX666 Nov 14, 2024
88a3500
wrong modif
DIGIX666 Feb 20, 2025
3811e19
change with the std package in accescontrol
DIGIX666 Feb 20, 2025
b142277
change std in timelock too
DIGIX666 Feb 20, 2025
b74e3da
run make generate
DIGIX666 Feb 20, 2025
26622eb
make tidy
DIGIX666 Feb 20, 2025
d72de48
Merge branch 'master' into z01-timelock
DIGIX666 Feb 20, 2025
78a99ab
Merge branch 'master' into z01-timelock
DIGIX666 Feb 22, 2025
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
185 changes: 185 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
Contributor

Choose a reason for hiding this comment

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

Imagine a user can belong to several groups with different permissions. With an ACL system, each permission has to be checked individually for the user, which can become complex depending on the number of users in the group. In Accesscontrol lets you manage access via hierarchical roles (e.g. Admin, Manager, Employee): each role has specific permissions automatically applied to all its members. This simplifies authorization management and makes the system more flexible, especially for large groups

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,185 @@
package accesscontrol

import (
"std"

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

const (
RoleName = "roleName"
Sender = "sender"
Account = "account"
NewAdminRole = "newAdminRole"
)

// Role struct to store role information
type Role struct {
Name string
Holders *avl.Tree // std.Address -> struct{}
Ownable *ownable.Ownable
}

// Roles struct to store all Roles information
type Roles struct {
AllRoles []*Role
Ownable *ownable.Ownable
}

func validRoleName(name string) error {
if len(name) > 30 || name == "" {
Copy link
Member

Choose a reason for hiding this comment

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

The limit on 30 seems kind of arbitrary for a package; more like a kind of validation that should be done, if anything, on the side of a realm (as an end-user application). But I don't expect many realms to publicly allow adding roles, anyway.

Copy link
Contributor

Choose a reason for hiding this comment

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

the limit of 30 is intended to prevent someone from too overloading the chaine, for example by setting it to 1M characters. Do you think it's better to set a higher maximum or to let the user do it himself, even if this leaves open the possibility of defining a large number of characters?

return ErrNameRole
}

return nil
}

// NewRole creates a new instance of Role
func NewRole(name string, admin std.Address) (*Role, error) {
if err := validRoleName(name); err != nil {
return nil, err
}

return &Role{
Name: name,
Holders: avl.NewTree(),
Ownable: ownable.NewWithAddress(admin),
}, nil
}

// CreateRole create a new role within the realm
func (rs *Roles) CreateRole(name string) (*Role, error) {
if err := validRoleName(name); err != nil {
return nil, ErrNameRole
}

if err := rs.Ownable.CallerIsOwner(); err != nil {
return nil, err
}

for _, role := range rs.AllRoles {
if role.Name == name {
return nil, ErrRoleSameName
}
}

role, err := NewRole(name, rs.Ownable.Owner())
if err != nil {
return nil, err
}

rs.AllRoles = append(rs.AllRoles, role)

std.Emit(
"RoleCreated",
RoleName, name,
Sender, rs.Ownable.Owner().String(),
)

return role, nil
}

// HasRole check if an account has a specific role
func (r *Role) HasRole(account std.Address) bool {
return r.Holders.Has(account.String())
}

// FindRole searches for a role by its name
func (rs *Roles) FindRole(name string) (*Role, error) {
for _, role := range rs.AllRoles {
if role.Name == name {
return role, nil
}
}

return nil, ErrRoleNotFound
}

// GrantRole grants a role to an account
func (rs *Roles) GrantRole(name string, account std.Address) error {
r, err := rs.FindRole(name)
if err != nil {
return ErrRoleNotFound
}

err = r.Ownable.CallerIsOwner()
if err != nil {
return err
}

r.Holders.Set(account.String(), struct{}{})

std.Emit(
"RoleGranted",
RoleName, r.Name,
Account, account.String(),
Sender, std.PrevRealm().Addr().String(),
)

return nil
}

// RevokeRole revokes a role from an account
func (rs *Roles) RevokeRole(name string, account std.Address) error {
r, err := rs.FindRole(name)
if err != nil {
return ErrRoleNotFound
}

err = r.Ownable.CallerIsOwner()
if err != nil {
return err
}

r.Holders.Remove(account.String())

std.Emit(
"RoleRevoked",
RoleName, r.Name,
Account, account.String(),
Sender, std.PrevRealm().Addr().String(),
)

return nil
}

// RenounceRole allows an account to renounce a role it holds
func (rs *Roles) RenounceRole(name string) error {
Comment on lines +166 to +167
Copy link
Member

Choose a reason for hiding this comment

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

I would say we remove this.

  • Roles is not likely to be publicly exposed in a realm, anyway.
  • But if it was, exposing RenounceRole means that the Roles cannot be used for a role like banned; because the user can "renounce it".

Copy link
Contributor

Choose a reason for hiding this comment

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

I know what you mean, it's true that I didn't take the case of the banned role. So it's not really safe to expose RenounceRole
Do you think it might be a good idea to keep RenounceRole but modify it for Roles defined as non-critical?
example: TestUser, TemporyUser, Guest...

r, err := rs.FindRole(name)
if err != nil {
return ErrRoleNotFound
}

caller := std.PrevRealm().Addr()

if !r.HasRole(caller) {
return ErrAccountNotRole
}

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

std.Emit(
"RoleRenounced",
RoleName, r.Name,
Account, caller.String(),
Sender, caller.String(),
)

return nil
}

// SetRoleAdmin transfers the ownership of the role to a new administrator
func (r *Role) SetRoleAdmin(admin std.Address) error {
if err := r.Ownable.TransferOwnership(admin); err != nil {
return err
}

std.Emit(
"RoleSet",
RoleName, r.Name,
NewAdminRole, r.Ownable.Owner().String(),
)

return nil
}
192 changes: 192 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,192 @@
package accesscontrol

import (
"std"
"testing"

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

var (
admin = testutils.TestAddress("admin1")
newAdmin = testutils.TestAddress("admin2")
user1 = testutils.TestAddress("user1")
user2 = testutils.TestAddress("user2")

roleName = "TestRole"
)

func initSetup(admin std.Address) *Roles {
return &Roles{
AllRoles: []*Role{},
Ownable: ownable.NewWithAddress(admin),
}
}

func TestCreateRole(t *testing.T) {
roles := initSetup(admin)

std.TestSetOrigCaller(admin)

role, err := roles.CreateRole(roleName)
uassert.NoError(t, err)
uassert.True(t, role != nil, "role should not be nil")
uassert.Equal(t, role.Name, roleName)

_, err = roles.CreateRole(roleName)
uassert.Error(t, err, "should fail on duplicate role creation")
}

func TestGrantRole(t *testing.T) {
roles := initSetup(admin)

_, err := roles.CreateRole(roleName)
uassert.NoError(t, err)

err = roles.GrantRole(roleName, user1)
uassert.NoError(t, err)

role, err := roles.FindRole(roleName)
uassert.NoError(t, err)
uassert.True(t, role.HasRole(user1), "user1 should have the TestRole")
}

func TestGrantRoleByNonOwner(t *testing.T) {
roles := initSetup(admin)

_, err := roles.CreateRole(roleName)
uassert.NoError(t, err)

std.TestSetOrigCaller(user2)
roles.Ownable.TransferOwnership(user2)
err = roles.GrantRole(roleName, user1)
uassert.Error(t, err, "non-owner should not be able to grant roles")

roles.Ownable.TransferOwnership(admin)
}

func TestRevokeRole(t *testing.T) {
roles := initSetup(admin)

std.TestSetOrigCaller(admin)

_, err := roles.CreateRole(roleName)
uassert.NoError(t, err)
err = roles.GrantRole(roleName, user1)
uassert.NoError(t, err)

err = roles.RevokeRole(roleName, user1)
uassert.NoError(t, err)

role, err := roles.FindRole(roleName)
uassert.NoError(t, err)
uassert.False(t, role.HasRole(user1), "user1 should no longer have the TestRole")
}

func TestRenounceRole(t *testing.T) {
roles := initSetup(admin)

std.TestSetOrigCaller(admin)

_, err := roles.CreateRole(roleName)
uassert.NoError(t, err)
err = roles.GrantRole(roleName, user1)
uassert.NoError(t, err)

roles.Ownable.TransferOwnership(user1)
std.TestSetOrigCaller(user1)
err = roles.RenounceRole(roleName)
uassert.NoError(t, err)

role, err := roles.FindRole(roleName)
uassert.NoError(t, err)
uassert.False(t, role.HasRole(user1), "user1 should have renounced the TestRole")
}

func TestSetRoleAdmin(t *testing.T) {
roles := initSetup(admin)

std.TestSetOrigCaller(admin)

role, err := roles.CreateRole(roleName)
uassert.NoError(t, err)

err = role.SetRoleAdmin(newAdmin)
uassert.NoError(t, err, "admin change should succeed")

std.TestSetOrigCaller(newAdmin)
uassert.Equal(t, role.Ownable.Owner(), newAdmin, "the new admin should be newAdmin")

std.TestSetOrigCaller(admin)
uassert.NotEqual(t, role.Ownable.Owner(), admin, "the old admin should no longer be the owner")
}

func TestCreateRoleInvalidName(t *testing.T) {
roles := initSetup(admin)

std.TestSetOrigCaller(admin)

_, err := roles.CreateRole("")
uassert.Error(t, err, "should fail on empty role name")

longRoleName := "thisisaverylongrolenamethatexceedsthenormallimitfortestingpurposes"
_, err = roles.CreateRole(longRoleName)
uassert.Error(t, err, "should fail on very long role name")
}

func TestRevokeRoleByNonOwner(t *testing.T) {
roles := initSetup(admin)

std.TestSetOrigCaller(admin)

_, err := roles.CreateRole(roleName)
uassert.NoError(t, err)
err = roles.GrantRole(roleName, user1)
uassert.NoError(t, err)

std.TestSetOrigCaller(user2)
err = roles.RevokeRole(roleName, user1)
uassert.Error(t, err, "non-owner should not be able to revoke roles")
}

func TestGrantRoleToNonExistentRole(t *testing.T) {
roles := initSetup(admin)

std.TestSetOrigCaller(admin)

err := roles.GrantRole("NonExistentRole", user1)
uassert.Error(t, err, "should fail when granting non-existent role")
}

func TestRevokeRoleFromNonExistentRole(t *testing.T) {
roles := initSetup(admin)

std.TestSetOrigCaller(admin)

err := roles.RevokeRole("NonExistentRole", user1)
uassert.Error(t, err, "should fail when revoking non-existent role")
}

func TestRenounceNonExistentRole(t *testing.T) {
roles := initSetup(admin)

std.TestSetOrigCaller(user1)

err := roles.RenounceRole("NonExistentRole")
uassert.Error(t, err, "should fail when renouncing non-existent role")
}

func TestDeleteRole(t *testing.T) {
roles := initSetup(admin)

std.TestSetOrigCaller(admin)

role, err := roles.CreateRole(roleName)
uassert.NoError(t, err)

roles.AllRoles = []*Role{} // Clear roles for testing purpose
_, err = roles.FindRole(roleName)
uassert.Error(t, err, "should fail when trying to find deleted role")
}
Loading
Loading