github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/teams/list_fast.go (about) 1 package teams 2 3 import ( 4 "fmt" 5 "sort" 6 "strings" 7 8 "golang.org/x/net/context" 9 10 "github.com/keybase/client/go/libkb" 11 "github.com/keybase/client/go/protocol/keybase1" 12 ) 13 14 // This is server trust version of TeamList functionality. It will not 15 // perform any team loads to verify if server does not lie about our 16 // membership in returned teams. It will also rely on the server to 17 // return member counts for each team. All of this makes this version 18 // much faster and less heavy on the server - even though UIDMapper is 19 // used in the untrusting functions, a lot of calls were made anyway 20 // during team loads (e.g. merkle paths). 21 22 // See also: teams/list.go 23 24 func ListTeamsUnverified(ctx context.Context, g *libkb.GlobalContext, arg keybase1.TeamListUnverifiedArg) (*keybase1.AnnotatedTeamList, error) { 25 tracer := g.CTimeTracer(ctx, "TeamList.ListTeamsUnverified", true) 26 defer tracer.Finish() 27 28 m := libkb.NewMetaContext(ctx, g) 29 30 tracer.Stage("Resolve QueryUID") 31 var queryUID keybase1.UID 32 if arg.UserAssertion != "" { 33 res := g.Resolver.ResolveFullExpression(m, arg.UserAssertion) 34 if res.GetError() != nil { 35 return nil, res.GetError() 36 } 37 queryUID = res.GetUID() 38 } 39 40 meUID := g.ActiveDevice.UID() 41 if meUID.IsNil() { 42 return nil, libkb.LoginRequiredError{} 43 } 44 45 tracer.Stage("Server") 46 47 // We have a very simple cache in case we error out on this call. In the 48 // case of a network error we try to serve old cached data if we have some. 49 // The cache is updated on successful requests but otherwise unmaintained 50 // so should only be used if we are trying to return a best-effort result. 51 cacheKey := listTeamsUnverifiedCacheKey(meUID, queryUID, arg.IncludeImplicitTeams) 52 teams, err := getTeamsListFromServer(ctx, g, queryUID, 53 false /* all */, true /* countMembers */, arg.IncludeImplicitTeams, keybase1.NilTeamID()) 54 switch err.(type) { 55 case nil: 56 if err = g.GetKVStore().PutObj(cacheKey, nil, teams); err != nil { 57 m.Debug("| ListTeamsUnverified unable to put cache item: %v", err) 58 } 59 case libkb.APINetError: 60 if found, cerr := g.GetKVStore().GetInto(&teams, cacheKey); cerr != nil || !found { 61 // Nothing we can do here. 62 m.Debug("| ListTeamsUnverified unable to get cache item: %v, found: %v", cerr, found) 63 return nil, err 64 } 65 default: 66 return nil, err 67 } 68 69 res := &keybase1.AnnotatedTeamList{ 70 Teams: nil, 71 } 72 73 if len(teams) == 0 { 74 return res, nil 75 } 76 77 if arg.UserAssertion == "" { 78 queryUID = meUID 79 } 80 81 tracer.Stage("LookupQueryUsername") 82 queryUsername, queryFullName, err := getUsernameAndFullName(context.Background(), g, queryUID) 83 if err != nil { 84 return nil, err 85 } 86 87 for _, memberInfo := range teams { 88 if memberInfo.IsImplicitTeam && !arg.IncludeImplicitTeams { 89 m.Debug("| ListTeamsUnverified skipping implicit team: server-team:%v server-uid:%v", memberInfo.TeamID, memberInfo.UserID) 90 continue 91 } 92 93 anMemberInfo := keybase1.AnnotatedMemberInfo{ 94 TeamID: memberInfo.TeamID, 95 FqName: memberInfo.FqName, 96 UserID: memberInfo.UserID, 97 Role: memberInfo.Role, 98 IsImplicitTeam: memberInfo.IsImplicitTeam, 99 IsOpenTeam: memberInfo.IsOpenTeam, 100 Implicit: memberInfo.Implicit, 101 Username: queryUsername.String(), 102 FullName: queryFullName, 103 MemberCount: g.TeamMemberCountCache.GetWithFallback(memberInfo.TeamID, memberInfo.MemberCount), 104 Status: keybase1.TeamMemberStatus_ACTIVE, 105 AllowProfilePromote: memberInfo.AllowProfilePromote, 106 IsMemberShowcased: memberInfo.IsMemberShowcased, 107 } 108 109 res.Teams = append(res.Teams, anMemberInfo) 110 } 111 112 return res, nil 113 } 114 115 func listTeamsUnverifiedCacheKey(meUID, queryUID keybase1.UID, includeImplicitTeams bool) libkb.DbKey { 116 return libkb.DbKey{ 117 Typ: libkb.DBTeamList, 118 Key: fmt.Sprintf("%v-%v-%v", meUID, queryUID, includeImplicitTeams), 119 } 120 } 121 122 func ListSubteamsUnverified(mctx libkb.MetaContext, name keybase1.TeamName) (res keybase1.SubteamListResult, err error) { 123 tracer := mctx.G().CTimeTracer(mctx.Ctx(), "TeamList.ListSubteamsUnverified", true) 124 defer tracer.Finish() 125 126 meUID := mctx.G().ActiveDevice.UID() 127 if meUID.IsNil() { 128 return res, libkb.LoginRequiredError{} 129 } 130 131 emptyUID := keybase1.UID("") 132 teams, err := getTeamsListFromServer(mctx.Ctx(), mctx.G(), emptyUID, 133 false /* all */, true /* countMembers */, false /* includeImplicitTeams */, name.RootID()) 134 if err != nil { 135 return res, libkb.LoginRequiredError{} 136 } 137 138 var entries []keybase1.SubteamListEntry 139 for _, potentialSubteam := range teams { 140 if isSubteamByName(name, potentialSubteam.FqName) { 141 subteamName, err := keybase1.TeamNameFromString(potentialSubteam.FqName) 142 if err != nil { 143 return res, err 144 } 145 entries = append(entries, keybase1.SubteamListEntry{Name: subteamName, MemberCount: potentialSubteam.MemberCount, TeamID: potentialSubteam.TeamID}) 146 } 147 } 148 149 // Order alphabetically: e.g. [a, a.b, a.b.c, a.b.d, a.e.f, a.e.g] 150 sort.Slice(entries, func(i, j int) bool { 151 return entries[i].Name.String() < entries[j].Name.String() 152 }) 153 154 res.Entries = entries 155 return res, nil 156 } 157 158 func isSubteamByName(teamName keybase1.TeamName, potentialSubteamName string) bool { 159 // e.g. strings.HasPrefix("keybase.private", "keybase.") => true 160 return strings.HasPrefix(potentialSubteamName, teamName.String()+".") 161 }