Search Apps Documentation Source Content File Folder Download Copy Actions Download

permissions_basic.gno

6.85 Kb · 252 lines
  1package boards2
  2
  3import (
  4	"gno.land/p/gnoland/boards"
  5	boardsdao "gno.land/p/gnoland/boards/dao"
  6	"gno.land/p/nt/avl"
  7	"gno.land/p/devx000/wip/nt/commondao"
  8)
  9
 10// ValidatorFunc defines a function type for permissions validators.
 11type ValidatorFunc func(boards.Permissions, boards.Args) error
 12
 13// BasicPermissions manages users, roles and permissions.
 14type BasicPermissions struct {
 15	superRole  boards.Role
 16	dao        *commondao.CommonDAO
 17	validators *avl.Tree // string(boards.Permission) -> BasicPermissionValidator
 18	public     *avl.Tree // string(boards.Permission) -> struct{}{}
 19}
 20
 21// NewBasicPermissions creates a new permissions type.
 22// This type is a default implementation to handle users, roles and permissions.
 23// It uses an underlying DAO to manage users and roles.
 24func NewBasicPermissions() *BasicPermissions {
 25	storage := boardsdao.NewMemberStorage()
 26	return &BasicPermissions{
 27		validators: avl.NewTree(),
 28		public:     avl.NewTree(),
 29		dao: commondao.New(
 30			// Use a custom boards member storage
 31			commondao.WithMemberStorage(storage),
 32		),
 33	}
 34}
 35
 36// DAO returns the underlying permissions DAO.
 37func (bp BasicPermissions) DAO() *commondao.CommonDAO {
 38	return bp.dao
 39}
 40
 41// ValidateFunc add a validator function for a permission.
 42func (bp *BasicPermissions) ValidateFunc(p boards.Permission, fn ValidatorFunc) {
 43	bp.validators.Set(string(p), fn)
 44}
 45
 46// SetPublicPermissions assigns permissions that are available to anyone.
 47// It removes previous public permissions and assigns the new ones.
 48// By default there are no public permissions.
 49func (bp *BasicPermissions) SetPublicPermissions(permissions ...boards.Permission) {
 50	bp.public = avl.NewTree()
 51	for _, p := range permissions {
 52		bp.public.Set(string(p), struct{}{})
 53	}
 54}
 55
 56// SetSuperRole assigns a super role.
 57// A super role is one that have all permissions.
 58// These type of role doesn't need to be mapped to any permission.
 59func (bp *BasicPermissions) SetSuperRole(r boards.Role) {
 60	if bp.superRole == r {
 61		return
 62	}
 63
 64	name := string(r)
 65	bp.dao.Members().Grouping().Add(name)
 66	bp.superRole = r
 67}
 68
 69// AddRole add a role with one or more assigned permissions.
 70// If role exists its permissions are overwritten with the new ones.
 71func (bp *BasicPermissions) AddRole(r boards.Role, p boards.Permission, extra ...boards.Permission) {
 72	// Get member group for the role if it exists or otherwise create a new group
 73	grouping := bp.dao.Members().Grouping()
 74	name := string(r)
 75	group, found := grouping.Get(name)
 76	if !found {
 77		var err error
 78		group, err = grouping.Add(name)
 79		if err != nil {
 80			panic(err)
 81		}
 82	}
 83
 84	// Save permissions within the member group overwritting any existing permissions
 85	group.SetMeta(append([]boards.Permission{p}, extra...))
 86}
 87
 88// RoleExists checks if a role exists.
 89func (bp BasicPermissions) RoleExists(r boards.Role) bool {
 90	return (bp.superRole != "" && r == bp.superRole) || bp.dao.Members().Grouping().Has(string(r))
 91}
 92
 93// GetUserRoles returns the list of roles assigned to a user.
 94func (bp BasicPermissions) GetUserRoles(user address) []boards.Role {
 95	groups := boardsdao.GetMemberGroups(bp.dao.Members(), user)
 96	if groups == nil {
 97		return nil
 98	}
 99
100	roles := make([]boards.Role, len(groups))
101	for i, name := range groups {
102		roles[i] = boards.Role(name)
103	}
104	return roles
105}
106
107// HasRole checks if a user has a specific role assigned.
108func (bp BasicPermissions) HasRole(user address, r boards.Role) bool {
109	name := string(r)
110	group, found := bp.dao.Members().Grouping().Get(name)
111	if !found {
112		return false
113	}
114	return group.Members().Has(user)
115}
116
117// HasPermission checks if a user has a specific permission.
118func (bp BasicPermissions) HasPermission(user address, perm boards.Permission) bool {
119	if bp.public.Has(string(perm)) {
120		return true
121	}
122
123	groups := boardsdao.GetMemberGroups(bp.dao.Members(), user)
124	if groups == nil {
125		return false
126	}
127
128	grouping := bp.dao.Members().Grouping()
129	for _, name := range groups {
130		role := boards.Role(name)
131		if bp.superRole == role {
132			return true
133		}
134
135		group, found := grouping.Get(name)
136		if !found {
137			continue
138		}
139
140		meta := group.GetMeta()
141		for _, p := range meta.([]boards.Permission) {
142			if p == perm {
143				return true
144			}
145		}
146	}
147	return false
148}
149
150// SetUserRoles adds a new user when it doesn't exist and sets its roles.
151// Method can also be called to change the roles of an existing user.
152// All user's roles can be removed by calling this method without roles.
153func (bp *BasicPermissions) SetUserRoles(_ realm, user address, roles ...boards.Role) {
154	groups := boardsdao.GetMemberGroups(bp.dao.Members(), user)
155	isGuest := len(roles) == 0
156
157	// If user has roles remove it from the groups its currently assigned
158	grouping := bp.dao.Members().Grouping()
159	if isGuest && groups != nil {
160		for _, name := range groups {
161			group, found := grouping.Get(name)
162			if !found {
163				continue
164			}
165
166			group.Members().Remove(user)
167		}
168	}
169
170	// Add user to the storage as guest when no roles are assigned
171	if isGuest {
172		bp.dao.Members().Add(user)
173		return
174	}
175
176	// Add user to role groups
177	for _, r := range roles {
178		name := string(r)
179		group, found := grouping.Get(name)
180		if !found {
181			panic("invalid role: " + name)
182		}
183
184		group.Members().Add(user)
185	}
186}
187
188// RemoveUser removes a user from permissions.
189func (bp *BasicPermissions) RemoveUser(_ realm, user address) bool {
190	groups := boardsdao.GetMemberGroups(bp.dao.Members(), user)
191	if groups == nil {
192		return bp.dao.Members().Remove(user)
193	}
194
195	grouping := bp.dao.Members().Grouping()
196	for _, name := range groups {
197		group, found := grouping.Get(name)
198		if !found {
199			continue
200		}
201
202		group.Members().Remove(user)
203	}
204	return true
205}
206
207// HasUser checks if a user exists.
208func (bp BasicPermissions) HasUser(user address) bool {
209	return bp.dao.Members().Has(user)
210}
211
212// UsersCount returns the total number of users the permissioner contains.
213func (bp BasicPermissions) UsersCount() int {
214	return bp.dao.Members().Size()
215}
216
217// IterateUsers iterates permissions' users.
218func (bp BasicPermissions) IterateUsers(start, count int, fn boards.UsersIterFn) (stopped bool) {
219	bp.dao.Members().IterateByOffset(start, count, func(addr address) bool {
220		user := boards.User{Address: addr}
221		groups := boardsdao.GetMemberGroups(bp.dao.Members(), addr)
222		if groups != nil {
223			user.Roles = make([]boards.Role, len(groups))
224			for i, name := range groups {
225				user.Roles[i] = boards.Role(name)
226			}
227		}
228
229		return fn(user)
230	})
231	return
232}
233
234// WithPermission calls a callback when a user has a specific permission.
235// It panics on error or when a handler panics.
236// Callbacks are by default called when there is no handle registered for the permission.
237func (bp *BasicPermissions) WithPermission(_ realm, user address, p boards.Permission, args boards.Args, cb func(realm)) {
238	if !bp.HasPermission(user, p) {
239		panic("unauthorized")
240	}
241
242	// Execute custom validation before calling the callback
243	v, found := bp.validators.Get(string(p))
244	if found {
245		err := v.(ValidatorFunc)(bp, args)
246		if err != nil {
247			panic(err)
248		}
249	}
250
251	cb(cross)
252}