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

     1  // Copyright 2018 Keybase, Inc. All rights reserved. Use of
     2  // this source code is governed by the included BSD license.
     3  
     4  package teams
     5  
     6  import (
     7  	"errors"
     8  	"fmt"
     9  
    10  	"golang.org/x/net/context"
    11  
    12  	"github.com/keybase/client/go/engine"
    13  	"github.com/keybase/client/go/externals"
    14  	"github.com/keybase/client/go/libkb"
    15  	"github.com/keybase/client/go/protocol/keybase1"
    16  	"github.com/keybase/client/go/teams/hidden"
    17  )
    18  
    19  // AddMemberTx helps build a transaction that may contain multiple team
    20  // sigchain links. The caller can use the transaction to add users to a team
    21  // whether they be PUKful or PUKless users, social or server-trust assertions.
    22  //
    23  // Behind the scenes cryptomembers and invites may be removed if they are for
    24  // stale versions of the addees.
    25  //
    26  // Not thread-safe.
    27  type AddMemberTx struct {
    28  	team     *Team
    29  	payloads []txPayload
    30  
    31  	// Error state: if a transaction operation failed and tainted the
    32  	// transaction, do not allow posting. We try to never get in a state when
    33  	// we modify a transaction and then realize there's an issue, but if this
    34  	// happens, make sure `Post` can't be called on such tx later.
    35  	err error
    36  
    37  	// completedInvites is used to mark completed invites, so they are
    38  	// skipped in sweeping methods.
    39  	completedInvites map[keybase1.TeamInviteID]bool
    40  
    41  	// keep track of how many multiple-use invites have been used so far
    42  	usedInviteCount map[keybase1.TeamInviteID]int
    43  
    44  	// lastChangeForUID holds index of last tx.payloads payload that
    45  	// affects given uid.
    46  	lastChangeForUID map[keybase1.UID]int
    47  
    48  	// Consumer can set the following to affect AddMemberTx operation:
    49  
    50  	// Allow adding users who do not have active Per User Key. Users without
    51  	// PUK will be added using a 'team.invite' link with type='keybase'
    52  	// invites.
    53  	//
    54  	// If this setting is 'false' (which is the default), it forces AddMemberTx
    55  	// to never add type='keybase' invites, and only `team.change_membership`
    56  	// is allowed for adding Keybase users as members. Calls to AddMember*
    57  	// functions that with a user that does not have a PUK result in an error.
    58  	AllowPUKless bool
    59  
    60  	// Do not return an error when trying to "add a member" who is already
    61  	// member of the team but has a different role.
    62  	//
    63  	// This does not affect team invites (including PUK-less users). For
    64  	// simplicity, their role can't be changed using AddMemberTx right now.
    65  	AllowRoleChanges bool
    66  
    67  	// Override whether the team key is rotated.
    68  	SkipKeyRotation *bool
    69  
    70  	// EmailInviteMsg is used for sending a welcome message in email invites
    71  	EmailInviteMsg *string
    72  }
    73  
    74  // TransactionTaintedError is used for unrecoverable error where we fail to add
    75  // a member and irreversibly break the transaction while doing so.
    76  type TransactionTaintedError struct {
    77  	inner error
    78  }
    79  
    80  func (e TransactionTaintedError) Error() string {
    81  	return fmt.Sprintf("Transaction is in error state: %s", e.inner)
    82  }
    83  
    84  // UserPUKlessError is returned when an attempt is made to add a PUKless user
    85  // to a transaction that has AllowPUKless=false.
    86  type UserPUKlessError struct {
    87  	username string
    88  	uv       keybase1.UserVersion
    89  }
    90  
    91  func (e UserPUKlessError) Error() string {
    92  	var userStr string
    93  	if e.username != "" {
    94  		userStr = fmt.Sprintf("%s (%s)", e.username, e.uv.String())
    95  	} else {
    96  		userStr = e.uv.String()
    97  	}
    98  	return fmt.Sprintf("User %s does not have a PUK, cannot be added to this transaction", userStr)
    99  }
   100  
   101  type txPayloadTag int
   102  
   103  const (
   104  	txPayloadTagCryptomembers txPayloadTag = iota
   105  	txPayloadTagInviteSocial
   106  	txPayloadTagInviteKeybase
   107  )
   108  
   109  type txPayload struct {
   110  	Tag txPayloadTag
   111  	// txPayload holds either of: *SCTeamInvites or
   112  	// *keybase1.TeamChangeReq.
   113  	Val interface{}
   114  }
   115  
   116  func CreateAddMemberTx(t *Team) *AddMemberTx {
   117  	return &AddMemberTx{
   118  		team:             t,
   119  		completedInvites: make(map[keybase1.TeamInviteID]bool),
   120  		lastChangeForUID: make(map[keybase1.UID]int),
   121  		usedInviteCount:  make(map[keybase1.TeamInviteID]int),
   122  	}
   123  }
   124  
   125  func (tx *AddMemberTx) DebugPayloads() (res []interface{}) {
   126  	for _, v := range tx.payloads {
   127  		res = append(res, v.Val)
   128  	}
   129  	return res
   130  }
   131  
   132  func (tx *AddMemberTx) IsEmpty() bool {
   133  	return len(tx.payloads) == 0
   134  }
   135  
   136  // Internal AddMemberTx methods. They should not be used by consumers
   137  // of AddMemberTx API. Users of this API should avoid lowercase
   138  // methods and fields at all cost, even from same package.
   139  
   140  func (tx *AddMemberTx) findPayload(tag txPayloadTag, forUID keybase1.UID) interface{} {
   141  	minSeqno := 0
   142  	hasUID := !forUID.IsNil()
   143  	if hasUID {
   144  		minSeqno = tx.lastChangeForUID[forUID]
   145  	}
   146  
   147  	for i, v := range tx.payloads {
   148  		if i >= minSeqno && v.Tag == tag {
   149  			if hasUID && i > minSeqno {
   150  				tx.lastChangeForUID[forUID] = i
   151  			}
   152  			return v.Val
   153  		}
   154  	}
   155  
   156  	if hasUID {
   157  		tx.lastChangeForUID[forUID] = len(tx.payloads)
   158  	}
   159  	ret := txPayload{
   160  		Tag: tag,
   161  	}
   162  	switch tag {
   163  	case txPayloadTagCryptomembers:
   164  		ret.Val = &keybase1.TeamChangeReq{}
   165  	case txPayloadTagInviteKeybase, txPayloadTagInviteSocial:
   166  		ret.Val = &SCTeamInvites{}
   167  	default:
   168  		panic(fmt.Sprintf("Unexpected tag %q", tag))
   169  	}
   170  	tx.payloads = append(tx.payloads, ret)
   171  	return ret.Val
   172  }
   173  
   174  func (tx *AddMemberTx) inviteKeybasePayload(forUID keybase1.UID) *SCTeamInvites {
   175  	return tx.findPayload(txPayloadTagInviteKeybase, forUID).(*SCTeamInvites)
   176  }
   177  
   178  func (tx *AddMemberTx) inviteSocialPayload(forUID keybase1.UID) *SCTeamInvites {
   179  	return tx.findPayload(txPayloadTagInviteSocial, forUID).(*SCTeamInvites)
   180  }
   181  
   182  func (tx *AddMemberTx) changeMembershipPayload(forUID keybase1.UID) *keybase1.TeamChangeReq {
   183  	return tx.findPayload(txPayloadTagCryptomembers, forUID).(*keybase1.TeamChangeReq)
   184  }
   185  
   186  // Methods modifying payloads are supposed to always succeed given the
   187  // preconditions are satisfied. If not, the usual result is either a
   188  // no-op or an invalid transaction that is rejected by team player
   189  // pre-check or by the server. Public methods should make sure that
   190  // internal methods are always called with these preconditions
   191  // satisfied.
   192  
   193  func (tx *AddMemberTx) removeMember(uv keybase1.UserVersion) {
   194  	// Precondition: UV is a cryptomember.
   195  	payload := tx.changeMembershipPayload(uv.Uid)
   196  	payload.None = append(payload.None, uv)
   197  }
   198  
   199  func (tx *AddMemberTx) addMember(uv keybase1.UserVersion, role keybase1.TeamRole, botSettings *keybase1.TeamBotSettings) error {
   200  	// Preconditions: UV is a PUKful user, role is valid enum value
   201  	// and not NONE.
   202  	payload := tx.changeMembershipPayload(uv.Uid)
   203  	err := payload.AddUVWithRole(uv, role, botSettings)
   204  	if err != nil {
   205  		tx.err = TransactionTaintedError{err}
   206  		err = tx.err
   207  	}
   208  	return err
   209  }
   210  
   211  func (tx *AddMemberTx) addMemberAndCompleteInvite(uv keybase1.UserVersion,
   212  	role keybase1.TeamRole, inviteID keybase1.TeamInviteID) error {
   213  	// Preconditions: UV is a PUKful user, role is valid and not NONE, invite
   214  	// exists. Role is not RESTRICTEDBOT as botSettings are set to nil.
   215  	payload := tx.changeMembershipPayload(uv.Uid)
   216  	err := payload.AddUVWithRole(uv, role, nil /* botSettings */)
   217  	if err != nil {
   218  		tx.err = TransactionTaintedError{err}
   219  		err = tx.err
   220  	}
   221  	payload.CompleteInviteID(inviteID, uv.PercentForm())
   222  	return err
   223  }
   224  
   225  func appendToInviteList(inv SCTeamInvite, list *[]SCTeamInvite) *[]SCTeamInvite {
   226  	var tmp []SCTeamInvite
   227  	if list != nil {
   228  		tmp = *list
   229  	}
   230  	tmp = append(tmp, inv)
   231  	return &tmp
   232  }
   233  
   234  // createKeybaseInvite queues Keybase-type invite for given UV and role.
   235  func (tx *AddMemberTx) createKeybaseInvite(uv keybase1.UserVersion, role keybase1.TeamRole) error {
   236  	// Preconditions: UV is a PUKless user, and not already in the
   237  	// team, role is valid enum value and not NONE or OWNER.
   238  	return tx.createInvite("keybase", uv.TeamInviteName(), role, uv.Uid)
   239  }
   240  
   241  // createInvite queues an invite for invite name with role.
   242  func (tx *AddMemberTx) createInvite(typ string, name keybase1.TeamInviteName, role keybase1.TeamRole, uid keybase1.UID) error {
   243  	var payload *SCTeamInvites
   244  	if typ == "keybase" {
   245  		payload = tx.inviteKeybasePayload(uid)
   246  	} else {
   247  		payload = tx.inviteSocialPayload(uid)
   248  	}
   249  
   250  	invite := SCTeamInvite{
   251  		Type: typ,
   252  		Name: name,
   253  		ID:   NewInviteID(),
   254  	}
   255  
   256  	switch role {
   257  	case keybase1.TeamRole_READER:
   258  		payload.Readers = appendToInviteList(invite, payload.Readers)
   259  	case keybase1.TeamRole_WRITER:
   260  		payload.Writers = appendToInviteList(invite, payload.Writers)
   261  	case keybase1.TeamRole_ADMIN:
   262  		payload.Admins = appendToInviteList(invite, payload.Admins)
   263  	case keybase1.TeamRole_OWNER:
   264  		payload.Owners = appendToInviteList(invite, payload.Owners)
   265  	default:
   266  		tx.err = TransactionTaintedError{fmt.Errorf("invalid role for invite %v", role)}
   267  		return tx.err
   268  	}
   269  	return nil
   270  }
   271  
   272  // sweepCryptoMembers will queue "removes" for all cryptomembers with given UID.
   273  // exceptAdminsRemovingOwners - But don't try to remove owners if we are admin.
   274  func (tx *AddMemberTx) sweepCryptoMembers(ctx context.Context, uid keybase1.UID,
   275  	exceptAdminsRemovingOwners bool) {
   276  	team := tx.team
   277  	var myRole keybase1.TeamRole
   278  	if exceptAdminsRemovingOwners {
   279  		var err error
   280  		myRole, err = tx.team.myRole(ctx)
   281  		if err != nil {
   282  			myRole = keybase1.TeamRole_NONE
   283  		}
   284  	}
   285  	for chainUv := range team.chain().inner.UserLog {
   286  		chainRole := team.chain().getUserRole(chainUv)
   287  		if chainUv.Uid.Equal(uid) && chainRole != keybase1.TeamRole_NONE {
   288  			if exceptAdminsRemovingOwners && myRole == keybase1.TeamRole_ADMIN && chainRole == keybase1.TeamRole_OWNER {
   289  				// Skip if we're an admin and they're an owner.
   290  				continue
   291  			}
   292  			tx.removeMember(chainUv)
   293  		}
   294  	}
   295  }
   296  
   297  // sweepCryptoMembersOlderThan will queue "removes" for all cryptomembers
   298  // with same UID as given and EldestSeqno lower than in given UV.
   299  func (tx *AddMemberTx) sweepCryptoMembersOlderThan(uv keybase1.UserVersion) {
   300  	team := tx.team
   301  	for chainUv := range team.chain().inner.UserLog {
   302  		if chainUv.Uid.Equal(uv.Uid) &&
   303  			chainUv.EldestSeqno < uv.EldestSeqno &&
   304  			team.chain().getUserRole(chainUv) != keybase1.TeamRole_NONE {
   305  			tx.removeMember(chainUv)
   306  		}
   307  	}
   308  }
   309  
   310  // sweepKeybaseInvites will queue "cancels" for all keybase-type
   311  // invites (PUKless members) for given UID.
   312  func (tx *AddMemberTx) sweepKeybaseInvites(uid keybase1.UID) {
   313  	team := tx.team
   314  	allInvites := team.GetActiveAndObsoleteInvites()
   315  	for _, invite := range allInvites {
   316  		if inviteUv, err := invite.KeybaseUserVersion(); err == nil {
   317  			if inviteUv.Uid.Equal(uid) && !tx.completedInvites[invite.Id] {
   318  				tx.CancelInvite(invite.Id, uid)
   319  			}
   320  		}
   321  	}
   322  }
   323  
   324  func (tx *AddMemberTx) findChangeReqForUV(uv keybase1.UserVersion) *keybase1.TeamChangeReq {
   325  	for _, v := range tx.payloads {
   326  		if v.Tag == txPayloadTagCryptomembers {
   327  			req := v.Val.(*keybase1.TeamChangeReq)
   328  			for _, x := range req.GetAllAdds() {
   329  				if x.Eq(uv) {
   330  					return req
   331  				}
   332  			}
   333  		}
   334  	}
   335  
   336  	return nil
   337  }
   338  
   339  // addMemberByUPKV2 is an internal method to add user once we have current
   340  // incarnation of UPAK. Public APIs are AddMemberByUV and AddMemberByUsername
   341  // that load UPAK and pass it to this function to continue membership changes.
   342  //
   343  // Return value `invite` is true if user was PUK-less and was added as
   344  // keybase-type invite.
   345  func (tx *AddMemberTx) addMemberByUPKV2(ctx context.Context, user keybase1.UserPlusKeysV2, role keybase1.TeamRole,
   346  	botSettings *keybase1.TeamBotSettings) (invite bool, err error) {
   347  	team := tx.team
   348  	g := team.G()
   349  
   350  	uv := NewUserVersion(user.Uid, user.EldestSeqno)
   351  	defer g.CTrace(ctx, fmt.Sprintf("AddMemberTx.addMemberByUPKV2(name:%q uv:%v, %v) to team: %q",
   352  		user.Username, uv, role, team.Name()), &err)()
   353  
   354  	if user.Status == keybase1.StatusCode_SCDeleted {
   355  		return false, libkb.UserDeletedError{Msg: fmt.Sprintf("User %q (%s) is deleted", user.Username, uv.Uid)}
   356  	}
   357  
   358  	if role == keybase1.TeamRole_OWNER && team.IsSubteam() {
   359  		return false, NewSubteamOwnersError()
   360  	}
   361  
   362  	if err := assertValidNewTeamMemberRole(role); err != nil {
   363  		return false, err
   364  	}
   365  
   366  	hasPUK := len(user.PerUserKeys) > 0
   367  	if !hasPUK {
   368  		g.Log.CDebugf(ctx, "Invite required for %v", uv)
   369  
   370  		if !tx.AllowPUKless {
   371  			return false, UserPUKlessError{username: user.Username, uv: uv}
   372  		}
   373  	}
   374  
   375  	normalizedUsername := libkb.NewNormalizedUsername(user.Username)
   376  
   377  	currentRole, err := team.MemberRole(ctx, uv)
   378  	if err != nil {
   379  		return false, err
   380  	}
   381  
   382  	if currentRole != keybase1.TeamRole_NONE {
   383  		if !hasPUK {
   384  			return false, fmt.Errorf("user %s (uv %s) is already a member of %s, yet they don't have a PUK",
   385  				normalizedUsername, uv, team.Name())
   386  		}
   387  		if tx.AllowRoleChanges {
   388  			if currentRole == role {
   389  				// No-op team.change_membership links that don't change
   390  				// member's role are legal, but we are trying to avoid
   391  				// them. Caller can catch this error and move onwards,
   392  				// it doesn't taint the transaction.
   393  				return false, libkb.ExistsError{Msg: fmt.Sprintf("user %s is already a member of team %s with role %s",
   394  					normalizedUsername, team.Name(), role.HumanString())}
   395  			}
   396  		} else {
   397  			return false, libkb.ExistsError{Msg: fmt.Sprintf("user %s is already a member of team %s",
   398  				normalizedUsername, team.Name())}
   399  		}
   400  	}
   401  
   402  	if existingUV, err := team.UserVersionByUID(ctx, uv.Uid); err == nil {
   403  		// There is an edge case where user is in the middle of resetting
   404  		// (after reset, before provisioning) and has EldestSeqno=0.
   405  		if hasPUK && existingUV.EldestSeqno > uv.EldestSeqno {
   406  			return false, fmt.Errorf("newer version of user %s (uid:%s) already exists in the team %q (%v > %v)",
   407  				normalizedUsername, uv.Uid, team.Name(), existingUV.EldestSeqno, uv.EldestSeqno)
   408  		}
   409  	}
   410  
   411  	curInvite, err := team.chain().FindActiveInvite(uv.TeamInviteName(), keybase1.NewTeamInviteTypeDefault(keybase1.TeamInviteCategory_KEYBASE))
   412  	if err != nil {
   413  		if _, ok := err.(libkb.NotFoundError); !ok {
   414  			return false, err
   415  		}
   416  		curInvite = nil
   417  		err = nil
   418  	}
   419  	if curInvite != nil && !hasPUK {
   420  		return false, libkb.ExistsError{Msg: fmt.Sprintf("user %s is already invited to team %q",
   421  			normalizedUsername, team.Name())}
   422  	}
   423  
   424  	// No going back after this point!
   425  
   426  	tx.sweepKeybaseInvites(uv.Uid)
   427  
   428  	if !hasPUK {
   429  		// An admin is only allowed to remove an owner UV when, in the same
   430  		// link, replacing them with a 'newer' UV with a greater eldest seqno.
   431  		// So, if we're an admin re-adding an owner who does not yet have a PUK
   432  		// then don't try to remove the owner's pre-reset UV. Note that the old
   433  		// owner UV will still be removed in the transaction during SBS
   434  		// resolution when they get a PUK later.
   435  		tx.sweepCryptoMembers(ctx, uv.Uid, true /* exceptAdminsRemovingOwners */)
   436  	} else {
   437  		// This might be a role change, only sweep UVs with EldestSeqno older
   438  		// than one currently being added, so it doesn't sweep the same UV we
   439  		// are currently adding.
   440  		tx.sweepCryptoMembersOlderThan(uv)
   441  	}
   442  
   443  	if !hasPUK {
   444  		if err = tx.createKeybaseInvite(uv, role); err != nil {
   445  			return false, err
   446  		}
   447  		return true /* invite */, nil
   448  	}
   449  	if err := tx.addMember(uv, role, botSettings); err != nil {
   450  		return false, err
   451  	}
   452  	return false, nil
   453  }
   454  
   455  func (tx *AddMemberTx) completeAllKeybaseInvitesForUID(uv keybase1.UserVersion) error {
   456  	// Find the right payload first
   457  	payload := tx.findChangeReqForUV(uv)
   458  	if payload == nil {
   459  		return fmt.Errorf("could not find uv %v in transaction", uv)
   460  	}
   461  
   462  	team := tx.team
   463  	for _, inviteMD := range team.chain().ActiveInvites() {
   464  		invite := inviteMD.Invite
   465  		if inviteUv, err := invite.KeybaseUserVersion(); err == nil {
   466  			if inviteUv.Uid.Equal(uv.Uid) {
   467  				if payload.CompletedInvites == nil {
   468  					payload.CompletedInvites = make(map[keybase1.TeamInviteID]keybase1.UserVersionPercentForm)
   469  				}
   470  				payload.CompletedInvites[invite.Id] = uv.PercentForm()
   471  			}
   472  		}
   473  	}
   474  
   475  	return nil
   476  }
   477  
   478  func assertValidNewTeamMemberRole(role keybase1.TeamRole) error {
   479  	switch role {
   480  	case keybase1.TeamRole_RESTRICTEDBOT,
   481  		keybase1.TeamRole_BOT,
   482  		keybase1.TeamRole_READER,
   483  		keybase1.TeamRole_WRITER,
   484  		keybase1.TeamRole_ADMIN,
   485  		keybase1.TeamRole_OWNER:
   486  		return nil
   487  	default:
   488  		return fmt.Errorf("Unexpected role: %v (%d)", role, int(role))
   489  	}
   490  }
   491  
   492  // AddMemberByUV will add member by UV and role. It checks if given UV is valid
   493  // (that we don't have outdated EldestSeqno), and if user has PUK, and if not,
   494  // it properly handles that by adding Keybase-type invite. It also cleans up
   495  // old invites and memberships.
   496  func (tx *AddMemberTx) AddMemberByUV(ctx context.Context, uv keybase1.UserVersion, role keybase1.TeamRole,
   497  	botSettings *keybase1.TeamBotSettings) (err error) {
   498  	team := tx.team
   499  	g := team.G()
   500  
   501  	defer g.CTrace(ctx, fmt.Sprintf("AddMemberTx.AddMemberByUV(%v,%v) to team %q", uv, role, team.Name()), &err)()
   502  	upak, err := loadUPAK2(ctx, g, uv.Uid, true /* forcePoll */)
   503  	if err != nil {
   504  		return err
   505  	}
   506  
   507  	current := upak.Current
   508  	if uv.EldestSeqno != current.EldestSeqno {
   509  		return fmt.Errorf("Bad eldestseqno for %s: expected %d, got %d", uv.Uid, current.EldestSeqno, uv.EldestSeqno)
   510  	}
   511  
   512  	_, err = tx.addMemberByUPKV2(ctx, current, role, botSettings)
   513  	return err
   514  }
   515  
   516  // AddMemberByUsername will add member by username and role. It checks if given
   517  // username can become crypto member or a PUKless member. It will also clean up
   518  // old invites and memberships if necessary.
   519  func (tx *AddMemberTx) AddMemberByUsername(ctx context.Context, username string, role keybase1.TeamRole,
   520  	botSettings *keybase1.TeamBotSettings) (err error) {
   521  	team := tx.team
   522  	mctx := team.MetaContext(ctx)
   523  
   524  	defer mctx.Trace(fmt.Sprintf("AddMemberTx.AddMemberByUsername(%s,%v) to team %q", username, role, team.Name()), &err)()
   525  
   526  	upak, err := engine.ResolveAndCheck(mctx, username, true /* useTracking */)
   527  	if err != nil {
   528  		return err
   529  	}
   530  	_, err = tx.addMemberByUPKV2(ctx, upak, role, botSettings)
   531  	return err
   532  }
   533  
   534  // preprocessAssertion takes an input assertion and determines if this is a
   535  // valid Keybase-style assertion. If it's an email (or phone) assertion, we
   536  // assert that it only has one part (and isn't a+b compound). If there is only
   537  // one factor in the assertion, then that's returned as single. Otherwise,
   538  // single is nil.
   539  func preprocessAssertion(mctx libkb.MetaContext, assertionStr string) (isServerTrustInvite bool, single libkb.AssertionURL, full libkb.AssertionExpression, err error) {
   540  	assertion, err := externals.AssertionParseAndOnly(mctx, assertionStr)
   541  	if err != nil {
   542  		return false, nil, nil, err
   543  	}
   544  	urls := assertion.CollectUrls(nil)
   545  	if len(urls) == 1 {
   546  		single = urls[0]
   547  	}
   548  	for _, u := range urls {
   549  		if u.IsServerTrust() {
   550  			isServerTrustInvite = true
   551  		}
   552  	}
   553  	if isServerTrustInvite && len(urls) > 1 {
   554  		return false, nil, nil, NewMixedServerTrustAssertionError()
   555  	}
   556  	return isServerTrustInvite, single, assertion, nil
   557  }
   558  
   559  // resolveServerTrustAssertion resolves server-trust assertion. The possible
   560  // outcomes are:
   561  // 1) assertion resolves to a user: userFound=true, non-empty upak.
   562  // 2) assertion did not resolve to a user: userFound=false, empty upak.
   563  // 3) we got a server error when resolving, abort.
   564  func resolveServerTrustAssertion(mctx libkb.MetaContext, assertion string) (upak keybase1.UserPlusKeysV2, userFound bool, err error) {
   565  	user, resolveRes, err := mctx.G().Resolver.ResolveUser(mctx, assertion)
   566  	if err != nil {
   567  		if shouldPreventTeamCreation(err) {
   568  			// Resolution failed because of server error, do not try to invite
   569  			// because it might be a resolvable assertion. We don't know if
   570  			// that assertion resolves to a user. Should abort anything we are
   571  			// trying to do.
   572  			return upak, false, err
   573  		}
   574  		// Assertion did not resolve, but we didn't get error preventing us
   575  		// from inviting either. Invite assertion to the team.
   576  		return upak, false, nil
   577  	}
   578  
   579  	if !resolveRes.IsServerTrust() {
   580  		return upak, false, fmt.Errorf("Unexpected non server-trust resolution returned: %q", assertion)
   581  	}
   582  	upak, err = engine.ResolveAndCheck(mctx, user.Username, true /* useTracking */)
   583  	if err != nil {
   584  		return upak, false, err
   585  	}
   586  	// Success - assertion server-trust resolves to upak, we can just add them
   587  	// as a member.
   588  	return upak, true, nil
   589  }
   590  
   591  // AddMemberCandidate is created by ResolveUPKV2FromAssertion and should be
   592  // passed to AddOrInviteMemberCandidate.
   593  type AddMemberCandidate struct {
   594  	SourceAssertion string
   595  
   596  	// Assertion parsing results:
   597  	Full   libkb.AssertionExpression // always set
   598  	Single libkb.AssertionURL        // not nil if assertion was a single (not compound)
   599  
   600  	// If resolved to a Keybase user, KeybaseUser is non-nil.
   601  	KeybaseUser *keybase1.UserPlusKeysV2
   602  
   603  	// Resolved user might be PUKless, so adding them might still result in an
   604  	// invite (type=keybase). If KeybaseUser is nil, adding that candidate
   605  	// shall result in social invite for Single assertion, provided by source
   606  	// assertion was not compound. If it was, that person can't be added.
   607  }
   608  
   609  func (a AddMemberCandidate) DebugString() string {
   610  	if a.KeybaseUser != nil {
   611  		return fmt.Sprintf("User=%q, IsSingle=%t", a.KeybaseUser.Username, a.Single != nil)
   612  	}
   613  	return fmt.Sprintf("User=nil, IsSingle=%t", a.Single != nil)
   614  }
   615  
   616  // ResolveUPKV2FromAssertion creates an AddMemberCandidate struct by parsing
   617  // and attempting to resolve an assertion. This result can be then passed to
   618  // AddOrInviteMemberCandidate to queue adding that person in the transaction.
   619  // ResolveUPKV2FromAssertion itself does not modify the transaction.
   620  //
   621  // If your use case is:
   622  //   - you have an assertion,
   623  //   - that should be resolved,
   624  //   - and based on the resolution it should either add it to the transaction or
   625  //     not,
   626  //
   627  // this is the way to go.
   628  //
   629  // See documentation of AddOrInviteMemberByAssertion to find out what assertion
   630  // types are supported.
   631  //
   632  // AddOrInviteMemberByAssertion does the same thing internally, but you don't
   633  // get to check resolution result until after transaction is modified.
   634  func (tx *AddMemberTx) ResolveUPKV2FromAssertion(m libkb.MetaContext, assertion string) (ret AddMemberCandidate, err error) {
   635  	isServerTrustInvite, single, full, err := preprocessAssertion(m, assertion)
   636  	if err != nil {
   637  		return ret, err
   638  	}
   639  
   640  	ret.SourceAssertion = assertion
   641  	ret.Full = full
   642  	ret.Single = single
   643  
   644  	if isServerTrustInvite {
   645  		// Server-trust assertions (`phone`/`email`): ask server if it resolves
   646  		// to a user.
   647  		upak, userFound, err := resolveServerTrustAssertion(m, assertion)
   648  		if err != nil {
   649  			return ret, err
   650  		}
   651  		if userFound {
   652  			ret.KeybaseUser = &upak
   653  		}
   654  	} else {
   655  		// Normal assertion: resolve and verify.
   656  		upak, err := engine.ResolveAndCheck(m, assertion, true /* useTracking */)
   657  		if err != nil {
   658  			if rErr, ok := err.(libkb.ResolutionError); !ok || (rErr.Kind != libkb.ResolutionErrorNotFound) {
   659  				return ret, err
   660  			}
   661  			// Resolution not found - fall through with nil KeybaseUser.
   662  		} else {
   663  			ret.KeybaseUser = &upak
   664  		}
   665  	}
   666  
   667  	return ret, nil
   668  }
   669  
   670  // AddOrInviteMemberCandidate adds a member using AddMemberCandidate struct
   671  // that can be obtained by calling ResolveUPKV2FromAssertion with assertion
   672  // string.
   673  func (tx *AddMemberTx) AddOrInviteMemberCandidate(ctx context.Context, candidate AddMemberCandidate, role keybase1.TeamRole, botSettings *keybase1.TeamBotSettings) (
   674  	username libkb.NormalizedUsername, uv keybase1.UserVersion, invite bool, err error) {
   675  	team := tx.team
   676  	mctx := team.MetaContext(ctx)
   677  
   678  	defer mctx.Trace(fmt.Sprintf("AddMemberTx.AddOrInviteMemberCandidate(%q,keybaseUser=%t,%v) to team %q",
   679  		candidate.SourceAssertion, candidate.KeybaseUser != nil, role, team.Name()), &err)()
   680  
   681  	if candidate.KeybaseUser != nil {
   682  		// We have a user and can add them to a team, no invite required.
   683  		username = libkb.NewNormalizedUsername(candidate.KeybaseUser.Username)
   684  		uv = candidate.KeybaseUser.ToUserVersion()
   685  		invite, err = tx.addMemberByUPKV2(ctx, *candidate.KeybaseUser, role, botSettings)
   686  		mctx.Debug("Adding Keybase user: %s (isInvite=%v)", username, invite)
   687  		return username, uv, invite, err
   688  	}
   689  
   690  	// We are on invite path here.
   691  
   692  	if candidate.Single == nil {
   693  		// Compound assertions are invalid at this point.
   694  		return "", uv, false, NewCompoundInviteError(candidate.SourceAssertion)
   695  	}
   696  
   697  	typ, name := candidate.Single.ToKeyValuePair()
   698  	mctx.Debug("team %s invite sbs member %s/%s", team.Name(), typ, name)
   699  
   700  	// Sanity checks:
   701  	// Can't do SBS invite with role=OWNER.
   702  	if role.IsOrAbove(keybase1.TeamRole_OWNER) {
   703  		return "", uv, false, NewAttemptedInviteSocialOwnerError(candidate.SourceAssertion)
   704  	}
   705  	inviteName := keybase1.TeamInviteName(name)
   706  	// Can't invite if invite for same SBS assertion already exists in that
   707  	// team.
   708  	existing, err := tx.team.HasActiveInvite(mctx, inviteName, typ)
   709  	if err != nil {
   710  		return "", uv, false, err
   711  	}
   712  	if existing {
   713  		return "", uv, false, libkb.ExistsError{
   714  			Msg: fmt.Sprintf("Invite for %q already exists", candidate.Single.String()),
   715  		}
   716  	}
   717  
   718  	// All good - add invite to tx.
   719  	if err = tx.createInvite(typ, inviteName, role, "" /* uid */); err != nil {
   720  		return "", uv, false, err
   721  	}
   722  	return "", uv, true, nil
   723  
   724  }
   725  
   726  // AddOrInviteMemberByAssertion adds an assertion to the team. It can
   727  // handle three major cases:
   728  //
   729  //  1. joe OR joe+foo@reddit WHERE joe is already a keybase user, or the
   730  //     assertions map to a unique Keybase user
   731  //  2. joe@reddit WHERE joe isn't a keybase user, and this is a social
   732  //     invitations
   733  //  3. [bob@gmail.com]@email WHERE server-trust resolution is performed and
   734  //     either TOFU invite is created or resolved member is added. Same works
   735  //     with `@phone`.
   736  //
   737  // **Does** attempt to resolve the assertion, to distinguish between case (1),
   738  // case (2) and an error The return values (uv, username) can both be
   739  // zero-valued if the assertion is not a Keybase user.
   740  func (tx *AddMemberTx) AddOrInviteMemberByAssertion(ctx context.Context, assertion string, role keybase1.TeamRole, botSettings *keybase1.TeamBotSettings) (
   741  	username libkb.NormalizedUsername, uv keybase1.UserVersion, invite bool, err error) {
   742  	team := tx.team
   743  	m := team.MetaContext(ctx)
   744  
   745  	defer m.Trace(fmt.Sprintf("AddMemberTx.AddOrInviteMemberByAssertion(%s,%v) to team %q", assertion, role, team.Name()), &err)()
   746  
   747  	candidate, err := tx.ResolveUPKV2FromAssertion(m, assertion)
   748  	if err != nil {
   749  		return "", uv, false, err
   750  	}
   751  	return tx.AddOrInviteMemberCandidate(ctx, candidate, role, botSettings)
   752  }
   753  
   754  // CanConsumeInvite checks if invite can be used. Has to be called before
   755  // calling `ConsumeInviteByID` with that invite ID. Does not modify the
   756  // transaction. When handling team invites, it should be called before
   757  // `ConsumeInviteByID` to assert that invite is still usable (new-style invites
   758  // may be expired or exceeded).
   759  func (tx *AddMemberTx) CanConsumeInvite(ctx context.Context, inviteID keybase1.TeamInviteID) error {
   760  	inviteMD, found := tx.team.chain().FindActiveInviteMDByID(inviteID)
   761  	if !found {
   762  		return fmt.Errorf("failed to find invite being used")
   763  	}
   764  	invite := inviteMD.Invite
   765  
   766  	isNewStyle, err := IsNewStyleInvite(invite)
   767  	if err != nil {
   768  		return err
   769  	}
   770  
   771  	if isNewStyle {
   772  		// Only need to check new-style invites. Old-style invites cannot have
   773  		// expiration date and can always be one-time use, and wouldn't show up
   774  		// in `FindActiveInviteByID` (because they are not active). New-style
   775  		// invites always stay active.
   776  		alreadyUsedBeforeTransaction := len(inviteMD.UsedInvites)
   777  		alreadyUsed := alreadyUsedBeforeTransaction + tx.usedInviteCount[inviteID]
   778  		if invite.IsUsedUp(alreadyUsed) {
   779  			return NewInviteLinkAcceptanceError("invite has no more uses left; so cannot add by this invite")
   780  		}
   781  
   782  		now := tx.team.G().Clock().Now()
   783  		if invite.IsExpired(now) {
   784  			return NewInviteLinkAcceptanceError("invite expired at %v which is before the current time of %v; rejecting", invite.Etime, now)
   785  		}
   786  	} else {
   787  		_, alreadyCompleted := tx.completedInvites[inviteID]
   788  		if alreadyCompleted {
   789  			return fmt.Errorf("invite ID %s was already completed in this transaction", inviteID)
   790  		}
   791  	}
   792  
   793  	return nil
   794  }
   795  
   796  // ConsumeInviteByID finds a change membership payload and either sets
   797  // "completed invite" or adds am "used invite". `CanConsumeInvite` has to be
   798  // called before this function.
   799  func (tx *AddMemberTx) ConsumeInviteByID(ctx context.Context, inviteID keybase1.TeamInviteID, uv keybase1.UserVersion) error {
   800  	payload := tx.findChangeReqForUV(uv)
   801  	if payload == nil {
   802  		return fmt.Errorf("could not find uv %v in transaction", uv)
   803  	}
   804  
   805  	inviteMD, found := tx.team.chain().FindActiveInviteMDByID(inviteID)
   806  	if !found {
   807  		return fmt.Errorf("failed to find invite being used")
   808  	}
   809  	invite := inviteMD.Invite
   810  
   811  	isNewStyle, err := IsNewStyleInvite(invite)
   812  	if err != nil {
   813  		return err
   814  	}
   815  
   816  	if isNewStyle {
   817  		payload.UseInviteID(inviteID, uv.PercentForm())
   818  		tx.usedInviteCount[inviteID]++
   819  	} else {
   820  		payload.CompleteInviteID(inviteID, uv.PercentForm())
   821  		tx.completedInvites[inviteID] = true
   822  	}
   823  
   824  	return nil
   825  }
   826  
   827  // CompleteSocialInvitesFor finds all proofs for `uv` and tries to match them
   828  // with active social invites in the team. Any invite that matches the proofs
   829  // and can therefore be "completed" by adding `uv` to the team is marked as
   830  // completed.
   831  //
   832  // The purpose of this function is to complete social invites when user is
   833  // being added outside of SBS handling. There are two cases in which this
   834  // function completes an invite:
   835  //
   836  //  1. An admin is racing SBS handler by adding a user after they add a proof but
   837  //     before server sends out SBS notifications.
   838  //  2. There was more than one social invite that resolved to the same user
   839  //  3. ...or other cases (or bugs) when there are outstanding invites that
   840  //     resolve to a user but they were not added through SBS handler.
   841  //
   842  // Note that (2) is likely still not handled correctly if there are social
   843  // invites that someone who is already in the team adds proofs for.
   844  func (tx *AddMemberTx) CompleteSocialInvitesFor(ctx context.Context, uv keybase1.UserVersion, username string) (err error) {
   845  	team := tx.team
   846  	g := team.G()
   847  
   848  	defer g.CTrace(ctx, fmt.Sprintf("AddMemberTx.CompleteSocialInvitesFor(%v,%s)", uv, username), &err)()
   849  	if team.NumActiveInvites() == 0 {
   850  		g.Log.CDebugf(ctx, "CompleteSocialInvitesFor: no active invites in team")
   851  		return nil
   852  	}
   853  
   854  	// Find the right payload first
   855  	payload := tx.findChangeReqForUV(uv)
   856  	if payload == nil {
   857  		return fmt.Errorf("could not find uv %v in transaction", uv)
   858  	}
   859  
   860  	proofs, identifyOutcome, err := getUserProofsNoTracking(ctx, g, username)
   861  	if err != nil {
   862  		return err
   863  	}
   864  
   865  	var completedInvites = map[keybase1.TeamInviteID]keybase1.UserVersionPercentForm{}
   866  
   867  	for _, inviteMD := range team.chain().ActiveInvites() {
   868  		invite := inviteMD.Invite
   869  		ityp, err := invite.Type.String()
   870  		if err != nil {
   871  			return err
   872  		}
   873  		category, err := invite.Type.C()
   874  		if err != nil {
   875  			return err
   876  		}
   877  
   878  		if category != keybase1.TeamInviteCategory_SBS {
   879  			continue
   880  		}
   881  
   882  		proofsWithType := proofs.Get([]string{ityp})
   883  
   884  		var proof *libkb.Proof
   885  		for _, p := range proofsWithType {
   886  			if p.Value == string(invite.Name) {
   887  				proof = &p
   888  				break
   889  			}
   890  		}
   891  
   892  		if proof == nil {
   893  			continue
   894  		}
   895  
   896  		g.Log.CDebugf(ctx, "CompleteSocialInvitesFor: Found proof in user's ProofSet: key: %s value: %q", proof.Key, proof.Value)
   897  		proofErr := identifyOutcome.GetRemoteCheckResultFor(ityp, string(invite.Name))
   898  		g.Log.CDebugf(ctx, "CompleteSocialInvitesFor: proof result -> %v", proofErr)
   899  		if proofErr == nil {
   900  			completedInvites[invite.Id] = uv.PercentForm()
   901  			g.Log.CDebugf(ctx, "CompleteSocialInvitesFor: Found completed invite: %s -> %v", invite.Id, uv)
   902  		}
   903  	}
   904  
   905  	// After checking everything, mutate payload.
   906  	g.Log.CDebugf(ctx, "CompleteSocialInvitesFor: checked invites without errors, adding %d complete(s)", len(completedInvites))
   907  	if len(completedInvites) > 0 {
   908  		if payload.CompletedInvites == nil {
   909  			payload.CompletedInvites = make(map[keybase1.TeamInviteID]keybase1.UserVersionPercentForm)
   910  		}
   911  		for i, v := range completedInvites {
   912  			payload.CompletedInvites[i] = v
   913  		}
   914  	}
   915  
   916  	return nil
   917  }
   918  
   919  func (tx *AddMemberTx) ReAddMemberToImplicitTeam(ctx context.Context, uv keybase1.UserVersion, hasPUK bool, role keybase1.TeamRole,
   920  	botSettings *keybase1.TeamBotSettings) error {
   921  	if !tx.team.IsImplicit() {
   922  		return fmt.Errorf("ReAddMemberToImplicitTeam only works on implicit teams")
   923  	}
   924  	if err := assertValidNewTeamMemberRole(role); err != nil {
   925  		return err
   926  	}
   927  
   928  	if hasPUK {
   929  		if err := tx.addMember(uv, role, botSettings); err != nil {
   930  			return err
   931  		}
   932  		tx.sweepCryptoMembers(ctx, uv.Uid, false)
   933  		if err := tx.completeAllKeybaseInvitesForUID(uv); err != nil {
   934  			return err
   935  		}
   936  	} else {
   937  		if !tx.AllowPUKless {
   938  			return UserPUKlessError{uv: uv}
   939  		}
   940  		if err := tx.createKeybaseInvite(uv, role); err != nil {
   941  			return err
   942  		}
   943  		tx.sweepKeybaseInvites(uv.Uid)
   944  		// We cannot sweep crypto members here because we need to ensure that
   945  		// we are only posting one link, and if we want to add a pukless
   946  		// member, it has to be invite link. Otherwise we would attempt to
   947  		// remove the old member without adding a new one. So old crypto
   948  		// members have to stay for now. However, old crypto member should be
   949  		// swept when Keybase-type invite goes through SBS handler and invited
   950  		// member becomes a real crypto dude.
   951  	}
   952  
   953  	if len(tx.payloads) != 1 {
   954  		return errors.New("ReAddMemberToImplicitTeam tried to create more than one link")
   955  	}
   956  
   957  	return nil
   958  }
   959  
   960  func (tx *AddMemberTx) CancelInvite(id keybase1.TeamInviteID, forUID keybase1.UID) {
   961  	payload := tx.inviteKeybasePayload(forUID)
   962  	if payload.Cancel == nil {
   963  		payload.Cancel = &[]SCTeamInviteID{SCTeamInviteID(id)}
   964  	} else {
   965  		tmp := append(*payload.Cancel, SCTeamInviteID(id))
   966  		payload.Cancel = &tmp
   967  	}
   968  }
   969  
   970  // AddMemberBySBS is very similar in what it does to addMemberByUPKV2
   971  // (or AddMemberBy* family of functions), but it has easier job
   972  // because it only adds cryptomembers and fails on PUKless users. It
   973  // also sets invite referenced by `invitee.InviteID` as Completed by
   974  // UserVersion from `invitee` in the same ChangeMembership link that
   975  // adds the user to the team.
   976  //
   977  // AddMemberBySBS assumes that member role is already checked by the
   978  // caller, so it might generate invalid signature if invitee is
   979  // already a member with same role.
   980  func (tx *AddMemberTx) AddMemberBySBS(ctx context.Context, invitee keybase1.TeamInvitee, role keybase1.TeamRole) (err error) {
   981  	team := tx.team
   982  	g := team.G()
   983  
   984  	defer g.CTrace(ctx, fmt.Sprintf("AddMemberTx.AddMemberBySBS(%v) to team: %q",
   985  		invitee, team.Name()), &err)()
   986  
   987  	uv := NewUserVersion(invitee.Uid, invitee.EldestSeqno)
   988  	upak, err := loadUPAK2(ctx, g, uv.Uid, true /* forcePoll */)
   989  	if err != nil {
   990  		return err
   991  	}
   992  
   993  	user := upak.Current
   994  	if uv.EldestSeqno != user.EldestSeqno {
   995  		return fmt.Errorf("Bad eldestseqno for %s: expected %d, got %d", uv.Uid, user.EldestSeqno, uv.EldestSeqno)
   996  	}
   997  
   998  	if len(user.PerUserKeys) == 0 {
   999  		return fmt.Errorf("Cannot add PUKless user %q (%s) for SBS", user.Username, uv.Uid)
  1000  	}
  1001  
  1002  	if user.Status == keybase1.StatusCode_SCDeleted {
  1003  		return fmt.Errorf("User %q (%s) is deleted", user.Username, uv.Uid)
  1004  	}
  1005  
  1006  	if role == keybase1.TeamRole_OWNER && team.IsSubteam() {
  1007  		return NewSubteamOwnersError()
  1008  	}
  1009  
  1010  	if err := assertValidNewTeamMemberRole(role); err != nil {
  1011  		return err
  1012  	}
  1013  
  1014  	// Mark that we will be completing inviteID so sweepKeybaseInvites
  1015  	// does not cancel it if it happens to be keybase-type.
  1016  	tx.completedInvites[invitee.InviteID] = true
  1017  
  1018  	tx.sweepKeybaseInvites(uv.Uid)
  1019  	tx.sweepCryptoMembersOlderThan(uv)
  1020  	if err := tx.addMemberAndCompleteInvite(uv, role, invitee.InviteID); err != nil {
  1021  		return err
  1022  	}
  1023  	return nil
  1024  }
  1025  
  1026  func (tx *AddMemberTx) Post(mctx libkb.MetaContext) (err error) {
  1027  	team := tx.team
  1028  	g := team.G()
  1029  
  1030  	defer g.CTrace(mctx.Ctx(), "AddMemberTx.Post", &err)()
  1031  
  1032  	if tx.err != nil {
  1033  		// AddMemberTx operation has irreversibly failed, potentially leaving
  1034  		// tx in bad state. Do not post.
  1035  		return tx.err
  1036  	}
  1037  
  1038  	if len(tx.payloads) == 0 {
  1039  		return errors.New("there are no signatures to post")
  1040  	}
  1041  
  1042  	g.Log.CDebugf(mctx.Ctx(), "AddMemberTx: Attempting to post %d signatures", len(tx.payloads))
  1043  
  1044  	// Initialize key manager.
  1045  	if _, err := team.SharedSecret(mctx.Ctx()); err != nil {
  1046  		return err
  1047  	}
  1048  
  1049  	// Make sure we know recent merkle root.
  1050  	if err := team.ForceMerkleRootUpdate(mctx.Ctx()); err != nil {
  1051  		return err
  1052  	}
  1053  
  1054  	// Get admin permission, we will use the same one for all sigs.
  1055  	admin, err := team.getAdminPermission(mctx.Ctx())
  1056  	if err != nil {
  1057  		return err
  1058  	}
  1059  
  1060  	var sections []SCTeamSection
  1061  	memSet := newMemberSet()
  1062  	var sectionsWithBoxSummaries []int
  1063  	var ratchet *hidden.Ratchet
  1064  
  1065  	// Transform payloads to SCTeamSections.
  1066  	for i, p := range tx.payloads {
  1067  		section := SCTeamSection{
  1068  			ID:       SCTeamID(team.ID),
  1069  			Admin:    admin,
  1070  			Implicit: team.IsImplicit(),
  1071  			Public:   team.IsPublic(),
  1072  		}
  1073  
  1074  		// Only add a ratchet to the first link in the sequence, it doesn't make sense
  1075  		// to add more than one, and it may as well be the first.
  1076  		if ratchet == nil {
  1077  			ratchet, err = team.makeRatchet(mctx.Ctx())
  1078  			if err != nil {
  1079  				return err
  1080  			}
  1081  		}
  1082  		section.Ratchets = ratchet.ToTeamSection()
  1083  
  1084  		switch p.Tag {
  1085  		case txPayloadTagCryptomembers:
  1086  			payload := p.Val.(*keybase1.TeamChangeReq)
  1087  			// We need memberSet for this particular payload, but also keep a
  1088  			// memberSet for entire transaction to generate boxes afterwards.
  1089  			payloadMemberSet, err := newMemberSetChange(mctx.Ctx(), g, *payload)
  1090  			if err != nil {
  1091  				return err
  1092  			}
  1093  
  1094  			memSet.appendMemberSet(payloadMemberSet)
  1095  
  1096  			section.Members, err = payloadMemberSet.Section()
  1097  			if err != nil {
  1098  				return err
  1099  			}
  1100  
  1101  			section.CompletedInvites = payload.CompletedInvites
  1102  			section.UsedInvites = makeSCMapInviteIDUVMap(payload.UsedInvites)
  1103  
  1104  			sections = append(sections, section)
  1105  
  1106  			// If there are additions, then there will be a new key involved.
  1107  			// If there are deletions, then we'll be rotating. So either way,
  1108  			// this section needs a box summary.
  1109  			sectionsWithBoxSummaries = append(sectionsWithBoxSummaries, i)
  1110  		case txPayloadTagInviteKeybase, txPayloadTagInviteSocial:
  1111  			entropy, err := makeSCTeamEntropy()
  1112  			if err != nil {
  1113  				return err
  1114  			}
  1115  
  1116  			section.Invites = p.Val.(*SCTeamInvites)
  1117  			if section.Invites.Len() == 0 {
  1118  				return fmt.Errorf("invalid invite, 0 members invited")
  1119  			}
  1120  			section.Entropy = entropy
  1121  			sections = append(sections, section)
  1122  
  1123  			if !tx.AllowPUKless && p.Tag == txPayloadTagInviteKeybase && section.Invites.HasNewInvites() {
  1124  				// This means we broke contract somewhere or that tx.AllowPUKless
  1125  				// was changed to false after adding PUKless user. Better fail here
  1126  				// instead of doing unexpected.
  1127  				return fmt.Errorf("Found payload with new Keybase invites but AllowPUKless is false")
  1128  			}
  1129  		default:
  1130  			return fmt.Errorf("Unhandled case in AddMemberTx.Post, unknown tag: %v", p.Tag)
  1131  		}
  1132  	}
  1133  
  1134  	// If memSet has any downgrades, request downgrade lease.
  1135  	var merkleRoot *libkb.MerkleRoot
  1136  	var lease *libkb.Lease
  1137  
  1138  	downgrades, err := team.getDowngradedUsers(mctx.Ctx(), memSet)
  1139  	if err != nil {
  1140  		return err
  1141  	}
  1142  	if len(downgrades) != 0 {
  1143  		lease, merkleRoot, err = libkb.RequestDowngradeLeaseByTeam(mctx.Ctx(), g, team.ID, downgrades)
  1144  		if err != nil {
  1145  			return err
  1146  		}
  1147  		// Always cancel lease so we don't leave any hanging.
  1148  		defer func() {
  1149  			err := libkb.CancelDowngradeLease(mctx.Ctx(), g, lease.LeaseID)
  1150  			if err != nil {
  1151  				g.Log.CWarningf(mctx.Ctx(), "Failed to cancel downgrade lease: %s", err.Error())
  1152  			}
  1153  		}()
  1154  	}
  1155  
  1156  	var skipKeyRotation bool
  1157  	if tx.SkipKeyRotation != nil {
  1158  		skipKeyRotation = *tx.SkipKeyRotation
  1159  	} else {
  1160  		skipKeyRotation = team.CanSkipKeyRotation()
  1161  	}
  1162  	secretBoxes, implicitAdminBoxes, perTeamKeySection, teamEKPayload, err := team.recipientBoxes(mctx.Ctx(), memSet, skipKeyRotation)
  1163  	if err != nil {
  1164  		return err
  1165  	}
  1166  
  1167  	// For all sections that we previously did add/remove members for, let's
  1168  	for _, s := range sectionsWithBoxSummaries {
  1169  		err = addSummaryHash(&sections[s], secretBoxes)
  1170  		if err != nil {
  1171  			return err
  1172  		}
  1173  	}
  1174  
  1175  	if perTeamKeySection != nil {
  1176  		// We have a new per team key, find first TeamChangeReq
  1177  		// section that removes users and add it there.
  1178  		found := false
  1179  		for i, v := range tx.payloads {
  1180  			if v.Tag == txPayloadTagCryptomembers {
  1181  				req := v.Val.(*keybase1.TeamChangeReq)
  1182  				if len(req.None) > 0 {
  1183  					sections[i].PerTeamKey = perTeamKeySection
  1184  					found = true
  1185  					break
  1186  				}
  1187  			}
  1188  		}
  1189  		if !found {
  1190  			return fmt.Errorf("AddMemberTx.Post got a PerTeamKey but couldn't find a link with None to attach it")
  1191  		}
  1192  	}
  1193  
  1194  	var teamEKBoxes *[]keybase1.TeamEkBoxMetadata
  1195  	if teamEKPayload == nil {
  1196  		ekLib := g.GetEKLib()
  1197  		if ekLib != nil && len(memSet.recipients) > 0 {
  1198  			uids := memSet.recipientUids()
  1199  			teamEKBoxes, err = ekLib.BoxLatestTeamEK(mctx, team.ID, uids)
  1200  			if err != nil {
  1201  				return err
  1202  			}
  1203  		}
  1204  	}
  1205  
  1206  	// Take payloads and team sections and generate chain of signatures.
  1207  	nextSeqno := team.NextSeqno()
  1208  	latestLinkID := team.chain().GetLatestLinkID()
  1209  
  1210  	var readySigs []libkb.SigMultiItem
  1211  	for i, section := range sections {
  1212  		var linkType libkb.LinkType
  1213  		switch tx.payloads[i].Tag {
  1214  		case txPayloadTagCryptomembers:
  1215  			linkType = libkb.LinkTypeChangeMembership
  1216  		case txPayloadTagInviteKeybase, txPayloadTagInviteSocial:
  1217  			linkType = libkb.LinkTypeInvite
  1218  		default:
  1219  			return fmt.Errorf("Unhandled case in AddMemberTx.Post, unknown tag: %v", tx.payloads[i].Tag)
  1220  		}
  1221  
  1222  		sigMultiItem, linkID, err := team.sigTeamItemRaw(mctx.Ctx(), section, linkType,
  1223  			nextSeqno, latestLinkID, merkleRoot)
  1224  		if err != nil {
  1225  			return err
  1226  		}
  1227  
  1228  		g.Log.CDebugf(mctx.Ctx(), "AddMemberTx: Prepared signature %d: Type: %v SeqNo: %d Hash: %q",
  1229  			i, linkType, nextSeqno, linkID)
  1230  
  1231  		nextSeqno++
  1232  		latestLinkID = linkID
  1233  		readySigs = append(readySigs, sigMultiItem)
  1234  	}
  1235  
  1236  	// Add a single bot_settings link if we are adding any RESTRICTEDBOT members
  1237  	if len(memSet.restrictedBotSettings) > 0 {
  1238  		var section SCTeamSection
  1239  		section, ratchet, err = team.botSettingsSection(mctx.Ctx(), memSet.restrictedBotSettings, ratchet, merkleRoot)
  1240  		if err != nil {
  1241  			return err
  1242  		}
  1243  
  1244  		sigMultiItem, linkID, err := team.sigTeamItemRaw(mctx.Ctx(), section, libkb.LinkTypeTeamBotSettings,
  1245  			nextSeqno, latestLinkID, merkleRoot)
  1246  		if err != nil {
  1247  			return err
  1248  		}
  1249  
  1250  		g.Log.CDebugf(mctx.Ctx(), "AddMemberTx: Prepared bot_settings signature: SeqNo: %d Hash: %q",
  1251  			nextSeqno, linkID)
  1252  		nextSeqno++
  1253  		readySigs = append(readySigs, sigMultiItem)
  1254  	}
  1255  
  1256  	if err := team.precheckLinksToPost(mctx.Ctx(), readySigs); err != nil {
  1257  		g.Log.CDebugf(mctx.Ctx(), "Precheck failed: %v", err)
  1258  		return err
  1259  	}
  1260  
  1261  	payloadArgs := sigPayloadArgs{
  1262  		secretBoxes:         secretBoxes,
  1263  		lease:               lease,
  1264  		implicitAdminBoxes:  implicitAdminBoxes,
  1265  		teamEKPayload:       teamEKPayload,
  1266  		teamEKBoxes:         teamEKBoxes,
  1267  		ratchetBlindingKeys: ratchet.ToSigPayload(),
  1268  	}
  1269  	payload := team.sigPayload(readySigs, payloadArgs)
  1270  	if tx.EmailInviteMsg != nil {
  1271  		payload["email_invite_msg"] = *tx.EmailInviteMsg
  1272  	}
  1273  
  1274  	if err := team.postMulti(mctx, payload); err != nil {
  1275  		return err
  1276  	}
  1277  
  1278  	// nextSeqno-1 should be the sequence number of last link that we sent.
  1279  	err = team.notify(mctx.Ctx(), keybase1.TeamChangeSet{MembershipChanged: true}, nextSeqno-1)
  1280  	if err != nil {
  1281  		mctx.Warning("Failed to send team change notification: %s", err)
  1282  	}
  1283  
  1284  	team.storeTeamEKPayload(mctx.Ctx(), teamEKPayload)
  1285  	createTeambotKeys(team.G(), team.ID, memSet.restrictedBotRecipientUids())
  1286  
  1287  	return nil
  1288  }