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}