blob: 68a66917289b991afdc1fbdfd311339ad79b4da6 [file] [log] [blame]
// Copyright 2019 The LUCI Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package legacy contains older implementation of IsMember check.
//
// To be deleted very soon.
package legacy
import (
"fmt"
"go.chromium.org/luci/auth/identity"
"go.chromium.org/luci/server/auth/service/protocol"
)
// Groups is a legacy representation of the groups graph.
type Groups struct {
groups map[string]*group // map of all known groups
}
// group is a node in a group graph. Nested groups are referenced directly via
// pointer.
type group struct {
members map[identity.Identity]struct{} // set of all members
globs []identity.Glob // list of all identity globs
nested []*group // pointers to nested groups
}
// BuildGroups builds the legacy representation of the groups graph.
func BuildGroups(groups []*protocol.AuthGroup) (*Groups, error) {
grs := make(map[string]*group, len(groups))
for _, g := range groups {
if grs[g.Name] != nil {
return nil, fmt.Errorf("auth: bad AuthDB, group %q is listed twice", g.Name)
}
gr := &group{}
if len(g.Members) != 0 {
gr.members = make(map[identity.Identity]struct{}, len(g.Members))
for _, ident := range g.Members {
gr.members[identity.Identity(ident)] = struct{}{}
}
}
if len(g.Globs) != 0 {
gr.globs = make([]identity.Glob, len(g.Globs))
for i, glob := range g.Globs {
gr.globs[i] = identity.Glob(glob)
}
}
if len(g.Nested) != 0 {
gr.nested = make([]*group, 0, len(g.Nested))
}
grs[g.Name] = gr
}
for _, g := range groups {
gr := grs[g.Name]
for _, nestedName := range g.Nested {
if nestedGroup := grs[nestedName]; nestedGroup != nil {
gr.nested = append(gr.nested, nestedGroup)
}
}
}
return &Groups{grs}, nil
}
// IsMember returns true if the given identity belongs to the given group.
func (g *Groups) IsMember(id identity.Identity, groupName string) bool {
var backingStore [8]*group
current := backingStore[:0]
visited := make(map[*group]struct{}, 10)
var isMember func(*group) bool
isMember = func(gr *group) bool {
if _, ok := gr.members[id]; ok {
return true
}
for _, glob := range gr.globs {
if glob.Match(id) {
return true
}
}
if len(gr.nested) == 0 {
return false
}
current = append(current, gr)
found := false
outer_loop:
for _, nested := range gr.nested {
for _, ancestor := range current {
if ancestor == nested {
continue outer_loop
}
}
if _, seen := visited[nested]; seen {
continue
}
if isMember(nested) {
found = true
break
}
}
current = current[:len(current)-1]
visited[gr] = struct{}{}
return found
}
if gr := g.groups[groupName]; gr != nil {
return isMember(gr)
}
return false
}