| // Copyright 2016 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 identityset implements a set-like structure for identity.Identity. |
| package identityset |
| |
| import ( |
| "context" |
| "fmt" |
| "sort" |
| "strings" |
| |
| "go.chromium.org/luci/auth/identity" |
| "go.chromium.org/luci/server/auth" |
| ) |
| |
| type identSet map[identity.Identity]struct{} |
| type groupSet map[string]struct{} |
| |
| // Set is a set of identities represented as sets of IDs and groups. |
| // |
| // Groups are generally not expanded, but treated as different kind of items, |
| // so essentially this struct represents two sets: a set of explicitly specified |
| // identities, and a set of groups. The exception to this rule is IsMember |
| // function that looks inside the groups. |
| type Set struct { |
| All bool // if true, this set contains all possible identities |
| IDs identSet // set of identity.Identity strings |
| Groups groupSet // set of group names |
| } |
| |
| // AddIdentity adds a single identity to the set. |
| // |
| // The receiver must not be nil. |
| func (s *Set) AddIdentity(id identity.Identity) { |
| if !s.All { |
| if s.IDs == nil { |
| s.IDs = make(identSet, 1) |
| } |
| s.IDs[id] = struct{}{} |
| } |
| } |
| |
| // AddGroup adds a single group to the set. |
| // |
| // The receiver must not be nil. |
| func (s *Set) AddGroup(group string) { |
| if !s.All { |
| if s.Groups == nil { |
| s.Groups = make(groupSet, 1) |
| } |
| s.Groups[group] = struct{}{} |
| } |
| } |
| |
| // IsEmpty returns true if this set is empty. |
| // |
| // 'nil' receiver value is valid and represents an empty set. |
| func (s *Set) IsEmpty() bool { |
| return s == nil || (!s.All && len(s.IDs) == 0 && len(s.Groups) == 0) |
| } |
| |
| // IsMember returns true if the given identity is in the set. |
| // |
| // It looks inside the groups too. |
| // |
| // 'nil' receiver value is valid and represents an empty set. |
| func (s *Set) IsMember(c context.Context, id identity.Identity) (bool, error) { |
| if s == nil { |
| return false, nil |
| } |
| |
| if s.All { |
| return true, nil |
| } |
| |
| if _, ok := s.IDs[id]; ok { |
| return true, nil |
| } |
| |
| if len(s.Groups) != 0 { |
| groups := make([]string, 0, len(s.Groups)) |
| for gr := range s.Groups { |
| groups = append(groups, gr) |
| } |
| return auth.GetState(c).DB().IsMember(c, id, groups) |
| } |
| |
| return false, nil |
| } |
| |
| // IsSubset returns true if this set if a subset of another set. |
| // |
| // Two equal sets are considered subsets of each other. |
| // |
| // It doesn't attempt to expand groups. Compares IDs and Groups sets separately, |
| // as independent kinds of entities. |
| // |
| // 'nil' receiver and argument values are valid and represent empty sets. |
| func (s *Set) IsSubset(superset *Set) bool { |
| // An empty set is subset of any other set (including empty sets). |
| if s.IsEmpty() { |
| return true |
| } |
| |
| // An empty set is not a superset of any non-empty set. |
| if superset.IsEmpty() { |
| return false |
| } |
| |
| // The universal set is subset of only itself. |
| if s.All { |
| return superset.All |
| } |
| |
| // The universal set is superset of any other set. |
| if superset.All { |
| return true |
| } |
| |
| // Is s.IDs a subset of superset.IDs? |
| if len(superset.IDs) < len(s.IDs) { |
| return false |
| } |
| for id := range s.IDs { |
| if _, ok := superset.IDs[id]; !ok { |
| return false |
| } |
| } |
| |
| // Is s.Groups a subset of superset.Groups? |
| if len(superset.Groups) < len(s.Groups) { |
| return false |
| } |
| for group := range s.Groups { |
| if _, ok := superset.Groups[group]; !ok { |
| return false |
| } |
| } |
| |
| return true |
| } |
| |
| // IsSuperset returns true if this set is a super set of another set. |
| // |
| // Two equal sets are considered supersets of each other. |
| // |
| // 'nil' receiver and argument values are valid and represent empty sets. |
| func (s *Set) IsSuperset(subset *Set) bool { |
| return subset.IsSubset(s) |
| } |
| |
| // ToStrings returns a sorted list of strings representing this set. |
| // |
| // See 'FromStrings' for the format of this list. |
| func (s *Set) ToStrings() []string { |
| if s.IsEmpty() { |
| return nil |
| } |
| if s.All { |
| return []string{"*"} |
| } |
| out := make([]string, 0, len(s.IDs)+len(s.Groups)) |
| for ident := range s.IDs { |
| out = append(out, string(ident)) |
| } |
| for group := range s.Groups { |
| out = append(out, "group:"+group) |
| } |
| sort.Strings(out) |
| return out |
| } |
| |
| // FromStrings constructs a Set by parsing a slice of strings. |
| // |
| // Each string is either: |
| // * "<kind>:<id>" identity string. |
| // * "group:<name>" group reference. |
| // * "*" token to mean "All identities". |
| // |
| // Any string that matches 'skip' predicate is skipped. |
| func FromStrings(str []string, skip func(string) bool) (*Set, error) { |
| set := &Set{} |
| for _, s := range str { |
| if skip != nil && skip(s) { |
| continue |
| } |
| switch { |
| case s == "*": |
| set.All = true |
| case strings.HasPrefix(s, "group:"): |
| gr := strings.TrimPrefix(s, "group:") |
| if gr == "" { |
| return nil, fmt.Errorf("invalid entry %q", s) |
| } |
| set.AddGroup(gr) |
| default: |
| id, err := identity.MakeIdentity(s) |
| if err != nil { |
| return nil, err |
| } |
| set.AddIdentity(id) |
| } |
| } |
| // If '*' was used, separately listed IDs and groups are redundant. |
| if set.All { |
| set.Groups = nil |
| set.IDs = nil |
| } |
| return set, nil |
| } |
| |
| // Union returns a union of a list of sets. |
| func Union(sets ...*Set) *Set { |
| estimateIDs := 0 |
| estimateGroups := 0 |
| |
| for _, s := range sets { |
| if s == nil { |
| continue |
| } |
| if s.All { |
| return &Set{All: true} |
| } |
| if len(s.IDs) > estimateIDs { |
| estimateIDs = len(s.IDs) |
| } |
| if len(s.Groups) > estimateGroups { |
| estimateGroups = len(s.Groups) |
| } |
| } |
| |
| union := &Set{} |
| if estimateIDs != 0 { |
| union.IDs = make(identSet, estimateIDs) |
| } |
| if estimateGroups != 0 { |
| union.Groups = make(groupSet, estimateGroups) |
| } |
| |
| for _, s := range sets { |
| if s == nil { |
| continue |
| } |
| for ident := range s.IDs { |
| union.IDs[ident] = struct{}{} |
| } |
| for group := range s.Groups { |
| union.Groups[group] = struct{}{} |
| } |
| } |
| |
| return union |
| } |
| |
| // Extend returns a throw-away set that has one additional member. |
| // |
| // The returned set must not be modified, since it references data of the |
| // original set (to avoid unnecessary copying). |
| func Extend(orig *Set, id identity.Identity) *Set { |
| if orig.IsEmpty() { |
| return &Set{ |
| IDs: identSet{ |
| id: struct{}{}, |
| }, |
| } |
| } |
| if orig.All { |
| return orig |
| } |
| if _, ok := orig.IDs[id]; ok { |
| return orig |
| } |
| |
| // Need to make a copy of orig.IDs to add 'id' there. |
| extended := make(identSet, len(orig.IDs)+1) |
| for origID := range orig.IDs { |
| extended[origID] = struct{}{} |
| } |
| extended[id] = struct{}{} |
| |
| return &Set{ |
| IDs: extended, |
| Groups: orig.Groups, |
| } |
| } |