go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/server/auth/authdb/internal/legacy/groups.go (about) 1 // Copyright 2019 The LUCI Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // Package legacy contains older implementation of IsMember check. 16 // 17 // To be deleted very soon. 18 package legacy 19 20 import ( 21 "fmt" 22 23 "go.chromium.org/luci/auth/identity" 24 25 "go.chromium.org/luci/server/auth/authdb/internal/graph" 26 "go.chromium.org/luci/server/auth/service/protocol" 27 ) 28 29 // Groups is a legacy representation of the groups graph. 30 type Groups struct { 31 groups map[string]*group // map of all known groups 32 } 33 34 // group is a node in a group graph. Nested groups are referenced directly via 35 // pointer. 36 type group struct { 37 members map[identity.Identity]struct{} // set of all members 38 globs []identity.Glob // list of all identity globs 39 nested []*group // pointers to nested groups 40 } 41 42 // BuildGroups builds the legacy representation of the groups graph. 43 func BuildGroups(groups []*protocol.AuthGroup) (*Groups, error) { 44 grs := make(map[string]*group, len(groups)) 45 for _, g := range groups { 46 if grs[g.Name] != nil { 47 return nil, fmt.Errorf("auth: bad AuthDB, group %q is listed twice", g.Name) 48 } 49 gr := &group{} 50 if len(g.Members) != 0 { 51 gr.members = make(map[identity.Identity]struct{}, len(g.Members)) 52 for _, ident := range g.Members { 53 gr.members[identity.Identity(ident)] = struct{}{} 54 } 55 } 56 if len(g.Globs) != 0 { 57 gr.globs = make([]identity.Glob, len(g.Globs)) 58 for i, glob := range g.Globs { 59 gr.globs[i] = identity.Glob(glob) 60 } 61 } 62 if len(g.Nested) != 0 { 63 gr.nested = make([]*group, 0, len(g.Nested)) 64 } 65 grs[g.Name] = gr 66 } 67 68 for _, g := range groups { 69 gr := grs[g.Name] 70 for _, nestedName := range g.Nested { 71 if nestedGroup := grs[nestedName]; nestedGroup != nil { 72 gr.nested = append(gr.nested, nestedGroup) 73 } 74 } 75 } 76 77 return &Groups{grs}, nil 78 } 79 80 // IsMember returns true if the given identity belongs to the given group. 81 func (g *Groups) IsMember(id identity.Identity, groupName string) graph.IsMemberResult { 82 var backingStore [8]*group 83 current := backingStore[:0] 84 85 visited := make(map[*group]struct{}, 10) 86 87 var isMember func(*group) bool 88 89 isMember = func(gr *group) bool { 90 if _, ok := gr.members[id]; ok { 91 return true 92 } 93 for _, glob := range gr.globs { 94 if glob.Match(id) { 95 return true 96 } 97 } 98 if len(gr.nested) == 0 { 99 return false 100 } 101 current = append(current, gr) 102 found := false 103 104 outer_loop: 105 for _, nested := range gr.nested { 106 for _, ancestor := range current { 107 if ancestor == nested { 108 continue outer_loop 109 } 110 } 111 if _, seen := visited[nested]; seen { 112 continue 113 } 114 if isMember(nested) { 115 found = true 116 break 117 } 118 } 119 120 current = current[:len(current)-1] 121 visited[gr] = struct{}{} 122 return found 123 } 124 125 if gr := g.groups[groupName]; gr != nil { 126 if isMember(gr) { 127 return graph.IdentIsMember 128 } 129 return graph.IdentIsNotMember 130 } 131 return graph.GroupIsUnknown 132 }