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}