mirror of
https://github.com/mumble-voip/grumble.git
synced 2025-12-19 21:59:59 -08:00
369 lines
9.3 KiB
Go
369 lines
9.3 KiB
Go
// 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
|
|
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// GroupNames gets 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
|
|
}
|