Skip to content

Commit

Permalink
feat: permissions support for individual boards (gnolang#3386)
Browse files Browse the repository at this point in the history
This is an initial implementation to unblock features that depend on
board permissions.
  • Loading branch information
jeronimoalbi authored and x1unix committed Jan 6, 2025
1 parent 46dc59b commit e5f7f08
Show file tree
Hide file tree
Showing 7 changed files with 52 additions and 42 deletions.
22 changes: 22 additions & 0 deletions examples/gno.land/r/demo/boards2/board.gno
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import (

"gno.land/p/demo/avl"
"gno.land/p/moul/txlink"

"gno.land/p/demo/boards2/admindao"
)

type BoardID uint64
Expand All @@ -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 {
Expand All @@ -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),
}
}

Expand All @@ -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 {
Expand Down Expand Up @@ -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
}
13 changes: 10 additions & 3 deletions examples/gno.land/r/demo/boards2/boards.gno
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
4 changes: 2 additions & 2 deletions examples/gno.land/r/demo/boards2/permission.gno
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
13 changes: 4 additions & 9 deletions examples/gno.land/r/demo/boards2/permission_default.gno
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
}
Original file line number Diff line number Diff line change
Expand Up @@ -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"}
Expand Down
4 changes: 2 additions & 2 deletions examples/gno.land/r/demo/boards2/permission_handlers.gno
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand All @@ -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 {
Expand Down
36 changes: 11 additions & 25 deletions examples/gno.land/r/demo/boards2/public.gno
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
})
}
Expand All @@ -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)
})
}
Expand All @@ -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)
})
}
Expand All @@ -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)
}
Expand All @@ -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")
}
Expand All @@ -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())
Expand Down

0 comments on commit e5f7f08

Please sign in to comment.