forked from mumble-voip/grumble
-
Notifications
You must be signed in to change notification settings - Fork 0
/
group.go
370 lines (335 loc) · 9.32 KB
/
group.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
// Copyright (c) 2010-2013 The Grumble Authors
// The use of this source code is goverened by a BSD-style
// license that can be found in the LICENSE-file.
package acl
import (
"log"
"strconv"
"strings"
)
// Group represents a Group in an Context.
type Group struct {
// The name of this group
Name string
// The inherit flag means that this group will inherit group
// members from its parent.
Inherit bool
// The inheritable flag means that subchannels can
// inherit the members of this group.
Inheritable bool
// Group adds permissions to these users
Add map[int]bool
// Group removes permissions from these users
Remove map[int]bool
// Temporary add (authenticators)
Temporary map[int]bool
}
// EmptyGroupWithName creates a new Group with the given name.
func EmptyGroupWithName(name string) Group {
grp := Group{}
grp.Name = name
grp.Add = make(map[int]bool)
grp.Remove = make(map[int]bool)
grp.Temporary = make(map[int]bool)
return grp
}
// AddContains checks whether the Add set contains id.
func (group *Group) AddContains(id int) (ok bool) {
_, ok = group.Add[id]
return
}
// AddUsers gets the list of user ids in the Add set.
func (group *Group) AddUsers() []int {
users := []int{}
for uid, _ := range group.Add {
users = append(users, uid)
}
return users
}
// RemoveContains checks whether the Remove set contains id.
func (group *Group) RemoveContains(id int) (ok bool) {
_, ok = group.Remove[id]
return
}
// RemoveUsers gets the list of user ids in the Remove set.
func (group *Group) RemoveUsers() []int {
users := []int{}
for uid, _ := range group.Remove {
users = append(users, uid)
}
return users
}
// TemporaryContains checks whether the Temporary set contains id.
func (group *Group) TemporaryContains(id int) (ok bool) {
_, ok = group.Temporary[id]
return
}
// MembersInContext gets the set of user id's from the group in the given context.
// This includes group members that have been inherited from an ancestor context.
func (group *Group) MembersInContext(ctx *Context) map[int]bool {
groups := []Group{}
members := map[int]bool{}
// Walk a group's context chain, starting with the context the group
// is defined on, followed by its parent contexts.
origCtx := ctx
for ctx != nil {
curgroup, ok := ctx.Groups[group.Name]
if ok {
// If the group is not inheritable, and we're looking at an
// ancestor group, we've looked in all the groups we should.
if ctx != origCtx && !curgroup.Inheritable {
break
}
// Add the group to the list of groups to be considered
groups = append([]Group{curgroup}, groups...)
// If this group does not inherit from groups in its ancestors, stop looking
// for more ancestor groups.
if !curgroup.Inherit {
break
}
}
ctx = ctx.Parent
}
for _, curgroup := range groups {
for uid, _ := range curgroup.Add {
members[uid] = true
}
for uid, _ := range curgroup.Remove {
delete(members, uid)
}
}
return members
}
// GroupMemberCheck checks whether a user is a member
// of the group as defined in the given context.
//
// The 'current' context is the context that group
// membership is currently being evaluated for.
//
// The 'acl' context is the context of the ACL that
// that group membership is being evaluated for.
//
// The acl context will always be either equal to
// current, or be an ancestor.
func GroupMemberCheck(current *Context, acl *Context, name string, user User) (ok bool) {
valid := true
invert := false
token := false
hash := false
// Returns the 'correct' return value considering the value
// of the invert flag.
defer func() {
if valid && invert {
ok = !ok
}
}()
channel := current
for {
// Empty group name are not valid.
if len(name) == 0 {
valid = false
return false
}
// Invert
if name[0] == '!' {
invert = true
name = name[1:]
continue
}
// Evaluate in ACL context (not current channel)
if name[0] == '~' {
channel = acl
name = name[1:]
continue
}
// Token
if name[0] == '#' {
token = true
name = name[1:]
continue
}
// Hash
if name[0] == '$' {
hash = true
name = name[1:]
continue
}
break
}
if token {
// The user is part of this group if the remaining name is part of
// his access token list. The name check is case-insensitive.
for _, token := range user.Tokens() {
if strings.ToLower(name) == strings.ToLower(token) {
return true
}
}
return false
} else if hash {
// The client is part of this group if the remaining name matches the
// client's cert hash.
if strings.ToLower(name) == strings.ToLower(user.CertHash()) {
return true
}
return false
} else if name == "none" {
// None
return false
} else if name == "all" {
// Everyone
return true
} else if name == "auth" {
// The user is part of the auth group is he is authenticated. That is,
// his UserId is >= 0.
return user.UserId() >= 0
} else if name == "strong" {
// The user is part of the strong group if he is authenticated to the server
// via a strong certificate (i.e. non-self-signed, trusted by the server's
// trusted set of root CAs).
log.Printf("GroupMemberCheck: Implement strong certificate matching")
return false
} else if name == "in" {
// Is the user in the currently evaluated channel?
return user.ACLContext() == channel
} else if name == "out" {
// Is the user not in the currently evaluated channel?
return user.ACLContext() != channel
} else if name == "sub" {
// fixme(mkrautz): The sub group implementation below hasn't been thoroughly
// tested yet. It might be a bit buggy!
// Strip away the "sub," part of the name
name = name[4:]
mindesc := 1
maxdesc := 1000
minpath := 0
// Parse the groupname to extract the values we should use
// for minpath (first argument), mindesc (second argument),
// and maxdesc (third argument).
args := strings.SplitN(name, ",", 3)
nargs := len(args)
if nargs == 3 {
if len(args[2]) > 0 {
if result, err := strconv.Atoi(args[2]); err == nil {
maxdesc = result
}
}
}
if nargs >= 2 {
if len(args[1]) > 0 {
if result, err := strconv.Atoi(args[1]); err == nil {
mindesc = result
}
}
}
if nargs >= 1 {
if len(args[0]) > 0 {
if result, err := strconv.Atoi(args[0]); err == nil {
minpath = result
}
}
}
// Build a context chain starting from the
// user's current context.
userChain := buildChain(user.ACLContext())
// Build a chain of contexts, starting from
// the 'current' context. This is the context
// that group membership is checked against,
// notwithstanding the ~ group operator.
groupChain := buildChain(current)
// Find the index of the context that the group
// is currently being evaluated on. This can be
// either the 'acl' context or 'current' context
// depending on the ~ group operator.
cofs := indexOf(groupChain, current)
if cofs == -1 {
valid = false
return false
}
// Add the first parameter of our sub group to cofs
// to get our base context.
cofs += minpath
// Check that the minpath parameter that was given
// is a valid index for groupChain.
if cofs >= len(groupChain) {
valid = false
return false
} else if cofs < 0 {
cofs = 0
}
// If our base context is not in the userChain, the
// group does not apply to the user.
if indexOf(userChain, groupChain[cofs]) == -1 {
return false
}
// Down here, we're certain that the userChain
// includes the base context somewhere in its
// chain. We must now determine if the path depth
// makes the user a member of the group.
mindepth := cofs + mindesc
maxdepth := cofs + maxdesc
pdepth := len(userChain) - 1
return pdepth >= mindepth && pdepth <= maxdepth
} else {
// Non-magic groups
groups := []Group{}
iter := channel
for iter != nil {
if group, ok := iter.Groups[name]; ok {
// Skip non-inheritable groups if we're in parents
// of our evaluated context.
if iter != channel && !group.Inheritable {
break
}
// Prepend group
groups = append([]Group{group}, groups...)
// If this group does not inherit from groups in its ancestors, stop looking
// for more ancestor groups.
if !group.Inherit {
break
}
}
iter = iter.Parent
}
isMember := false
for _, group := range groups {
if group.AddContains(user.UserId()) || group.TemporaryContains(user.UserId()) || group.TemporaryContains(-int(user.Session())) {
isMember = true
}
if group.RemoveContains(user.UserId()) {
isMember = false
}
}
return isMember
}
return false
}
// Get the list of group names for the given ACL context.
//
// This function walks the through the context chain to figure
// out all groups that affect the given context whilst considering
// group inheritance.
func (ctx *Context) GroupNames() []string {
names := map[string]bool{}
origCtx := ctx
contexts := []*Context{}
// Walk through the whole context chain and all groups in it.
for _, ctx := range contexts {
for _, group := range ctx.Groups {
// A non-inheritable group in parent. Discard it.
if ctx != origCtx && !group.Inheritable {
delete(names, group.Name)
// An inheritable group. Add it to the list.
} else {
names[group.Name] = true
}
}
}
// Convert to slice
stringNames := make([]string, 0, len(names))
for name, ok := range names {
if ok {
stringNames = append(stringNames, name)
}
}
return stringNames
}