permissions_basic.gno

5.26 Kb · 194 lines
  1package boards2
  2
  3import (
  4	"errors"
  5	"std"
  6
  7	"gno.land/p/nt/avl"
  8	"gno.land/p/nt/commondao"
  9
 10	"gno.land/r/sys/users"
 11)
 12
 13// ValidatorFunc defines a function type for permissions validators.
 14type ValidatorFunc func(Permissions, Args) error
 15
 16// BasicPermissions manages users, roles and permissions.
 17type BasicPermissions struct {
 18	superRole  Role
 19	dao        *commondao.CommonDAO
 20	users      *avl.Tree // string(std.Address) -> []Role
 21	roles      *avl.Tree // string(role) -> []Permission
 22	validators *avl.Tree // string(Permission) -> BasicPermissionValidator
 23}
 24
 25// NewBasicPermissions creates a new permissions type.
 26// This type is a default implementation to handle users, roles and permissions.
 27func NewBasicPermissions(dao *commondao.CommonDAO) *BasicPermissions {
 28	if dao == nil {
 29		panic("basic permissions require a DAO")
 30	}
 31
 32	return &BasicPermissions{
 33		dao:        dao,
 34		roles:      avl.NewTree(),
 35		users:      avl.NewTree(),
 36		validators: avl.NewTree(),
 37	}
 38}
 39
 40// ValidateFunc add a validator function for a permission.
 41func (bp *BasicPermissions) ValidateFunc(p Permission, fn ValidatorFunc) {
 42	bp.validators.Set(string(p), fn)
 43}
 44
 45// SetSuperRole assigns a super role.
 46// A super role is one that have all permissions.
 47// These type of role doesn't need to be mapped to any permission.
 48func (bp *BasicPermissions) SetSuperRole(r Role) {
 49	bp.superRole = r
 50}
 51
 52// AddRole add a role with one or more assigned permissions.
 53func (bp *BasicPermissions) AddRole(r Role, p Permission, extra ...Permission) {
 54	bp.roles.Set(string(r), append([]Permission{p}, extra...))
 55}
 56
 57// RoleExists checks if a role exists.
 58func (bp BasicPermissions) RoleExists(r Role) bool {
 59	return (bp.superRole != "" && r == bp.superRole) || bp.roles.Has(string(r))
 60}
 61
 62// GetUserRoles returns the list of roles assigned to a user.
 63func (bp BasicPermissions) GetUserRoles(user std.Address) []Role {
 64	v, found := bp.users.Get(user.String())
 65	if !found {
 66		return nil
 67	}
 68	return v.([]Role)
 69}
 70
 71// HasRole checks if a user has a specific role assigned.
 72func (bp BasicPermissions) HasRole(user std.Address, r Role) bool {
 73	for _, role := range bp.GetUserRoles(user) {
 74		if role == r {
 75			return true
 76		}
 77	}
 78	return false
 79}
 80
 81// HasPermission checks if a user has a specific permission.
 82func (bp BasicPermissions) HasPermission(user std.Address, perm Permission) bool {
 83	for _, r := range bp.GetUserRoles(user) {
 84		if bp.superRole == r {
 85			return true
 86		}
 87
 88		v, found := bp.roles.Get(string(r))
 89		if !found {
 90			continue
 91		}
 92
 93		for _, p := range v.([]Permission) {
 94			if p == perm {
 95				return true
 96			}
 97		}
 98	}
 99	return false
100}
101
102// SetUserRoles adds a new user when it doesn't exist and sets its roles.
103// Method can also be called to change the roles of an existing user.
104// All user's roles can be removed by calling this method without roles.
105func (bp *BasicPermissions) SetUserRoles(_ realm, user std.Address, roles ...Role) error {
106	if !bp.HasUser(user) {
107		bp.dao.Members().Add(user)
108	}
109
110	for _, r := range roles {
111		if !bp.RoleExists(r) {
112			return errors.New("invalid role: " + string(r))
113		}
114	}
115
116	bp.users.Set(user.String(), append([]Role(nil), roles...))
117	return nil
118}
119
120// RemoveUser removes a user from permissions.
121func (bp *BasicPermissions) RemoveUser(_ realm, user std.Address) bool {
122	_, removed := bp.users.Remove(user.String())
123	bp.dao.Members().Remove(user)
124	return removed
125}
126
127// HasUser checks if a user exists.
128func (bp BasicPermissions) HasUser(user std.Address) bool {
129	return bp.dao.Members().Has(user)
130}
131
132// UsersCount returns the total number of users the permissioner contains.
133func (bp BasicPermissions) UsersCount() int {
134	return bp.dao.Members().Size()
135}
136
137// IterateUsers iterates permissions' users.
138func (bp BasicPermissions) IterateUsers(start, count int, fn UsersIterFn) (stopped bool) {
139	bp.dao.Members().IterateByOffset(start, count, func(addr std.Address) bool {
140		roles := bp.GetUserRoles(addr)
141		stopped = fn(User{
142			Address: addr,
143			Roles:   roles,
144		})
145		return stopped
146	})
147	return
148}
149
150// WithPermission calls a callback when a user has a specific permission.
151// It panics on error or when a handler panics.
152// Callbacks are by default called when there is no handle registered for the permission.
153func (bp *BasicPermissions) WithPermission(_ realm, user std.Address, p Permission, args Args, cb func(realm, Args)) {
154	if !bp.HasPermission(user, p) || !bp.dao.Members().Has(user) {
155		panic("unauthorized")
156	}
157
158	v, found := bp.validators.Get(string(p))
159	if found {
160		err := v.(ValidatorFunc)(bp, args)
161		if err != nil {
162			panic(err)
163		}
164	}
165
166	cb(cross, args)
167}
168
169func createBasicPermissions(owners ...std.Address) *BasicPermissions {
170	perms := NewBasicPermissions(commondao.New())
171	perms.SetSuperRole(RoleOwner)
172	perms.AddRole(RoleAdmin, PermissionBoardCreate)
173	for _, owner := range owners {
174		perms.SetUserRoles(cross, owner, RoleOwner)
175	}
176	return perms
177}
178
179func checkBoardNameIsNotAddress(s string) error {
180	if std.Address(s).IsValid() {
181		return errors.New("addresses are not allowed as board name")
182	}
183	return nil
184}
185
186func checkBoardNameBelongsToAddress(owner std.Address, name string) error {
187	// When the board name is the name of a registered user
188	// check that caller is the owner of the name.
189	user, _ := users.ResolveName(name)
190	if user != nil && user.Addr() != owner {
191		return errors.New("board name is a user name registered to a different user")
192	}
193	return nil
194}