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

     1  package teams
     2  
     3  import (
     4  	"context"
     5  	"encoding/base64"
     6  	"errors"
     7  	"fmt"
     8  
     9  	"github.com/keybase/client/go/engine"
    10  	"github.com/keybase/client/go/gregor"
    11  	"github.com/keybase/client/go/libkb"
    12  	"github.com/keybase/client/go/protocol/keybase1"
    13  )
    14  
    15  func HandleRotateRequest(ctx context.Context, g *libkb.GlobalContext, msg keybase1.TeamCLKRMsg) (err error) {
    16  	ctx = libkb.WithLogTag(ctx, "CLKR")
    17  	defer g.CTrace(ctx, fmt.Sprintf("HandleRotateRequest(%s,%d)", msg.TeamID, msg.Generation), &err)()
    18  
    19  	teamID := msg.TeamID
    20  
    21  	var needTeamReload bool
    22  	loadTeamArg := keybase1.LoadTeamArg{
    23  		ID:          teamID,
    24  		Public:      teamID.IsPublic(),
    25  		ForceRepoll: true,
    26  	}
    27  	team, err := Load(ctx, g, loadTeamArg)
    28  	if err != nil {
    29  		return err
    30  	}
    31  
    32  	isAdmin := func() bool {
    33  		role, err := team.myRole(ctx)
    34  		return err == nil && role.IsOrAbove(keybase1.TeamRole_ADMIN)
    35  	}
    36  	if len(msg.ResetUsersUntrusted) > 0 && team.IsOpen() && isAdmin() {
    37  		// NOTE: One day, this code path will be unused. Server should not
    38  		// issue CLKRs with ResetUsersUntrusted for open teams. Instead, there
    39  		// is a new work type to sweep reset users: OPENSWEEP. See
    40  		// `HandleOpenTeamSweepRequest`.
    41  
    42  		// Even though this is open team, and we are aiming to not rotate them,
    43  		// the server asked us specifically to do so with this CLKR. We have to
    44  		// obey, otherwise that CLKR will stay undone and server will keep
    45  		// asking users to rotate.
    46  		postedLink, err := sweepOpenTeamResetAndDeletedMembers(ctx, g, team, msg.ResetUsersUntrusted, true /* rotate */)
    47  		if err != nil {
    48  			g.Log.CDebugf(ctx, "Failed to sweep deleted members: %s", err)
    49  		} else {
    50  			// If sweepOpenTeamResetAndDeletedMembers does not do anything to
    51  			// the team, do not load team again later. Otherwise, if new link
    52  			// was posted, we need to reload.
    53  			needTeamReload = postedLink
    54  		}
    55  
    56  		// * NOTE * Still call the regular rotate key routine even if sweep
    57  		// succeeds and posts link.
    58  
    59  		// In normal case, it will reload team, see that generation is higher
    60  		// than one requested in CLKR (because we rotated key during sweeping),
    61  		// and then bail out.
    62  	}
    63  
    64  	return RetryIfPossible(ctx, g, func(ctx context.Context, _ int) error {
    65  		if needTeamReload {
    66  			team2, err := Load(ctx, g, loadTeamArg)
    67  			if err != nil {
    68  				return err
    69  			}
    70  			team = team2
    71  		}
    72  		needTeamReload = true // subsequent calls to Load here need repoll.
    73  
    74  		if team.Generation() > msg.Generation {
    75  			g.Log.CDebugf(ctx, "current team generation %d > team.clkr generation %d, not rotating",
    76  				team.Generation(), msg.Generation)
    77  			return nil
    78  		}
    79  
    80  		g.Log.CDebugf(ctx, "rotating team %s (%s)", team.Name(), teamID)
    81  
    82  		rotationType := keybase1.RotationType_CLKR
    83  		if teamID.IsPublic() {
    84  			rotationType = keybase1.RotationType_VISIBLE
    85  		}
    86  
    87  		if err := team.Rotate(ctx, rotationType); err != nil {
    88  			g.Log.CDebugf(ctx, "rotating team %s (%s) error: %s", team.Name(), teamID, err)
    89  			return err
    90  		}
    91  
    92  		g.Log.CDebugf(ctx, "success rotating team %s (%s)", team.Name(), teamID)
    93  		return nil
    94  	})
    95  }
    96  
    97  func HandleOpenTeamSweepRequest(ctx context.Context, g *libkb.GlobalContext, msg keybase1.TeamOpenSweepMsg) (err error) {
    98  	ctx = libkb.WithLogTag(ctx, "CLKR")
    99  	defer g.CTrace(ctx, fmt.Sprintf("HandleOpenTeamSweepRequest(teamID=%s,len(resetUsers)=%d)", msg.TeamID, len(msg.ResetUsersUntrusted)), &err)()
   100  
   101  	team, err := Load(ctx, g, keybase1.LoadTeamArg{
   102  		ID:          msg.TeamID,
   103  		Public:      msg.TeamID.IsPublic(),
   104  		ForceRepoll: true,
   105  	})
   106  	if err != nil {
   107  		return err
   108  	}
   109  
   110  	if !team.IsOpen() {
   111  		return fmt.Errorf("OpenSweep request for team %s that is not open", team.ID)
   112  	}
   113  
   114  	role, err := team.myRole(ctx)
   115  	if err != nil {
   116  		return err
   117  	}
   118  	if !role.IsOrAbove(keybase1.TeamRole_ADMIN) {
   119  		return fmt.Errorf("OpenSweep request for team %s but our role is: %s", team.ID, role.String())
   120  	}
   121  
   122  	rotate := !team.CanSkipKeyRotation()
   123  	_, err = sweepOpenTeamResetAndDeletedMembers(ctx, g, team, msg.ResetUsersUntrusted, rotate)
   124  	return err
   125  }
   126  
   127  func sweepOpenTeamResetAndDeletedMembers(ctx context.Context, g *libkb.GlobalContext,
   128  	team *Team, resetUsersUntrusted []keybase1.TeamCLKRResetUser, rotate bool) (postedLink bool, err error) {
   129  	// When CLKR is invoked because of account reset and it's an open team,
   130  	// we go ahead and boot reset readers and writers out of the team. Key
   131  	// is also rotated in the process (in the same ChangeMembership link).
   132  	defer g.CTrace(ctx, fmt.Sprintf("sweepOpenTeamResetAndDeletedMembers(rotate=%t)", rotate),
   133  		&err)()
   134  
   135  	// Go through resetUsersUntrusted and fetch non-cached latest
   136  	// EldestSeqnos/Status.
   137  	type seqnoAndStatus struct {
   138  		eldestSeqno keybase1.Seqno
   139  		status      keybase1.StatusCode
   140  	}
   141  	resetUsers := make(map[keybase1.UID]seqnoAndStatus)
   142  	for _, u := range resetUsersUntrusted {
   143  		if _, found := resetUsers[u.Uid]; found {
   144  			// User was in the list more than once.
   145  			continue
   146  		}
   147  
   148  		arg := libkb.NewLoadUserArg(g).
   149  			WithNetContext(ctx).
   150  			WithUID(u.Uid).
   151  			WithPublicKeyOptional().
   152  			WithForcePoll(true)
   153  		upak, _, err := g.GetUPAKLoader().LoadV2(arg)
   154  		if err == nil {
   155  			resetUsers[u.Uid] = seqnoAndStatus{
   156  				eldestSeqno: upak.Current.EldestSeqno,
   157  				status:      upak.Current.Status,
   158  			}
   159  		} else {
   160  			g.Log.CDebugf(ctx, "Could not load uid:%s through UPAKLoader: %s", u.Uid)
   161  		}
   162  	}
   163  
   164  	err = RetryIfPossible(ctx, g, func(ctx context.Context, attempt int) error {
   165  		if attempt > 0 {
   166  			var err error
   167  			team, err = Load(ctx, g, keybase1.LoadTeamArg{
   168  				ID:          team.ID,
   169  				Public:      team.ID.IsPublic(),
   170  				ForceRepoll: true,
   171  			})
   172  			if err != nil {
   173  				return err
   174  			}
   175  		}
   176  
   177  		changeReq := keybase1.TeamChangeReq{None: []keybase1.UserVersion{}}
   178  
   179  		// We are iterating through resetUsers map, which is map of
   180  		// uid->EldestSeqno that we loaded via UPAKLoader. Do not rely
   181  		// on server provided resetUsersUntrusted for EldestSeqnos,
   182  		// just use UIDs and see if these users are reset.
   183  
   184  		// We do not need to consider PUKless members here, because we
   185  		// are not auto-adding PUKless people to open teams (server
   186  		// doesn't send PUKless TARs in OPENREQ msg), so it shouldn't
   187  		// be an issue.
   188  		for uid, u := range resetUsers {
   189  			members := team.AllUserVersionsByUID(ctx, uid)
   190  			for _, memberUV := range members {
   191  				if memberUV.EldestSeqno == u.eldestSeqno && u.status != keybase1.StatusCode_SCDeleted {
   192  					// Member is the current incarnation of the user
   193  					// (or user has never reset).
   194  					continue
   195  				}
   196  				role, err := team.MemberRole(ctx, memberUV)
   197  				if err != nil {
   198  					continue
   199  				}
   200  				switch role {
   201  				case
   202  					keybase1.TeamRole_RESTRICTEDBOT,
   203  					keybase1.TeamRole_BOT,
   204  					keybase1.TeamRole_READER,
   205  					keybase1.TeamRole_WRITER:
   206  					changeReq.None = append(changeReq.None, memberUV)
   207  				}
   208  			}
   209  		}
   210  
   211  		if len(changeReq.None) == 0 {
   212  			// no one to kick out
   213  			g.Log.CDebugf(ctx, "No one to remove from a CLKR list of %d users, after UPAKLoading %d of them",
   214  				len(resetUsersUntrusted), len(resetUsers))
   215  			return nil
   216  		}
   217  
   218  		g.Log.CDebugf(ctx, "Posting ChangeMembership with %d removals (CLKR list was %d)",
   219  			len(changeReq.None), len(resetUsersUntrusted))
   220  
   221  		opts := ChangeMembershipOptions{
   222  			// Make it possible for user to come back in once they reprovision.
   223  			Permanent:       false,
   224  			SkipKeyRotation: !rotate,
   225  		}
   226  		if err := team.ChangeMembershipWithOptions(ctx, changeReq, opts); err != nil {
   227  			return err
   228  		}
   229  
   230  		// Notify the caller that we posted a sig and they have to
   231  		// load team again.
   232  		postedLink = true
   233  		return nil
   234  	})
   235  
   236  	return postedLink, err
   237  }
   238  
   239  func invalidateCaches(mctx libkb.MetaContext, teamID keybase1.TeamID) {
   240  	// refresh the KBFS Favorites cache since it no longer should contain
   241  	// this team.
   242  	mctx.G().NotifyRouter.HandleFavoritesChanged(mctx.G().GetMyUID())
   243  	if ekLib := mctx.G().GetEKLib(); ekLib != nil {
   244  		ekLib.PurgeTeamEKCachesForTeamID(mctx, teamID)
   245  		ekLib.PurgeTeambotEKCachesForTeamID(mctx, teamID)
   246  	}
   247  	if keyer := mctx.G().GetTeambotMemberKeyer(); keyer != nil {
   248  		keyer.PurgeCache(mctx)
   249  	}
   250  }
   251  
   252  func handleChangeSingle(ctx context.Context, g *libkb.GlobalContext, row keybase1.TeamChangeRow, change keybase1.TeamChangeSet) (changedMetadata bool, err error) {
   253  	change.KeyRotated = row.KeyRotated
   254  	change.MembershipChanged = row.MembershipChanged
   255  	change.Misc = row.Misc
   256  	mctx := libkb.NewMetaContext(ctx, g)
   257  
   258  	defer mctx.Trace(fmt.Sprintf("team.handleChangeSingle [%s] (%+v, %+v)", g.Env.GetUsername(), row, change),
   259  		&err)()
   260  
   261  	// Any errors are already logged in their respective functions.
   262  	_ = HintLatestSeqno(mctx, row.Id, row.LatestSeqno)
   263  	_ = HintLatestHiddenSeqno(mctx, row.Id, row.LatestHiddenSeqno)
   264  
   265  	// If we're handling a rename we should also purge the resolver cache and
   266  	// the KBFS favorites cache
   267  	if change.Renamed {
   268  		if err = PurgeResolverTeamID(ctx, g, row.Id); err != nil {
   269  			mctx.Warning("error in PurgeResolverTeamID: %v", err)
   270  			err = nil // non-fatal
   271  		}
   272  		invalidateCaches(mctx, row.Id)
   273  	}
   274  	// Send teamID and teamName in two separate notifications. It is
   275  	// server-trust that they are the same team.
   276  	g.NotifyRouter.HandleTeamChangedByBothKeys(ctx, row.Id, row.Name, row.LatestSeqno, row.ImplicitTeam, change,
   277  		row.LatestHiddenSeqno, row.LatestOffchainSeqno, keybase1.TeamChangedSource_SERVER)
   278  
   279  	// Note we only get updates about new subteams we create because the flow
   280  	// is that we join the team as an admin when we create them and then
   281  	// immediately leave.
   282  	if change.Renamed || change.MembershipChanged || change.Misc {
   283  		changedMetadata = true
   284  	}
   285  	if change.MembershipChanged {
   286  		g.NotifyRouter.HandleCanUserPerformChanged(ctx, row.Name)
   287  	}
   288  	return changedMetadata, nil
   289  }
   290  
   291  func HandleChangeNotification(ctx context.Context, g *libkb.GlobalContext, rows []keybase1.TeamChangeRow, changes keybase1.TeamChangeSet) (err error) {
   292  	ctx = libkb.WithLogTag(ctx, "THCN")
   293  	defer g.CTrace(ctx, "HandleChangeNotification", &err)()
   294  	var anyChangedMetadata bool
   295  	for _, row := range rows {
   296  		if changedMetadata, err := handleChangeSingle(ctx, g, row, changes); err != nil {
   297  			return err
   298  		} else if changedMetadata {
   299  			anyChangedMetadata = true
   300  		}
   301  	}
   302  	if anyChangedMetadata {
   303  		g.NotifyRouter.HandleTeamMetadataUpdate(ctx)
   304  	}
   305  	return nil
   306  }
   307  
   308  func HandleTeamMemberShowcaseChange(ctx context.Context, g *libkb.GlobalContext) (err error) {
   309  	defer g.CTrace(ctx, "HandleTeamMemberShowcaseChange", &err)()
   310  	g.NotifyRouter.HandleTeamMetadataUpdate(ctx)
   311  	return nil
   312  }
   313  
   314  func HandleDeleteNotification(ctx context.Context, g *libkb.GlobalContext, rows []keybase1.TeamChangeRow) (err error) {
   315  	mctx := libkb.NewMetaContext(ctx, g)
   316  	defer mctx.Trace(fmt.Sprintf("team.HandleDeleteNotification(%v)", len(rows)),
   317  		&err)()
   318  
   319  	g.NotifyRouter.HandleTeamMetadataUpdate(ctx)
   320  
   321  	for _, row := range rows {
   322  		g.Log.CDebugf(ctx, "team.HandleDeleteNotification: (%+v)", row)
   323  		if err := TombstoneTeam(libkb.NewMetaContext(ctx, g), row.Id); err != nil {
   324  			g.Log.CDebugf(ctx, "team.HandleDeleteNotification: failed to Tombstone: %s", err)
   325  		}
   326  		invalidateCaches(mctx, row.Id)
   327  		g.NotifyRouter.HandleTeamDeleted(ctx, row.Id)
   328  	}
   329  
   330  	return nil
   331  }
   332  
   333  func HandleExitNotification(ctx context.Context, g *libkb.GlobalContext, rows []keybase1.TeamExitRow) (err error) {
   334  	mctx := libkb.NewMetaContext(ctx, g)
   335  	defer mctx.Trace(fmt.Sprintf("team.HandleExitNotification(%v)", len(rows)),
   336  		&err)()
   337  
   338  	g.NotifyRouter.HandleTeamMetadataUpdate(ctx)
   339  	for _, row := range rows {
   340  		mctx.Debug("team.HandleExitNotification: (%+v)", row)
   341  		if err := FreezeTeam(mctx, row.Id); err != nil {
   342  			mctx.Debug("team.HandleExitNotification: failed to FreezeTeam: %s", err)
   343  		}
   344  		invalidateCaches(mctx, row.Id)
   345  		mctx.G().NotifyRouter.HandleTeamExit(ctx, row.Id)
   346  	}
   347  	return nil
   348  }
   349  
   350  func HandleNewlyAddedToTeamNotification(ctx context.Context, g *libkb.GlobalContext, rows []keybase1.TeamNewlyAddedRow) (err error) {
   351  	mctx := libkb.NewMetaContext(ctx, g)
   352  	defer mctx.Trace(fmt.Sprintf("team.HandleNewlyAddedToTeamNotification(%v)", len(rows)),
   353  		&err)()
   354  	for _, row := range rows {
   355  		mctx.Debug("team.HandleNewlyAddedToTeamNotification: (%+v)", row)
   356  		mctx.G().NotifyRouter.HandleNewlyAddedToTeam(mctx.Ctx(), row.Id)
   357  		invalidateCaches(mctx, row.Id)
   358  	}
   359  	return nil
   360  }
   361  
   362  func HandleSBSRequest(ctx context.Context, g *libkb.GlobalContext, msg keybase1.TeamSBSMsg) (err error) {
   363  	ctx = libkb.WithLogTag(ctx, "CLKR")
   364  	defer g.CTrace(ctx, "HandleSBSRequest", &err)()
   365  	for _, invitee := range msg.Invitees {
   366  		if err := handleSBSSingle(ctx, g, msg.TeamID, invitee); err != nil {
   367  			return err
   368  		}
   369  	}
   370  	return nil
   371  }
   372  
   373  func handleSBSSingle(ctx context.Context, g *libkb.GlobalContext, teamID keybase1.TeamID, untrustedInviteeFromGregor keybase1.TeamInvitee) (err error) {
   374  	defer g.CTrace(ctx, fmt.Sprintf("team.handleSBSSingle(teamID: %v, invitee: %+v)", teamID, untrustedInviteeFromGregor), &err)()
   375  
   376  	return RetryIfPossible(ctx, g, func(ctx context.Context, _ int) error {
   377  		team, err := Load(ctx, g, keybase1.LoadTeamArg{
   378  			ID:          teamID,
   379  			Public:      teamID.IsPublic(),
   380  			ForceRepoll: true,
   381  		})
   382  		if err != nil {
   383  			g.Log.CDebugf(ctx, "Load of team failed")
   384  			return err
   385  		}
   386  
   387  		// verify the invite info:
   388  
   389  		// find the invite in the team chain
   390  		inviteMD, found := team.chain().FindActiveInviteMDByID(untrustedInviteeFromGregor.InviteID)
   391  		if !found {
   392  			g.Log.CDebugf(ctx, "FindActiveInviteByID failed for invite %s", untrustedInviteeFromGregor.InviteID)
   393  			return libkb.NotFoundError{Msg: "Invite not found"}
   394  		}
   395  		invite := inviteMD.Invite
   396  		g.Log.CDebugf(ctx, "Found invite: %+v", invite)
   397  		category, err := invite.Type.C()
   398  		if err != nil {
   399  			return err
   400  		}
   401  		ityp, err := invite.Type.String()
   402  		if err != nil {
   403  			return err
   404  		}
   405  		switch category {
   406  		case keybase1.TeamInviteCategory_SBS:
   407  			//  resolve assertion in link (with uid in invite msg)
   408  			assertion := fmt.Sprintf("%s@%s+uid:%s", string(invite.Name), ityp, untrustedInviteeFromGregor.Uid)
   409  
   410  			arg := keybase1.Identify2Arg{
   411  				UserAssertion:    assertion,
   412  				UseDelegateUI:    false,
   413  				Reason:           keybase1.IdentifyReason{Reason: "process team invite"},
   414  				CanSuppressUI:    true,
   415  				IdentifyBehavior: keybase1.TLFIdentifyBehavior_CHAT_GUI,
   416  			}
   417  			eng := engine.NewResolveThenIdentify2(g, &arg)
   418  			m := libkb.NewMetaContext(ctx, g)
   419  			if err := engine.RunEngine2(m, eng); err != nil {
   420  				return err
   421  			}
   422  		case keybase1.TeamInviteCategory_EMAIL, keybase1.TeamInviteCategory_PHONE:
   423  			// nothing to verify, need to trust the server
   424  		case keybase1.TeamInviteCategory_KEYBASE:
   425  			// Check if UV in `untrustedInviteeFromGregor` is the same
   426  			// person as in `invite`, and that we can bring them in.
   427  			if err := assertCanAcceptKeybaseInvite(ctx, g, untrustedInviteeFromGregor, invite); err != nil {
   428  				g.Log.CDebugf(ctx, "Failed assertCanAcceptKeybaseInvite")
   429  				return err
   430  			}
   431  		default:
   432  			return fmt.Errorf("no verification implemented for invite category %s (%+v)", category, invite)
   433  		}
   434  
   435  		// It's fine to use untrustedInviteeFromGregor Uid and EldestSeqno.
   436  		// Code above verifies that Uid/Eldest passed by the server really
   437  		// belongs to crypto-person described in invite in sigchain. So
   438  		// right now untrustedInviteeFromGregor is *verified*.
   439  		verifiedInvitee := untrustedInviteeFromGregor
   440  		uv := NewUserVersion(verifiedInvitee.Uid, verifiedInvitee.EldestSeqno)
   441  
   442  		currentRole, err := team.MemberRole(ctx, uv)
   443  		if err != nil {
   444  			g.Log.CDebugf(ctx, "Failed to lookup memberRole for %+v", uv)
   445  			return err
   446  		}
   447  
   448  		if currentRole.IsOrAbove(invite.Role) {
   449  			if team.IsImplicit() {
   450  				g.Log.CDebugf(ctx, "This is implicit team SBS resolution, mooting invite %s", invite.Id)
   451  				req := keybase1.TeamChangeReq{}
   452  				req.CompletedInvites = make(SCMapInviteIDToUV)
   453  				req.CompletedInvites[invite.Id] = uv.PercentForm()
   454  				return team.ChangeMembership(ctx, req)
   455  			}
   456  
   457  			g.Log.CDebugf(ctx, "User already has same or higher role, canceling invite %s", invite.Id)
   458  			return removeInviteID(ctx, team, invite.Id)
   459  		}
   460  
   461  		tx := CreateAddMemberTx(team)
   462  		// NOTE: AddMemberBySBS errors out when encountering PUK-less users,
   463  		// and this transaction is also set to default AllowPUKless=false.
   464  		if err := tx.AddMemberBySBS(ctx, verifiedInvitee, invite.Role); err != nil {
   465  			return err
   466  		}
   467  		if err := tx.Post(libkb.NewMetaContext(ctx, g)); err != nil {
   468  			return err
   469  		}
   470  
   471  		// Send chat welcome message
   472  		if team.IsImplicit() {
   473  			// Do not send messages about keybase-type invites being resolved.
   474  			// They are supposed to be transparent for the users and look like
   475  			// a real members even though they have to be SBS-ed in.
   476  			if category != keybase1.TeamInviteCategory_KEYBASE {
   477  				iteamName, err := team.ImplicitTeamDisplayNameString(ctx)
   478  				if err != nil {
   479  					return err
   480  				}
   481  				g.Log.CDebugf(ctx,
   482  					"sending resolution message for successful SBS handle")
   483  				SendChatSBSResolutionMessage(ctx, g, iteamName,
   484  					string(invite.Name), ityp, verifiedInvitee.Uid)
   485  			}
   486  		} else {
   487  			g.Log.CDebugf(ctx, "sending welcome message for successful SBS handle")
   488  			SendChatInviteWelcomeMessage(ctx, g, team.Name().String(), category, invite.Inviter.Uid,
   489  				verifiedInvitee.Uid, invite.Role)
   490  		}
   491  
   492  		return nil
   493  	})
   494  }
   495  
   496  func assertCanAcceptKeybaseInvite(ctx context.Context, g *libkb.GlobalContext, untrustedInviteeFromGregor keybase1.TeamInvitee, chainInvite keybase1.TeamInvite) error {
   497  	chainUV, err := chainInvite.KeybaseUserVersion()
   498  	if err != nil {
   499  		return err
   500  	}
   501  	if chainUV.Uid.NotEqual(untrustedInviteeFromGregor.Uid) {
   502  		return fmt.Errorf("chain keybase invite link uid %s does not match uid %s in team.sbs message", chainUV.Uid, untrustedInviteeFromGregor.Uid)
   503  	}
   504  
   505  	if chainUV.EldestSeqno.Eq(untrustedInviteeFromGregor.EldestSeqno) {
   506  		return nil
   507  	}
   508  
   509  	if chainUV.EldestSeqno == 0 {
   510  		g.Log.CDebugf(ctx, "team.sbs invitee eldest seqno: %d, allowing it to take the invite for eldest seqno 0 (reset account)", untrustedInviteeFromGregor.EldestSeqno)
   511  		return nil
   512  	}
   513  
   514  	return fmt.Errorf("chain keybase invite link eldest seqno %d does not match eldest seqno %d in team.sbs message", chainUV.EldestSeqno, untrustedInviteeFromGregor.EldestSeqno)
   515  }
   516  
   517  func HandleOpenTeamAccessRequest(ctx context.Context, g *libkb.GlobalContext, msg keybase1.TeamOpenReqMsg) (err error) {
   518  	ctx = libkb.WithLogTag(ctx, "CLKR")
   519  	defer g.CTrace(ctx, "HandleOpenTeamAccessRequest", &err)()
   520  
   521  	return RetryIfPossible(ctx, g, func(ctx context.Context, _ int) error {
   522  		team, err := Load(ctx, g, keybase1.LoadTeamArg{
   523  			ID:          msg.TeamID,
   524  			Public:      msg.TeamID.IsPublic(),
   525  			ForceRepoll: true,
   526  		})
   527  		if err != nil {
   528  			return err
   529  		}
   530  
   531  		if !team.IsOpen() {
   532  			g.Log.CDebugf(ctx, "team %q is not an open team", team.Name())
   533  			return nil // Not an error - let the handler dismiss the message.
   534  		}
   535  
   536  		joinAsRole := team.chain().inner.OpenTeamJoinAs
   537  		switch joinAsRole {
   538  		case keybase1.TeamRole_READER, keybase1.TeamRole_WRITER:
   539  		default:
   540  			return fmt.Errorf("unexpected role to add to open team: %v", joinAsRole)
   541  		}
   542  
   543  		tx := CreateAddMemberTx(team)
   544  		for _, tar := range msg.Tars {
   545  			uv := NewUserVersion(tar.Uid, tar.EldestSeqno)
   546  			err := tx.AddMemberByUV(ctx, uv, joinAsRole, nil)
   547  			g.Log.CDebugf(ctx, "Open team request: adding %v, returned err: %v", uv, err)
   548  		}
   549  
   550  		if tx.IsEmpty() {
   551  			g.Log.CDebugf(ctx, "Nothing to do - transaction is empty")
   552  			return nil
   553  		}
   554  
   555  		return tx.Post(libkb.NewMetaContext(ctx, g))
   556  	})
   557  }
   558  
   559  type chatSeitanRecip struct {
   560  	inviter keybase1.UID
   561  	invitee keybase1.UID
   562  	role    keybase1.TeamRole
   563  }
   564  
   565  func HandleTeamSeitan(ctx context.Context, g *libkb.GlobalContext, msg keybase1.TeamSeitanMsg) (err error) {
   566  	ctx = libkb.WithLogTag(ctx, "SEIT")
   567  	mctx := libkb.NewMetaContext(ctx, g)
   568  	defer mctx.Trace("HandleTeamSeitan", &err)()
   569  
   570  	team, err := Load(ctx, g, keybase1.LoadTeamArg{
   571  		ID:          msg.TeamID,
   572  		Public:      msg.TeamID.IsPublic(),
   573  		ForceRepoll: true,
   574  	})
   575  	if err != nil {
   576  		return err
   577  	}
   578  
   579  	var chats []chatSeitanRecip
   580  
   581  	// we only reject invalid or used up invites after the transaction was
   582  	// correctly submitted.
   583  	var invitesToReject []keybase1.TeamSeitanRequest
   584  
   585  	// Only allow crypto-members added through 'team.change_membership' to be
   586  	// added for Seitan invites (AllowPUKless=false).
   587  	tx := CreateAddMemberTx(team)
   588  
   589  	for _, seitan := range msg.Seitans {
   590  		inviteMD, found := team.chain().FindActiveInviteMDByID(seitan.InviteID)
   591  		if !found {
   592  			mctx.Debug("Couldn't find specified invite id %q; skipping", seitan.InviteID)
   593  			continue
   594  		}
   595  		invite := inviteMD.Invite
   596  
   597  		mctx.Debug("Processing Seitan acceptance for invite %s", invite.Id)
   598  
   599  		err := verifySeitanSingle(ctx, g, team, invite, seitan)
   600  		if err != nil {
   601  			if _, ok := err.(InviteLinkAcceptanceError); ok {
   602  				mctx.Debug("Provided AKey failed to verify with error: %v; ignoring and scheduling for rejection", err)
   603  				invitesToReject = append(invitesToReject, seitan)
   604  			} else {
   605  				mctx.Debug("Provided AKey failed to verify with error: %v; ignoring", err)
   606  			}
   607  			continue
   608  		}
   609  
   610  		uv := NewUserVersion(seitan.Uid, seitan.EldestSeqno)
   611  		currentRole, err := team.MemberRole(ctx, uv)
   612  		if err != nil {
   613  			mctx.Debug("Failure in team.MemberRole: %v", err)
   614  			return err
   615  		}
   616  
   617  		err = tx.CanConsumeInvite(ctx, invite.Id)
   618  		if err != nil {
   619  			if _, ok := err.(InviteLinkAcceptanceError); ok {
   620  				mctx.Debug("Can't use invite: %s; ignoring and scheduling for rejection", err)
   621  				invitesToReject = append(invitesToReject, seitan)
   622  			} else {
   623  				mctx.Debug("Can't use invite: %s", err)
   624  			}
   625  			continue
   626  		}
   627  
   628  		isNewStyle, err := IsNewStyleInvite(invite)
   629  		if err != nil {
   630  			mctx.Debug("Error checking whether invite is new-style: %s", isNewStyle)
   631  			continue
   632  		}
   633  
   634  		if currentRole.IsOrAbove(invite.Role) {
   635  			mctx.Debug("User already has same or higher role.")
   636  			if !isNewStyle {
   637  				mctx.Debug("User already has same or higher role; since is not a new-style invite, cancelling invite.")
   638  				tx.CancelInvite(invite.Id, uv.Uid)
   639  			} else {
   640  				mctx.Debug("User already has same or higher role; scheduling for rejection.")
   641  				invitesToReject = append(invitesToReject, seitan)
   642  			}
   643  			continue
   644  		}
   645  
   646  		err = tx.AddMemberByUV(ctx, uv, invite.Role, nil)
   647  		if err != nil {
   648  			mctx.Debug("Failed to add %v to transaction: %v", uv, err)
   649  			continue
   650  		}
   651  
   652  		// Only allow adding members as cryptomembers. Server should never send
   653  		// us PUKless users accepting seitan tokens. When PUKless user accepts
   654  		// seitan token invite status is set to WAITING_FOR_PUK and team_rekeyd
   655  		// hold on it till user gets a PUK and status is set to ACCEPTED.
   656  		err = tx.ConsumeInviteByID(ctx, invite.Id, uv)
   657  		if err != nil {
   658  			mctx.Debug("Failed to consume invite: %v", err)
   659  			continue
   660  		}
   661  
   662  		chats = append(chats, chatSeitanRecip{
   663  			inviter: invite.Inviter.Uid,
   664  			invitee: seitan.Uid,
   665  			role:    invite.Role,
   666  		})
   667  	}
   668  
   669  	if tx.IsEmpty() {
   670  		mctx.Debug("Transaction is empty - nothing to post")
   671  	} else {
   672  		err = tx.Post(mctx)
   673  		if err != nil {
   674  			return fmt.Errorf("HandleTeamSeitan: Error posting transaction: %w", err)
   675  		}
   676  
   677  		// Send chats
   678  		for _, chat := range chats {
   679  			mctx.Debug("sending welcome message for successful Seitan handle: inviter: %s invitee: %s, role: %v",
   680  				chat.inviter, chat.invitee, chat.role)
   681  			SendChatInviteWelcomeMessage(ctx, g, team.Name().String(), keybase1.TeamInviteCategory_SEITAN,
   682  				chat.inviter, chat.invitee, chat.role)
   683  		}
   684  	}
   685  
   686  	if err = rejectInviteLinkAcceptances(mctx, invitesToReject); err != nil {
   687  		// the transaction posted correctly, and rejecting an invite is not a critical step, so just log and swallow the error
   688  		mctx.Debug("HandleTeamSeitan: error rejecting invite acceptances: %v", err)
   689  	}
   690  
   691  	return nil
   692  }
   693  
   694  func rejectInviteLinkAcceptances(mctx libkb.MetaContext, requests []keybase1.TeamSeitanRequest) error {
   695  	failed := 0
   696  	var lastErr error
   697  	for _, request := range requests {
   698  		arg := libkb.APIArg{
   699  			Endpoint:    "team/reject_invite_acceptance",
   700  			SessionType: libkb.APISessionTypeREQUIRED,
   701  			Args: libkb.HTTPArgs{
   702  				"invite_id":    libkb.S{Val: string(request.InviteID)},
   703  				"uid":          libkb.S{Val: request.Uid.String()},
   704  				"eldest_seqno": libkb.I{Val: int(request.EldestSeqno)},
   705  				"akey":         libkb.S{Val: string(request.Akey)},
   706  			},
   707  		}
   708  
   709  		if _, err := mctx.G().API.Post(mctx, arg); err != nil {
   710  			failed++
   711  			mctx.Debug("rejectInviteLinkAcceptances: failed to call cancel_invite_acceptance(%v,%v,%v): %s", request.InviteID, request.Uid, request.EldestSeqno, err)
   712  			lastErr = err
   713  		}
   714  	}
   715  
   716  	if failed > 0 {
   717  		return fmt.Errorf("Failed to reject %v (out of %v) InviteLink Acceptance requests. Last error: %w", failed, len(requests), lastErr)
   718  	}
   719  	return nil
   720  }
   721  
   722  func verifySeitanSingle(ctx context.Context, g *libkb.GlobalContext, team *Team, invite keybase1.TeamInvite, seitan keybase1.TeamSeitanRequest) (err error) {
   723  	pkey, err := SeitanDecodePKey(string(invite.Name))
   724  	if err != nil {
   725  		return err
   726  	}
   727  
   728  	keyAndLabel, err := pkey.DecryptKeyAndLabel(ctx, team)
   729  	if err != nil {
   730  		return err
   731  	}
   732  
   733  	labelversion, err := keyAndLabel.V()
   734  	if err != nil {
   735  		return fmt.Errorf("while parsing KeyAndLabel: %s", err)
   736  	}
   737  
   738  	category, err := invite.Type.C()
   739  	if err != nil {
   740  		return err
   741  	}
   742  
   743  	switch labelversion {
   744  	case keybase1.SeitanKeyAndLabelVersion_V1:
   745  		if category != keybase1.TeamInviteCategory_SEITAN {
   746  			return fmt.Errorf("HandleTeamSeitan wanted to claim an invite with category %v; wanted seitan", category)
   747  		}
   748  		return verifySeitanSingleV1(keyAndLabel.V1().I, invite, seitan)
   749  	case keybase1.SeitanKeyAndLabelVersion_V2:
   750  		if category != keybase1.TeamInviteCategory_SEITAN {
   751  			return fmt.Errorf("HandleTeamSeitan wanted to claim an invite with category %v; wanted seitan", category)
   752  		}
   753  		return verifySeitanSingleV2(keyAndLabel.V2().K, invite, seitan)
   754  	case keybase1.SeitanKeyAndLabelVersion_Invitelink:
   755  		if category != keybase1.TeamInviteCategory_INVITELINK {
   756  			return fmt.Errorf("HandleTeamSeitan wanted to claim an invite with category %v; wanted invitelink", category)
   757  		}
   758  		return verifySeitanSingleInvitelink(ctx, g, team, keyAndLabel.Invitelink().I, invite, seitan)
   759  	default:
   760  		return fmt.Errorf("unknown KeyAndLabel version: %v", labelversion)
   761  	}
   762  }
   763  
   764  func verifySeitanSingleV1(key keybase1.SeitanIKey, invite keybase1.TeamInvite, seitan keybase1.TeamSeitanRequest) (err error) {
   765  	// We repeat the steps that user does when they request access using the
   766  	// invite ID and see if we get the same answer for the same parameters (UV
   767  	// and unixCTime).
   768  	ikey := SeitanIKey(key)
   769  	uv := keybase1.UserVersion{
   770  		Uid:         seitan.Uid,
   771  		EldestSeqno: seitan.EldestSeqno,
   772  	}
   773  	ourAccept, err := generateAcceptanceSeitanV1(ikey, uv, seitan.UnixCTime)
   774  	if err != nil {
   775  		return fmt.Errorf("failed to generate acceptance key to test: %w", err)
   776  	}
   777  
   778  	if !ourAccept.inviteID.Eq(invite.Id) {
   779  		return errors.New("invite ID mismatch (seitan)")
   780  	}
   781  
   782  	// Decode AKey received from the user to be able to do secure hash
   783  	// comparison.
   784  	decodedAKey, err := base64.StdEncoding.DecodeString(string(seitan.Akey))
   785  	if err != nil {
   786  		return err
   787  	}
   788  
   789  	if !libkb.SecureByteArrayEq(ourAccept.akey, decodedAKey) {
   790  		return fmt.Errorf("did not end up with the same AKey")
   791  	}
   792  
   793  	return nil
   794  }
   795  
   796  func verifySeitanSingleV2(key keybase1.SeitanPubKey, invite keybase1.TeamInvite, seitan keybase1.TeamSeitanRequest) (err error) {
   797  	// Do the public key signature verification. Signature coming from the user
   798  	// is encoded in seitan.Akey. Recreate message using UV and ctime, then
   799  	// verify signature.
   800  	pubKey, err := ImportSeitanPubKey(key)
   801  	if err != nil {
   802  		return err
   803  	}
   804  
   805  	// For V2 the server responds with sig in the akey field.
   806  	var sig SeitanSig
   807  	decodedSig, err := base64.StdEncoding.DecodeString(string(seitan.Akey))
   808  	if err != nil || len(sig) != len(decodedSig) {
   809  		return errors.New("Signature length verification failed (seitan)")
   810  	}
   811  	copy(sig[:], decodedSig)
   812  
   813  	// For V2 this is ms since the epoch, not seconds (like in V1 or InviteLink)
   814  	now := keybase1.Time(seitan.UnixCTime)
   815  	// NOTE: Since we are re-serializing the values from seitan here to
   816  	// generate the message, if we want to change the fields present in the
   817  	// signature in the future, old clients will not be compatible.
   818  	msg, err := GenerateSeitanSignatureMessage(seitan.Uid, seitan.EldestSeqno, SCTeamInviteID(seitan.InviteID), now)
   819  	if err != nil {
   820  		return err
   821  	}
   822  
   823  	err = VerifySeitanSignatureMessage(pubKey, msg, sig)
   824  	if err != nil {
   825  		return err
   826  	}
   827  
   828  	return nil
   829  }
   830  
   831  func verifySeitanSingleInvitelink(ctx context.Context, g *libkb.GlobalContext, team *Team, ikey keybase1.SeitanIKeyInvitelink, invite keybase1.TeamInvite, seitan keybase1.TeamSeitanRequest) (err error) {
   832  	// We repeat the steps that user does when they request access using the
   833  	// invite ID and see if we get the same answer for the same parameters (UV
   834  	// and unixCTime).
   835  	//
   836  	// Also, we check that the state of the user in the team has not changed
   837  	// since they signed the acceptance.
   838  	uv := keybase1.UserVersion{
   839  		Uid:         seitan.Uid,
   840  		EldestSeqno: seitan.EldestSeqno,
   841  	}
   842  	ourAccept, err := generateAcceptanceSeitanInviteLink(ikey, uv, seitan.UnixCTime)
   843  	if err != nil {
   844  		return NewInviteLinkAcceptanceError("failed to generate acceptance key to test: %w", err)
   845  	}
   846  
   847  	if !ourAccept.inviteID.Eq(invite.Id) {
   848  		return NewInviteLinkAcceptanceError("invite ID mismatch (seitan invitelink)")
   849  	}
   850  
   851  	// Decode AKey received from the user to be able to do secure hash
   852  	// comparison.
   853  	decodedAKey, err := base64.StdEncoding.DecodeString(string(seitan.Akey))
   854  	if err != nil {
   855  		return NewInviteLinkAcceptanceError("Unable to decode AKey: %s", err)
   856  	}
   857  
   858  	if !libkb.SecureByteArrayEq(ourAccept.akey, decodedAKey) {
   859  		return NewInviteLinkAcceptanceError("did not end up with the same invitelink AKey")
   860  	}
   861  
   862  	roleChangeTime, wasMember := team.UserLastRoleChangeTime(uv)
   863  	if wasMember && seitan.UnixCTime < roleChangeTime.UnixSeconds() {
   864  		return NewInviteLinkAcceptanceError("invite link was accepted before the user last changed their role")
   865  	}
   866  
   867  	if g.Clock().Now().Unix() < seitan.UnixCTime {
   868  		// In this case we do not return an InviteLinkAcceptanceError to avoid
   869  		// triggering a rejection in case this client's clock is off. If the
   870  		// server is honest, clients shouldn't get asked to process these invite
   871  		// links anyways, and if it is malicious than it does not matter if we
   872  		// ask it to reject. Moreover, not rejecting might be cause a slight
   873  		// performance degradation that can be detected server side, while
   874  		// rejecting erroneously means people can't get into the teams they want
   875  		// with no immediate feedback error.
   876  		return fmt.Errorf("acceptance was produced with a future timestamp: ignoring (without rejecting)")
   877  	}
   878  
   879  	return nil
   880  }
   881  
   882  func HandleForceRepollNotification(ctx context.Context, g *libkb.GlobalContext, dtime gregor.TimeOrOffset) error {
   883  	e1 := g.GetTeamLoader().ForceRepollUntil(ctx, dtime)
   884  	e2 := g.GetFastTeamLoader().ForceRepollUntil(libkb.NewMetaContext(ctx, g), dtime)
   885  	if e1 != nil {
   886  		return e1
   887  	}
   888  	return e2
   889  }