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: permissions support for individual boards #3386

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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
Loading