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(boards2): member role management #3512

31 changes: 26 additions & 5 deletions examples/gno.land/r/demo/boards2/board.gno
Original file line number Diff line number Diff line change
Expand Up @@ -131,14 +131,35 @@ 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 {
return NewDefaultPermissions(
admindao.New(admindao.WithMember(owner)),
admindao.New(),
Comment on lines -137 to +136
Copy link
Member Author

Choose a reason for hiding this comment

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

Removed member initialization because WithUser(owner, RoleOwner) now adds the user to the DAO if it's not a member.

WithSuperRole(RoleOwner),
WithRole(RoleAdmin, PermissionMemberInvite, PermissionBoardRename),
// TODO: Finish assigning all roles and permissions
// WithRole(RoleModerator, permissions...),
WithRole(
RoleAdmin,
PermissionBoardRename,
PermissionMemberInvite,
PermissionMemberRemove,
PermissionThreadCreate,
PermissionThreadEdit,
PermissionThreadDelete,
PermissionThreadFlag,
PermissionReplyDelete,
PermissionReplyFlag,
PermissionRoleChange,
),
WithRole(
RoleModerator,
PermissionThreadCreate,
PermissionThreadEdit,
PermissionThreadFlag,
PermissionReplyFlag,
),
WithRole(
RoleGuest,
PermissionThreadCreate,
PermissionThreadRepost,
),
WithUser(owner, RoleOwner),
)
}
7 changes: 7 additions & 0 deletions examples/gno.land/r/demo/boards2/permission.gno
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const (
PermissionReplyFlag = "reply:flag"
PermissionMemberInvite = "member:invite"
PermissionMemberRemove = "member:remove"
PermissionRoleChange = "role:change"
)

const (
Expand Down Expand Up @@ -52,9 +53,15 @@ type (
// AddUser adds a new user to the permissioner.
AddUser(std.Address, ...Role) error

// SetUserRoles sets the roles of a user.
SetUserRoles(std.Address, ...Role) error

// RemoveUser removes a user from the permissioner.
RemoveUser(std.Address) (removed bool)

// HasUser checks if a user exists.
HasUser(std.Address) bool

// GetDAO returns the underlying DAO.
// Returned value can be nil if the implementation doesn't have a DAO.
GetDAO() *admindao.AdminDAO // TODO: should return an interface
Expand Down
108 changes: 80 additions & 28 deletions examples/gno.land/r/demo/boards2/permission_default.gno
Original file line number Diff line number Diff line change
Expand Up @@ -32,20 +32,6 @@ func NewDefaultPermissions(dao *admindao.AdminDAO, options ...DefaultPermissions
return dp
}

// Roles returns the list of roles.
func (dp DefaultPermissions) Roles() []Role {
var roles []Role
if dp.superRole != "" {
roles = append(roles, dp.superRole)
}

dp.roles.Iterate("", "", func(name string, _ interface{}) bool {
roles = append(roles, Role(name))
return false
})
return roles
}
Comment on lines -36 to -47
Copy link
Member Author

Choose a reason for hiding this comment

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

Removed Roles() because it's not being used anymore


// RoleExists checks if a role exists.
func (dp DefaultPermissions) RoleExists(r Role) bool {
if dp.superRole != "" && r == dp.superRole {
Expand Down Expand Up @@ -104,18 +90,18 @@ func (dp *DefaultPermissions) AddUser(user std.Address, roles ...Role) error {
return errors.New("user already exists")
}

for _, r := range roles {
if !dp.RoleExists(r) {
return errors.New("invalid role: " + string(r))
}
}

if err := dp.dao.AddMember(user); err != nil {
return err
}
return dp.setUserRoles(user, roles...)
}

dp.users.Set(user.String(), append([]Role(nil), roles...))
return nil
// SetUserRoles sets the roles of a user.
func (dp *DefaultPermissions) SetUserRoles(user std.Address, roles ...Role) error {
if !dp.users.Has(user.String()) {
return errors.New("user not found")
}
return dp.setUserRoles(user, roles...)
}

// RemoveUser removes a user from permissions.
Expand All @@ -125,6 +111,11 @@ func (dp *DefaultPermissions) RemoveUser(user std.Address) bool {
return removed
}

// HasUser checks if a user exists.
func (dp DefaultPermissions) HasUser(user std.Address) bool {
return dp.users.Has(user.String())
}

// GetDAO returns the underlying DAO.
// Returned value can be nil if the implementation doesn't have a DAO.
func (dp DefaultPermissions) GetDAO() *admindao.AdminDAO {
Expand All @@ -146,11 +137,24 @@ func (dp *DefaultPermissions) WithPermission(user std.Address, perm Permission,
dp.handleBoardRename(args, cb)
case PermissionMemberInvite:
dp.handleMemberInvite(args, cb)
case PermissionRoleChange:
dp.handleRoleChange(args, cb)
default:
cb(args)
}
}

func (dp *DefaultPermissions) setUserRoles(user std.Address, roles ...Role) error {
for _, r := range roles {
if !dp.RoleExists(r) {
return errors.New("invalid role: " + string(r))
}
}

dp.users.Set(user.String(), append([]Role(nil), roles...))
return nil
}

func (DefaultPermissions) handleBoardCreate(args Args, cb func(Args)) {
name, ok := args[0].(string)
if !ok {
Expand Down Expand Up @@ -192,15 +196,63 @@ func (dp DefaultPermissions) handleMemberInvite(args Args, cb func(Args)) {
cb(args)
}

func (dp DefaultPermissions) handleRoleChange(args Args, cb func(Args)) {
// Owners and Admins can change roles.
// Admins should not be able to assign or remove the Owner role from members.
caller := std.GetOrigCaller()
if dp.HasRole(caller, RoleAdmin) {
role, ok := args[2].(Role)
if !ok {
panic("expected a valid member role")
}

if role == RoleOwner {
panic("admins are not allowed to promote members to Owner")
} else {
member, ok := args[1].(std.Address)
if !ok {
panic("expected a valid member address")
}

if dp.HasRole(member, RoleOwner) {
panic("admins are not allowed to remove the Owner role")
}
}
}

cb(args)
}

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 `Permissions`??
return NewDefaultPermissions(
admindao.New(admindao.WithMember(owner)),
admindao.New(),
WithSuperRole(RoleOwner),
WithRole(RoleAdmin, PermissionBoardCreate, PermissionMemberInvite),
// TODO: Finish assigning all roles and permissions
// WithRole(RoleModerator, permissions...),
WithRole(
RoleAdmin,
PermissionBoardCreate,
PermissionBoardRename,
PermissionMemberInvite,
PermissionMemberRemove,
PermissionThreadCreate,
PermissionThreadEdit,
PermissionThreadDelete,
PermissionThreadFlag,
PermissionReplyDelete,
PermissionReplyFlag,
PermissionRoleChange,
),
WithRole(
RoleModerator,
PermissionThreadCreate,
PermissionThreadEdit,
PermissionThreadFlag,
PermissionReplyFlag,
),
WithRole(
RoleGuest,
PermissionThreadCreate,
PermissionThreadRepost,
),
WithUser(owner, RoleOwner),
)
}
Expand Down
Loading
Loading