package store import "sort" // maxDepth bounds every tree walk so a corrupted parent cycle cannot hang the // server. Group creation rejects cycles, so this is a backstop only. const maxDepth = 64 // Ancestors returns groupID and every parent above it, nearest first. func (s *State) Ancestors(groupID string) []string { out := []string{} seen := map[string]bool{} for id := groupID; id != "" && !seen[id] && len(out) < maxDepth; { g, ok := s.Groups[id] if !ok { break } seen[id] = true out = append(out, id) id = g.ParentID } return out } // Descendants returns groupID and every group below it. func (s *State) Descendants(groupID string) []string { if _, ok := s.Groups[groupID]; !ok { return nil } out := []string{groupID} for i := 0; i < len(out); i++ { for id, g := range s.Groups { if g.ParentID == out[i] && !contains(out, id) { out = append(out, id) } } } sort.Strings(out) return out } // IsAncestor reports whether anc is at or above desc in the tree. func (s *State) IsAncestor(anc, desc string) bool { return contains(s.Ancestors(desc), anc) } // EffectiveGroups is the set of groups whose grants reach the user: every // group the user belongs to, plus every group above those. A grant on // "Company" therefore reaches a member of "Company/Engineering/Backend". func (s *State) EffectiveGroups(userID string) map[string]bool { out := map[string]bool{} for _, m := range s.Memberships { if m.UserID != userID { continue } for _, id := range s.Ancestors(m.GroupID) { out[id] = true } } return out } // IsGroupAdmin reports whether the user administers groupID, either directly // or by administering a group above it. Server admins administer everything. func (s *State) IsGroupAdmin(userID, groupID string) bool { if u := s.Users[userID]; u != nil && u.ServerAdmin { return true } anc := s.Ancestors(groupID) for _, m := range s.Memberships { if m.UserID == userID && m.Role == RoleAdmin && contains(anc, m.GroupID) { return true } } return false } // DatabasePerm is the strongest permission the user holds on a database. func (s *State) DatabasePerm(userID, dbID string) Perm { db, ok := s.Databases[dbID] if !ok { return PermNone } if s.IsGroupAdmin(userID, db.OwnerGroupID) { return PermAdmin } reach := s.EffectiveGroups(userID) best := PermNone for _, g := range s.Grants { if g.DatabaseID != dbID || !reach[g.GroupID] { continue } if g.Perm.Rank() > best.Rank() { best = g.Perm } } return best } // Assignment pairs a database with the caller's permission on it. type Assignment struct { DB *Database Perm Perm } // Assignments lists every database the user may see, strongest permission // first resolved, sorted by name for a stable client view. func (s *State) Assignments(userID string) []Assignment { out := []Assignment{} for id, db := range s.Databases { if p := s.DatabasePerm(userID, id); p != PermNone { out = append(out, Assignment{DB: db, Perm: p}) } } sort.Slice(out, func(i, j int) bool { if out[i].DB.Name != out[j].DB.Name { return out[i].DB.Name < out[j].DB.Name } return out[i].DB.ID < out[j].DB.ID }) return out } // Watchers lists the user IDs that must be told when a database changes. func (s *State) Watchers(dbID string) []string { out := []string{} for id, u := range s.Users { if !u.Disabled && s.DatabasePerm(id, dbID) != PermNone { out = append(out, id) } } sort.Strings(out) return out } func contains(ss []string, s string) bool { for _, v := range ss { if v == s { return true } } return false }