github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/teams/list.go (about)

     1  package teams
     2  
     3  import (
     4  	"fmt"
     5  	"sort"
     6  
     7  	"golang.org/x/net/context"
     8  
     9  	"github.com/keybase/client/go/libkb"
    10  	"github.com/keybase/client/go/protocol/keybase1"
    11  	"github.com/keybase/client/go/uidmap"
    12  )
    13  
    14  type statusList struct {
    15  	Teams  []keybase1.MemberInfo `json:"teams"`
    16  	Status libkb.AppStatus       `json:"status"`
    17  }
    18  
    19  func (r *statusList) GetAppStatus() *libkb.AppStatus {
    20  	return &r.Status
    21  }
    22  
    23  func getTeamsListFromServer(ctx context.Context, g *libkb.GlobalContext, uid keybase1.UID, all bool,
    24  	countMembers bool, includeImplicitTeams bool, rootTeamID keybase1.TeamID) ([]keybase1.MemberInfo, error) {
    25  	var endpoint string
    26  	if all {
    27  		endpoint = "team/teammates_for_user"
    28  	} else {
    29  		endpoint = "team/for_user"
    30  	}
    31  	a := libkb.NewAPIArg(endpoint)
    32  	a.Args = libkb.HTTPArgs{}
    33  	if uid.Exists() {
    34  		a.Args["uid"] = libkb.S{Val: uid.String()}
    35  	}
    36  	if countMembers {
    37  		a.Args["count_members"] = libkb.B{Val: true}
    38  	}
    39  	if !rootTeamID.IsNil() {
    40  		a.Args["root_team_id"] = libkb.S{Val: rootTeamID.String()}
    41  	}
    42  	if includeImplicitTeams {
    43  		a.Args["include_implicit_teams"] = libkb.B{Val: true}
    44  	}
    45  	mctx := libkb.NewMetaContext(ctx, g)
    46  	a.SessionType = libkb.APISessionTypeREQUIRED
    47  	var list statusList
    48  	if err := mctx.G().API.GetDecode(mctx, a, &list); err != nil {
    49  		return nil, err
    50  	}
    51  	return list.Teams, nil
    52  }
    53  
    54  func memberNeedAdmin(member keybase1.MemberInfo, meUID keybase1.UID) bool {
    55  	return member.UserID == meUID &&
    56  		(member.Role.IsAdminOrAbove() || (member.Implicit != nil && member.Implicit.Role.IsAdminOrAbove()))
    57  }
    58  
    59  // verifyMemberRoleInTeam checks if role give in MemberInfo matches
    60  // what team chain says. Nothing is checked when MemberInfo's role is
    61  // NONE, in this context it means that user has implied membership in
    62  // the team and no role given in sigchain.
    63  func verifyMemberRoleInTeam(ctx context.Context, userID keybase1.UID, expectedRole keybase1.TeamRole,
    64  	team *Team) (res keybase1.UserVersion, err error) {
    65  	if expectedRole == keybase1.TeamRole_NONE {
    66  		return res, nil
    67  	}
    68  
    69  	memberUV, err := team.chain().GetLatestUVWithUID(userID)
    70  	if err != nil {
    71  		return res, err
    72  	}
    73  	role, err := team.chain().GetUserRole(memberUV)
    74  	if err != nil {
    75  		return res, err
    76  	}
    77  	if role != expectedRole {
    78  		return res, fmt.Errorf("unexpected member role: expected %v but actual role is %v", expectedRole, role)
    79  	}
    80  	return memberUV, nil
    81  }
    82  
    83  type localLoadedTeams struct {
    84  	libkb.Contextified
    85  	teams map[keybase1.TeamID]*Team
    86  }
    87  
    88  func newLocalLoadedTeams(g *libkb.GlobalContext) localLoadedTeams {
    89  	return localLoadedTeams{
    90  		Contextified: libkb.NewContextified(g),
    91  		teams:        make(map[keybase1.TeamID]*Team),
    92  	}
    93  }
    94  
    95  // getTeamForMember tries to return *Team in a recent enough state to
    96  // contain member with correct role as set in MemberInfo. It might
    97  // trigger a reload with ForceRepoll if cached state does not match.
    98  func (l *localLoadedTeams) getTeamForMember(ctx context.Context, member keybase1.MemberInfo,
    99  	needAdmin bool) (team *Team, uv keybase1.UserVersion, err error) {
   100  	teamID := member.TeamID
   101  	team = l.teams[teamID]
   102  	if team == nil {
   103  		// Team was not there in local cache - this is the first time
   104  		// localLoadedTeams is asked for this team. Try with no
   105  		// forceRepoll first.
   106  		team, err = Load(ctx, l.G(), keybase1.LoadTeamArg{
   107  			ID:               teamID,
   108  			NeedAdmin:        needAdmin,
   109  			Public:           teamID.IsPublic(),
   110  			ForceRepoll:      false,
   111  			RefreshUIDMapper: true,
   112  		})
   113  		if err != nil {
   114  			return nil, uv, err
   115  		}
   116  		l.teams[teamID] = team
   117  	}
   118  
   119  	memberUV, err := verifyMemberRoleInTeam(ctx, member.UserID, member.Role, team)
   120  	if err != nil {
   121  		team, err = Load(ctx, l.G(), keybase1.LoadTeamArg{
   122  			ID:          teamID,
   123  			NeedAdmin:   needAdmin,
   124  			Public:      teamID.IsPublic(),
   125  			ForceRepoll: true,
   126  		})
   127  		if err != nil {
   128  			return nil, uv, err
   129  		}
   130  		l.teams[teamID] = team
   131  
   132  		memberUV, err = verifyMemberRoleInTeam(ctx, member.UserID, member.Role, team)
   133  		if err != nil {
   134  			return nil, uv, fmt.Errorf("server was wrong about role in team : %v", err)
   135  		}
   136  	}
   137  
   138  	return team, memberUV, nil
   139  }
   140  
   141  func getUsernameAndFullName(ctx context.Context, g *libkb.GlobalContext,
   142  	uid keybase1.UID) (username libkb.NormalizedUsername, fullName string, err error) {
   143  	username, err = g.GetUPAKLoader().LookupUsername(ctx, uid)
   144  	if err != nil {
   145  		return "", "", err
   146  	}
   147  	fullName, err = libkb.GetFullName(libkb.NewMetaContext(ctx, g), uid)
   148  	if err != nil {
   149  		return "", "", err
   150  	}
   151  
   152  	return username, fullName, err
   153  }
   154  
   155  func ListTeamsVerified(ctx context.Context, g *libkb.GlobalContext,
   156  	arg keybase1.TeamListVerifiedArg) (*keybase1.AnnotatedTeamList, error) {
   157  	tracer := g.CTimeTracer(ctx, "TeamList.ListTeamsVerified", true)
   158  	defer tracer.Finish()
   159  	m := libkb.NewMetaContext(ctx, g)
   160  
   161  	tracer.Stage("Resolve QueryUID")
   162  	var queryUID keybase1.UID
   163  	if arg.UserAssertion != "" {
   164  		res := g.Resolver.ResolveFullExpression(m, arg.UserAssertion)
   165  		if res.GetError() != nil {
   166  			return nil, res.GetError()
   167  		}
   168  		queryUID = res.GetUID()
   169  	}
   170  
   171  	meUID := g.ActiveDevice.UID()
   172  	if meUID.IsNil() {
   173  		return nil, libkb.LoginRequiredError{}
   174  	}
   175  
   176  	tracer.Stage("Server")
   177  	teams, err := getTeamsListFromServer(ctx, g, queryUID, false, /* all */
   178  		false /* countMembers */, arg.IncludeImplicitTeams, keybase1.NilTeamID())
   179  	if err != nil {
   180  		return nil, err
   181  	}
   182  
   183  	if arg.UserAssertion == "" {
   184  		queryUID = meUID
   185  	}
   186  
   187  	tracer.Stage("LookupQueryUsername")
   188  	queryUsername, queryFullName, err := getUsernameAndFullName(context.Background(), g, queryUID)
   189  	if err != nil {
   190  		return nil, err
   191  	}
   192  
   193  	res := &keybase1.AnnotatedTeamList{
   194  		Teams: nil,
   195  	}
   196  
   197  	if len(teams) == 0 {
   198  		return res, nil
   199  	}
   200  
   201  	tracer.Stage("Loads")
   202  
   203  	loadedTeams := newLocalLoadedTeams(g)
   204  	expectEmptyList := true
   205  
   206  	for _, memberInfo := range teams {
   207  		serverSaysNeedAdmin := memberNeedAdmin(memberInfo, meUID)
   208  		team, _, err := loadedTeams.getTeamForMember(ctx, memberInfo, serverSaysNeedAdmin)
   209  		if err != nil {
   210  			m.Debug("| Error in getTeamForMember ID:%s UID:%s: %v; skipping team", memberInfo.TeamID, memberInfo.UserID, err)
   211  			expectEmptyList = false // so we tell user about errors at the end.
   212  			continue
   213  		}
   214  
   215  		if memberInfo.IsImplicitTeam && !arg.IncludeImplicitTeams {
   216  			m.Debug("| TeamList skipping implicit team: server-team:%v server-uid:%v", memberInfo.TeamID, memberInfo.UserID)
   217  			continue
   218  		}
   219  
   220  		expectEmptyList = false
   221  
   222  		if memberInfo.UserID != queryUID {
   223  			m.Debug("| Expected memberInfo for UID:%s, got UID:%s", queryUID, memberInfo.UserID)
   224  			continue
   225  		}
   226  
   227  		anMemberInfo := &keybase1.AnnotatedMemberInfo{
   228  			TeamID:              team.ID,
   229  			FqName:              team.Name().String(),
   230  			UserID:              memberInfo.UserID,
   231  			Role:                memberInfo.Role, // memberInfo.Role has been verified during getTeamForMember
   232  			IsImplicitTeam:      team.IsImplicit(),
   233  			IsOpenTeam:          team.IsOpen(),
   234  			Implicit:            memberInfo.Implicit, // This part is still server trust
   235  			Username:            queryUsername.String(),
   236  			FullName:            queryFullName,
   237  			MemberCount:         0,
   238  			Status:              keybase1.TeamMemberStatus_ACTIVE,
   239  			AllowProfilePromote: memberInfo.AllowProfilePromote,
   240  			IsMemberShowcased:   memberInfo.IsMemberShowcased,
   241  		}
   242  
   243  		if team.IsImplicit() {
   244  			displayName, err := team.ImplicitTeamDisplayNameString(ctx)
   245  			if err != nil {
   246  				m.Warning("| Failed to get ImplicitTeamDisplayNameString() for team %q: %v", team.ID, err)
   247  			} else {
   248  				anMemberInfo.ImpTeamDisplayName = displayName
   249  			}
   250  		}
   251  
   252  		anMemberInfo.MemberCount, err = team.calculateAndCacheMemberCount(ctx)
   253  		if err != nil {
   254  			continue
   255  		}
   256  
   257  		res.Teams = append(res.Teams, *anMemberInfo)
   258  	}
   259  
   260  	if len(res.Teams) == 0 && !expectEmptyList {
   261  		return res, fmt.Errorf("multiple errors while loading team list")
   262  	}
   263  
   264  	return res, nil
   265  }
   266  
   267  func ListAll(ctx context.Context, g *libkb.GlobalContext, arg keybase1.TeamListTeammatesArg) (*keybase1.AnnotatedTeamList, error) {
   268  	tracer := g.CTimeTracer(ctx, "TeamList.ListAll", true)
   269  	defer tracer.Finish()
   270  
   271  	meUID := g.ActiveDevice.UID()
   272  	if meUID.IsNil() {
   273  		return nil, libkb.LoginRequiredError{}
   274  	}
   275  
   276  	tracer.Stage("Server")
   277  	teams, err := getTeamsListFromServer(ctx, g, "" /*uid*/, true /*all*/, false /* countMembers */, arg.IncludeImplicitTeams, keybase1.NilTeamID())
   278  	if err != nil {
   279  		return nil, err
   280  	}
   281  
   282  	res := &keybase1.AnnotatedTeamList{
   283  		Teams: nil,
   284  	}
   285  
   286  	if len(teams) == 0 {
   287  		return res, nil
   288  	}
   289  
   290  	tracer.Stage("Loads")
   291  
   292  	loadedTeams := newLocalLoadedTeams(g)
   293  	expectEmptyList := true
   294  
   295  	for _, memberInfo := range teams {
   296  		serverSaysNeedAdmin := memberNeedAdmin(memberInfo, meUID)
   297  		team, memberUV, err := loadedTeams.getTeamForMember(ctx, memberInfo, serverSaysNeedAdmin)
   298  		if err != nil {
   299  			g.Log.CDebugf(ctx, "| Error in getTeamForMember ID:%s UID:%s: %v; skipping team", memberInfo.TeamID, memberInfo.UserID, err)
   300  			expectEmptyList = false // so we tell user about errors at the end.
   301  			continue
   302  		}
   303  
   304  		// TODO: memberUV is always empty for implicit admins that are
   305  		// not real members, because getTeamForMember will not try to
   306  		// look into ancestor teams.
   307  
   308  		if memberInfo.IsImplicitTeam && !arg.IncludeImplicitTeams {
   309  			g.Log.CDebugf(ctx, "| TeamList skipping implicit team: server-team:%v server-uid:%v", memberInfo.TeamID, memberInfo.UserID)
   310  			continue
   311  		}
   312  
   313  		anMemberInfo := &keybase1.AnnotatedMemberInfo{
   314  			TeamID:         team.ID,
   315  			FqName:         team.Name().String(),
   316  			UserID:         memberInfo.UserID,
   317  			EldestSeqno:    memberUV.EldestSeqno,
   318  			Role:           memberInfo.Role, // memberInfo.Role has been verified during getTeamForMember
   319  			IsImplicitTeam: team.IsImplicit(),
   320  			Implicit:       memberInfo.Implicit, // This part is still server trust
   321  			// Assume member is active, later this field might be
   322  			// mutated to false after consulting UIDMapper.
   323  			Status: keybase1.TeamMemberStatus_ACTIVE,
   324  		}
   325  
   326  		res.Teams = append(res.Teams, *anMemberInfo)
   327  	}
   328  
   329  	if len(teams) == 0 && !expectEmptyList {
   330  		return res, fmt.Errorf("Multiple errors during loading listAll")
   331  	}
   332  
   333  	tracer.Stage("UIDMapper")
   334  
   335  	var uids []keybase1.UID
   336  	for _, member := range res.Teams {
   337  		uids = append(uids, member.UserID)
   338  	}
   339  
   340  	namePkgs, err := uidmap.MapUIDsReturnMap(ctx, g.UIDMapper, g, uids,
   341  		defaultFullnameFreshness, defaultNetworkTimeBudget, true)
   342  	if err != nil {
   343  		// UIDMap returned an error, but there may be useful data in the result.
   344  		g.Log.CDebugf(ctx, "| MapUIDsReturnMap returned: %v", err)
   345  	}
   346  
   347  	for i := range res.Teams {
   348  		member := &res.Teams[i]
   349  		if pkg, ok := namePkgs[member.UserID]; ok {
   350  			member.Username = pkg.NormalizedUsername.String()
   351  			if pkg.FullName != nil {
   352  				member.FullName = string(pkg.FullName.FullName)
   353  				// TODO: We can't check if purely implicit admin is
   354  				// reset because we are not looking deep enough to get
   355  				// member uv. member.EldestSeqno will always be 0 for
   356  				// implicit admins. Only flag members that have actual
   357  				// role in the team here.
   358  				if member.Role != keybase1.TeamRole_NONE && pkg.FullName.EldestSeqno != member.EldestSeqno {
   359  					member.Status = keybase1.TeamMemberStatus_RESET
   360  				}
   361  				if pkg.FullName.Status == keybase1.StatusCode_SCDeleted {
   362  					member.Status = keybase1.TeamMemberStatus_DELETED
   363  				}
   364  			}
   365  		}
   366  	}
   367  
   368  	return res, nil
   369  }
   370  
   371  func ListSubteamsRecursive(ctx context.Context, g *libkb.GlobalContext,
   372  	parentTeamName string, forceRepoll bool) (res []keybase1.TeamIDAndName, err error) {
   373  	parent, err := Load(ctx, g, keybase1.LoadTeamArg{
   374  		Name:        parentTeamName,
   375  		NeedAdmin:   true,
   376  		ForceRepoll: forceRepoll,
   377  	})
   378  	if err != nil {
   379  		return nil, err
   380  	}
   381  
   382  	teams, err := parent.loadAllTransitiveSubteams(ctx, forceRepoll)
   383  	if err != nil {
   384  		return nil, err
   385  	}
   386  
   387  	for _, team := range teams {
   388  		res = append(res, keybase1.TeamIDAndName{
   389  			Id:   team.ID,
   390  			Name: team.Name(),
   391  		})
   392  	}
   393  	return res, nil
   394  }
   395  
   396  const blankSeitanLabel = "<token without label>"
   397  
   398  func ComputeSeitanInviteDisplayName(ctx context.Context, team *Team, invite keybase1.TeamInvite) (name keybase1.TeamInviteDisplayName, err error) {
   399  	pkey, err := SeitanDecodePKey(string(invite.Name))
   400  	if err != nil {
   401  		return name, err
   402  	}
   403  	keyAndLabel, err := pkey.DecryptKeyAndLabel(ctx, team)
   404  	if err != nil {
   405  		return name, err
   406  	}
   407  
   408  	version, err := keyAndLabel.V()
   409  	if err != nil {
   410  		return name, err
   411  	}
   412  	var label keybase1.SeitanKeyLabel
   413  	switch version {
   414  	case keybase1.SeitanKeyAndLabelVersion_V1:
   415  		v1 := keyAndLabel.V1()
   416  		label = v1.L
   417  	case keybase1.SeitanKeyAndLabelVersion_V2:
   418  		v2 := keyAndLabel.V2()
   419  		label = v2.L
   420  	default:
   421  		return "", fmt.Errorf("Unknown version: %v", version)
   422  	}
   423  
   424  	labelType, err := label.T()
   425  	if err != nil {
   426  		return name, err
   427  	}
   428  	switch labelType {
   429  	case keybase1.SeitanKeyLabelType_SMS:
   430  		sms := label.Sms()
   431  		smsName := blankSeitanLabel
   432  		if sms.F != "" && sms.N != "" {
   433  			smsName = fmt.Sprintf("%s (%s)", sms.F, sms.N)
   434  		} else if sms.F != "" {
   435  			smsName = sms.F
   436  		} else if sms.N != "" {
   437  			smsName = sms.N
   438  		}
   439  		return keybase1.TeamInviteDisplayName(smsName), nil
   440  	case keybase1.SeitanKeyLabelType_GENERIC:
   441  		return keybase1.TeamInviteDisplayName(label.Generic().L), nil
   442  	default:
   443  		return keybase1.TeamInviteDisplayName(blankSeitanLabel), nil
   444  	}
   445  }
   446  
   447  func ComputeInvitelinkDisplayName(mctx libkb.MetaContext, team *Team, invite keybase1.TeamInvite) (name keybase1.TeamInviteDisplayName, err error) {
   448  	pkey, err := SeitanDecodePKey(string(invite.Name))
   449  	if err != nil {
   450  		return name, err
   451  	}
   452  	keyAndLabel, err := pkey.DecryptKeyAndLabel(mctx.Ctx(), team)
   453  	if err != nil {
   454  		return name, err
   455  	}
   456  
   457  	version, err := keyAndLabel.V()
   458  	if err != nil {
   459  		return name, err
   460  	}
   461  	if version != keybase1.SeitanKeyAndLabelVersion_Invitelink {
   462  		return name, fmt.Errorf("AnnotateInvitelink: version not an invitelink: %v", version)
   463  	}
   464  
   465  	ikey := keyAndLabel.Invitelink().I
   466  	sikey, err := GenerateSIKeyInvitelink(ikey)
   467  	if err != nil {
   468  		return name, err
   469  	}
   470  	id, err := sikey.GenerateShortTeamInviteID()
   471  	if err != nil {
   472  		return name, err
   473  	}
   474  
   475  	url := GenerateInvitelinkURL(mctx, ikey, id)
   476  	name = keybase1.TeamInviteDisplayName(url)
   477  	return name, nil
   478  }
   479  
   480  func annotateTeamUsedInviteLogPoints(points []keybase1.TeamUsedInviteLogPoint, namePkgs map[keybase1.UID]libkb.UsernamePackage) (ret []keybase1.AnnotatedTeamUsedInviteLogPoint) {
   481  	for _, point := range points {
   482  		username := namePkgs[point.Uv.Uid].NormalizedUsername
   483  		ret = append(ret, keybase1.AnnotatedTeamUsedInviteLogPoint{TeamUsedInviteLogPoint: point, Username: username.String()})
   484  	}
   485  	return ret
   486  }
   487  
   488  // GetAnnotatedInvitesAndMembersForUI returns annotated invites and members lists from the sigchain,
   489  // except that Keybase-type invites are treated as team members. This is for the purpose of
   490  // collecting them together in the UI.
   491  func GetAnnotatedInvitesAndMembersForUI(mctx libkb.MetaContext, team *Team,
   492  ) (members []keybase1.TeamMemberDetails, annotatedInvites []keybase1.AnnotatedTeamInvite, err error) {
   493  
   494  	allAnnotatedInvites, err := getAnnotatedInvites(mctx, team)
   495  	if err != nil {
   496  		return nil, nil, err
   497  	}
   498  	members, err = MembersDetails(mctx.Ctx(), mctx.G(), team)
   499  	if err != nil {
   500  		return nil, nil, err
   501  	}
   502  
   503  	for _, annotatedInvite := range allAnnotatedInvites {
   504  		inviteC, err := annotatedInvite.InviteMetadata.Invite.Type.C()
   505  		if err != nil {
   506  			return nil, nil, err
   507  		}
   508  		extC, err := annotatedInvite.InviteExt.C()
   509  		if err != nil {
   510  			return nil, nil, err
   511  		}
   512  		if inviteC != extC {
   513  			return nil, nil, fmt.Errorf("got invite category %v from invite but %v from inviteExt", inviteC, extC)
   514  		}
   515  		if inviteC == keybase1.TeamInviteCategory_KEYBASE {
   516  			code, err := annotatedInvite.InviteMetadata.Status.Code()
   517  			if err != nil {
   518  				return nil, nil, err
   519  			}
   520  			if code != keybase1.TeamInviteMetadataStatusCode_ACTIVE {
   521  				// Skip non active keybase-type invites
   522  				continue
   523  			}
   524  
   525  			keybaseExt := annotatedInvite.InviteExt.Keybase()
   526  			details := keybase1.TeamMemberDetails{
   527  				Uv:       keybaseExt.InviteeUv,
   528  				Username: keybaseExt.Username,
   529  				NeedsPUK: true,
   530  				FullName: keybaseExt.FullName,
   531  				Status:   keybaseExt.Status,
   532  				Role:     annotatedInvite.InviteMetadata.Invite.Role,
   533  			}
   534  
   535  			// Add the keybase-type invite to the members list. However, if there already exists a
   536  			// cryptomember who is inactive (reset or deleted), and additionally there is a
   537  			// keybase-type invite for the same UID, overwrite the inactive one with the invite.
   538  			overrodeExistingInactiveMember := false
   539  			for idx, member := range members {
   540  				if details.Uv.Uid.Equal(member.Uv.Uid) && !member.Status.IsActive() {
   541  					members[idx] = details
   542  					overrodeExistingInactiveMember = true
   543  				}
   544  			}
   545  			if !overrodeExistingInactiveMember {
   546  				members = append(members, details)
   547  			}
   548  		} else {
   549  			annotatedInvites = append(annotatedInvites, annotatedInvite)
   550  		}
   551  	}
   552  
   553  	return members, annotatedInvites, nil
   554  }
   555  
   556  func getAnnotatedInvites(mctx libkb.MetaContext, team *Team) (annotatedInvites []keybase1.AnnotatedTeamInvite, err error) {
   557  	inviteMDs := team.chain().inner.InviteMetadatas
   558  	if len(inviteMDs) == 0 {
   559  		return nil, nil
   560  	}
   561  
   562  	// UID list to pass to UIDMapper to get full names. Duplicate UIDs
   563  	// are fine, MapUIDsReturnMap will filter them out.
   564  	var uids []keybase1.UID
   565  	for _, inviteMD := range inviteMDs {
   566  		invite := inviteMD.Invite
   567  		uids = append(uids, invite.Inviter.Uid)
   568  
   569  		category, err := invite.Type.C()
   570  		if err != nil {
   571  			return nil, err
   572  		}
   573  		if category == keybase1.TeamInviteCategory_KEYBASE {
   574  			uv, err := invite.KeybaseUserVersion()
   575  			if err != nil {
   576  				return nil, err
   577  			}
   578  			uids = append(uids, uv.Uid)
   579  		}
   580  		for _, usedInviteLogPoint := range inviteMD.UsedInvites {
   581  			uids = append(uids, usedInviteLogPoint.Uv.Uid)
   582  		}
   583  	}
   584  
   585  	namePkgs, err := uidmap.MapUIDsReturnMapMctx(mctx, uids, defaultFullnameFreshness,
   586  		defaultNetworkTimeBudget, true)
   587  	if err != nil {
   588  		// UIDMap returned an error, but there may be useful data in the result.
   589  		mctx.Debug("AnnotateInvites: MapUIDsReturnMap failed: %s", err)
   590  	}
   591  
   592  	teamName := team.Name().String()
   593  	for _, inviteMD := range inviteMDs {
   594  		invite := inviteMD.Invite
   595  		inviterUsername := namePkgs[invite.Inviter.Uid].NormalizedUsername
   596  
   597  		category, err := invite.Type.C()
   598  		if err != nil {
   599  			return nil, err
   600  		}
   601  		// defaults; overridden by some invite types later
   602  		inviteExt := keybase1.NewAnnotatedTeamInviteExtDefault(category)
   603  		displayName := keybase1.TeamInviteDisplayName(invite.Name)
   604  		switch category {
   605  		case keybase1.TeamInviteCategory_SBS:
   606  			displayName = keybase1.TeamInviteDisplayName(fmt.Sprintf("%s@%s", invite.Name, string(invite.Type.Sbs())))
   607  		case keybase1.TeamInviteCategory_KEYBASE:
   608  			// "keybase" invites (i.e. pukless users) have user version for name
   609  			inviteeUV, err := invite.KeybaseUserVersion()
   610  			if err != nil {
   611  				return nil, err
   612  			}
   613  			pkg := namePkgs[inviteeUV.Uid]
   614  			status := keybase1.TeamMemberStatus_ACTIVE
   615  			var fullName keybase1.FullName
   616  			if pkg.FullName != nil {
   617  				if pkg.FullName.EldestSeqno != inviteeUV.EldestSeqno {
   618  					status = keybase1.TeamMemberStatus_RESET
   619  				}
   620  				if pkg.FullName.Status == keybase1.StatusCode_SCDeleted {
   621  					status = keybase1.TeamMemberStatus_DELETED
   622  				}
   623  				fullName = pkg.FullName.FullName
   624  			}
   625  
   626  			if pkg.NormalizedUsername.IsNil() {
   627  				return nil, fmt.Errorf("failed to get username from UIDMapper for uv %v", inviteeUV)
   628  			}
   629  
   630  			displayName = keybase1.TeamInviteDisplayName(pkg.NormalizedUsername.String())
   631  			inviteExt = keybase1.NewAnnotatedTeamInviteExtWithKeybase(keybase1.KeybaseInviteExt{
   632  				InviteeUv: inviteeUV,
   633  				Status:    status,
   634  				FullName:  fullName,
   635  				Username:  pkg.NormalizedUsername.String(),
   636  			})
   637  		case keybase1.TeamInviteCategory_SEITAN:
   638  			displayName, err = ComputeSeitanInviteDisplayName(mctx.Ctx(), team, invite)
   639  			if err != nil {
   640  				// There are seitan invites in the wild from before
   641  				// https://github.com/keybase/client/pull/9816 These can no
   642  				// longer be decrypted, we hide them.
   643  				mctx.Debug("error annotating seitan invite (%v): %v", invite.Id, err)
   644  				continue
   645  			}
   646  		case keybase1.TeamInviteCategory_INVITELINK:
   647  			displayName, err = ComputeInvitelinkDisplayName(mctx, team, invite)
   648  			if err != nil {
   649  				mctx.Warning("error annotating invitelink (%v): %v", invite.Id, err)
   650  				continue
   651  			}
   652  			inviteExt = keybase1.NewAnnotatedTeamInviteExtWithInvitelink(keybase1.InvitelinkInviteExt{
   653  				AnnotatedUsedInvites: annotateTeamUsedInviteLogPoints(inviteMD.UsedInvites, namePkgs),
   654  			})
   655  		default:
   656  		}
   657  
   658  		now := mctx.G().Clock().Now()
   659  		isValid, validityDescription := inviteMD.ComputeValidity(now, team.Data.Chain.UserLog)
   660  		annotatedInvites = append(annotatedInvites, keybase1.AnnotatedTeamInvite{
   661  			InviteMetadata:      inviteMD,
   662  			DisplayName:         displayName,
   663  			InviterUsername:     inviterUsername.String(),
   664  			TeamName:            teamName,
   665  			InviteExt:           inviteExt,
   666  			IsValid:             isValid,
   667  			ValidityDescription: validityDescription,
   668  		})
   669  	}
   670  
   671  	// Sort most recent first
   672  	sort.Slice(annotatedInvites, func(i, j int) bool {
   673  		iSeqno := annotatedInvites[i].InviteMetadata.TeamSigMeta.SigMeta.SigChainLocation.Seqno
   674  		jSeqno := annotatedInvites[j].InviteMetadata.TeamSigMeta.SigMeta.SigChainLocation.Seqno
   675  		return iSeqno >= jSeqno
   676  	})
   677  
   678  	return annotatedInvites, nil
   679  }
   680  
   681  func TeamTreeUnverified(ctx context.Context, g *libkb.GlobalContext, arg keybase1.TeamTreeUnverifiedArg) (res keybase1.TeamTreeResult, err error) {
   682  	serverList, err := getTeamsListFromServer(ctx, g, "" /* uid */, false, /* all */
   683  		false /* countMembers */, false /* includeImplicitTeams */, arg.Name.RootID())
   684  	if err != nil {
   685  		return res, err
   686  	}
   687  	rootName := arg.Name.RootAncestorName()
   688  
   689  	// Map from team name (string) to entry
   690  	entryMap := make(map[string]keybase1.TeamTreeEntry)
   691  
   692  	// The server might have omitted some teams, oh well.
   693  	// Trusts the server for role.
   694  	// Load the teams by ID to make sure they are valid and get the validated names.
   695  	for _, info := range serverList {
   696  		serverName, err := info.TeamName()
   697  		if err != nil {
   698  			return res, err
   699  		}
   700  		if !rootName.IsAncestorOf(serverName) && !rootName.Eq(serverName) {
   701  			// Skip those not in this tree.
   702  			continue
   703  		}
   704  		team, err := Load(ctx, g, keybase1.LoadTeamArg{
   705  			ID:          info.TeamID,
   706  			ForceRepoll: true,
   707  		})
   708  		if err != nil {
   709  			return res, err
   710  		}
   711  		var admin bool // true if an admin or implicit admin
   712  		if info.Role.IsAdminOrAbove() {
   713  			admin = true
   714  		}
   715  		if info.Implicit != nil && info.Implicit.Role.IsAdminOrAbove() {
   716  			admin = true
   717  		}
   718  		entryMap[team.Name().String()] = keybase1.TeamTreeEntry{
   719  			Name:  team.Name(),
   720  			Admin: admin,
   721  		}
   722  	}
   723  
   724  	// Add all parent names (recursively) So that if only A.B.C is in the list,
   725  	// we add A.B and A as well.  Adding map entries while iterating is safe.
   726  	// "If map entries are created during iteration, that entry may be produced
   727  	// during the iteration or may be skipped."
   728  	for _, entry := range entryMap {
   729  		name := entry.Name.DeepCopy()
   730  		for name.Depth() > 0 {
   731  			_, ok := entryMap[name.String()]
   732  			if !ok {
   733  				entryMap[name.String()] = keybase1.TeamTreeEntry{
   734  					Name:  name,
   735  					Admin: false,
   736  				}
   737  			}
   738  			name, err = name.Parent()
   739  			if err != nil || (!rootName.IsAncestorOf(name) && !rootName.Eq(name)) {
   740  				break
   741  			}
   742  		}
   743  	}
   744  
   745  	for _, entry := range entryMap {
   746  		res.Entries = append(res.Entries, entry)
   747  	}
   748  
   749  	if len(res.Entries) == 0 {
   750  		g.Log.CDebugf(ctx, "| TeamTree not teams matched")
   751  		// Try to get a nicer error by loading the team directly.
   752  		_, err = Load(ctx, g, keybase1.LoadTeamArg{Name: arg.Name.String()})
   753  		if err != nil {
   754  			return res, err
   755  		}
   756  		return res, fmt.Errorf("team not found: %v", rootName)
   757  	}
   758  
   759  	// Order into a tree order. Which happens to be alphabetical ordering.
   760  	// Example: [a, a.b, a.b.c, a.b.d, a.e.f, a.e.g]
   761  	sort.Slice(res.Entries, func(i, j int) bool {
   762  		return res.Entries[i].Name.String() < res.Entries[j].Name.String()
   763  	})
   764  
   765  	return res, nil
   766  }