github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/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  }