go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/tokenserver/appengine/impl/utils/identityset/identityset.go (about) 1 // Copyright 2016 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 identityset implements a set-like structure for identity.Identity. 16 package identityset 17 18 import ( 19 "context" 20 "fmt" 21 "sort" 22 "strings" 23 24 "go.chromium.org/luci/auth/identity" 25 "go.chromium.org/luci/server/auth" 26 ) 27 28 type identSet map[identity.Identity]struct{} 29 type groupSet map[string]struct{} 30 31 // Set is a set of identities represented as sets of IDs and groups. 32 // 33 // Groups are generally not expanded, but treated as different kind of items, 34 // so essentially this struct represents two sets: a set of explicitly specified 35 // identities, and a set of groups. The exception to this rule is IsMember 36 // function that looks inside the groups. 37 type Set struct { 38 All bool // if true, this set contains all possible identities 39 IDs identSet // set of identity.Identity strings 40 Groups groupSet // set of group names 41 } 42 43 // AddIdentity adds a single identity to the set. 44 // 45 // The receiver must not be nil. 46 func (s *Set) AddIdentity(id identity.Identity) { 47 if !s.All { 48 if s.IDs == nil { 49 s.IDs = make(identSet, 1) 50 } 51 s.IDs[id] = struct{}{} 52 } 53 } 54 55 // AddGroup adds a single group to the set. 56 // 57 // The receiver must not be nil. 58 func (s *Set) AddGroup(group string) { 59 if !s.All { 60 if s.Groups == nil { 61 s.Groups = make(groupSet, 1) 62 } 63 s.Groups[group] = struct{}{} 64 } 65 } 66 67 // IsEmpty returns true if this set is empty. 68 // 69 // 'nil' receiver value is valid and represents an empty set. 70 func (s *Set) IsEmpty() bool { 71 return s == nil || (!s.All && len(s.IDs) == 0 && len(s.Groups) == 0) 72 } 73 74 // IsMember returns true if the given identity is in the set. 75 // 76 // It looks inside the groups too. 77 // 78 // 'nil' receiver value is valid and represents an empty set. 79 func (s *Set) IsMember(c context.Context, id identity.Identity) (bool, error) { 80 if s == nil { 81 return false, nil 82 } 83 84 if s.All { 85 return true, nil 86 } 87 88 if _, ok := s.IDs[id]; ok { 89 return true, nil 90 } 91 92 if len(s.Groups) != 0 { 93 groups := make([]string, 0, len(s.Groups)) 94 for gr := range s.Groups { 95 groups = append(groups, gr) 96 } 97 return auth.GetState(c).DB().IsMember(c, id, groups) 98 } 99 100 return false, nil 101 } 102 103 // IsSubset returns true if this set if a subset of another set. 104 // 105 // Two equal sets are considered subsets of each other. 106 // 107 // It doesn't attempt to expand groups. Compares IDs and Groups sets separately, 108 // as independent kinds of entities. 109 // 110 // 'nil' receiver and argument values are valid and represent empty sets. 111 func (s *Set) IsSubset(superset *Set) bool { 112 // An empty set is subset of any other set (including empty sets). 113 if s.IsEmpty() { 114 return true 115 } 116 117 // An empty set is not a superset of any non-empty set. 118 if superset.IsEmpty() { 119 return false 120 } 121 122 // The universal set is subset of only itself. 123 if s.All { 124 return superset.All 125 } 126 127 // The universal set is superset of any other set. 128 if superset.All { 129 return true 130 } 131 132 // Is s.IDs a subset of superset.IDs? 133 if len(superset.IDs) < len(s.IDs) { 134 return false 135 } 136 for id := range s.IDs { 137 if _, ok := superset.IDs[id]; !ok { 138 return false 139 } 140 } 141 142 // Is s.Groups a subset of superset.Groups? 143 if len(superset.Groups) < len(s.Groups) { 144 return false 145 } 146 for group := range s.Groups { 147 if _, ok := superset.Groups[group]; !ok { 148 return false 149 } 150 } 151 152 return true 153 } 154 155 // IsSuperset returns true if this set is a super set of another set. 156 // 157 // Two equal sets are considered supersets of each other. 158 // 159 // 'nil' receiver and argument values are valid and represent empty sets. 160 func (s *Set) IsSuperset(subset *Set) bool { 161 return subset.IsSubset(s) 162 } 163 164 // ToStrings returns a sorted list of strings representing this set. 165 // 166 // See 'FromStrings' for the format of this list. 167 func (s *Set) ToStrings() []string { 168 if s.IsEmpty() { 169 return nil 170 } 171 if s.All { 172 return []string{"*"} 173 } 174 out := make([]string, 0, len(s.IDs)+len(s.Groups)) 175 for ident := range s.IDs { 176 out = append(out, string(ident)) 177 } 178 for group := range s.Groups { 179 out = append(out, "group:"+group) 180 } 181 sort.Strings(out) 182 return out 183 } 184 185 // FromStrings constructs a Set by parsing a slice of strings. 186 // 187 // Each string is either: 188 // - "<kind>:<id>" identity string. 189 // - "group:<name>" group reference. 190 // - "*" token to mean "All identities". 191 // 192 // Any string that matches 'skip' predicate is skipped. 193 func FromStrings(str []string, skip func(string) bool) (*Set, error) { 194 set := &Set{} 195 for _, s := range str { 196 if skip != nil && skip(s) { 197 continue 198 } 199 switch { 200 case s == "*": 201 set.All = true 202 case strings.HasPrefix(s, "group:"): 203 gr := strings.TrimPrefix(s, "group:") 204 if gr == "" { 205 return nil, fmt.Errorf("invalid entry %q", s) 206 } 207 set.AddGroup(gr) 208 default: 209 id, err := identity.MakeIdentity(s) 210 if err != nil { 211 return nil, err 212 } 213 set.AddIdentity(id) 214 } 215 } 216 // If '*' was used, separately listed IDs and groups are redundant. 217 if set.All { 218 set.Groups = nil 219 set.IDs = nil 220 } 221 return set, nil 222 } 223 224 // Union returns a union of a list of sets. 225 func Union(sets ...*Set) *Set { 226 estimateIDs := 0 227 estimateGroups := 0 228 229 for _, s := range sets { 230 if s == nil { 231 continue 232 } 233 if s.All { 234 return &Set{All: true} 235 } 236 if len(s.IDs) > estimateIDs { 237 estimateIDs = len(s.IDs) 238 } 239 if len(s.Groups) > estimateGroups { 240 estimateGroups = len(s.Groups) 241 } 242 } 243 244 union := &Set{} 245 if estimateIDs != 0 { 246 union.IDs = make(identSet, estimateIDs) 247 } 248 if estimateGroups != 0 { 249 union.Groups = make(groupSet, estimateGroups) 250 } 251 252 for _, s := range sets { 253 if s == nil { 254 continue 255 } 256 for ident := range s.IDs { 257 union.IDs[ident] = struct{}{} 258 } 259 for group := range s.Groups { 260 union.Groups[group] = struct{}{} 261 } 262 } 263 264 return union 265 } 266 267 // Extend returns a throw-away set that has one additional member. 268 // 269 // The returned set must not be modified, since it references data of the 270 // original set (to avoid unnecessary copying). 271 func Extend(orig *Set, id identity.Identity) *Set { 272 if orig.IsEmpty() { 273 return &Set{ 274 IDs: identSet{ 275 id: struct{}{}, 276 }, 277 } 278 } 279 if orig.All { 280 return orig 281 } 282 if _, ok := orig.IDs[id]; ok { 283 return orig 284 } 285 286 // Need to make a copy of orig.IDs to add 'id' there. 287 extended := make(identSet, len(orig.IDs)+1) 288 for origID := range orig.IDs { 289 extended[origID] = struct{}{} 290 } 291 extended[id] = struct{}{} 292 293 return &Set{ 294 IDs: extended, 295 Groups: orig.Groups, 296 } 297 }