From 261e3167071afc00cc6fe22b45d928d3fc5a810c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jer=C3=B3nimo=20Albi?= Date: Mon, 23 Dec 2024 16:42:32 +0100 Subject: [PATCH] feat: permissions support for individual boards (#3386) This is an initial implementation to unblock features that depend on board permissions. --- examples/gno.land/r/demo/boards2/board.gno | 22 ++++++++++++ examples/gno.land/r/demo/boards2/boards.gno | 13 +++++-- .../gno.land/r/demo/boards2/permission.gno | 4 +-- .../r/demo/boards2/permission_default.gno | 13 +++---- .../demo/boards2/permission_default_test.gno | 2 +- .../r/demo/boards2/permission_handlers.gno | 4 +-- examples/gno.land/r/demo/boards2/public.gno | 36 ++++++------------- 7 files changed, 52 insertions(+), 42 deletions(-) diff --git a/examples/gno.land/r/demo/boards2/board.gno b/examples/gno.land/r/demo/boards2/board.gno index 088500cb4a5..1920cd41225 100644 --- a/examples/gno.land/r/demo/boards2/board.gno +++ b/examples/gno.land/r/demo/boards2/board.gno @@ -8,6 +8,8 @@ import ( "gno.land/p/demo/avl" "gno.land/p/moul/txlink" + + "gno.land/p/demo/boards2/admindao" ) type BoardID uint64 @@ -28,6 +30,7 @@ type Board struct { postsCtr uint64 // increments Post.id createdAt time.Time deleted avl.Tree // TODO reserved for fast-delete. + perms Permissions } func newBoard(id BoardID, name string, creator std.Address) *Board { @@ -38,6 +41,7 @@ func newBoard(id BoardID, name string, creator std.Address) *Board { threads: avl.Tree{}, createdAt: time.Now(), deleted: avl.Tree{}, + perms: createDefaultBoardPermissions(creator), } } @@ -64,6 +68,10 @@ func (board *Board) GetURL() string { return strings.TrimPrefix(std.CurrentRealm().PkgPath(), "gno.land") + ":" + board.name } +func (board *Board) GetPermissions() Permissions { + return board.perms +} + func (board *Board) GetThread(threadID PostID) (_ *Post, found bool) { v, found := board.threads.Get(threadID.Key()) if !found { @@ -116,3 +124,17 @@ func (board *Board) GetURLFromReplyID(threadID, replyID PostID) string { func (board *Board) GetPostFormURL() string { return txlink.Call("CreateThread", "bid", board.id.String()) } + +// TODO: This is a temporary implementation until the permissions and DAO mecahnics are defined +func createDefaultBoardPermissions(owner std.Address) *DefaultPermissions { + perms := NewDefaultPermissions( + admindao.New(admindao.WithMember(owner)), + WithSuperRole(RoleOwner), + WithRole(RoleAdmin, PermissionMemberInvite), + // TODO: Finish assigning all roles and permissions + // WithRole(RoleModerator, permissions...), + WithUser(owner, RoleOwner), + ) + perms.HandleFunc(PermissionMemberInvite, handleMemberInvite) + return perms +} diff --git a/examples/gno.land/r/demo/boards2/boards.gno b/examples/gno.land/r/demo/boards2/boards.gno index 53072e37542..70e02d5dffe 100644 --- a/examples/gno.land/r/demo/boards2/boards.gno +++ b/examples/gno.land/r/demo/boards2/boards.gno @@ -1,17 +1,24 @@ package boards2 -import "gno.land/p/demo/avl" +import ( + "std" + + "gno.land/p/demo/avl" +) var ( - gPerm Permissioner // TODO: Support changing the permissioner + gPerm Permissions // TODO: Support assigning a different implementation gLastBoardID BoardID gBoardsByID avl.Tree // string(id) -> *Board gBoardsByName avl.Tree // string(name) -> *Board ) func init() { + // TODO: Define and change the default realm owner (or owners) + owner := std.Address("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5") // @test1 + // Initialize the default realm permissions - gPerm = createDefaultPermissions() + gPerm = createDefaultPermissions(owner) } // incGetBoardID returns a new board ID. diff --git a/examples/gno.land/r/demo/boards2/permission.gno b/examples/gno.land/r/demo/boards2/permission.gno index 0fc5dc9515d..d287b4f8e8b 100644 --- a/examples/gno.land/r/demo/boards2/permission.gno +++ b/examples/gno.land/r/demo/boards2/permission.gno @@ -29,8 +29,8 @@ type ( // Args is a list of generic arguments. Args []interface{} - // Permissioner define an interface to for permissioned execution. - Permissioner interface { + // Permissions define an interface to for permissioned execution. + Permissions interface { // HasRole checks if a user has a specific role assigned. HasRole(std.Address, Role) bool diff --git a/examples/gno.land/r/demo/boards2/permission_default.gno b/examples/gno.land/r/demo/boards2/permission_default.gno index fc8efee14d2..b4558f82bcc 100644 --- a/examples/gno.land/r/demo/boards2/permission_default.gno +++ b/examples/gno.land/r/demo/boards2/permission_default.gno @@ -12,7 +12,7 @@ type ( // PermissionsHandlerFunc defines a function to handle permission callbacks. // Handlers are called by the `WithPermission()` method to execute callbacks // when users have the permission assigned. - PermissionsHandlerFunc func(Permissioner, Args, func(Args)) + PermissionsHandlerFunc func(Permissions, Args, func(Args)) // DefaultPermissions manages users, roles and permissions. DefaultPermissions struct { @@ -147,23 +147,18 @@ func (dp *DefaultPermissions) WithPermission(user std.Address, perm Permission, fn(dp, args, cb) } -func createDefaultPermissions() *DefaultPermissions { - // TODO: Define and change the default realm owner (or owners) - owner := std.Address("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5") // @test1 +func createDefaultPermissions(owner std.Address) *DefaultPermissions { // TODO: DAO should be a different realm or proposal and voting functions should be part of boards realm? - // Permissions and DAO mechanics should be discussed and improved. Add `GetDAO()` to `Permissioner`?? - dao := admindao.New(admindao.WithMember(owner)) + // Permissions and DAO mechanics should be discussed and improved. Add `GetDAO()` to `Permissions`?? perms := NewDefaultPermissions( - dao, + admindao.New(admindao.WithMember(owner)), WithSuperRole(RoleOwner), WithRole(RoleAdmin, PermissionBoardCreate, PermissionMemberInvite), // TODO: Finish assigning all roles and permissions // WithRole(RoleModerator, permissions...), WithUser(owner, RoleOwner), ) - perms.HandleFunc(PermissionBoardCreate, handleBoardCreate) perms.HandleFunc(PermissionMemberInvite, handleMemberInvite) - return perms } diff --git a/examples/gno.land/r/demo/boards2/permission_default_test.gno b/examples/gno.land/r/demo/boards2/permission_default_test.gno index bf64018c11a..a3c51fb6abe 100644 --- a/examples/gno.land/r/demo/boards2/permission_default_test.gno +++ b/examples/gno.land/r/demo/boards2/permission_default_test.gno @@ -9,7 +9,7 @@ import ( "gno.land/p/demo/urequire" ) -var _ Permissioner = (*DefaultPermissions)(nil) +var _ Permissions = (*DefaultPermissions)(nil) func TestNewDefaultPermissions(t *testing.T) { roles := []Role{"a", "b"} diff --git a/examples/gno.land/r/demo/boards2/permission_handlers.gno b/examples/gno.land/r/demo/boards2/permission_handlers.gno index 9c6a24b90f0..63b351f1d53 100644 --- a/examples/gno.land/r/demo/boards2/permission_handlers.gno +++ b/examples/gno.land/r/demo/boards2/permission_handlers.gno @@ -6,7 +6,7 @@ import ( "gno.land/r/demo/users" ) -func handleBoardCreate(_ Permissioner, args Args, cb func(Args)) { +func handleBoardCreate(_ Permissions, args Args, cb func(Args)) { // TODO: This way of dealing with arguments is delicate, ideally types should be used name := args[0].(string) if std.Address(name).IsValid() { @@ -24,7 +24,7 @@ func handleBoardCreate(_ Permissioner, args Args, cb func(Args)) { cb(args) } -func handleMemberInvite(p Permissioner, args Args, cb func(Args)) { +func handleMemberInvite(p Permissions, args Args, cb func(Args)) { // Make sure that only owners invite other owners role := args[1].(Role) if role == RoleOwner { diff --git a/examples/gno.land/r/demo/boards2/public.gno b/examples/gno.land/r/demo/boards2/public.gno index 9820c45499f..083f8daff65 100644 --- a/examples/gno.land/r/demo/boards2/public.gno +++ b/examples/gno.land/r/demo/boards2/public.gno @@ -24,14 +24,11 @@ func CreateBoard(name string) BoardID { caller := std.GetOrigCaller() id := incGetBoardID() args := Args{name, id} - gPerm.WithPermission(caller, PermissionBoardCreate, args, func(a Args) { - // TODO: Do the callback really need the args or we could have the same result directly referencing? - name := a[0].(string) + gPerm.WithPermission(caller, PermissionBoardCreate, args, func(Args) { if gBoardsByName.Has(name) { panic("board already exists") } - id := a[1].(BoardID) board := newBoard(id, name, caller) gBoardsByID.Set(id.Key(), board) gBoardsByName.Set(name, board) @@ -106,11 +103,8 @@ func DeleteThread(bid BoardID, threadID PostID) { caller := std.GetOrigCaller() args := Args{bid, threadID} - gPerm.WithPermission(caller, PermissionThreadDelete, args, func(a Args) { - bid := a[0].(BoardID) + gPerm.WithPermission(caller, PermissionThreadDelete, args, func(Args) { board := mustGetBoard(bid) - - threadID := a[1].(PostID) board.DeleteThread(threadID) }) } @@ -128,13 +122,8 @@ func DeleteReply(bid BoardID, threadID, replyID PostID) { caller := std.GetOrigCaller() args := Args{bid, threadID, replyID} gPerm.WithPermission(caller, PermissionReplyDelete, args, func(a Args) { - bid := a[0].(BoardID) board := mustGetBoard(bid) - - threadID := a[1].(PostID) thread := mustGetThread(board, threadID) - - replyID := a[2].(PostID) thread.DeleteReply(replyID) }) } @@ -147,15 +136,9 @@ func EditThread(bid BoardID, threadID PostID, title, body string) { caller := std.GetOrigCaller() args := Args{bid, threadID, title, body} - gPerm.WithPermission(caller, PermissionThreadEdit, args, func(a Args) { - bid := a[0].(BoardID) + gPerm.WithPermission(caller, PermissionThreadEdit, args, func(Args) { board := mustGetBoard(bid) - - threadID := a[1].(PostID) thread := mustGetThread(board, threadID) - - title := a[2].(string) - body := a[3].(string) thread.Update(title, body) }) } @@ -180,9 +163,7 @@ func InviteMember(user std.Address, role Role) { caller := std.GetOrigCaller() args := Args{user, role} - gPerm.WithPermission(caller, PermissionMemberInvite, args, func(a Args) { - user := a[0].(std.Address) - role := a[1].(Role) + gPerm.WithPermission(caller, PermissionMemberInvite, args, func(Args) { if err := gPerm.AddUser(user, role); err != nil { panic(err) } @@ -193,8 +174,7 @@ func RemoveMember(user std.Address) { assertIsUserCall() caller := std.GetOrigCaller() - gPerm.WithPermission(caller, PermissionMemberRemove, Args{user}, func(a Args) { - user := a[0].(std.Address) + gPerm.WithPermission(caller, PermissionMemberRemove, Args{user}, func(Args) { if !gPerm.RemoveUser(user) { panic("member not found") } @@ -213,6 +193,12 @@ func assertHasPermission(user std.Address, p Permission) { } } +func assertHasBoardPermission(b *Board, user std.Address, p Permission) { + if !b.perms.HasPermission(user, p) { + panic("unauthorized") + } +} + func assertBoardExists(id BoardID) { if _, found := getBoard(id); !found { panic("board not found: " + id.String())