github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/teams/service_helper.go (about)

     1  package teams
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"strings"
     7  	"time"
     8  
     9  	"golang.org/x/net/context"
    10  
    11  	"github.com/keybase/client/go/avatars"
    12  	email_utils "github.com/keybase/client/go/emails"
    13  	"github.com/keybase/client/go/engine"
    14  	"github.com/keybase/client/go/externals"
    15  	"github.com/keybase/client/go/kbun"
    16  	"github.com/keybase/client/go/libkb"
    17  	"github.com/keybase/client/go/protocol/keybase1"
    18  )
    19  
    20  func LoadTeamPlusApplicationKeys(ctx context.Context, g *libkb.GlobalContext, id keybase1.TeamID,
    21  	application keybase1.TeamApplication, refreshers keybase1.TeamRefreshers, includeKBFSKeys bool) (res keybase1.TeamPlusApplicationKeys, err error) {
    22  
    23  	team, err := Load(ctx, g, keybase1.LoadTeamArg{
    24  		ID:         id,
    25  		Public:     id.IsPublic(), // infer publicness from id
    26  		Refreshers: refreshers,
    27  	})
    28  	if err != nil {
    29  		return res, err
    30  	}
    31  	return team.ExportToTeamPlusApplicationKeys(ctx, keybase1.Time(0), application, includeKBFSKeys)
    32  }
    33  
    34  // GetAnnotatedTeam bundles up various data, both on and off chain, about a specific team for
    35  // consumption by the UI. In particular, it supplies almost all of the information on a team's
    36  // subpage in the Teams tab.
    37  // It always repolls to ensure latest version of a team, but member infos (username, full name, if
    38  // they reset or not) are subject to UIDMapper caching.
    39  func GetAnnotatedTeam(ctx context.Context, g *libkb.GlobalContext, teamID keybase1.TeamID) (res keybase1.AnnotatedTeam, err error) {
    40  	tracer := g.CTimeTracer(ctx, "GetAnnotatedTeam", true)
    41  	defer tracer.Finish()
    42  
    43  	mctx := libkb.NewMetaContext(ctx, g)
    44  
    45  	tracer.Stage("load team")
    46  	t, err := GetMaybeAdminByID(ctx, g, teamID, teamID.IsPublic())
    47  	if err != nil {
    48  		return res, err
    49  	}
    50  
    51  	settings := t.Settings()
    52  
    53  	tracer.Stage("members & invites")
    54  	members, annotatedInvites, err := GetAnnotatedInvitesAndMembersForUI(mctx, t)
    55  	if err != nil {
    56  		return res, err
    57  	}
    58  	members = membersFilterDeletedUsers(mctx.Ctx(), mctx.G(), members)
    59  	members = membersHideInactiveDuplicates(mctx.Ctx(), mctx.G(), members)
    60  
    61  	var transitiveSubteamsUnverified keybase1.SubteamListResult
    62  	var joinRequests []keybase1.TeamJoinRequest
    63  	var tarsDisabled bool
    64  	var showcase keybase1.TeamShowcase
    65  	if !t.IsImplicit() {
    66  		if settings.Open {
    67  			g.Log.CDebugf(ctx, "GetAnnotatedTeam: %q is an open team, filtering reset writers and readers", t.Name().String())
    68  			members = keybase1.FilterInactiveReadersWriters(members)
    69  		}
    70  
    71  		tracer.Stage("transitive subteams")
    72  		transitiveSubteamsUnverified, err = ListSubteamsUnverified(mctx, t.Name())
    73  		if err != nil {
    74  			return res, err
    75  		}
    76  
    77  		teamNameStr := t.Name().String()
    78  		myRole, err := t.myRole(ctx)
    79  		if err != nil {
    80  			return res, err
    81  		}
    82  		if myRole == keybase1.TeamRole_NONE || myRole.IsOrAbove(keybase1.TeamRole_ADMIN) {
    83  			// If we are an implicit admin, our role is NONE. Note that we would not be
    84  			// this far if we are not a member of that team - we would have failed with a
    85  			// membership error during team loading at the beginning of this function.
    86  			joinRequests, err = ListRequests(ctx, g, &teamNameStr)
    87  			if err != nil {
    88  				mctx.Debug("GetAnnotatedTeam: failed to load access requests: %s", err)
    89  			}
    90  			tarsDisabled, err = GetTarsDisabled(ctx, g, teamID)
    91  			if err != nil {
    92  				mctx.Debug("GetAnnotatedTeam: failed to load disabled TARs setting: %s", err)
    93  			}
    94  		}
    95  
    96  		showcase, err = GetTeamShowcase(ctx, g, teamID)
    97  		if err != nil {
    98  			return res, err
    99  		}
   100  	}
   101  
   102  	return keybase1.AnnotatedTeam{
   103  		TeamID:                       teamID,
   104  		Name:                         t.Name().String(),
   105  		TransitiveSubteamsUnverified: transitiveSubteamsUnverified,
   106  		Members:                      members,
   107  		Invites:                      annotatedInvites,
   108  		Settings:                     settings,
   109  		JoinRequests:                 joinRequests,
   110  		TarsDisabled:                 tarsDisabled,
   111  		KeyGeneration:                t.Generation(),
   112  		Showcase:                     showcase,
   113  	}, nil
   114  }
   115  
   116  func GetAnnotatedTeamByName(ctx context.Context, g *libkb.GlobalContext, teamName string) (res keybase1.AnnotatedTeam, err error) {
   117  	nameParsed, err := keybase1.TeamNameFromString(teamName)
   118  	if err != nil {
   119  		return res, err
   120  	}
   121  	teamID, err := ResolveNameToID(ctx, g, nameParsed)
   122  	if err != nil {
   123  		return res, err
   124  	}
   125  	return GetAnnotatedTeam(ctx, g, teamID)
   126  }
   127  
   128  func Details(ctx context.Context, g *libkb.GlobalContext, name string) (res keybase1.TeamDetails, err error) {
   129  	t, err := GetAnnotatedTeamByName(ctx, g, name)
   130  	if err != nil {
   131  		return res, err
   132  	}
   133  	return t.ToLegacyTeamDetails(), nil
   134  }
   135  
   136  func membersFilterDeletedUsers(ctx context.Context, g *libkb.GlobalContext, members []keybase1.TeamMemberDetails) (ret []keybase1.TeamMemberDetails) {
   137  	for _, member := range members {
   138  		if member.Status != keybase1.TeamMemberStatus_DELETED {
   139  			ret = append(ret, member)
   140  		} else {
   141  			g.Log.CDebugf(ctx, "membersHideDeletedUsers filtered out row: %v %v", member.Uv, member.Status)
   142  		}
   143  	}
   144  	return ret
   145  }
   146  
   147  // If a UID appears multiple times with different TeamMemberStatus, only show the 'ACTIVE' version.
   148  // This can happen when an owner resets and is re-added by an admin (though the admin could
   149  // choose to remove the old owner if they so wished).
   150  func membersHideInactiveDuplicates(ctx context.Context, g *libkb.GlobalContext, members []keybase1.TeamMemberDetails) (ret []keybase1.TeamMemberDetails) {
   151  	seenActive := make(map[keybase1.UID]bool)
   152  	// Scan for active rows
   153  	for _, member := range members {
   154  		if member.Status == keybase1.TeamMemberStatus_ACTIVE {
   155  			seenActive[member.Uv.Uid] = true
   156  		}
   157  	}
   158  	// Filter out superseded inactive rows
   159  	for _, member := range members {
   160  		if member.Status == keybase1.TeamMemberStatus_ACTIVE || !seenActive[member.Uv.Uid] {
   161  			ret = append(ret, member)
   162  		} else {
   163  			g.Log.CDebugf(ctx, "membersHideInactiveDuplicates filtered out row: %v %v", member.Uv, member.Status)
   164  		}
   165  	}
   166  	return ret
   167  }
   168  
   169  func MembersDetails(ctx context.Context, g *libkb.GlobalContext, t *Team) (ret []keybase1.TeamMemberDetails, err error) {
   170  	mctx := libkb.NewMetaContext(ctx, g)
   171  	m, err := t.Members()
   172  	if err != nil {
   173  		return nil, err
   174  	}
   175  
   176  	owners, err := userVersionsToDetails(mctx, m.Owners, t)
   177  	if err != nil {
   178  		return nil, err
   179  	}
   180  	admins, err := userVersionsToDetails(mctx, m.Admins, t)
   181  	if err != nil {
   182  		return nil, err
   183  	}
   184  	writers, err := userVersionsToDetails(mctx, m.Writers, t)
   185  	if err != nil {
   186  		return nil, err
   187  	}
   188  	readers, err := userVersionsToDetails(mctx, m.Readers, t)
   189  	if err != nil {
   190  		return nil, err
   191  	}
   192  	bots, err := userVersionsToDetails(mctx, m.Bots, t)
   193  	if err != nil {
   194  		return nil, err
   195  	}
   196  	restrictedBots, err := userVersionsToDetails(mctx, m.RestrictedBots, t)
   197  	if err != nil {
   198  		return nil, err
   199  	}
   200  	ret = append(ret, owners...)
   201  	ret = append(ret, admins...)
   202  	ret = append(ret, writers...)
   203  	ret = append(ret, readers...)
   204  	ret = append(ret, bots...)
   205  	ret = append(ret, restrictedBots...)
   206  	return ret, nil
   207  }
   208  
   209  // userVersionsToDetails returns a slice of TeamMemberDetails objects, to be
   210  // used in relation to a specific team. It requires the *Team parameter to get
   211  // the role and joinTime.
   212  func userVersionsToDetails(mctx libkb.MetaContext, uvs []keybase1.UserVersion, t *Team) (ret []keybase1.TeamMemberDetails, err error) {
   213  	uids := make([]keybase1.UID, len(uvs))
   214  	for i, uv := range uvs {
   215  		uids[i] = uv.Uid
   216  	}
   217  	packages, err := mctx.G().UIDMapper.MapUIDsToUsernamePackages(mctx.Ctx(), mctx.G(), uids,
   218  		defaultFullnameFreshness, defaultNetworkTimeBudget, true /* forceNetworkForFullNames */)
   219  	if err != nil {
   220  		return nil, err
   221  	}
   222  
   223  	ret = make([]keybase1.TeamMemberDetails, len(uvs))
   224  
   225  	for i, uv := range uvs {
   226  		pkg := packages[i]
   227  		status := keybase1.TeamMemberStatus_ACTIVE
   228  		var fullName keybase1.FullName
   229  		if pkg.FullName != nil {
   230  			if pkg.FullName.EldestSeqno != uv.EldestSeqno {
   231  				status = keybase1.TeamMemberStatus_RESET
   232  			}
   233  			if pkg.FullName.Status == keybase1.StatusCode_SCDeleted {
   234  				status = keybase1.TeamMemberStatus_DELETED
   235  			}
   236  			fullName = pkg.FullName.FullName
   237  		}
   238  		role, err := t.chain().GetUserRole(uv)
   239  		if err != nil {
   240  			return nil, err
   241  		}
   242  		ret[i] = keybase1.TeamMemberDetails{
   243  			Uv:       uvs[i],
   244  			Username: pkg.NormalizedUsername.String(),
   245  			FullName: fullName,
   246  			Status:   status,
   247  			Role:     role,
   248  		}
   249  		if status == keybase1.TeamMemberStatus_ACTIVE && t != nil {
   250  			joinTime, err := t.UserLastJoinTime(uv)
   251  			if err != nil {
   252  				return nil, err
   253  			}
   254  			ret[i].JoinTime = &joinTime
   255  		}
   256  	}
   257  	return ret, nil
   258  }
   259  
   260  func SetRoleOwner(ctx context.Context, g *libkb.GlobalContext, teamname, username string) error {
   261  	uv, err := loadUserVersionByUsername(ctx, g, username, true /* useTracking */)
   262  	if err != nil {
   263  		return err
   264  	}
   265  	return ChangeRoles(ctx, g, teamname, keybase1.TeamChangeReq{Owners: []keybase1.UserVersion{uv}})
   266  }
   267  
   268  func SetRoleAdmin(ctx context.Context, g *libkb.GlobalContext, teamname, username string) error {
   269  	uv, err := loadUserVersionByUsername(ctx, g, username, true /* useTracking */)
   270  	if err != nil {
   271  		return err
   272  	}
   273  	return ChangeRoles(ctx, g, teamname, keybase1.TeamChangeReq{Admins: []keybase1.UserVersion{uv}})
   274  }
   275  
   276  func SetRoleWriter(ctx context.Context, g *libkb.GlobalContext, teamname, username string) error {
   277  	uv, err := loadUserVersionByUsername(ctx, g, username, true /* useTracking */)
   278  	if err != nil {
   279  		return err
   280  	}
   281  	return ChangeRoles(ctx, g, teamname, keybase1.TeamChangeReq{Writers: []keybase1.UserVersion{uv}})
   282  }
   283  
   284  func SetRoleReader(ctx context.Context, g *libkb.GlobalContext, teamname, username string) error {
   285  	uv, err := loadUserVersionByUsername(ctx, g, username, true /* useTracking */)
   286  	if err != nil {
   287  		return err
   288  	}
   289  	return ChangeRoles(ctx, g, teamname, keybase1.TeamChangeReq{Readers: []keybase1.UserVersion{uv}})
   290  }
   291  
   292  func SetRoleBot(ctx context.Context, g *libkb.GlobalContext, teamname, username string) error {
   293  	uv, err := loadUserVersionByUsername(ctx, g, username, true /* useTracking */)
   294  	if err != nil {
   295  		return err
   296  	}
   297  	return ChangeRoles(ctx, g, teamname, keybase1.TeamChangeReq{Bots: []keybase1.UserVersion{uv}})
   298  }
   299  
   300  func SetRoleRestrictedBot(ctx context.Context, g *libkb.GlobalContext, teamname, username string,
   301  	botSettings keybase1.TeamBotSettings) error {
   302  	uv, err := loadUserVersionByUsername(ctx, g, username, true /* useTracking */)
   303  	if err != nil {
   304  		return err
   305  	}
   306  	req := keybase1.TeamChangeReq{
   307  		RestrictedBots: map[keybase1.UserVersion]keybase1.TeamBotSettings{
   308  			uv: botSettings,
   309  		},
   310  	}
   311  	return ChangeRoles(ctx, g, teamname, req)
   312  }
   313  
   314  func getUserProofsNoTracking(ctx context.Context, g *libkb.GlobalContext, username string) (*libkb.ProofSet, *libkb.IdentifyOutcome, error) {
   315  	arg := keybase1.Identify2Arg{
   316  		UserAssertion:    username,
   317  		UseDelegateUI:    false,
   318  		Reason:           keybase1.IdentifyReason{Reason: "clear invitation when adding team member"},
   319  		CanSuppressUI:    true,
   320  		IdentifyBehavior: keybase1.TLFIdentifyBehavior_RESOLVE_AND_CHECK,
   321  		NeedProofSet:     true,
   322  		ActLoggedOut:     true,
   323  	}
   324  	eng := engine.NewResolveThenIdentify2(g, &arg)
   325  	m := libkb.NewMetaContext(ctx, g)
   326  	if err := engine.RunEngine2(m, eng); err != nil {
   327  		return nil, nil, err
   328  	}
   329  	return eng.GetProofSet(), eng.GetIdentifyOutcome(), nil
   330  }
   331  
   332  func AddMemberByID(ctx context.Context, g *libkb.GlobalContext, teamID keybase1.TeamID, username string,
   333  	role keybase1.TeamRole, botSettings *keybase1.TeamBotSettings, emailInviteMsg *string) (res keybase1.TeamAddMemberResult, err error) {
   334  
   335  	err = RetryIfPossible(ctx, g, func(ctx context.Context, _ int) error {
   336  		t, err := GetForTeamManagementByTeamID(ctx, g, teamID, true /*needAdmin*/)
   337  		if err != nil {
   338  			return err
   339  		}
   340  
   341  		loggedInRole, err := t.myRole(ctx)
   342  		if err != nil {
   343  			return err
   344  		}
   345  		if role == keybase1.TeamRole_OWNER && loggedInRole == keybase1.TeamRole_ADMIN {
   346  			return fmt.Errorf("Cannot add owner to team as an admin")
   347  		}
   348  
   349  		tx := CreateAddMemberTx(t)
   350  		tx.AllowPUKless = true
   351  		tx.EmailInviteMsg = emailInviteMsg
   352  		resolvedUsername, uv, invite, err := tx.AddOrInviteMemberByAssertion(ctx, username, role, botSettings)
   353  		if err != nil {
   354  			return err
   355  		}
   356  
   357  		if !uv.IsNil() {
   358  			// Try to mark completed any invites for the user's social assertions.
   359  			// This can be a time-intensive process since it involves checking proofs.
   360  			// It is limited to a few seconds and failure is non-fatal.
   361  			timeoutCtx, timeoutCancel := context.WithTimeout(ctx, 2*time.Second)
   362  			if err := tx.CompleteSocialInvitesFor(timeoutCtx, uv, username); err != nil {
   363  				g.Log.CDebugf(ctx, "Failed in CompleteSocialInvitesFor, no invites will be cleared. Err was: %v", err)
   364  			}
   365  			timeoutCancel()
   366  		}
   367  
   368  		err = tx.Post(libkb.NewMetaContext(ctx, g))
   369  		if err != nil {
   370  			return err
   371  		}
   372  
   373  		// return value assign to escape closure
   374  		res = keybase1.TeamAddMemberResult{
   375  			User:    &keybase1.User{Uid: uv.Uid, Username: resolvedUsername.String()},
   376  			Invited: invite,
   377  		}
   378  		return nil
   379  	})
   380  	return res, err
   381  }
   382  
   383  func AddMember(ctx context.Context, g *libkb.GlobalContext, teamname, username string, role keybase1.TeamRole,
   384  	botSettings *keybase1.TeamBotSettings) (res keybase1.TeamAddMemberResult, err error) {
   385  	team, err := Load(ctx, g, keybase1.LoadTeamArg{
   386  		Name:        teamname,
   387  		ForceRepoll: true,
   388  	})
   389  	if err != nil {
   390  		return res, err
   391  	}
   392  	return AddMemberByID(ctx, g, team.ID, username, role, botSettings, nil /* emailInviteMsg */)
   393  }
   394  
   395  type AddMembersRes struct {
   396  	Invite   bool                     // Whether the membership addition was an invite.
   397  	Username libkb.NormalizedUsername // Resolved username. May be nil for social assertions.
   398  }
   399  
   400  // AddMembers adds a bunch of people to a team. Assertions can contain usernames or social assertions.
   401  // Adds them all in a transaction so it's all or nothing.
   402  // If the first transaction fails due to TeamContactSettingsBlock error, it
   403  // will remove restricted users returned by the error, and retry once.
   404  // On success, returns a list where len(added) + len(noAdded) = len(assertions) and in
   405  // corresponding order, with restricted users having an empty AddMembersRes.
   406  //
   407  // @emailInviteMsg *string is an argument used as a welcome message in email invitations sent from the server
   408  func AddMembers(ctx context.Context, g *libkb.GlobalContext, teamID keybase1.TeamID, users []keybase1.UserRolePair,
   409  	emailInviteMsg *string) (added []AddMembersRes, notAdded []keybase1.User, err error) {
   410  	mctx := libkb.NewMetaContext(ctx, g)
   411  	tracer := g.CTimeTracer(ctx, "team.AddMembers", true)
   412  	defer tracer.Finish()
   413  
   414  	// restrictedUsers is nil initially, but if first attempt at adding members
   415  	// results in "contact settings block error", restrictedUsers becomes a set
   416  	// of blocked uids.
   417  	var restrictedUsers contactRestrictedUsers
   418  
   419  	addNonRestrictedMembersFunc := func(ctx context.Context, _ int) error {
   420  		added = []AddMembersRes{}
   421  		notAdded = []keybase1.User{}
   422  
   423  		team, err := GetForTeamManagementByTeamID(ctx, g, teamID, true /*needAdmin*/)
   424  		if err != nil {
   425  			return err
   426  		}
   427  
   428  		if team.IsSubteam() && keybase1.UserRolePairsHaveOwner(users) {
   429  			// Do the "owner in subteam" check early before we do anything else.
   430  			return NewSubteamOwnersError()
   431  		}
   432  
   433  		tx := CreateAddMemberTx(team)
   434  		tx.AllowPUKless = true
   435  		tx.EmailInviteMsg = emailInviteMsg
   436  
   437  		type sweepEntry struct {
   438  			Assertion string
   439  			UV        keybase1.UserVersion
   440  		}
   441  		var sweep []sweepEntry
   442  		for _, user := range users {
   443  			candidate, err := tx.ResolveUPKV2FromAssertion(mctx, user.Assertion)
   444  			if err != nil {
   445  				return NewAddMembersError(candidate.Full, err)
   446  			}
   447  
   448  			g.Log.CDebugf(ctx, "%q resolved to %s", user.Assertion, candidate.DebugString())
   449  
   450  			if restricted, kbUser := restrictedUsers.checkCandidate(candidate); restricted {
   451  				// Skip users with contact setting restrictions.
   452  				notAdded = append(notAdded, kbUser)
   453  				continue
   454  			}
   455  
   456  			username, uv, invite, err := tx.AddOrInviteMemberCandidate(ctx, candidate, user.Role, user.BotSettings)
   457  			if err != nil {
   458  				if _, ok := err.(AttemptedInviteSocialOwnerError); ok {
   459  					return err
   460  				}
   461  				return NewAddMembersError(candidate.Full, err)
   462  			}
   463  			var normalizedUsername libkb.NormalizedUsername
   464  			if !username.IsNil() {
   465  				normalizedUsername = username
   466  			}
   467  
   468  			added = append(added, AddMembersRes{
   469  				Invite:   invite,
   470  				Username: normalizedUsername,
   471  			})
   472  			if !uv.IsNil() {
   473  				sweep = append(sweep, sweepEntry{
   474  					Assertion: user.Assertion,
   475  					UV:        uv,
   476  				})
   477  			}
   478  		}
   479  
   480  		// Try to mark completed any invites for the users' social assertions.
   481  		// This can be a time-intensive process since it involves checking proofs.
   482  		// It is limited to a few seconds and failure is non-fatal.
   483  		timeoutCtx, timeoutCancel := context.WithTimeout(ctx, 2*time.Second)
   484  		for _, x := range sweep {
   485  			if err := tx.CompleteSocialInvitesFor(timeoutCtx, x.UV, x.Assertion); err != nil {
   486  				g.Log.CWarningf(ctx, "Failed in CompleteSocialInvitesFor(%v, %v) -> %v", x.UV, x.Assertion, err)
   487  			}
   488  		}
   489  		timeoutCancel()
   490  
   491  		return tx.Post(libkb.NewMetaContext(ctx, g))
   492  	}
   493  
   494  	// try to add
   495  	err = RetryIfPossible(ctx, g, addNonRestrictedMembersFunc)
   496  	if blockError, ok := err.(libkb.TeamContactSettingsBlockError); ok {
   497  		mctx.Debug("AddMembers: initial attempt failed with contact settings error: %v", err)
   498  		uids := blockError.BlockedUIDs()
   499  		if len(uids) == len(users) {
   500  			// If all users can't be added, quit. Do this check before calling
   501  			// `unpackContactRestrictedUsers` to avoid allocating and setting
   502  			// up the uid set if we fall in this case.
   503  			mctx.Debug("AddMembers: initial attempt failed and all users were restricted from being added. Not retrying.")
   504  			return nil, nil, err
   505  		}
   506  		restrictedUsers = unpackContactRestrictedUsers(blockError)
   507  		mctx.Debug("AddMembers: retrying without restricted users: %+v", blockError.BlockedUsernames())
   508  		err = RetryIfPossible(ctx, g, addNonRestrictedMembersFunc)
   509  	}
   510  
   511  	if err != nil {
   512  		return nil, nil, err
   513  	}
   514  	return added, notAdded, nil
   515  }
   516  
   517  func ReAddMemberAfterReset(ctx context.Context, g *libkb.GlobalContext, teamID keybase1.TeamID,
   518  	username string) (err error) {
   519  	defer g.CTrace(ctx, fmt.Sprintf("ReAddMemberAfterReset(%v,%v)", teamID, username), &err)()
   520  	err = reAddMemberAfterResetInner(ctx, g, teamID, username)
   521  	switch err.(type) {
   522  	case UserHasNotResetError:
   523  		// No-op is ok
   524  		g.Log.CDebugf(ctx, "suppressing error: %v", err)
   525  		return nil
   526  	default:
   527  		return err
   528  	}
   529  }
   530  
   531  func reAddMemberAfterResetInner(ctx context.Context, g *libkb.GlobalContext, teamID keybase1.TeamID,
   532  	username string) error {
   533  	arg := libkb.NewLoadUserArg(g).
   534  		WithNetContext(ctx).
   535  		WithName(username).
   536  		WithPublicKeyOptional().
   537  		WithForcePoll(true)
   538  	upak, _, err := g.GetUPAKLoader().LoadV2(arg)
   539  	if err != nil {
   540  		return err
   541  	}
   542  	_ = g.Pegboard.TrackUPAK(libkb.NewMetaContext(ctx, g), upak.Current)
   543  	uv := upak.Current.ToUserVersion()
   544  	return RetryIfPossible(ctx, g, func(ctx context.Context, _ int) error {
   545  		t, err := GetForTeamManagementByTeamID(ctx, g, teamID, true)
   546  		if err != nil {
   547  			return err
   548  		}
   549  
   550  		// Look for invites first - invite will always be obsoleted (and
   551  		// removed) by membership or another invite; but membership can
   552  		// stay un-removed when superseded by new invite (is removed by
   553  		// new membership though).
   554  		var existingRole keybase1.TeamRole
   555  		var existingBotSettings *keybase1.TeamBotSettings
   556  		invite, existingUV, found := t.FindActiveKeybaseInvite(uv.Uid)
   557  		if found {
   558  			// User is PUKless member.
   559  			existingRole = invite.Role
   560  		} else {
   561  			foundUV, err := t.UserVersionByUID(ctx, uv.Uid)
   562  			if err != nil {
   563  				if _, ok := err.(libkb.NotFoundError); ok {
   564  					// username is neither crypto UV nor keybase invite in
   565  					// that team. bail out.
   566  					return libkb.NotFoundError{Msg: fmt.Sprintf("User %q (%s) is not a member of this team.",
   567  						username, uv.Uid)}
   568  				}
   569  				// ... or something else failed
   570  				return err
   571  			}
   572  
   573  			// User is existing crypto member - get their current role.
   574  			role, err := t.MemberRole(ctx, foundUV)
   575  			if err != nil {
   576  				return err
   577  			}
   578  			existingRole = role
   579  			existingUV = foundUV
   580  
   581  			if existingRole.IsRestrictedBot() {
   582  				bots, err := t.TeamBotSettings()
   583  				if err != nil {
   584  					return err
   585  				}
   586  				botSettings, ok := bots[existingUV]
   587  				if !ok {
   588  					botSettings = keybase1.TeamBotSettings{}
   589  				}
   590  				existingBotSettings = &botSettings
   591  			}
   592  		}
   593  
   594  		if existingUV.EldestSeqno == uv.EldestSeqno {
   595  			return NewUserHasNotResetError("user %s has not reset, no need to re-add, existing: %v new: %v",
   596  				username, existingUV.EldestSeqno, uv.EldestSeqno)
   597  		}
   598  
   599  		hasPUK := len(upak.Current.PerUserKeys) > 0
   600  
   601  		loggedInRole, err := t.myRole(ctx)
   602  		if err != nil {
   603  			return err
   604  		}
   605  
   606  		targetRole := existingRole
   607  		if existingRole.IsOrAbove(loggedInRole) {
   608  			// If an admin is trying to re-add an owner, re-add them as an admin.
   609  			// An admin cannot grant owner privileges, so this is the best we can do.
   610  			targetRole = loggedInRole
   611  		}
   612  
   613  		if !t.IsImplicit() {
   614  			_, err = AddMemberByID(ctx, g, t.ID, username, targetRole, existingBotSettings, nil /* emailInviteMsg */)
   615  			return err
   616  		}
   617  
   618  		tx := CreateAddMemberTx(t)
   619  		tx.AllowPUKless = true
   620  		if err := tx.ReAddMemberToImplicitTeam(ctx, uv, hasPUK, targetRole, existingBotSettings); err != nil {
   621  			return err
   622  		}
   623  
   624  		return tx.Post(libkb.NewMetaContext(ctx, g))
   625  	})
   626  }
   627  
   628  func InviteEmailPhoneMember(ctx context.Context, g *libkb.GlobalContext, teamID keybase1.TeamID, name string, typ string, role keybase1.TeamRole) error {
   629  	return RetryIfPossible(ctx, g, func(ctx context.Context, _ int) error {
   630  		t, err := GetForTeamManagementByTeamID(ctx, g, teamID, true)
   631  		if err != nil {
   632  			return err
   633  		}
   634  
   635  		return t.InviteEmailPhoneMember(ctx, name, role, typ)
   636  	})
   637  }
   638  
   639  func AddEmailsBulk(ctx context.Context, g *libkb.GlobalContext, teamname, emails string, role keybase1.TeamRole) (res keybase1.BulkRes, err error) {
   640  	mctx := libkb.NewMetaContext(ctx, g)
   641  
   642  	mctx.Debug("parsing email list for team %q", teamname)
   643  
   644  	actx := mctx.G().MakeAssertionContext(mctx)
   645  
   646  	emailList := email_utils.ParseSeparatedEmails(mctx, emails, &res.Malformed)
   647  	var toAdd []keybase1.UserRolePair
   648  	for _, email := range emailList {
   649  		assertion, err := libkb.ParseAssertionURLKeyValue(actx, "email", email, false /* strict */)
   650  		if err != nil {
   651  			res.Malformed = append(res.Malformed, email)
   652  			mctx.Debug("Failed to create assertion from email %q: %s", email, err)
   653  			continue
   654  		}
   655  		toAdd = append(toAdd, keybase1.UserRolePair{Assertion: assertion.String(), Role: role})
   656  	}
   657  
   658  	if len(toAdd) == 0 {
   659  		// Nothing to do.
   660  		return res, nil
   661  	}
   662  
   663  	t, err := GetForTeamManagementByStringName(ctx, g, teamname, true)
   664  	if err != nil {
   665  		return res, err
   666  	}
   667  
   668  	_, _, err = AddMembers(ctx, g, t.ID, toAdd, nil /* emailInviteMsg */)
   669  	return res, err
   670  }
   671  
   672  func EditMember(ctx context.Context, g *libkb.GlobalContext, teamname, username string,
   673  	role keybase1.TeamRole, botSettings *keybase1.TeamBotSettings) error {
   674  	teamGetter := func() (*Team, error) { return GetForTeamManagementByStringName(ctx, g, teamname, true) }
   675  	return editMember(ctx, g, teamGetter, username, role, botSettings)
   676  }
   677  
   678  func EditMemberByID(ctx context.Context, g *libkb.GlobalContext, teamID keybase1.TeamID,
   679  	username string, role keybase1.TeamRole, botSettings *keybase1.TeamBotSettings) error {
   680  	teamGetter := func() (*Team, error) { return GetForTeamManagementByTeamID(ctx, g, teamID, true) }
   681  	return editMember(ctx, g, teamGetter, username, role, botSettings)
   682  }
   683  
   684  func EditMembers(ctx context.Context, g *libkb.GlobalContext, teamID keybase1.TeamID, users []keybase1.UserRolePair) (res keybase1.TeamEditMembersResult, err error) {
   685  	var failedToEdit []keybase1.UserRolePair
   686  
   687  	for _, userRolePair := range users {
   688  		err := EditMemberByID(ctx, g, teamID, userRolePair.Assertion, userRolePair.Role, userRolePair.BotSettings)
   689  		if err != nil {
   690  			failedToEdit = append(failedToEdit, userRolePair)
   691  			continue
   692  		}
   693  	}
   694  
   695  	res = keybase1.TeamEditMembersResult{Failures: failedToEdit}
   696  	return res, nil
   697  }
   698  
   699  func editMember(ctx context.Context, g *libkb.GlobalContext, teamGetter func() (*Team, error),
   700  	username string, role keybase1.TeamRole, botSettings *keybase1.TeamBotSettings) error {
   701  
   702  	uv, err := loadUserVersionByUsername(ctx, g, username, true /* useTracking */)
   703  	if err == errInviteRequired {
   704  		return editMemberInvite(ctx, g, teamGetter, username, role, uv, botSettings)
   705  	}
   706  	if err != nil {
   707  		return err
   708  	}
   709  
   710  	return RetryIfPossible(ctx, g, func(ctx context.Context, _ int) error {
   711  		t, err := teamGetter()
   712  		if err != nil {
   713  			return err
   714  		}
   715  		if !t.IsMember(ctx, uv) {
   716  			return libkb.NotFoundError{Msg: fmt.Sprintf("user %q is not a member of team %q", username, t.Name())}
   717  		}
   718  		existingRole, err := t.MemberRole(ctx, uv)
   719  		if err != nil {
   720  			return err
   721  		}
   722  
   723  		if existingRole == role {
   724  			if !role.IsRestrictedBot() {
   725  				g.Log.CDebugf(ctx, "bailing out, role given is the same as current")
   726  				return nil
   727  			}
   728  			teamBotSettings, err := t.TeamBotSettings()
   729  			if err != nil {
   730  				return err
   731  			}
   732  			existingBotSettings := teamBotSettings[uv]
   733  			if botSettings.Eq(&existingBotSettings) {
   734  				g.Log.CDebugf(ctx, "bailing out, role given is the same as current, botSettings unchanged")
   735  				return nil
   736  			}
   737  		}
   738  
   739  		req, err := reqFromRole(uv, role, botSettings)
   740  		if err != nil {
   741  			return err
   742  		}
   743  
   744  		return t.ChangeMembership(ctx, req)
   745  	})
   746  
   747  }
   748  
   749  func editMemberInvite(ctx context.Context, g *libkb.GlobalContext, teamGetter func() (*Team, error),
   750  	username string, role keybase1.TeamRole,
   751  	uv keybase1.UserVersion, botSettings *keybase1.TeamBotSettings) error {
   752  	t, err := teamGetter()
   753  	if err != nil {
   754  		return err
   755  	}
   756  	g.Log.CDebugf(ctx, "team %s: edit member %s, member is an invite link", t.ID, username)
   757  
   758  	// Note that there could be a problem if removeMemberInvite works but AddMember doesn't
   759  	// as the original invite will be lost. But the user will get an error and can try
   760  	// again.
   761  	if err := removeMemberInvite(ctx, g, t, username, uv); err != nil {
   762  		g.Log.CDebugf(ctx, "editMemberInvite error in removeMemberInvite: %s", err)
   763  		return err
   764  	}
   765  	// use AddMember in case it's possible to add them directly now
   766  	if _, err := AddMemberByID(ctx, g, t.ID, username, role, nil, nil /* emailInviteMsg */); err != nil {
   767  		g.Log.CDebugf(ctx, "editMemberInvite error in AddMember: %s", err)
   768  		return err
   769  	}
   770  	return nil
   771  }
   772  
   773  func SetBotSettings(ctx context.Context, g *libkb.GlobalContext, teamname, username string,
   774  	botSettings keybase1.TeamBotSettings) error {
   775  	teamGetter := func() (*Team, error) {
   776  		return GetForTeamManagementByStringName(ctx, g, teamname, false)
   777  	}
   778  
   779  	return setBotSettings(ctx, g, teamGetter, username, botSettings)
   780  }
   781  
   782  func SetBotSettingsByID(ctx context.Context, g *libkb.GlobalContext, teamID keybase1.TeamID,
   783  	username string, botSettings keybase1.TeamBotSettings) error {
   784  	teamGetter := func() (*Team, error) {
   785  		return GetForTeamManagementByTeamID(ctx, g, teamID, false)
   786  	}
   787  	return setBotSettings(ctx, g, teamGetter, username, botSettings)
   788  }
   789  
   790  func setBotSettings(ctx context.Context, g *libkb.GlobalContext, teamGetter func() (*Team, error),
   791  	username string, botSettings keybase1.TeamBotSettings) error {
   792  
   793  	uv, err := loadUserVersionByUsername(ctx, g, username, true /* useTracking */)
   794  	if err != nil {
   795  		return err
   796  	}
   797  
   798  	return RetryIfPossible(ctx, g, func(ctx context.Context, _ int) error {
   799  		t, err := teamGetter()
   800  		if err != nil {
   801  			return err
   802  		}
   803  
   804  		if !t.IsMember(ctx, uv) {
   805  			return libkb.NotFoundError{Msg: fmt.Sprintf("user %q is not a member of team %q", username, t.Name())}
   806  		}
   807  		role, err := t.MemberRole(ctx, uv)
   808  		if err != nil {
   809  			return err
   810  		}
   811  		if !role.IsRestrictedBot() {
   812  			return fmt.Errorf("%s is not a %v, but has the role %v",
   813  				username, keybase1.TeamRole_RESTRICTEDBOT, role)
   814  		}
   815  
   816  		return t.PostTeamBotSettings(ctx, map[keybase1.UserVersion]keybase1.TeamBotSettings{
   817  			uv: botSettings,
   818  		})
   819  	})
   820  }
   821  
   822  func GetBotSettings(ctx context.Context, g *libkb.GlobalContext,
   823  	teamname, username string) (res keybase1.TeamBotSettings, err error) {
   824  	team, err := Load(ctx, g, keybase1.LoadTeamArg{
   825  		Name:        teamname,
   826  		ForceRepoll: true,
   827  	})
   828  	if err != nil {
   829  		return res, err
   830  	}
   831  	return getBotSettings(ctx, g, team, username)
   832  }
   833  
   834  func GetBotSettingsByID(ctx context.Context, g *libkb.GlobalContext, teamID keybase1.TeamID,
   835  	username string) (res keybase1.TeamBotSettings, err error) {
   836  	team, err := Load(ctx, g, keybase1.LoadTeamArg{
   837  		ID:          teamID,
   838  		ForceRepoll: true,
   839  	})
   840  	if err != nil {
   841  		return res, err
   842  	}
   843  	return getBotSettings(ctx, g, team, username)
   844  }
   845  
   846  func getBotSettings(ctx context.Context, g *libkb.GlobalContext,
   847  	team *Team, username string) (res keybase1.TeamBotSettings, err error) {
   848  	uv, err := loadUserVersionByUsername(ctx, g, username, true /* useTracking */)
   849  	if err != nil {
   850  		return res, err
   851  	}
   852  
   853  	if !team.IsMember(ctx, uv) {
   854  		return res, libkb.NotFoundError{Msg: fmt.Sprintf("user %q is not a member of team %q", username, team.Name())}
   855  	}
   856  
   857  	role, err := team.MemberRole(ctx, uv)
   858  	if err != nil {
   859  		return res, err
   860  	}
   861  	if !role.IsRestrictedBot() {
   862  		return res, fmt.Errorf("%s is not a %v, but has the role %v",
   863  			username, keybase1.TeamRole_RESTRICTEDBOT, role)
   864  	}
   865  
   866  	botSettings, err := team.TeamBotSettings()
   867  	if err != nil {
   868  		return res, err
   869  	}
   870  	return botSettings[uv], nil
   871  }
   872  
   873  func MemberRole(ctx context.Context, g *libkb.GlobalContext, teamname, username string) (role keybase1.TeamRole, err error) {
   874  	uv, err := loadUserVersionByUsername(ctx, g, username, false /* useTracking */)
   875  	if err != nil {
   876  		return keybase1.TeamRole_NONE, err
   877  	}
   878  
   879  	err = RetryIfPossible(ctx, g, func(ctx context.Context, _ int) error {
   880  		t, err := GetForTeamManagementByStringName(ctx, g, teamname, false)
   881  		if err != nil {
   882  			return err
   883  		}
   884  		// return value assign to escape closure
   885  		role, err = t.MemberRole(ctx, uv)
   886  		return err
   887  	})
   888  	return role, err
   889  }
   890  
   891  func MemberRoleFromID(ctx context.Context, g *libkb.GlobalContext, teamID keybase1.TeamID, username string) (role keybase1.TeamRole, err error) {
   892  	uv, err := loadUserVersionByUsername(ctx, g, username, false /* useTracking */)
   893  	if err != nil {
   894  		return keybase1.TeamRole_NONE, err
   895  	}
   896  
   897  	err = RetryIfPossible(ctx, g, func(ctx context.Context, _ int) error {
   898  		t, err := GetForTeamManagementByTeamID(ctx, g, teamID, false)
   899  		if err != nil {
   900  			return err
   901  		}
   902  		// return value assign to escape closure
   903  		role, err = t.MemberRole(ctx, uv)
   904  		return err
   905  	})
   906  	return role, err
   907  }
   908  
   909  func RemoveMemberSingle(ctx context.Context, g *libkb.GlobalContext, teamID keybase1.TeamID,
   910  	member keybase1.TeamMemberToRemove) (err error) {
   911  	members := []keybase1.TeamMemberToRemove{member}
   912  	res, err := RemoveMembers(ctx, g, teamID, members, false /* NoErrorOnPartialFailure */)
   913  	if err != nil {
   914  		msg := fmt.Sprintf("failed to remove member: %s", err)
   915  		if len(res.Failures) > 0 {
   916  			if res.Failures[0].ErrorAtTarget != nil {
   917  				msg += "; " + *res.Failures[0].ErrorAtTarget
   918  			}
   919  			if res.Failures[0].ErrorAtSubtree != nil {
   920  				msg += "; " + *res.Failures[0].ErrorAtSubtree
   921  			}
   922  		}
   923  		err = errors.New(msg)
   924  	}
   925  	return err
   926  }
   927  
   928  func RemoveMembers(ctx context.Context, g *libkb.GlobalContext, teamID keybase1.TeamID,
   929  	members []keybase1.TeamMemberToRemove, shouldNotErrorOnPartialFailure bool,
   930  ) (res keybase1.TeamRemoveMembersResult, err error) {
   931  	mctx := libkb.NewMetaContext(ctx, g)
   932  
   933  	// Preliminary checks
   934  	for _, member := range members {
   935  		typ, err := member.Type()
   936  		if err != nil {
   937  			return res, err
   938  		}
   939  		switch typ {
   940  		case keybase1.TeamMemberToRemoveType_ASSERTION:
   941  		case keybase1.TeamMemberToRemoveType_INVITEID:
   942  		default:
   943  			return res, fmt.Errorf("unknown TeamMemberToRemoveType %v", typ)
   944  		}
   945  	}
   946  
   947  	// Removals
   948  	teamGetter := func() (*Team, error) {
   949  		return GetForTeamManagementByTeamID(ctx, g, teamID, true /* needAdmin */)
   950  	}
   951  	errstrp := func(e error) *string {
   952  		if e == nil {
   953  			return nil
   954  		}
   955  		s := e.Error()
   956  		return &s
   957  	}
   958  	var failures []keybase1.RemoveTeamMemberFailure
   959  	for _, member := range members {
   960  		typ, _ := member.Type()
   961  		switch typ {
   962  		case keybase1.TeamMemberToRemoveType_ASSERTION:
   963  			targetErr := remove(ctx, g, teamGetter, member.Assertion().Assertion)
   964  			var subtreeErr error
   965  			if member.Assertion().RemoveFromSubtree {
   966  				if targetErr == nil {
   967  					subtreeErr = removeMemberFromSubtree(mctx, teamID, member.Assertion().Assertion)
   968  				} else {
   969  					subtreeErr = errors.New("did not attempt to remove from subtree since removal failed at specified team")
   970  				}
   971  			}
   972  			if targetErr != nil || subtreeErr != nil {
   973  				failures = append(failures, keybase1.RemoveTeamMemberFailure{
   974  					TeamMember:     member,
   975  					ErrorAtTarget:  errstrp(targetErr),
   976  					ErrorAtSubtree: errstrp(subtreeErr),
   977  				})
   978  			}
   979  		case keybase1.TeamMemberToRemoveType_INVITEID:
   980  			targetErr := CancelInviteByID(ctx, g, teamID, member.Inviteid().InviteID)
   981  			if targetErr != nil {
   982  				failures = append(failures, keybase1.RemoveTeamMemberFailure{
   983  					TeamMember:     member,
   984  					ErrorAtSubtree: errstrp(targetErr),
   985  				})
   986  			}
   987  		}
   988  	}
   989  
   990  	if !shouldNotErrorOnPartialFailure && len(failures) > 0 {
   991  		err = fmt.Errorf("failed to remove %d members", len(failures))
   992  	} else {
   993  		err = nil
   994  	}
   995  	return keybase1.TeamRemoveMembersResult{Failures: failures}, err
   996  }
   997  
   998  // removeMemberFromSubtree removes member from all teams in the subtree of targetTeamID,
   999  // *not including* targetTeamID itself
  1000  func removeMemberFromSubtree(mctx libkb.MetaContext, targetTeamID keybase1.TeamID,
  1001  	assertion string) error {
  1002  	// We don't care about the roles; we just want the list of teams. So we can pass our
  1003  	// own username.
  1004  	myUsername := mctx.G().Env.GetUsername()
  1005  	guid := 0
  1006  	treeloader, err := NewTreeloader(mctx, myUsername.String(), targetTeamID, guid,
  1007  		false /* includeAncestors */)
  1008  	if err != nil {
  1009  		return fmt.Errorf("could not start loading subteams: %w", err)
  1010  	}
  1011  	// All or nothing; we can assume all results will be OK.
  1012  	teamTreeMemberships, err := treeloader.LoadSync(mctx)
  1013  	if err != nil {
  1014  		return fmt.Errorf("could not load subteams: %w", err)
  1015  	}
  1016  	var errs []error
  1017  	for _, membership := range teamTreeMemberships {
  1018  		status, _ := membership.Result.S()
  1019  		if status != keybase1.TeamTreeMembershipStatus_OK {
  1020  			return fmt.Errorf("removeMemberFromSubtree: unexpectedly got a non-OK status from Treeloader.LoadSync: %v", status)
  1021  		}
  1022  
  1023  		teamID := membership.Result.Ok().TeamID
  1024  
  1025  		// Don't remove member from the targetTeam; just the subtree
  1026  		if teamID == targetTeamID {
  1027  			continue
  1028  		}
  1029  
  1030  		teamGetter := func() (*Team, error) {
  1031  			return GetForTeamManagementByTeamID(mctx.Ctx(), mctx.G(), teamID, true /* needAdmin */)
  1032  		}
  1033  
  1034  		removeErr := remove(mctx.Ctx(), mctx.G(), teamGetter, assertion)
  1035  		var memberNotFoundErr *MemberNotFoundInChainError
  1036  		switch {
  1037  		case removeErr == nil:
  1038  		case errors.As(removeErr, &memberNotFoundErr):
  1039  			// If the member was not found in the sigchain (either via invite or cryptomember), we can
  1040  			// ignore the error. Because we got team memberships for ourselves and not the user, we
  1041  			// can't use the membership data provided by the Treeloader. (We're not using the
  1042  			// membership data from the treeloader because it does not support looking up by invites).
  1043  		default:
  1044  			errs = append(errs, fmt.Errorf("failed to remove from %s: %w",
  1045  				membership.TeamName, removeErr))
  1046  		}
  1047  	}
  1048  	return libkb.CombineErrors(errs...)
  1049  }
  1050  
  1051  func RemoveMemberByID(ctx context.Context, g *libkb.GlobalContext, teamID keybase1.TeamID, username string) error {
  1052  	teamGetter := func() (*Team, error) {
  1053  		return GetForTeamManagementByTeamID(ctx, g, teamID, true)
  1054  	}
  1055  	return remove(ctx, g, teamGetter, username)
  1056  }
  1057  
  1058  // RemoveMember removes members by username or assertions. For a function that can handle removal
  1059  // from subteams and inviteIDs, see RemoveMemberSingle and RemoveMembers.
  1060  func RemoveMember(ctx context.Context, g *libkb.GlobalContext, teamName string, username string) error {
  1061  	teamGetter := func() (*Team, error) {
  1062  		return GetForTeamManagementByStringName(ctx, g, teamName, true)
  1063  	}
  1064  	return remove(ctx, g, teamGetter, username)
  1065  }
  1066  
  1067  func remove(ctx context.Context, g *libkb.GlobalContext, teamGetter func() (*Team, error), username string) error {
  1068  	var inviteRequired bool
  1069  	uv, err := loadUserVersionByUsername(ctx, g, username, false /* useTracking */)
  1070  	if err != nil {
  1071  		switch err {
  1072  		case errInviteRequired:
  1073  			inviteRequired = true
  1074  		case errUserDeleted: // no-op
  1075  		default:
  1076  			return err
  1077  		}
  1078  		g.Log.CDebugf(ctx, "loadUserVersionByUsername(%s) returned %v,%q", username, uv, err)
  1079  	}
  1080  
  1081  	me, err := loadMeForSignatures(ctx, g)
  1082  	if err != nil {
  1083  		return err
  1084  	}
  1085  
  1086  	if me.GetNormalizedName().Eq(libkb.NewNormalizedUsername(username)) {
  1087  		return leave(ctx, g, teamGetter, false)
  1088  	}
  1089  
  1090  	return RetryIfPossible(ctx, g, func(ctx context.Context, _ int) error {
  1091  		t, err := teamGetter()
  1092  		if err != nil {
  1093  			return err
  1094  		}
  1095  
  1096  		if inviteRequired && !uv.Uid.Exists() {
  1097  			// Remove a non-keybase invite.
  1098  			return removeMemberInvite(ctx, g, t, username, uv)
  1099  		}
  1100  
  1101  		if _, _, found := t.FindActiveKeybaseInvite(uv.Uid); found {
  1102  			// Remove keybase invites.
  1103  			return removeKeybaseTypeInviteForUID(ctx, g, t, uv.Uid)
  1104  		}
  1105  
  1106  		existingUV, err := t.UserVersionByUID(ctx, uv.Uid)
  1107  		if err != nil {
  1108  			return NewMemberNotFoundInChainError(libkb.NotFoundError{Msg: fmt.Sprintf(
  1109  				"user %q is not a member of team %q", username, t.Name())})
  1110  		}
  1111  
  1112  		removePermanently := t.IsOpen() ||
  1113  			t.WasMostRecentlyAddedByInvitelink(existingUV)
  1114  		req := keybase1.TeamChangeReq{None: []keybase1.UserVersion{existingUV}}
  1115  		opts := ChangeMembershipOptions{
  1116  			Permanent:       removePermanently,
  1117  			SkipKeyRotation: t.CanSkipKeyRotation(),
  1118  		}
  1119  		return t.ChangeMembershipWithOptions(ctx, req, opts)
  1120  	})
  1121  }
  1122  
  1123  func CancelEmailInvite(ctx context.Context, g *libkb.GlobalContext, teamID keybase1.TeamID, email string) (err error) {
  1124  	g.CTrace(ctx, "CancelEmailInvite", &err)
  1125  	return RetryIfPossible(ctx, g, func(ctx context.Context, _ int) error {
  1126  		t, err := GetForTeamManagementByTeamID(ctx, g, teamID, true)
  1127  		if err != nil {
  1128  			return err
  1129  		}
  1130  
  1131  		if !libkb.CheckEmail.F(email) {
  1132  			return errors.New("Invalid email address")
  1133  		}
  1134  
  1135  		return removeMemberInviteOfType(ctx, g, t, keybase1.TeamInviteName(email), "email")
  1136  	})
  1137  }
  1138  
  1139  func CancelInviteByID(ctx context.Context, g *libkb.GlobalContext, teamID keybase1.TeamID, inviteID keybase1.TeamInviteID) (err error) {
  1140  	g.CTrace(ctx, "CancelInviteByID", &err)
  1141  	return RetryIfPossible(ctx, g, func(ctx context.Context, _ int) error {
  1142  		t, err := GetForTeamManagementByTeamID(ctx, g, teamID, true)
  1143  		if err != nil {
  1144  			return err
  1145  		}
  1146  
  1147  		// Service-side check for invite id, even though we operate on
  1148  		// TeamInviteID type, API consumer can give us any string.
  1149  		if _, err := keybase1.TeamInviteIDFromString(string(inviteID)); err != nil {
  1150  			return fmt.Errorf("Invalid invite ID: %s", err)
  1151  		}
  1152  
  1153  		return removeInviteID(ctx, t, inviteID)
  1154  	})
  1155  }
  1156  
  1157  func leave(ctx context.Context, g *libkb.GlobalContext, teamGetter func() (*Team, error), permanent bool) error {
  1158  	return RetryIfPossible(ctx, g, func(ctx context.Context, _ int) error {
  1159  		t, err := teamGetter()
  1160  		if err != nil {
  1161  			return err
  1162  		}
  1163  		err = t.Leave(ctx, permanent)
  1164  		if err != nil {
  1165  			return err
  1166  		}
  1167  
  1168  		err = FreezeTeam(libkb.NewMetaContext(ctx, g), t.ID)
  1169  		if err != nil {
  1170  			g.Log.CDebugf(ctx, "leave FreezeTeam error: %+v", err)
  1171  		}
  1172  
  1173  		return nil
  1174  	})
  1175  }
  1176  
  1177  func LeaveByID(ctx context.Context, g *libkb.GlobalContext, teamID keybase1.TeamID, permanent bool) error {
  1178  	teamGetter := func() (*Team, error) {
  1179  		return GetForTeamManagementByTeamID(ctx, g, teamID, false)
  1180  	}
  1181  	return leave(ctx, g, teamGetter, permanent)
  1182  }
  1183  
  1184  func Leave(ctx context.Context, g *libkb.GlobalContext, teamname string, permanent bool) error {
  1185  	teamGetter := func() (*Team, error) {
  1186  		return GetForTeamManagementByStringName(ctx, g, teamname, false)
  1187  	}
  1188  	return leave(ctx, g, teamGetter, permanent)
  1189  }
  1190  
  1191  func Delete(ctx context.Context, g *libkb.GlobalContext, ui keybase1.TeamsUiInterface, teamID keybase1.TeamID) error {
  1192  	alreadyConfirmed := false
  1193  	return RetryIfPossible(ctx, g, func(ctx context.Context, _ int) error {
  1194  		t, err := GetForTeamManagementByTeamID(ctx, g, teamID, true)
  1195  		if err != nil {
  1196  			return err
  1197  		}
  1198  
  1199  		if !alreadyConfirmed {
  1200  			var confirmed bool
  1201  			if t.chain().IsSubteam() {
  1202  				confirmed, err = ui.ConfirmSubteamDelete(ctx, keybase1.ConfirmSubteamDeleteArg{TeamName: t.Name().String()})
  1203  			} else {
  1204  				confirmed, err = ui.ConfirmRootTeamDelete(ctx, keybase1.ConfirmRootTeamDeleteArg{TeamName: t.Name().String()})
  1205  			}
  1206  			if err != nil {
  1207  				return err
  1208  			}
  1209  			if !confirmed {
  1210  				return errors.New("team delete not confirmed")
  1211  			}
  1212  			alreadyConfirmed = true
  1213  		}
  1214  
  1215  		if t.chain().IsSubteam() {
  1216  			err = t.deleteSubteam(ctx)
  1217  		} else {
  1218  			err = t.deleteRoot(ctx)
  1219  		}
  1220  		if err != nil {
  1221  			return err
  1222  		}
  1223  
  1224  		err = TombstoneTeam(libkb.NewMetaContext(ctx, g), t.ID)
  1225  		if err != nil {
  1226  			g.Log.CDebugf(ctx, "Delete TombstoneTeam error: %+v", err)
  1227  			if g.Env.GetRunMode() == libkb.DevelRunMode {
  1228  				return err
  1229  			}
  1230  		}
  1231  
  1232  		return nil
  1233  	})
  1234  }
  1235  
  1236  func AcceptServerTrustInvite(ctx context.Context, g *libkb.GlobalContext, token string) error {
  1237  	mctx := libkb.NewMetaContext(ctx, g)
  1238  	arg := apiArg("team/token")
  1239  	arg.Args.Add("token", libkb.S{Val: token})
  1240  	_, err := mctx.G().API.Post(mctx, arg)
  1241  	return err
  1242  }
  1243  
  1244  func ChangeRoles(ctx context.Context, g *libkb.GlobalContext, teamname string, req keybase1.TeamChangeReq) error {
  1245  	return RetryIfPossible(ctx, g, func(ctx context.Context, _ int) error {
  1246  		// Don't needAdmin because we might be leaving, and this needs no information from stubbable links.
  1247  		t, err := GetForTeamManagementByStringName(ctx, g, teamname, false)
  1248  		if err != nil {
  1249  			return err
  1250  		}
  1251  		return t.ChangeMembership(ctx, req)
  1252  	})
  1253  }
  1254  
  1255  var errInviteRequired = errors.New("invite required for username")
  1256  var errUserDeleted = errors.New("user is deleted")
  1257  
  1258  // loadUserVersionByUsername is a wrapper around `engine.ResolveAndCheck` to
  1259  // return UV by username or assertion. When the argument does not resolve to a
  1260  // Keybase user with PUK, `errInviteRequired` is returned.
  1261  //
  1262  // Returns `errInviteRequired` if given argument cannot be brought in as a
  1263  // crypto member - so it is either a reset and not provisioned Keybase user
  1264  // (keybase-type invite is required), or a social assertion that does not
  1265  // resolve to a user.
  1266  //
  1267  // NOTE: This also doesn't try to resolve server-trust assertions.
  1268  func loadUserVersionByUsername(ctx context.Context, g *libkb.GlobalContext, username string, useTracking bool) (keybase1.UserVersion, error) {
  1269  	m := libkb.NewMetaContext(ctx, g)
  1270  	upk, err := engine.ResolveAndCheck(m, username, useTracking)
  1271  	if err != nil {
  1272  		if e, ok := err.(libkb.ResolutionError); ok && e.Kind == libkb.ResolutionErrorNotFound {
  1273  			// couldn't find a keybase user for username assertion
  1274  			return keybase1.UserVersion{}, errInviteRequired
  1275  		}
  1276  		return keybase1.UserVersion{}, err
  1277  	}
  1278  
  1279  	return filterUserCornerCases(ctx, upk)
  1280  }
  1281  
  1282  func loadUserVersionByUID(ctx context.Context, g *libkb.GlobalContext, uid keybase1.UID) (keybase1.UserVersion, error) {
  1283  	upak, err := loadUPAK2(ctx, g, uid, true /*forcePoll */)
  1284  	if err != nil {
  1285  		return keybase1.UserVersion{}, err
  1286  	}
  1287  	return filterUserCornerCases(ctx, upak.Current)
  1288  }
  1289  
  1290  func filterUserCornerCases(ctx context.Context, upak keybase1.UserPlusKeysV2) (keybase1.UserVersion, error) {
  1291  	uv := upak.ToUserVersion()
  1292  	if upak.Status == keybase1.StatusCode_SCDeleted {
  1293  		return uv, errUserDeleted
  1294  	}
  1295  	if len(upak.PerUserKeys) == 0 {
  1296  		return uv, errInviteRequired
  1297  	}
  1298  	return uv, nil
  1299  }
  1300  
  1301  func reqFromRole(uv keybase1.UserVersion, role keybase1.TeamRole, botSettings *keybase1.TeamBotSettings) (req keybase1.TeamChangeReq, err error) {
  1302  	list := []keybase1.UserVersion{uv}
  1303  	if !role.IsRestrictedBot() && botSettings != nil {
  1304  		return req, fmt.Errorf("Unexpected botSettings for role %v", role)
  1305  	}
  1306  	switch role {
  1307  	case keybase1.TeamRole_OWNER:
  1308  		req.Owners = list
  1309  	case keybase1.TeamRole_ADMIN:
  1310  		req.Admins = list
  1311  	case keybase1.TeamRole_WRITER:
  1312  		req.Writers = list
  1313  	case keybase1.TeamRole_READER:
  1314  		req.Readers = list
  1315  	case keybase1.TeamRole_BOT:
  1316  		req.Bots = list
  1317  	case keybase1.TeamRole_RESTRICTEDBOT:
  1318  		if botSettings == nil {
  1319  			return req, errors.New("botSettings must be specified for RESTRICTEDBOT role")
  1320  		}
  1321  		req.RestrictedBots = map[keybase1.UserVersion]keybase1.TeamBotSettings{
  1322  			uv: *botSettings,
  1323  		}
  1324  	default:
  1325  		return req, errors.New("invalid team role")
  1326  	}
  1327  
  1328  	return req, nil
  1329  }
  1330  
  1331  func makeIdentifyLiteRes(id keybase1.TeamID, name keybase1.TeamName) keybase1.IdentifyLiteRes {
  1332  	return keybase1.IdentifyLiteRes{
  1333  		Ul: keybase1.UserOrTeamLite{
  1334  			Id:   id.AsUserOrTeam(),
  1335  			Name: name.String(),
  1336  		},
  1337  	}
  1338  }
  1339  
  1340  func identifyLiteByID(ctx context.Context, g *libkb.GlobalContext, utid keybase1.UserOrTeamID, id2 keybase1.TeamID) (res keybase1.IdentifyLiteRes, err error) {
  1341  
  1342  	var id1 keybase1.TeamID
  1343  	if utid.Exists() {
  1344  		id1, err = utid.AsTeam()
  1345  		if err != nil {
  1346  			return res, err
  1347  		}
  1348  	}
  1349  
  1350  	if id1.Exists() && id2.Exists() && !id1.Eq(id2) {
  1351  		return res, errors.New("two team IDs given that don't match")
  1352  	}
  1353  	if !id1.Exists() {
  1354  		id1 = id2
  1355  	}
  1356  	if !id1.Exists() {
  1357  		return res, errors.New("empty IDs given")
  1358  	}
  1359  	var name keybase1.TeamName
  1360  	name, err = ResolveIDToName(ctx, g, id1)
  1361  	if err != nil {
  1362  		return res, err
  1363  	}
  1364  
  1365  	return makeIdentifyLiteRes(id1, name), nil
  1366  }
  1367  
  1368  func identifyLiteByName(ctx context.Context, g *libkb.GlobalContext, name keybase1.TeamName) (res keybase1.IdentifyLiteRes, err error) {
  1369  	var id keybase1.TeamID
  1370  	id, err = ResolveNameToID(ctx, g, name)
  1371  	if err != nil {
  1372  		return res, err
  1373  	}
  1374  	return makeIdentifyLiteRes(id, name), nil
  1375  }
  1376  
  1377  func IdentifyLite(ctx context.Context, g *libkb.GlobalContext, arg keybase1.IdentifyLiteArg, au libkb.AssertionURL) (res keybase1.IdentifyLiteRes, err error) {
  1378  
  1379  	if arg.Id.Exists() || au.IsTeamID() {
  1380  		return identifyLiteByID(ctx, g, arg.Id, au.ToTeamID())
  1381  	}
  1382  	if au.IsTeamName() {
  1383  		return identifyLiteByName(ctx, g, au.ToTeamName())
  1384  	}
  1385  	return res, errors.New("could not identify team by ID or name")
  1386  }
  1387  
  1388  func memberInvite(ctx context.Context, g *libkb.GlobalContext, teamname string, iname keybase1.TeamInviteName, itype keybase1.TeamInviteType) (*keybase1.TeamInvite, error) {
  1389  	t, err := GetForTeamManagementByStringName(ctx, g, teamname, true)
  1390  	if err != nil {
  1391  		return nil, err
  1392  	}
  1393  	return t.chain().FindActiveInvite(iname, itype)
  1394  }
  1395  
  1396  func RequestAccess(ctx context.Context, g *libkb.GlobalContext, teamname string) (keybase1.TeamRequestAccessResult, error) {
  1397  	arg := apiArg("team/request_access")
  1398  	arg.Args.Add("team", libkb.S{Val: teamname})
  1399  	mctx := libkb.NewMetaContext(ctx, g)
  1400  	apiRes, err := g.API.Post(mctx, arg)
  1401  
  1402  	ret := keybase1.TeamRequestAccessResult{}
  1403  	if apiRes != nil && apiRes.Body != nil {
  1404  		// "is_open" key may not be included in result payload and it's
  1405  		// not an error.
  1406  		ret.Open, _ = apiRes.Body.AtKey("is_open").GetBool()
  1407  	}
  1408  	return ret, err
  1409  }
  1410  
  1411  func TeamAcceptInviteOrRequestAccess(ctx context.Context, g *libkb.GlobalContext, ui keybase1.TeamsUiInterface, tokenOrName string) (keybase1.TeamAcceptOrRequestResult, error) {
  1412  	g.Log.CDebugf(ctx, "trying seitan token")
  1413  
  1414  	mctx := libkb.NewMetaContext(ctx, g)
  1415  
  1416  	// If token looks at all like Seitan, don't pass to functions that might log or send to server.
  1417  	maybeSeitanToken, isSeitany := ParseSeitanTokenFromPaste(tokenOrName)
  1418  	if isSeitany {
  1419  		g.Log.CDebugf(ctx, "found seitan-y token")
  1420  		wasSeitan, err := ParseAndAcceptSeitanToken(mctx, ui, maybeSeitanToken)
  1421  		return keybase1.TeamAcceptOrRequestResult{WasSeitan: wasSeitan}, err
  1422  	}
  1423  
  1424  	g.Log.CDebugf(ctx, "trying email-style invite")
  1425  	err := AcceptServerTrustInvite(ctx, g, tokenOrName)
  1426  	if err == nil {
  1427  		return keybase1.TeamAcceptOrRequestResult{
  1428  			WasToken: true,
  1429  		}, nil
  1430  	}
  1431  	g.Log.CDebugf(ctx, "email-style invite error: %v", err)
  1432  	var reportErr error
  1433  	switch err := err.(type) {
  1434  	case libkb.TeamInviteTokenReusedError:
  1435  		reportErr = err
  1436  	default:
  1437  		reportErr = libkb.TeamInviteBadTokenError{}
  1438  	}
  1439  
  1440  	g.Log.CDebugf(ctx, "trying team name")
  1441  	_, err = keybase1.TeamNameFromString(tokenOrName)
  1442  	if err == nil {
  1443  		ret2, err := RequestAccess(ctx, g, tokenOrName)
  1444  		ret := keybase1.TeamAcceptOrRequestResult{
  1445  			WasTeamName: true,
  1446  			WasOpenTeam: ret2.Open, // this is probably just false in error case
  1447  		}
  1448  		return ret, err
  1449  	}
  1450  	g.Log.CDebugf(ctx, "not a team name")
  1451  
  1452  	// We don't know what this thing is. Return the error from AcceptInvite.
  1453  	return keybase1.TeamAcceptOrRequestResult{}, reportErr
  1454  }
  1455  
  1456  type accessRequest struct {
  1457  	FQName   string          `json:"fq_name"`
  1458  	TeamID   keybase1.TeamID `json:"team_id"`
  1459  	UID      keybase1.UID    `json:"uid"`
  1460  	Ctime    time.Time       `json:"ctime"`
  1461  	Username string          `json:"username"`
  1462  }
  1463  
  1464  type accessRequestList struct {
  1465  	Requests []accessRequest `json:"requests"`
  1466  	Status   libkb.AppStatus `json:"status"`
  1467  }
  1468  
  1469  func (r *accessRequestList) GetAppStatus() *libkb.AppStatus {
  1470  	return &r.Status
  1471  }
  1472  
  1473  // Lists all requests in all of user-owned teams or a single team and tries to
  1474  // resolve their full names.
  1475  //
  1476  // Full names are not guaranteed to be present in the response. Given a large
  1477  // enough volume of access requests by unknown (to us) users, it's possible to
  1478  // run into a scenario where resolving thousands of username bundles would take
  1479  // longer than the 10s.
  1480  func ListRequests(ctx context.Context, g *libkb.GlobalContext, teamName *string) ([]keybase1.TeamJoinRequest, error) {
  1481  	var arg libkb.APIArg
  1482  	mctx := libkb.NewMetaContext(ctx, g)
  1483  	if teamName != nil {
  1484  		arg = apiArg("team/access_requests")
  1485  		arg.Args.Add("team", libkb.S{Val: *teamName})
  1486  	} else {
  1487  		arg = apiArg("team/laar")
  1488  	}
  1489  
  1490  	var arList accessRequestList
  1491  	if err := mctx.G().API.GetDecode(mctx, arg, &arList); err != nil {
  1492  		return nil, err
  1493  	}
  1494  
  1495  	var (
  1496  		joinRequests  = make([]keybase1.TeamJoinRequest, len(arList.Requests))
  1497  		requesterUIDs = make([]keybase1.UID, len(arList.Requests))
  1498  	)
  1499  	for i, ar := range arList.Requests {
  1500  		username := libkb.NewNormalizedUsername(ar.Username)
  1501  		uid := libkb.GetUIDByNormalizedUsername(g, username)
  1502  
  1503  		requesterUIDs[i] = uid
  1504  		joinRequests[i] = keybase1.TeamJoinRequest{
  1505  			Name:     ar.FQName,
  1506  			Username: username.String(),
  1507  			Ctime:    keybase1.ToUnixTime(ar.Ctime),
  1508  		}
  1509  	}
  1510  
  1511  	packages, err := g.UIDMapper.MapUIDsToUsernamePackages(ctx, g, requesterUIDs,
  1512  		defaultFullnameFreshness, 10*time.Second, true /* forceNetworkForFullNames */)
  1513  	if err != nil {
  1514  		g.Log.Debug("TeamsListRequests: failed to run uid mapper: %s", err)
  1515  	}
  1516  	for i, uid := range requesterUIDs {
  1517  		if packages[i].NormalizedUsername.IsNil() {
  1518  			g.Log.Debug("TeamsListRequests: failed to get username for: %s", uid)
  1519  			continue
  1520  		}
  1521  		if packages[i].FullName != nil {
  1522  			joinRequests[i].FullName = packages[i].FullName.FullName
  1523  		}
  1524  	}
  1525  
  1526  	return joinRequests, nil
  1527  }
  1528  
  1529  type myAccessRequestsList struct {
  1530  	Requests []struct {
  1531  		FQName string          `json:"fq_name"`
  1532  		TeamID keybase1.TeamID `json:"team_id"`
  1533  	} `json:"requests"`
  1534  	Status libkb.AppStatus `json:"status"`
  1535  }
  1536  
  1537  func (r *myAccessRequestsList) GetAppStatus() *libkb.AppStatus {
  1538  	return &r.Status
  1539  }
  1540  
  1541  func ListMyAccessRequests(ctx context.Context, g *libkb.GlobalContext, teamName *string) (res []keybase1.TeamName, err error) {
  1542  	mctx := libkb.NewMetaContext(ctx, g)
  1543  	arg := apiArg("team/my_access_requests")
  1544  	if teamName != nil {
  1545  		arg.Args.Add("team", libkb.S{Val: *teamName})
  1546  	}
  1547  
  1548  	var arList myAccessRequestsList
  1549  	if err := mctx.G().API.GetDecode(mctx, arg, &arList); err != nil {
  1550  		return nil, err
  1551  	}
  1552  
  1553  	for _, req := range arList.Requests {
  1554  		name, err := keybase1.TeamNameFromString(req.FQName)
  1555  		if err != nil {
  1556  			return nil, err
  1557  		}
  1558  		res = append(res, name)
  1559  	}
  1560  
  1561  	return res, nil
  1562  }
  1563  
  1564  func IgnoreRequest(ctx context.Context, g *libkb.GlobalContext, teamName, username string) error {
  1565  	mctx := libkb.NewMetaContext(ctx, g)
  1566  	uv, err := loadUserVersionByUsername(ctx, g, username, false /* useTracking */)
  1567  	if err != nil {
  1568  		if err == errInviteRequired {
  1569  			return libkb.NotFoundError{
  1570  				Msg: fmt.Sprintf("No keybase user found (%s)", username),
  1571  			}
  1572  		}
  1573  		return err
  1574  	}
  1575  	arg := apiArg("team/deny_access")
  1576  	arg.Args.Add("team", libkb.S{Val: teamName})
  1577  	arg.Args.Add("uid", libkb.S{Val: uv.Uid.String()})
  1578  	if _, err := mctx.G().API.Post(mctx, arg); err != nil {
  1579  		return err
  1580  	}
  1581  	t, err := GetForTeamManagementByStringName(ctx, g, teamName, true)
  1582  	if err != nil {
  1583  		return err
  1584  	}
  1585  	return t.notifyNoChainChange(ctx, keybase1.TeamChangeSet{Misc: true})
  1586  }
  1587  
  1588  func apiArg(endpoint string) libkb.APIArg {
  1589  	arg := libkb.NewAPIArg(endpoint)
  1590  	arg.Args = libkb.NewHTTPArgs()
  1591  	arg.SessionType = libkb.APISessionTypeREQUIRED
  1592  	return arg
  1593  }
  1594  
  1595  func GetRootID(ctx context.Context, g *libkb.GlobalContext, id keybase1.TeamID) (keybase1.TeamID, error) {
  1596  	team, _, err := g.GetTeamLoader().Load(ctx, keybase1.LoadTeamArg{
  1597  		ID:      id,
  1598  		Public:  id.IsPublic(),
  1599  		StaleOK: true,
  1600  	})
  1601  
  1602  	if err != nil {
  1603  		return keybase1.TeamID(""), err
  1604  	}
  1605  
  1606  	return team.Name.RootAncestorName().ToTeamID(id.IsPublic()), nil
  1607  }
  1608  
  1609  func ChangeTeamSettings(ctx context.Context, g *libkb.GlobalContext, teamName string, settings keybase1.TeamSettings) error {
  1610  	mctx := libkb.NewMetaContext(ctx, g)
  1611  	id, err := GetTeamIDByNameRPC(mctx, teamName)
  1612  	if err != nil {
  1613  		return err
  1614  	}
  1615  	return ChangeTeamSettingsByID(ctx, g, id, settings)
  1616  }
  1617  
  1618  func ChangeTeamSettingsByID(ctx context.Context, g *libkb.GlobalContext, id keybase1.TeamID,
  1619  	settings keybase1.TeamSettings) error {
  1620  	return RetryIfPossible(ctx, g, func(ctx context.Context, _ int) error {
  1621  		t, err := GetForTeamManagementByTeamID(ctx, g, id, true)
  1622  		if err != nil {
  1623  			return err
  1624  		}
  1625  
  1626  		if !settings.Open && !t.IsOpen() {
  1627  			g.Log.CDebugf(ctx, "team is already closed, just returning: %s", id)
  1628  			return nil
  1629  		}
  1630  
  1631  		if settings.Open && t.IsOpen() && t.OpenTeamJoinAs() == settings.JoinAs {
  1632  			g.Log.CDebugf(ctx, "team is already open with default role: team: %s role: %s",
  1633  				id, strings.ToLower(t.OpenTeamJoinAs().String()))
  1634  			return nil
  1635  		}
  1636  
  1637  		// Rotate if team is moving from open to closed.
  1638  		rotateKey := t.IsOpen() && !settings.Open
  1639  		return t.PostTeamSettings(ctx, settings, rotateKey)
  1640  	})
  1641  }
  1642  
  1643  func removeMemberInvite(ctx context.Context, g *libkb.GlobalContext, team *Team, username string, uv keybase1.UserVersion) (err error) {
  1644  	g.CTrace(ctx, "removeMemberInvite", &err)
  1645  	var lookingFor keybase1.TeamInviteName
  1646  	var typ string
  1647  	if !uv.IsNil() {
  1648  		lookingFor = uv.TeamInviteName()
  1649  		typ = "keybase"
  1650  	} else {
  1651  		ptyp, name, err := parseSocialAssertion(libkb.NewMetaContext(ctx, g), username)
  1652  		if err != nil {
  1653  			return err
  1654  		}
  1655  		lookingFor = keybase1.TeamInviteName(name)
  1656  		typ = ptyp
  1657  	}
  1658  
  1659  	return removeMemberInviteOfType(ctx, g, team, lookingFor, typ)
  1660  }
  1661  
  1662  func removeMemberInviteOfType(ctx context.Context, g *libkb.GlobalContext, team *Team, inviteName keybase1.TeamInviteName, typ string) error {
  1663  	g.Log.CDebugf(ctx, "looking for active invite in %s for %s/%s", team.Name(), typ, inviteName)
  1664  
  1665  	// make sure this is a valid invite type
  1666  	itype, err := TeamInviteTypeFromString(libkb.NewMetaContext(ctx, g), typ)
  1667  	if err != nil {
  1668  		return err
  1669  	}
  1670  	validatedType, err := itype.String()
  1671  	if err != nil {
  1672  		return err
  1673  	}
  1674  
  1675  	for _, invMD := range team.chain().ActiveInvites() {
  1676  		inv := invMD.Invite
  1677  		invTypeStr, err := inv.Type.String()
  1678  		if err != nil {
  1679  			return err
  1680  		}
  1681  		if invTypeStr != validatedType {
  1682  			continue
  1683  		}
  1684  		if inv.Name != inviteName {
  1685  			continue
  1686  		}
  1687  
  1688  		g.Log.CDebugf(ctx, "found invite %s for %s/%s, removing it", inv.Id, validatedType, inviteName)
  1689  		return removeInviteID(ctx, team, inv.Id)
  1690  	}
  1691  
  1692  	g.Log.CDebugf(ctx, "no invites found to remove for %s/%s", validatedType, inviteName)
  1693  	return NewMemberNotFoundInChainError(libkb.NotFoundError{})
  1694  }
  1695  
  1696  func removeKeybaseTypeInviteForUID(ctx context.Context, g *libkb.GlobalContext, team *Team, uid keybase1.UID) (err error) {
  1697  	g.CTrace(ctx, "removeKeybaseTypeInviteForUID", &err)
  1698  	g.Log.CDebugf(ctx, "looking for active or obsolete keybase-type invite in %s for %s", team.Name(), uid)
  1699  
  1700  	// Remove all invites with given UID, so we don't have to worry
  1701  	// about old teams that might have duplicates.
  1702  
  1703  	var toRemove []keybase1.TeamInviteID
  1704  	allInvites := team.GetActiveAndObsoleteInvites()
  1705  	for _, invite := range allInvites {
  1706  		if inviteUv, err := invite.KeybaseUserVersion(); err == nil {
  1707  			if inviteUv.Uid.Equal(uid) {
  1708  				g.Log.CDebugf(ctx, "found keybase-type invite %s for %s, removing", invite.Id, invite.Name)
  1709  				toRemove = append(toRemove, invite.Id)
  1710  			}
  1711  		}
  1712  	}
  1713  
  1714  	if len(toRemove) > 0 {
  1715  		g.Log.CDebugf(ctx, "found %d keybase-type invites for %s, trying to post remove invite link",
  1716  			len(toRemove), uid)
  1717  		return removeMultipleInviteIDs(ctx, team, toRemove)
  1718  	}
  1719  
  1720  	g.Log.CDebugf(ctx, "no keybase-invites found to remove %s", uid)
  1721  	return NewMemberNotFoundInChainError(libkb.NotFoundError{})
  1722  }
  1723  
  1724  func removeMultipleInviteIDs(ctx context.Context, team *Team, invIDs []keybase1.TeamInviteID) error {
  1725  	var cancelList []SCTeamInviteID
  1726  	for _, invID := range invIDs {
  1727  		cancelList = append(cancelList, SCTeamInviteID(invID))
  1728  	}
  1729  	invites := SCTeamInvites{
  1730  		Cancel: &cancelList,
  1731  	}
  1732  	return team.postTeamInvites(ctx, invites)
  1733  }
  1734  
  1735  func removeInviteID(ctx context.Context, team *Team, invID keybase1.TeamInviteID) (err error) {
  1736  	defer team.MetaContext(ctx).Trace("remoteInviteID", &err)()
  1737  	cancelList := []SCTeamInviteID{SCTeamInviteID(invID)}
  1738  	invites := SCTeamInvites{
  1739  		Cancel: &cancelList,
  1740  	}
  1741  	return team.postTeamInvites(ctx, invites)
  1742  }
  1743  
  1744  func CreateSeitanToken(ctx context.Context, g *libkb.GlobalContext, teamname string, role keybase1.TeamRole, label keybase1.SeitanKeyLabel) (keybase1.SeitanIKey, error) {
  1745  	t, err := GetForTeamManagementByStringName(ctx, g, teamname, true)
  1746  	if err != nil {
  1747  		return "", err
  1748  	}
  1749  	ikey, err := t.InviteSeitan(ctx, role, label)
  1750  	if err != nil {
  1751  		return "", err
  1752  	}
  1753  
  1754  	return keybase1.SeitanIKey(ikey), err
  1755  }
  1756  
  1757  func CreateSeitanTokenV2(ctx context.Context, g *libkb.GlobalContext, teamname string, role keybase1.TeamRole, label keybase1.SeitanKeyLabel) (keybase1.SeitanIKeyV2, error) {
  1758  	t, err := GetForTeamManagementByStringName(ctx, g, teamname, true)
  1759  	if err != nil {
  1760  		return "", err
  1761  	}
  1762  	ikey, err := t.InviteSeitanV2(ctx, role, label)
  1763  	if err != nil {
  1764  		return "", err
  1765  	}
  1766  
  1767  	return keybase1.SeitanIKeyV2(ikey), err
  1768  }
  1769  
  1770  func CreateInvitelink(mctx libkb.MetaContext, teamname string,
  1771  	role keybase1.TeamRole, maxUses keybase1.TeamInviteMaxUses,
  1772  	etime *keybase1.UnixTime) (invitelink keybase1.Invitelink, err error) {
  1773  	t, err := GetForTeamManagementByStringName(mctx.Ctx(), mctx.G(), teamname, true)
  1774  	if err != nil {
  1775  		return invitelink, err
  1776  	}
  1777  	ikey, id, err := t.InviteInvitelink(mctx.Ctx(), role, maxUses, etime)
  1778  	if err != nil {
  1779  		return invitelink, err
  1780  	}
  1781  	shortID, err := id.ToShortInviteID()
  1782  	if err != nil {
  1783  		return invitelink, err
  1784  	}
  1785  	return keybase1.Invitelink{
  1786  		Ikey: ikey,
  1787  		Url:  GenerateInvitelinkURL(mctx, ikey, shortID),
  1788  	}, err
  1789  }
  1790  
  1791  // CreateTLF is called by KBFS when a TLF ID is associated with an implicit team.
  1792  // Should work on either named or implicit teams.
  1793  func CreateTLF(ctx context.Context, g *libkb.GlobalContext, arg keybase1.CreateTLFArg) (err error) {
  1794  	defer g.CTrace(ctx, fmt.Sprintf("CreateTLF(%v)", arg), &err)()
  1795  	ctx = libkb.WithLogTag(ctx, "CREATETLF")
  1796  	return RetryIfPossible(ctx, g, func(ctx context.Context, _ int) error {
  1797  		t, err := GetForTeamManagementByTeamID(ctx, g, arg.TeamID, false)
  1798  		if err != nil {
  1799  			return err
  1800  		}
  1801  		role, err := t.myRole(ctx)
  1802  		if err != nil {
  1803  			return err
  1804  		}
  1805  		if !role.IsWriterOrAbove() {
  1806  			return fmt.Errorf("permission denied: need writer access (or above)")
  1807  		}
  1808  		return t.AssociateWithTLFID(ctx, arg.TlfID)
  1809  	})
  1810  }
  1811  
  1812  func GetKBFSTeamSettings(ctx context.Context, g *libkb.GlobalContext, isPublic bool, teamID keybase1.TeamID) (res keybase1.KBFSTeamSettings, err error) {
  1813  	defer g.CTrace(ctx, fmt.Sprintf("GetKBFSTeamSettings(%v,%v)", isPublic, teamID), &err)()
  1814  	team, err := Load(ctx, g, keybase1.LoadTeamArg{
  1815  		ID:     teamID,
  1816  		Public: isPublic,
  1817  	})
  1818  	if err != nil {
  1819  		return res, err
  1820  	}
  1821  	res.TlfID = team.LatestKBFSTLFID()
  1822  	g.Log.CDebugf(ctx, "res: %+v", res)
  1823  	return res, err
  1824  }
  1825  
  1826  func CanUserPerform(ctx context.Context, g *libkb.GlobalContext, teamname string) (ret keybase1.TeamOperation, err error) {
  1827  	team, err := Load(ctx, g, keybase1.LoadTeamArg{
  1828  		Name:                      teamname,
  1829  		StaleOK:                   true,
  1830  		Public:                    false, // assume private team
  1831  		AllowNameLookupBurstCache: true,
  1832  		AuditMode:                 keybase1.AuditMode_SKIP,
  1833  	})
  1834  	if err != nil {
  1835  		// Note: we eat the error here, assuming it meant this user
  1836  		// is not a member
  1837  		g.Log.CWarningf(ctx, "CanUserPerform team Load failure, continuing: %v)", err)
  1838  		return ret, nil
  1839  	}
  1840  	meUV, err := team.currentUserUV(ctx)
  1841  	if err != nil {
  1842  		return ret, err
  1843  	}
  1844  
  1845  	getIsImplicitAdmin := func() (bool, error) {
  1846  		if team.ID.IsRootTeam() {
  1847  			return false, nil
  1848  		}
  1849  		uvs, err := g.GetTeamLoader().ImplicitAdmins(ctx, team.ID)
  1850  		if err != nil {
  1851  			return false, err
  1852  		}
  1853  		for _, uv := range uvs {
  1854  			if uv == meUV {
  1855  				return true, nil
  1856  			}
  1857  		}
  1858  		return false, nil
  1859  	}
  1860  
  1861  	teamRole, err := team.MemberRole(ctx, meUV)
  1862  	if err != nil {
  1863  		return ret, err
  1864  	}
  1865  
  1866  	isRoleOrAbove := teamRole.IsOrAbove
  1867  
  1868  	canMemberShowcase := func() (bool, error) {
  1869  		if teamRole.IsOrAbove(keybase1.TeamRole_ADMIN) {
  1870  			return true, nil
  1871  		} else if teamRole == keybase1.TeamRole_NONE {
  1872  			return false, nil
  1873  		}
  1874  		showcase, err := GetTeamShowcase(ctx, g, team.ID)
  1875  		if err != nil {
  1876  			return false, err
  1877  		}
  1878  		return showcase.AnyMemberShowcase, nil
  1879  	}
  1880  
  1881  	getHasOtherOwner := func() (bool, error) {
  1882  		owners, err := team.UsersWithRole(keybase1.TeamRole_OWNER)
  1883  		if err != nil {
  1884  			return false, err
  1885  		}
  1886  		if len(owners) > 1 {
  1887  			return true, nil
  1888  		}
  1889  		for _, owner := range owners {
  1890  			if owner == meUV {
  1891  				g.Log.CDebugf(ctx, "hasOtherOwner: I am the sole owner")
  1892  				return false, nil
  1893  			}
  1894  		}
  1895  		return true, nil
  1896  	}
  1897  
  1898  	isBot := isRoleOrAbove(keybase1.TeamRole_BOT)
  1899  	isWriter := isRoleOrAbove(keybase1.TeamRole_WRITER)
  1900  	isAdmin := isRoleOrAbove(keybase1.TeamRole_ADMIN)
  1901  	isOwner := isRoleOrAbove(keybase1.TeamRole_OWNER)
  1902  	isImplicitAdmin, err := getIsImplicitAdmin()
  1903  	if err != nil {
  1904  		return ret, err
  1905  	}
  1906  
  1907  	// team settings
  1908  	ret.ListFirst = isImplicitAdmin
  1909  	ret.JoinTeam = teamRole == keybase1.TeamRole_NONE && isImplicitAdmin
  1910  	ret.SetPublicityAny = isAdmin || isImplicitAdmin
  1911  	ret.ManageMembers = isAdmin || isImplicitAdmin
  1912  	ret.ManageSubteams = isAdmin || isImplicitAdmin
  1913  	ret.RenameTeam = team.IsSubteam() && isImplicitAdmin
  1914  	ret.SetTeamShowcase = isAdmin || isImplicitAdmin
  1915  	ret.ChangeOpenTeam = isAdmin || isImplicitAdmin
  1916  	ret.ChangeTarsDisabled = isAdmin || isImplicitAdmin
  1917  	ret.EditTeamDescription = isAdmin || isImplicitAdmin
  1918  	ret.ManageBots = isAdmin || isImplicitAdmin
  1919  	ret.ManageEmojis = isWriter
  1920  	ret.DeleteOtherEmojis = isAdmin
  1921  	ret.SetMemberShowcase, err = canMemberShowcase()
  1922  	if err != nil {
  1923  		return ret, err
  1924  	}
  1925  	if team.chain().IsSubteam() {
  1926  		ret.DeleteTeam = isImplicitAdmin
  1927  	} else {
  1928  		ret.DeleteTeam = isOwner
  1929  	}
  1930  
  1931  	// only check hasOtherOwner if we have to.
  1932  	if teamRole != keybase1.TeamRole_NONE {
  1933  		leaveTeam := true
  1934  		if isOwner {
  1935  			hasOtherOwner, err := getHasOtherOwner()
  1936  			if err != nil {
  1937  				return ret, err
  1938  			}
  1939  			leaveTeam = hasOtherOwner
  1940  		}
  1941  		ret.LeaveTeam = leaveTeam
  1942  	}
  1943  
  1944  	// chat settings
  1945  	ret.Chat = isBot
  1946  	ret.CreateChannel = isWriter
  1947  	ret.RenameChannel = isWriter
  1948  	ret.EditChannelDescription = isWriter
  1949  	ret.DeleteChannel = isAdmin
  1950  	ret.SetRetentionPolicy = isAdmin
  1951  	ret.SetMinWriterRole = isAdmin
  1952  	ret.DeleteChatHistory = isAdmin
  1953  	ret.DeleteOtherMessages = isAdmin
  1954  	ret.PinMessage = isWriter
  1955  
  1956  	return ret, err
  1957  }
  1958  
  1959  func RotateKey(ctx context.Context, g *libkb.GlobalContext, arg keybase1.TeamRotateKeyArg) (err error) {
  1960  	teamID := arg.TeamID
  1961  	defer g.CTrace(ctx, fmt.Sprintf("RotateKey(%+v)", arg), &err)()
  1962  	return RetryIfPossible(ctx, g, func(ctx context.Context, attempt int) error {
  1963  		team, err := Load(ctx, g, keybase1.LoadTeamArg{
  1964  			ID:          teamID,
  1965  			Public:      teamID.IsPublic(),
  1966  			ForceRepoll: attempt > 0,
  1967  		})
  1968  		if err != nil {
  1969  			return err
  1970  		}
  1971  		return team.Rotate(ctx, arg.Rt)
  1972  	})
  1973  }
  1974  
  1975  func RotateKeyVisible(ctx context.Context, g *libkb.GlobalContext, id keybase1.TeamID) error {
  1976  	return RotateKey(ctx, g, keybase1.TeamRotateKeyArg{TeamID: id, Rt: keybase1.RotationType_VISIBLE})
  1977  }
  1978  
  1979  func TeamDebug(ctx context.Context, g *libkb.GlobalContext, teamID keybase1.TeamID) (res keybase1.TeamDebugRes, err error) {
  1980  	defer g.CTrace(ctx, fmt.Sprintf("TeamDebug(%v)", teamID), &err)()
  1981  	team, err := Load(ctx, g, keybase1.LoadTeamArg{
  1982  		ID:          teamID,
  1983  		Public:      teamID.IsPublic(),
  1984  		ForceRepoll: true,
  1985  	})
  1986  	if err != nil {
  1987  		return res, err
  1988  	}
  1989  	return keybase1.TeamDebugRes{Chain: team.Data.Chain}, nil
  1990  }
  1991  
  1992  func MapImplicitTeamIDToDisplayName(ctx context.Context, g *libkb.GlobalContext, id keybase1.TeamID, isPublic bool) (folder keybase1.Folder, err error) {
  1993  
  1994  	team, err := Load(ctx, g, keybase1.LoadTeamArg{
  1995  		ID:     id,
  1996  		Public: isPublic,
  1997  	})
  1998  	if err != nil {
  1999  		return folder, err
  2000  	}
  2001  
  2002  	if !team.IsImplicit() {
  2003  		return folder, NewExplicitTeamOperationError("MapImplicitTeamIDToDisplayName")
  2004  	}
  2005  
  2006  	itdn, err := team.ImplicitTeamDisplayName(ctx)
  2007  	if err != nil {
  2008  		return folder, err
  2009  	}
  2010  
  2011  	folder.Name, err = FormatImplicitTeamDisplayName(ctx, g, itdn)
  2012  	if err != nil {
  2013  		return folder, err
  2014  	}
  2015  	folder.Private = !isPublic
  2016  	if isPublic {
  2017  		folder.FolderType = keybase1.FolderType_PUBLIC
  2018  	} else {
  2019  		folder.FolderType = keybase1.FolderType_PRIVATE
  2020  	}
  2021  	return folder, nil
  2022  }
  2023  
  2024  type disableTARsRes struct {
  2025  	Status   libkb.AppStatus `json:"status"`
  2026  	Disabled bool            `json:"disabled"`
  2027  }
  2028  
  2029  func (c *disableTARsRes) GetAppStatus() *libkb.AppStatus {
  2030  	return &c.Status
  2031  }
  2032  
  2033  func GetTarsDisabled(ctx context.Context, g *libkb.GlobalContext, id keybase1.TeamID) (bool, error) {
  2034  	mctx := libkb.NewMetaContext(ctx, g)
  2035  	arg := apiArg("team/disable_tars")
  2036  	arg.Args.Add("tid", libkb.S{Val: id.String()})
  2037  	var ret disableTARsRes
  2038  	if err := mctx.G().API.GetDecode(mctx, arg, &ret); err != nil {
  2039  		return false, err
  2040  	}
  2041  
  2042  	return ret.Disabled, nil
  2043  }
  2044  
  2045  func SetTarsDisabled(ctx context.Context, g *libkb.GlobalContext, id keybase1.TeamID, disabled bool) error {
  2046  	mctx := libkb.NewMetaContext(ctx, g)
  2047  	t, err := GetForTeamManagementByTeamID(ctx, g, id, true)
  2048  	if err != nil {
  2049  		return err
  2050  	}
  2051  
  2052  	arg := apiArg("team/disable_tars")
  2053  	arg.Args.Add("tid", libkb.S{Val: id.String()})
  2054  	arg.Args.Add("disabled", libkb.B{Val: disabled})
  2055  	if _, err := mctx.G().API.Post(mctx, arg); err != nil {
  2056  		return err
  2057  	}
  2058  	return t.notifyNoChainChange(ctx, keybase1.TeamChangeSet{Misc: true})
  2059  }
  2060  
  2061  type listProfileAddServerRes struct {
  2062  	libkb.AppStatusEmbed
  2063  	Teams []listProfileAddResEntry `json:"teams"`
  2064  }
  2065  
  2066  type listProfileAddResEntry struct {
  2067  	TeamID     keybase1.TeamID `json:"team_id"`
  2068  	FqName     string          `json:"fq_name"`
  2069  	IsOpenTeam bool            `json:"is_open_team"`
  2070  	// Whether the caller has admin powers.
  2071  	CallerAdmin bool `json:"caller_admin"`
  2072  	// Whether the 'them' user is an explicit member.
  2073  	ThemMember bool `json:"them_member"`
  2074  }
  2075  
  2076  func TeamProfileAddList(ctx context.Context, g *libkb.GlobalContext, username string) (res []keybase1.TeamProfileAddEntry, err error) {
  2077  	uname := kbun.NewNormalizedUsername(username)
  2078  	uid, err := g.GetUPAKLoader().LookupUID(ctx, uname)
  2079  	if err != nil {
  2080  		return nil, err
  2081  	}
  2082  	arg := apiArg("team/list_profile_add")
  2083  	arg.Args.Add("uid", libkb.S{Val: uid.String()})
  2084  	var serverRes listProfileAddServerRes
  2085  	mctx := libkb.NewMetaContext(ctx, g)
  2086  	if err = mctx.G().API.GetDecode(mctx, arg, &serverRes); err != nil {
  2087  		return nil, err
  2088  	}
  2089  	for _, entry := range serverRes.Teams {
  2090  		teamName, err := keybase1.TeamNameFromString(entry.FqName)
  2091  		if err != nil {
  2092  			mctx.Debug("TeamProfileAddList server returned bad team name %v: %v", entry.FqName, err)
  2093  			continue
  2094  		}
  2095  		disabledReason := ""
  2096  		if !entry.CallerAdmin {
  2097  			disabledReason = "Only admins can add people."
  2098  		} else if entry.ThemMember {
  2099  			disabledReason = fmt.Sprintf("%v is already a member.", uname.String())
  2100  		}
  2101  		res = append(res, keybase1.TeamProfileAddEntry{
  2102  			TeamID:         entry.TeamID,
  2103  			TeamName:       teamName,
  2104  			Open:           entry.IsOpenTeam,
  2105  			DisabledReason: disabledReason,
  2106  		})
  2107  	}
  2108  	return res, nil
  2109  }
  2110  
  2111  func ChangeTeamAvatar(mctx libkb.MetaContext, arg keybase1.UploadTeamAvatarArg) error {
  2112  	team, err := Load(mctx.Ctx(), mctx.G(), keybase1.LoadTeamArg{
  2113  		Name:        arg.Teamname,
  2114  		Public:      false,
  2115  		ForceRepoll: false,
  2116  		NeedAdmin:   true,
  2117  	})
  2118  	if err != nil {
  2119  		return fixupTeamGetError(mctx.Ctx(), mctx.G(), err, arg.Teamname, false /* public */)
  2120  	}
  2121  
  2122  	if err := avatars.UploadImage(mctx, arg.Filename, &team.ID, arg.Crop); err != nil {
  2123  		return err
  2124  	}
  2125  
  2126  	if arg.SendChatNotification {
  2127  		SendTeamChatChangeAvatar(mctx, team.Name().String(), mctx.G().Env.GetUsername().String())
  2128  	}
  2129  	return nil
  2130  }
  2131  
  2132  func FindNextMerkleRootAfterRemoval(mctx libkb.MetaContext, arg keybase1.FindNextMerkleRootAfterTeamRemovalBySigningKeyArg) (res keybase1.NextMerkleRootRes, err error) {
  2133  	defer mctx.Trace(fmt.Sprintf("teams.FindNextMerkleRootAfterRemoval(%+v)", arg), &err)()
  2134  
  2135  	team, err := Load(mctx.Ctx(), mctx.G(), keybase1.LoadTeamArg{
  2136  		ID:          arg.Team,
  2137  		Public:      arg.IsPublic,
  2138  		ForceRepoll: false,
  2139  		NeedAdmin:   false,
  2140  	})
  2141  	if err != nil {
  2142  		return res, err
  2143  	}
  2144  	upak, _, err := mctx.G().GetUPAKLoader().LoadV2(libkb.NewLoadUserArgWithMetaContext(mctx).
  2145  		WithUID(arg.Uid).
  2146  		WithPublicKeyOptional().
  2147  		WithForcePoll(false))
  2148  	if err != nil {
  2149  		return res, err
  2150  	}
  2151  
  2152  	vers, _ := upak.FindKID(arg.SigningKey)
  2153  	if vers == nil {
  2154  		return res, libkb.NotFoundError{Msg: fmt.Sprintf("KID %s not found for %s", arg.SigningKey, arg.Uid)}
  2155  	}
  2156  
  2157  	uv := vers.ToUserVersion()
  2158  	logPoints := team.chain().inner.UserLog[uv]
  2159  	demotionPredicate := func(p keybase1.UserLogPoint) bool {
  2160  		if arg.AnyRoleAllowed {
  2161  			return !p.Role.IsBotOrAbove()
  2162  		}
  2163  		return !p.Role.IsWriterOrAbove()
  2164  	}
  2165  	var earliestDemotion int
  2166  	var logPoint *keybase1.UserLogPoint
  2167  	for i := len(logPoints) - 1; i >= 0; i-- {
  2168  		if demotionPredicate(logPoints[i]) {
  2169  			earliestDemotion = i
  2170  		} else if earliestDemotion != 0 {
  2171  			p := logPoints[earliestDemotion].DeepCopy()
  2172  			logPoint = &p
  2173  			break
  2174  		}
  2175  	}
  2176  	if logPoint == nil {
  2177  		return res, libkb.NotFoundError{Msg: "no downgraded log point for user found"}
  2178  	}
  2179  
  2180  	return libkb.FindNextMerkleRootAfterTeamRemoval(mctx, keybase1.FindNextMerkleRootAfterTeamRemovalArg{
  2181  		Uid:               arg.Uid,
  2182  		Team:              arg.Team,
  2183  		IsPublic:          arg.IsPublic,
  2184  		TeamSigchainSeqno: logPoint.SigMeta.SigChainLocation.Seqno,
  2185  		Prev:              logPoint.SigMeta.PrevMerkleRootSigned,
  2186  	})
  2187  }
  2188  
  2189  func ProfileTeamLoad(mctx libkb.MetaContext, arg keybase1.LoadTeamArg) (res keybase1.ProfileTeamLoadRes, err error) {
  2190  	pre := mctx.G().Clock().Now()
  2191  	_, err = Load(mctx.Ctx(), mctx.G(), arg)
  2192  	post := mctx.G().Clock().Now()
  2193  	res.LoadTimeNsec = post.Sub(pre).Nanoseconds()
  2194  	return res, err
  2195  }
  2196  
  2197  func GetTeamIDByNameRPC(mctx libkb.MetaContext, teamName string) (res keybase1.TeamID, err error) {
  2198  	nameParsed, err := keybase1.TeamNameFromString(teamName)
  2199  	if err != nil {
  2200  		return "", err
  2201  	}
  2202  	id, err := ResolveNameToID(mctx.Ctx(), mctx.G(), nameParsed)
  2203  	if err != nil {
  2204  		return "", err
  2205  	}
  2206  	return id, nil
  2207  }
  2208  
  2209  func FindAssertionsInTeamNoResolve(mctx libkb.MetaContext, teamID keybase1.TeamID, assertions []string) (ret []string, err error) {
  2210  	team, err := GetForTeamManagementByTeamID(mctx.Ctx(), mctx.G(), teamID, true /* needAdmin */)
  2211  	if err != nil {
  2212  		return nil, err
  2213  	}
  2214  
  2215  	// Don't check one assertion more than once, if we got duplicates.
  2216  	checkedAssertions := make(map[string]struct{})
  2217  
  2218  	actx := externals.MakeAssertionContext(mctx)
  2219  	for _, assertionStr := range assertions {
  2220  		if _, found := checkedAssertions[assertionStr]; found {
  2221  			continue
  2222  		}
  2223  		checkedAssertions[assertionStr] = struct{}{}
  2224  
  2225  		assertion, err := libkb.AssertionParseAndOnly(actx, assertionStr)
  2226  		if err != nil {
  2227  			return nil, fmt.Errorf("failed to parse assertion %q: %w", assertionStr, err)
  2228  		}
  2229  
  2230  		var url libkb.AssertionURL
  2231  		urls := assertion.CollectUrls(nil)
  2232  		if len(urls) > 1 {
  2233  			// For compound assertions, try to find exactly one Keybase
  2234  			// assertion among the factors. Any other compound assertions
  2235  			// should not have been passed to this function.
  2236  			for _, u := range urls {
  2237  				if u.IsKeybase() {
  2238  					if url != nil {
  2239  						return nil, fmt.Errorf("assertion %q has more than one Keybase username", assertionStr)
  2240  					}
  2241  					url = u
  2242  				}
  2243  			}
  2244  			if url == nil {
  2245  				return nil, fmt.Errorf("assertion %q does not have Keybase username", assertionStr)
  2246  			}
  2247  		} else {
  2248  			url = urls[0]
  2249  		}
  2250  
  2251  		if url.IsKeybase() {
  2252  			// Load the user to get the right eldest seqno. We don't want
  2253  			// untrusted seqnos here from uidmapper, because UI might be
  2254  			// making decisions about whom to add to the team basing on
  2255  			// results from this function.
  2256  			loadUserArg := libkb.NewLoadUserArgWithMetaContext(mctx).WithName(url.GetValue()).WithPublicKeyOptional()
  2257  			user, err := libkb.LoadUser(loadUserArg)
  2258  			if err != nil {
  2259  				if _, ok := err.(libkb.NotFoundError); ok {
  2260  					// User not found - that's fine.
  2261  					continue
  2262  				}
  2263  				return nil, fmt.Errorf("error when loading user for assertion %q: %w", assertionStr, err)
  2264  			}
  2265  			if user.GetStatus() != keybase1.StatusCode_SCOk {
  2266  				// User is deleted or similar. Skip for now.
  2267  				continue
  2268  			}
  2269  			uv := user.ToUserVersion()
  2270  			_, kbInviteUV, found := team.FindActiveKeybaseInvite(user.GetUID())
  2271  			if found && kbInviteUV.Eq(uv) {
  2272  				// Either user still doesn't have a PUK, or if they do, they
  2273  				// should be added automatically through team_rekeyd
  2274  				// notification soon.
  2275  				ret = append(ret, assertionStr)
  2276  				continue
  2277  			}
  2278  
  2279  			teamUVs := team.AllUserVersionsByUID(mctx.Ctx(), user.GetUID())
  2280  			for _, teamUV := range teamUVs {
  2281  				if teamUV.Eq(uv) {
  2282  					ret = append(ret, assertionStr)
  2283  					break
  2284  				}
  2285  				// or else user is in the team but with old UV, so it's fine to
  2286  				// add them again.
  2287  			}
  2288  		} else {
  2289  			social, err := assertion.ToSocialAssertion()
  2290  			if err != nil {
  2291  				return nil, fmt.Errorf(
  2292  					"Don't know what to do with %q - not a social assertion or keybase username: %w",
  2293  					assertionStr, err)
  2294  			}
  2295  			hasInvite, err := team.HasActiveInvite(mctx, social.TeamInviteName(), social.TeamInviteType())
  2296  			if err != nil {
  2297  				return nil, fmt.Errorf("Failed checking %q: %w", assertionStr, err)
  2298  			}
  2299  			if hasInvite {
  2300  				ret = append(ret, assertionStr)
  2301  			}
  2302  		}
  2303  	}
  2304  
  2305  	return ret, nil
  2306  }