github.com/keybase/client/go@v0.0.0-20240520164431-4f512a4c85a3/kbfs/tlfhandle/resolve.go (about)

     1  // Copyright 2016 Keybase Inc. All rights reserved.
     2  // Use of this source code is governed by a BSD
     3  // license that can be found in the LICENSE file.
     4  
     5  package tlfhandle
     6  
     7  // This file has the online resolving functionality for TlfHandles.
     8  
     9  import (
    10  	"fmt"
    11  	"reflect"
    12  	"sort"
    13  	"strings"
    14  
    15  	"github.com/keybase/client/go/kbfs/idutil"
    16  	"github.com/keybase/client/go/kbfs/kbfscodec"
    17  	"github.com/keybase/client/go/kbfs/kbfsmd"
    18  	"github.com/keybase/client/go/kbfs/tlf"
    19  	kbname "github.com/keybase/client/go/kbun"
    20  	"github.com/keybase/client/go/libkb"
    21  	"github.com/keybase/client/go/logger"
    22  	"github.com/keybase/client/go/protocol/keybase1"
    23  	"github.com/pkg/errors"
    24  	"golang.org/x/net/context"
    25  )
    26  
    27  type nameIDPair struct {
    28  	name kbname.NormalizedUsername
    29  	id   keybase1.UserOrTeamID
    30  }
    31  
    32  type resolvableUser interface {
    33  	// resolve must do exactly one of the following:
    34  	//
    35  	//   - return a non-zero nameIDPair;
    36  	//   - return a non-zero keybase1.SocialAssertion;
    37  	//   - return a non-nil error.
    38  	//
    39  	// The TLF ID may or may not be filled in, depending on whether
    40  	// the name is backed by an implicit team.
    41  	resolve(context.Context) (
    42  		nameIDPair, keybase1.SocialAssertion, tlf.ID, error)
    43  }
    44  
    45  func resolveOneUser(
    46  	ctx context.Context, user resolvableUser,
    47  	errCh chan<- error, userInfoResults chan<- nameIDPair,
    48  	socialAssertionResults chan<- keybase1.SocialAssertion,
    49  	idResults chan<- tlf.ID) {
    50  	userInfo, socialAssertion, id, err := user.resolve(ctx)
    51  	if err != nil {
    52  		select {
    53  		case errCh <- err:
    54  		default:
    55  			// another worker reported an error before us;
    56  			// first one wins
    57  		}
    58  		return
    59  	}
    60  
    61  	// The ID has to be sent first, to guarantee it's in the channel
    62  	// before it is closed.
    63  	if id != tlf.NullID {
    64  		select {
    65  		case idResults <- id:
    66  		default:
    67  			errCh <- fmt.Errorf(
    68  				"Sending ID %s failed; one ID has probably already been sent",
    69  				id)
    70  			return
    71  		}
    72  	}
    73  
    74  	if userInfo != (nameIDPair{}) {
    75  		userInfoResults <- userInfo
    76  		return
    77  	}
    78  
    79  	if socialAssertion != (keybase1.SocialAssertion{}) {
    80  		socialAssertionResults <- socialAssertion
    81  		return
    82  	}
    83  
    84  	errCh <- fmt.Errorf("Resolving %v resulted in empty userInfo and empty socialAssertion", user)
    85  }
    86  
    87  func getNames(idToName map[keybase1.UserOrTeamID]kbname.NormalizedUsername) []kbname.NormalizedUsername {
    88  	var names []kbname.NormalizedUsername
    89  	for _, name := range idToName {
    90  		names = append(names, name)
    91  	}
    92  	return names
    93  }
    94  
    95  func makeHandleHelper(
    96  	ctx context.Context, t tlf.Type, writers, readers []resolvableUser,
    97  	extensions []tlf.HandleExtension, idGetter IDGetter) (
    98  	*Handle, error) {
    99  	if t != tlf.Private && len(readers) > 0 {
   100  		return nil, errors.New("public or team folder cannot have readers")
   101  	} else if t == tlf.SingleTeam && len(writers) != 1 {
   102  		return nil, errors.New("team folder cannot have more than one writer")
   103  	}
   104  
   105  	// parallelize the resolutions for each user
   106  	ctx, cancel := context.WithCancel(ctx)
   107  	defer cancel()
   108  
   109  	errCh := make(chan error, 1)
   110  
   111  	wc := make(chan nameIDPair, len(writers))
   112  	uwc := make(chan keybase1.SocialAssertion, len(writers))
   113  	// We are only expecting at most one ID.  `resolveOneUser` should
   114  	// error if it can't send an ID immediately.
   115  	idc := make(chan tlf.ID, 1)
   116  	for _, writer := range writers {
   117  		go resolveOneUser(ctx, writer, errCh, wc, uwc, idc)
   118  	}
   119  
   120  	rc := make(chan nameIDPair, len(readers))
   121  	urc := make(chan keybase1.SocialAssertion, len(readers))
   122  	for _, reader := range readers {
   123  		go resolveOneUser(ctx, reader, errCh, rc, urc, idc)
   124  	}
   125  
   126  	usedWNames :=
   127  		make(map[keybase1.UserOrTeamID]kbname.NormalizedUsername, len(writers))
   128  	usedRNames :=
   129  		make(map[keybase1.UserOrTeamID]kbname.NormalizedUsername, len(readers))
   130  	usedUnresolvedWriters := make(map[keybase1.SocialAssertion]bool)
   131  	usedUnresolvedReaders := make(map[keybase1.SocialAssertion]bool)
   132  	for i := 0; i < len(writers)+len(readers); i++ {
   133  		select {
   134  		case err := <-errCh:
   135  			return nil, err
   136  		case userInfo := <-wc:
   137  			usedWNames[userInfo.id] = userInfo.name
   138  		case userInfo := <-rc:
   139  			usedRNames[userInfo.id] = userInfo.name
   140  		case socialAssertion := <-uwc:
   141  			usedUnresolvedWriters[socialAssertion] = true
   142  		case socialAssertion := <-urc:
   143  			usedUnresolvedReaders[socialAssertion] = true
   144  		case <-ctx.Done():
   145  			return nil, ctx.Err()
   146  		}
   147  	}
   148  	// It's safe to close the channel now before we receive from it,
   149  	// since the ID is always sent first, before the usernames and
   150  	// assertions.
   151  	close(idc)
   152  	tlfID := tlf.NullID
   153  	more := false
   154  	select {
   155  	case tlfID, more = <-idc:
   156  	default:
   157  	}
   158  
   159  	if more {
   160  		// Just make sure a second one didn't slip in (only possible if
   161  		// `resolveOneUser` has a bug).
   162  		select {
   163  		case tlfID2, more := <-idc:
   164  			if more {
   165  				return nil, fmt.Errorf(
   166  					"More than one TLF ID returned: %s and %s", tlfID, tlfID2)
   167  			}
   168  		default:
   169  		}
   170  	}
   171  
   172  	for id := range usedWNames {
   173  		delete(usedRNames, id)
   174  	}
   175  
   176  	for sa := range usedUnresolvedWriters {
   177  		delete(usedUnresolvedReaders, sa)
   178  	}
   179  
   180  	unresolvedWriters := getSortedUnresolved(usedUnresolvedWriters)
   181  
   182  	var unresolvedReaders []keybase1.SocialAssertion
   183  	if t == tlf.Private {
   184  		unresolvedReaders = getSortedUnresolved(usedUnresolvedReaders)
   185  	}
   186  
   187  	extensionList := tlf.HandleExtensionList(extensions)
   188  	sort.Sort(extensionList)
   189  	conflictInfo, finalizedInfo := extensionList.Splat()
   190  
   191  	isImplicit := false
   192  	if t != tlf.SingleTeam && len(writers) == 1 && len(readers) == 0 {
   193  		// There's only one ID, but iterating is the only good way to
   194  		// get it out of the map.
   195  		for id := range usedWNames {
   196  			isImplicit = id.IsTeam()
   197  		}
   198  	}
   199  
   200  	var canonicalName tlf.CanonicalName
   201  	if isImplicit || t == tlf.SingleTeam {
   202  		canonicalName = tlf.MakeCanonicalNameForTeam(
   203  			getNames(usedWNames), unresolvedWriters,
   204  			getNames(usedRNames), unresolvedReaders, extensions)
   205  	} else {
   206  		canonicalName = tlf.MakeCanonicalName(
   207  			getNames(usedWNames), unresolvedWriters,
   208  			getNames(usedRNames), unresolvedReaders, extensions)
   209  	}
   210  
   211  	switch t {
   212  	case tlf.Private:
   213  		// All writers and readers must be users.
   214  		for id := range usedWNames {
   215  			if !isImplicit && !id.IsUser() {
   216  				return nil, NoSuchNameError{Name: string(canonicalName)}
   217  			}
   218  		}
   219  		for id := range usedRNames {
   220  			if !id.IsUser() {
   221  				return nil, NoSuchNameError{Name: string(canonicalName)}
   222  			}
   223  		}
   224  	case tlf.Public:
   225  		// All writers must be users.
   226  		for id := range usedWNames {
   227  			if !isImplicit && !id.IsUser() {
   228  				return nil, NoSuchNameError{Name: string(canonicalName)}
   229  			}
   230  		}
   231  	case tlf.SingleTeam:
   232  		// The writer must be a team.
   233  		for id := range usedWNames {
   234  			if !id.IsTeamOrSubteam() {
   235  				return nil, NoSuchNameError{Name: string(canonicalName)}
   236  			}
   237  		}
   238  	default:
   239  		panic(fmt.Sprintf("Unknown TLF type: %s", t))
   240  	}
   241  
   242  	h := &Handle{
   243  		tlfType:           t,
   244  		resolvedWriters:   usedWNames,
   245  		resolvedReaders:   usedRNames,
   246  		unresolvedWriters: unresolvedWriters,
   247  		unresolvedReaders: unresolvedReaders,
   248  		conflictInfo:      conflictInfo,
   249  		finalizedInfo:     finalizedInfo,
   250  		name:              canonicalName,
   251  		tlfID:             tlfID,
   252  	}
   253  
   254  	needIDLookup := (!isImplicit && h.tlfID == tlf.NullID) ||
   255  		(conflictInfo != nil &&
   256  			conflictInfo.Type == tlf.HandleExtensionLocalConflict)
   257  	if needIDLookup && idGetter != nil {
   258  		// If this isn't an implicit team yet, look up possible
   259  		// pre-existing TLF ID from the mdserver.  If this is a local
   260  		// conflict branch, look up the fake TLF ID from the journal.
   261  		tlfID, err := idGetter.GetIDForHandle(ctx, h)
   262  		if err != nil {
   263  			return nil, err
   264  		}
   265  		h.tlfID = tlfID
   266  	}
   267  
   268  	if h.tlfID != tlf.NullID && h.tlfID.Type() != t {
   269  		return nil, fmt.Errorf("ID type=%s doesn't match expected type=%s",
   270  			h.tlfID.Type(), t)
   271  	}
   272  
   273  	return h, nil
   274  }
   275  
   276  type resolvableID struct {
   277  	resolver idutil.Resolver
   278  	idGetter IDGetter
   279  	nug      idutil.NormalizedUsernameGetter
   280  	id       keybase1.UserOrTeamID
   281  	tlfType  tlf.Type
   282  	offline  keybase1.OfflineAvailability
   283  }
   284  
   285  func (ruid resolvableID) resolve(ctx context.Context) (
   286  	nameIDPair, keybase1.SocialAssertion, tlf.ID, error) {
   287  	if doResolveImplicit(ctx, ruid.tlfType) && ruid.id.IsTeamOrSubteam() {
   288  		// First check if this is an implicit team.
   289  		iteamInfo, err := ruid.resolver.ResolveImplicitTeamByID(
   290  			ctx, ruid.id.AsTeamOrBust(), ruid.tlfType, ruid.offline)
   291  		switch errors.Cause(err).(type) {
   292  		case nil:
   293  			if ruid.id != iteamInfo.TID.AsUserOrTeam() {
   294  				return nameIDPair{}, keybase1.SocialAssertion{}, tlf.NullID,
   295  					fmt.Errorf("Implicit team ID %s doesn't match ID in "+
   296  						"handle %s", iteamInfo.TID, ruid.id)
   297  			}
   298  
   299  			return nameIDPair{
   300  				name: iteamInfo.Name,
   301  				id:   iteamInfo.TID.AsUserOrTeam(),
   302  			}, keybase1.SocialAssertion{}, iteamInfo.TlfID, nil
   303  		case libkb.AppStatusError:
   304  			// This could indicate a temporary error, like a network
   305  			// failure.  So fail it; the user should see a failure
   306  			// rather than possibly attempting to look up an implicit
   307  			// team through the path below.  See HOTPOT-1698.
   308  			return nameIDPair{}, keybase1.SocialAssertion{}, tlf.NullID, err
   309  		default:
   310  			// Fallthrough to the classic TLF case.  Normally this
   311  			// would be an error string that looks like: "Operation
   312  			// only allowed on implicit teams:
   313  			// MapImplicitTeamIDToDisplayName".  TODO: make that an
   314  			// exported error and treat only it specially.
   315  		}
   316  	}
   317  
   318  	// If not, resolve it the normal way, and assume it's a classic
   319  	// TLF.
   320  	name, err := ruid.nug.GetNormalizedUsername(ctx, ruid.id, ruid.offline)
   321  	if err != nil {
   322  		return nameIDPair{}, keybase1.SocialAssertion{}, tlf.NullID, err
   323  	}
   324  	var tlfID tlf.ID
   325  	// Only get a team TLF ID if the caller expects to use it.
   326  	if ruid.idGetter != nil && ruid.id.IsTeamOrSubteam() {
   327  		tlfID, err = ruid.resolver.ResolveTeamTLFID(
   328  			ctx, ruid.id.AsTeamOrBust(), ruid.offline)
   329  		if err != nil {
   330  			return nameIDPair{}, keybase1.SocialAssertion{}, tlf.NullID, err
   331  		}
   332  	}
   333  	return nameIDPair{
   334  		name: name,
   335  		id:   ruid.id,
   336  	}, keybase1.SocialAssertion{}, tlfID, nil
   337  }
   338  
   339  type resolvableSocialAssertion keybase1.SocialAssertion
   340  
   341  func (rsa resolvableSocialAssertion) resolve(ctx context.Context) (
   342  	nameIDPair, keybase1.SocialAssertion, tlf.ID, error) {
   343  	return nameIDPair{}, keybase1.SocialAssertion(rsa), tlf.NullID, nil
   344  }
   345  
   346  // MakeHandle creates a Handle from the given tlf.Handle and the
   347  // given NormalizedUsernameGetter (which is usually a KBPKI). `t` is
   348  // the `tlf.Type` of the new handle.  (Note this could be different
   349  // from `bareHandle.Type()`, if this is an implicit team TLF.)
   350  func MakeHandle(
   351  	ctx context.Context, bareHandle tlf.Handle, t tlf.Type,
   352  	resolver idutil.Resolver, nug idutil.NormalizedUsernameGetter,
   353  	idGetter IDGetter, offline keybase1.OfflineAvailability) (
   354  	*Handle, error) {
   355  	writers := make([]resolvableUser, 0, len(bareHandle.Writers)+len(bareHandle.UnresolvedWriters))
   356  	for _, w := range bareHandle.Writers {
   357  		writers = append(
   358  			writers, resolvableID{resolver, idGetter, nug, w, t, offline})
   359  	}
   360  	for _, uw := range bareHandle.UnresolvedWriters {
   361  		writers = append(writers, resolvableSocialAssertion(uw))
   362  	}
   363  
   364  	var readers []resolvableUser
   365  	if bareHandle.Type() == tlf.Private {
   366  		readers = make([]resolvableUser, 0, len(bareHandle.Readers)+len(bareHandle.UnresolvedReaders))
   367  		for _, r := range bareHandle.Readers {
   368  			readers = append(
   369  				readers, resolvableID{resolver, idGetter, nug, r, t, offline})
   370  		}
   371  		for _, ur := range bareHandle.UnresolvedReaders {
   372  			readers = append(readers, resolvableSocialAssertion(ur))
   373  		}
   374  	}
   375  
   376  	h, err := makeHandleHelper(
   377  		ctx, t, writers, readers, bareHandle.Extensions(), idGetter)
   378  	if err != nil {
   379  		return nil, err
   380  	}
   381  
   382  	newHandle, err := h.ToBareHandle()
   383  	if err != nil {
   384  		return nil, err
   385  	}
   386  	if !reflect.DeepEqual(newHandle, bareHandle) {
   387  		panic(fmt.Errorf("newHandle=%+v unexpectedly not equal to bareHandle=%+v", newHandle, bareHandle))
   388  	}
   389  
   390  	return h, nil
   391  }
   392  
   393  // MakeHandleWithTlfID is like `MakeHandle`, but it ensures the
   394  // handle's TLF ID is always set to `tlfID`, even if it corresponds to
   395  // an implicit team for which the iteam settings don't yet contain a
   396  // TLF ID (due to a cross-device race or certain testing scenarios).
   397  func MakeHandleWithTlfID(
   398  	ctx context.Context, bareHandle tlf.Handle, t tlf.Type,
   399  	resolver idutil.Resolver, nug idutil.NormalizedUsernameGetter,
   400  	tlfID tlf.ID, offline keybase1.OfflineAvailability) (
   401  	*Handle, error) {
   402  	handle, err := MakeHandle(
   403  		ctx, bareHandle, t, resolver, nug, ConstIDGetter{ID: tlfID}, offline)
   404  	if err != nil {
   405  		return nil, err
   406  	}
   407  	if handle.TlfID() == tlf.NullID {
   408  		// In cases (mostly in testing) where the iteam settings don't
   409  		// yet contain the TLF ID, the idGetter will never even get
   410  		// called, so we need to set it manually here.
   411  		handle.SetTlfID(tlfID)
   412  	}
   413  	return handle, nil
   414  }
   415  
   416  type resolvableNameUIDPair nameIDPair
   417  
   418  func (rp resolvableNameUIDPair) resolve(ctx context.Context) (
   419  	nameIDPair, keybase1.SocialAssertion, tlf.ID, error) {
   420  	return nameIDPair(rp), keybase1.SocialAssertion{}, tlf.NullID, nil
   421  }
   422  
   423  // ResolveAgainForUser tries to resolve any unresolved assertions in
   424  // the given handle and returns a new handle with the results. As an
   425  // optimization, if h contains no unresolved assertions, it just
   426  // returns itself.  If uid != keybase1.UID(""), it only allows
   427  // assertions that resolve to uid.
   428  func (h *Handle) ResolveAgainForUser(
   429  	ctx context.Context, resolver idutil.Resolver, idGetter IDGetter,
   430  	osg idutil.OfflineStatusGetter, uid keybase1.UID) (*Handle, error) {
   431  	if len(h.unresolvedWriters)+len(h.unresolvedReaders) == 0 {
   432  		return h, nil
   433  	}
   434  
   435  	offline := keybase1.OfflineAvailability_NONE
   436  	if osg != nil {
   437  		offline = osg.OfflineAvailabilityForID(h.tlfID)
   438  	}
   439  
   440  	writers := make([]resolvableUser, 0, len(h.resolvedWriters)+len(h.unresolvedWriters))
   441  	for uid, w := range h.resolvedWriters {
   442  		writers = append(writers, resolvableNameUIDPair{w, uid})
   443  	}
   444  	for _, uw := range h.unresolvedWriters {
   445  		writers = append(writers, resolvableAssertion{
   446  			resolver, nil, idGetter, uw.String(), uid, offline})
   447  	}
   448  
   449  	var readers []resolvableUser
   450  	if h.Type() == tlf.Private {
   451  		readers = make([]resolvableUser, 0, len(h.resolvedReaders)+len(h.unresolvedReaders))
   452  		for uid, r := range h.resolvedReaders {
   453  			readers = append(readers, resolvableNameUIDPair{r, uid})
   454  		}
   455  		for _, ur := range h.unresolvedReaders {
   456  			readers = append(readers, resolvableAssertion{
   457  				resolver, nil, idGetter, ur.String(), uid, offline})
   458  		}
   459  	}
   460  
   461  	newH, err := makeHandleHelper(
   462  		ctx, h.Type(), writers, readers, h.Extensions(), idGetter)
   463  	if err != nil {
   464  		return nil, err
   465  	}
   466  
   467  	return newH, nil
   468  }
   469  
   470  // ResolveAgain tries to resolve any unresolved assertions in the
   471  // given handle and returns a new handle with the results. As an
   472  // optimization, if h contains no unresolved assertions, it just
   473  // returns itself.
   474  func (h *Handle) ResolveAgain(
   475  	ctx context.Context, resolver idutil.Resolver, idGetter IDGetter,
   476  	osg idutil.OfflineStatusGetter) (*Handle, error) {
   477  	if h.IsFinal() {
   478  		// Don't attempt to further resolve final handles.
   479  		return h, nil
   480  	}
   481  	return h.ResolveAgainForUser(
   482  		ctx, resolver, idGetter, osg, keybase1.UID(""))
   483  }
   484  
   485  type partialResolver struct {
   486  	idutil.Resolver
   487  	unresolvedAssertions map[string]bool
   488  }
   489  
   490  func (pr partialResolver) Resolve(
   491  	ctx context.Context, assertion string,
   492  	offline keybase1.OfflineAvailability) (
   493  	kbname.NormalizedUsername, keybase1.UserOrTeamID, error) {
   494  	if pr.unresolvedAssertions[assertion] {
   495  		// Force an unresolved assertion.
   496  		return kbname.NormalizedUsername(""),
   497  			keybase1.UserOrTeamID(""), idutil.NoSuchUserError{Input: assertion}
   498  	}
   499  	return pr.Resolver.Resolve(ctx, assertion, offline)
   500  }
   501  
   502  // ResolvesTo returns whether this handle resolves to the given one.
   503  // It also returns the partially-resolved version of h, i.e. h
   504  // resolved except for unresolved assertions in other; this should
   505  // equal other if and only if true is returned.
   506  func (h Handle) ResolvesTo(
   507  	ctx context.Context, codec kbfscodec.Codec, resolver idutil.Resolver,
   508  	idGetter IDGetter, osg idutil.OfflineStatusGetter, other Handle) (
   509  	resolvesTo bool, partialResolvedH *Handle, err error) {
   510  	// Check the conflict extension.
   511  	var conflictAdded, finalizedAdded bool
   512  	if (h.IsConflict() && other.IsLocalConflict()) ||
   513  		(h.IsLocalConflict() && other.IsConflict()) {
   514  		return false, nil, errors.New(
   515  			"Can't transition between conflict and local conflict")
   516  	} else if (!h.IsConflict() && other.IsConflict()) ||
   517  		(!h.IsLocalConflict() && other.IsLocalConflict()) {
   518  		conflictAdded = true
   519  		// Ignore the added extension for resolution comparison purposes.
   520  		other.conflictInfo = nil
   521  	}
   522  
   523  	// Check the finalized extension.
   524  	if h.IsFinal() {
   525  		if conflictAdded {
   526  			// Can't add conflict info to a finalized handle.
   527  			return false, nil, HandleFinalizedError{}
   528  		}
   529  	} else if other.IsFinal() {
   530  		finalizedAdded = true
   531  		// Ignore the added extension for resolution comparison purposes.
   532  		other.finalizedInfo = nil
   533  	}
   534  
   535  	if h.TypeForKeying() == tlf.TeamKeying {
   536  		// Nothing to resolve for team-based TLFs, just use `other` by
   537  		// itself.
   538  		partialResolvedH = other.DeepCopy()
   539  	} else {
   540  		unresolvedAssertions := make(map[string]bool)
   541  		for _, uw := range other.unresolvedWriters {
   542  			unresolvedAssertions[uw.String()] = true
   543  		}
   544  		for _, ur := range other.unresolvedReaders {
   545  			unresolvedAssertions[ur.String()] = true
   546  		}
   547  
   548  		// TODO: Once we keep track of the original assertions in
   549  		// Handle, restrict the resolver to use other's assertions
   550  		// only, so that we don't hit the network at all.
   551  		partialResolvedH, err = h.ResolveAgain(
   552  			ctx, partialResolver{resolver, unresolvedAssertions}, idGetter,
   553  			osg)
   554  		if err != nil {
   555  			return false, nil, err
   556  		}
   557  
   558  		// If we're migrating, use the partially-resolved handle's
   559  		// list of writers, readers, and conflict info, rather than
   560  		// explicitly checking team membership.  This is assuming that
   561  		// we've already validated the migrating folder's name against
   562  		// the user list in the current MD head (done via
   563  		// `MDOps.GetIDForHandle()`).
   564  		if other.TypeForKeying() == tlf.TeamKeying {
   565  			if h.IsFinal() {
   566  				return false, nil,
   567  					errors.New("Can't migrate a finalized folder")
   568  			}
   569  
   570  			other.resolvedWriters = partialResolvedH.resolvedWriters
   571  			other.resolvedReaders = partialResolvedH.resolvedReaders
   572  			other.unresolvedWriters = partialResolvedH.unresolvedWriters
   573  			other.unresolvedReaders = partialResolvedH.unresolvedReaders
   574  			other.conflictInfo = partialResolvedH.conflictInfo
   575  		}
   576  	}
   577  
   578  	if conflictAdded || finalizedAdded {
   579  		resolvesTo, err = partialResolvedH.EqualsIgnoreName(codec, other)
   580  	} else {
   581  		resolvesTo, err = partialResolvedH.Equals(codec, other)
   582  	}
   583  	if err != nil {
   584  		return false, nil, err
   585  	}
   586  
   587  	return resolvesTo, partialResolvedH, nil
   588  }
   589  
   590  // MutuallyResolvesTo checks that the target handle, and the provided
   591  // `other` handle, resolve to each other.
   592  func (h Handle) MutuallyResolvesTo(
   593  	ctx context.Context, codec kbfscodec.Codec,
   594  	resolver idutil.Resolver, idGetter IDGetter,
   595  	osg idutil.OfflineStatusGetter, other Handle, rev kbfsmd.Revision,
   596  	tlfID tlf.ID, log logger.Logger) error {
   597  	handleResolvesToOther, partialResolvedHandle, err :=
   598  		h.ResolvesTo(ctx, codec, resolver, idGetter, osg, other)
   599  	if err != nil {
   600  		return err
   601  	}
   602  
   603  	// TODO: If h has conflict info, other should, too.
   604  	otherResolvesToHandle, partialResolvedOther, err :=
   605  		other.ResolvesTo(ctx, codec, resolver, idGetter, osg, h)
   606  	if err != nil {
   607  		return err
   608  	}
   609  
   610  	handlePath := h.GetCanonicalPath()
   611  	otherPath := other.GetCanonicalPath()
   612  	if !handleResolvesToOther && !otherResolvesToHandle {
   613  		return HandleMismatchError{
   614  			rev, h.GetCanonicalPath(), tlfID,
   615  			fmt.Errorf(
   616  				"MD contained unexpected handle path %s (%s -> %s) (%s -> %s)",
   617  				otherPath,
   618  				h.GetCanonicalPath(),
   619  				partialResolvedHandle.GetCanonicalPath(),
   620  				other.GetCanonicalPath(),
   621  				partialResolvedOther.GetCanonicalPath()),
   622  		}
   623  	}
   624  
   625  	if handlePath != otherPath {
   626  		log.CDebugf(ctx, "handle for %s resolved to %s",
   627  			handlePath, otherPath)
   628  	}
   629  	return nil
   630  }
   631  
   632  type resolvableAssertionWithChangeReport struct {
   633  	resolvableAssertion
   634  	changed chan struct{}
   635  }
   636  
   637  func (ra resolvableAssertionWithChangeReport) resolve(ctx context.Context) (
   638  	nameIDPair, keybase1.SocialAssertion, tlf.ID, error) {
   639  	nuid, sa, tlfID, err := ra.resolvableAssertion.resolve(ctx)
   640  	if err != nil {
   641  		return nameIDPair{}, keybase1.SocialAssertion{}, tlf.NullID, err
   642  	}
   643  	sendIfPossible := func() {
   644  		select {
   645  		case ra.changed <- struct{}{}:
   646  		default:
   647  		}
   648  	}
   649  	if nuid.name.String() != "" {
   650  		if nuid.name.String() != strings.TrimPrefix(ra.assertion, "team:") {
   651  			sendIfPossible()
   652  		}
   653  	} else if sa != (keybase1.SocialAssertion{}) {
   654  		if sa.String() != ra.assertion {
   655  			sendIfPossible()
   656  		}
   657  	}
   658  	return nuid, sa, tlfID, nil
   659  }
   660  
   661  type resolvableAssertion struct {
   662  	resolver   idutil.Resolver
   663  	identifier idutil.Identifier // only needed until KBFS-2022 is fixed
   664  	idGetter   IDGetter
   665  	assertion  string
   666  	mustBeUser keybase1.UID
   667  	offline    keybase1.OfflineAvailability
   668  }
   669  
   670  func (ra resolvableAssertion) resolve(ctx context.Context) (
   671  	nameIDPair, keybase1.SocialAssertion, tlf.ID, error) {
   672  	if ra.assertion == idutil.PublicUIDName {
   673  		return nameIDPair{}, keybase1.SocialAssertion{}, tlf.NullID,
   674  			fmt.Errorf("Invalid name %s", ra.assertion)
   675  	}
   676  	name, id, err := ra.resolver.Resolve(ctx, ra.assertion, ra.offline)
   677  	if err == nil && ra.mustBeUser != keybase1.UID("") &&
   678  		ra.mustBeUser.AsUserOrTeam() != id {
   679  		// Force an unresolved assertion sinced the forced user doesn't match
   680  		err = idutil.NoSuchUserError{Input: ra.assertion}
   681  	}
   682  	// The service's Resolve2 doesn't handle compound assertions
   683  	// correctly because it would rely too much on server trust.  It
   684  	// just resolves the first component, so something like
   685  	// "strib+therealdonaldtrump@twitter" would actually resolve
   686  	// correctly.  So we need to do an explicit identify in that case,
   687  	// at least until KBFS-2022 is finished.  Note that this
   688  	// explicitly avoids checking any of the extended identify context
   689  	// info, since we need to do this regardless of what the
   690  	// originator wants.
   691  	if strings.Contains(ra.assertion, "+") {
   692  		if ra.identifier == nil {
   693  			return nameIDPair{}, keybase1.SocialAssertion{}, tlf.NullID,
   694  				errors.New(
   695  					"Can't resolve an AND assertion without an identifier")
   696  		}
   697  		reason := fmt.Sprintf("You accessed a folder with %s.", ra.assertion)
   698  		var resName kbname.NormalizedUsername
   699  		resName, err = IdentifySingleAssertion(
   700  			ctx, ra.assertion, reason, ra.identifier, ra.offline)
   701  		if err == nil && resName != name {
   702  			return nameIDPair{}, keybase1.SocialAssertion{}, tlf.NullID,
   703  				fmt.Errorf(
   704  					"Resolved name %s doesn't match identified name %s for "+
   705  						"assertion %s", name, resName, ra.assertion)
   706  		}
   707  	}
   708  	switch err := err.(type) {
   709  	default:
   710  		return nameIDPair{}, keybase1.SocialAssertion{}, tlf.NullID, err
   711  	case nil:
   712  		var tlfID tlf.ID
   713  		// Only get a team TLF ID if the caller expects to use it.
   714  		if ra.idGetter != nil && id.IsTeamOrSubteam() {
   715  			tlfID, err = ra.resolver.ResolveTeamTLFID(
   716  				ctx, id.AsTeamOrBust(), ra.offline)
   717  			if err != nil {
   718  				return nameIDPair{}, keybase1.SocialAssertion{}, tlf.NullID, err
   719  			}
   720  		}
   721  
   722  		return nameIDPair{
   723  			name: name,
   724  			id:   id,
   725  		}, keybase1.SocialAssertion{}, tlfID, nil
   726  	case idutil.NoSuchUserError:
   727  		socialAssertion, serr := ra.resolver.NormalizeSocialAssertion(ctx, ra.assertion)
   728  		if serr != nil {
   729  			// NOTE: we return the original `err` here since callers depend on
   730  			// the `NoSuchUserError` type.
   731  			return nameIDPair{}, keybase1.SocialAssertion{}, tlf.NullID, err
   732  		}
   733  		return nameIDPair{}, socialAssertion, tlf.NullID, nil
   734  	}
   735  }
   736  
   737  type resolvableImplicitTeam struct {
   738  	resolver idutil.Resolver
   739  	name     string
   740  	tlfType  tlf.Type
   741  	offline  keybase1.OfflineAvailability
   742  }
   743  
   744  func (rit resolvableImplicitTeam) resolve(ctx context.Context) (
   745  	nameIDPair, keybase1.SocialAssertion, tlf.ID, error) {
   746  	// Need to separate the extension from the assertions.
   747  	assertions, extensionSuffix, err := tlf.SplitExtension(rit.name)
   748  	if err != nil {
   749  		return nameIDPair{}, keybase1.SocialAssertion{}, tlf.NullID, err
   750  	}
   751  
   752  	iteamInfo, err := rit.resolver.ResolveImplicitTeam(
   753  		ctx, assertions, extensionSuffix, rit.tlfType, rit.offline)
   754  	if err != nil {
   755  		return nameIDPair{}, keybase1.SocialAssertion{}, tlf.NullID, err
   756  	}
   757  
   758  	return nameIDPair{
   759  		name: iteamInfo.Name,
   760  		id:   iteamInfo.TID.AsUserOrTeam(),
   761  	}, keybase1.SocialAssertion{}, iteamInfo.TlfID, nil
   762  }
   763  
   764  func doResolveImplicit(_ context.Context, t tlf.Type) bool {
   765  	return t != tlf.SingleTeam
   766  }
   767  
   768  // parseHandleLoose parses a TLF handle but leaves some of the canonicality
   769  // checking to public routines like ParseHandle and ParseHandlePreferred.
   770  func parseHandleLoose(
   771  	ctx context.Context, kbpki idutil.KBPKI, idGetter IDGetter,
   772  	osg idutil.OfflineStatusGetter, name string, t tlf.Type) (
   773  	*Handle, error) {
   774  	writerNames, readerNames, extensionSuffix, err :=
   775  		idutil.SplitAndNormalizeTLFName(name, t)
   776  	if err != nil {
   777  		return nil, err
   778  	}
   779  
   780  	var extensions []tlf.HandleExtension
   781  	if len(extensionSuffix) != 0 {
   782  		extensions, err = tlf.ParseHandleExtensionSuffix(extensionSuffix)
   783  		if err != nil {
   784  			return nil, err
   785  		}
   786  	}
   787  
   788  	offline := keybase1.OfflineAvailability_NONE
   789  	if osg != nil {
   790  		normalizedName, _, err := idutil.NormalizeNamesInTLF(
   791  			writerNames, readerNames, t, extensionSuffix)
   792  		if err != nil {
   793  			return nil, err
   794  		}
   795  		offline = osg.OfflineAvailabilityForPath(
   796  			BuildCanonicalPathForTlfName(t, tlf.CanonicalName(normalizedName)))
   797  
   798  		// Make sure we always pass the normalized name to the
   799  		// service, so it can use its cache effectively.
   800  		name = normalizedName
   801  	}
   802  
   803  	// First try resolving this full name as an implicit team.  If
   804  	// that doesn't work, fall through to individual name resolution.
   805  	var iteamHandle *Handle
   806  	if doResolveImplicit(ctx, t) {
   807  		// The service doesn't know about local conflict branches, so
   808  		// strip those out before resolving if needed.
   809  		assertions, extensionSuffix, err := tlf.SplitExtension(name)
   810  		if err != nil {
   811  			return nil, err
   812  		}
   813  
   814  		iteamName := name
   815  		var iteamExtensions tlf.HandleExtensionList
   816  		if tlf.ContainsLocalConflictExtensionPrefix(extensionSuffix) {
   817  			iteamName = assertions
   818  			iteamExtensions = tlf.HandleExtensionList(extensions)
   819  		}
   820  
   821  		rit := resolvableImplicitTeam{kbpki, iteamName, t, offline}
   822  		iteamHandle, err = makeHandleHelper(
   823  			ctx, t, []resolvableUser{rit}, nil, iteamExtensions, idGetter)
   824  		if err == nil && iteamHandle.tlfID != tlf.NullID {
   825  			// The iteam already has a TLF ID, let's use it.
   826  
   827  			extensionList := tlf.HandleExtensionList(extensions)
   828  			sort.Sort(extensionList)
   829  			_, finalizedInfo := extensionList.Splat()
   830  
   831  			if finalizedInfo == nil && idGetter != nil {
   832  				// Implicit team chat migration might have set the ID to an
   833  				// old folder that has since been reset.  Check with the
   834  				// server to see if that has happened, and if so, don't use
   835  				// that ID.  When we migrate existing TLFs to iteams, we can
   836  				// probably override the sigchain link with a new one
   837  				// containing the correct TLF ID.
   838  				valid, err := idGetter.ValidateLatestHandleNotFinal(
   839  					ctx, iteamHandle)
   840  				if err != nil {
   841  					return nil, err
   842  				}
   843  				if !valid {
   844  					iteamHandle.tlfID = tlf.NullID
   845  				}
   846  			}
   847  
   848  			if iteamHandle.tlfID != tlf.NullID {
   849  				return iteamHandle, nil
   850  			}
   851  
   852  		} else {
   853  			switch err.(type) {
   854  			case libkb.TeamContactSettingsBlockError:
   855  				// The implicit team couldn't be created due to one of the
   856  				// users' privacy settings, so fail the handle lookup completely.
   857  				return nil, err
   858  			default:
   859  				// This is not an implicit team, so continue on to check for a
   860  				// normal team.  TODO: return non-nil errors immediately if they
   861  				// don't simply indicate the implicit team doesn't exist yet
   862  				// (i.e., when we start creating them by default).
   863  			}
   864  		}
   865  	}
   866  
   867  	// Before parsing the tlf handle (which results in identify
   868  	// calls that cause tracker popups), first see if there's any
   869  	// quick normalization of usernames we can do.  For example,
   870  	// this avoids an identify in the case of "HEAD" which might
   871  	// just be a shell trying to look for a git repo rather than a
   872  	// real user lookup for "head" (KBFS-531).  Note that the name
   873  	// might still contain assertions, which will result in
   874  	// another alias in a subsequent lookup.
   875  	// This also contains an offline check for canonicality and
   876  	// whether a public folder has readers.
   877  	changesCh := make(chan struct{}, 1)
   878  	writers := make([]resolvableUser, len(writerNames))
   879  	for i, w := range writerNames {
   880  		if t == tlf.SingleTeam {
   881  			w = "team:" + w
   882  		}
   883  		writers[i] = resolvableAssertionWithChangeReport{resolvableAssertion{
   884  			kbpki, kbpki, idGetter, w, keybase1.UID(""), offline}, changesCh}
   885  	}
   886  	readers := make([]resolvableUser, len(readerNames))
   887  	for i, r := range readerNames {
   888  		readers[i] = resolvableAssertionWithChangeReport{resolvableAssertion{
   889  			kbpki, kbpki, idGetter, r, keybase1.UID(""), offline}, changesCh}
   890  	}
   891  
   892  	h, err := makeHandleHelper(
   893  		ctx, t, writers, readers, extensions, idGetter)
   894  	if err != nil {
   895  		return nil, err
   896  	}
   897  
   898  	if h.tlfID == tlf.NullID && iteamHandle != nil {
   899  		// If the server hasn't provided a TLF ID for the regular
   900  		// handle, switch over to using the i-team handle (which
   901  		// currently must have a null TLF ID).  Before the caller
   902  		// creates and uses the TLF, they must generate a TLF ID and
   903  		// store it in the i-team's sigchain.
   904  		return iteamHandle, nil
   905  	}
   906  
   907  	if t == tlf.Private {
   908  		session, err := kbpki.GetCurrentSession(ctx)
   909  		if err != nil {
   910  			return nil, err
   911  		}
   912  
   913  		if !h.IsReader(session.UID) {
   914  			return nil, NewReadAccessError(h, session.Name, h.GetCanonicalPath())
   915  		}
   916  	}
   917  
   918  	canonicalName := string(h.GetCanonicalName())
   919  	if extensionSuffix != "" {
   920  		extensionList := tlf.HandleExtensionList(extensions)
   921  		sort.Sort(extensionList)
   922  		var canonExtensionString string
   923  		// If this resolve is being done in a "quick" way that doesn't
   924  		// check for implicit teams, we might not know if this handle
   925  		// is backed by a team or not.  But (mostly for tests) we need
   926  		// to make sure the suffix reflects the right format,
   927  		// otherwise we'll get too many levels of non-canonical
   928  		// redirects.  So if the user explicitly specified a "#1" in
   929  		// their suffix, let's keep it there and assume the user knows
   930  		// what they're doing.
   931  		if h.IsBackedByTeam() || strings.Contains(extensionSuffix, "#1") {
   932  			canonExtensionString = extensionList.SuffixForTeamHandle()
   933  		} else {
   934  			canonExtensionString = extensionList.Suffix()
   935  		}
   936  		_, _, currExtensionSuffix, _ :=
   937  			idutil.SplitAndNormalizeTLFName(canonicalName, t)
   938  		canonicalName = strings.Replace(
   939  			canonicalName, tlf.HandleExtensionSep+currExtensionSuffix,
   940  			canonExtensionString, 1)
   941  		h.name = tlf.CanonicalName(canonicalName)
   942  		if canonExtensionString != tlf.HandleExtensionSep+extensionSuffix {
   943  			return nil, errors.WithStack(
   944  				idutil.TlfNameNotCanonical{
   945  					Name:      name,
   946  					NameToTry: canonicalName,
   947  				})
   948  		}
   949  	}
   950  
   951  	select {
   952  	case <-changesCh:
   953  	default:
   954  		// No changes were performed because of resolver.
   955  		return h, nil
   956  	}
   957  
   958  	// Otherwise, identify before returning the canonical name.
   959  	err = IdentifyHandle(ctx, kbpki, kbpki, osg, h)
   960  	if err != nil {
   961  		return nil, err
   962  	}
   963  
   964  	// In this case return both the handle and the error,
   965  	// ParseHandlePreferred uses this to make the redirection
   966  	// better.
   967  	return h, errors.WithStack(idutil.TlfNameNotCanonical{
   968  		Name:      name,
   969  		NameToTry: canonicalName,
   970  	})
   971  }
   972  
   973  // ParseHandle parses a Handle from an encoded string. See
   974  // Handle.GetCanonicalName() for the opposite direction.
   975  //
   976  // Some errors that may be returned and can be specially handled:
   977  //
   978  // idutil.TlfNameNotCanonical: Returned when the given name is not
   979  // canonical -- another name to try (which itself may not be
   980  // canonical) is in the error. Usually, you want to treat this as a
   981  // symlink to the name to try.
   982  //
   983  // idutil.NoSuchNameError: Returned when public is set and the given
   984  // folder has no public folder.
   985  //
   986  // TODO In future perhaps all code should switch over to preferred handles,
   987  // and rename TlfNameNotCanonical to TlfNameNotPreferred.
   988  func ParseHandle(
   989  	ctx context.Context, kbpki idutil.KBPKI, idGetter IDGetter,
   990  	osg idutil.OfflineStatusGetter, name string, t tlf.Type) (
   991  	*Handle, error) {
   992  	h, err := parseHandleLoose(ctx, kbpki, idGetter, osg, name, t)
   993  	if err != nil {
   994  		return nil, err
   995  	}
   996  	if name != string(h.GetCanonicalName()) {
   997  		return nil, errors.WithStack(
   998  			idutil.TlfNameNotCanonical{
   999  				Name:      name,
  1000  				NameToTry: string(h.GetCanonicalName()),
  1001  			})
  1002  	}
  1003  	return h, nil
  1004  }
  1005  
  1006  // ParseHandlePreferred returns idutil.TlfNameNotCanonical if not
  1007  // in the preferred format.
  1008  // Preferred format means that the users own username (from kbpki)
  1009  // as a writer is put before other usernames in the tlf name.
  1010  // i.e.
  1011  // Canon            Preferred
  1012  // myname,other     myname,other
  1013  // another,myname   myname,another
  1014  // This function also can return idutil.NoSuchNameError or
  1015  // idutil.TlfNameNotCanonical.  idutil.TlfNameNotCanonical is
  1016  // returned from this function when the name is not the *preferred*
  1017  // name.
  1018  func ParseHandlePreferred(
  1019  	ctx context.Context, kbpki idutil.KBPKI, idGetter IDGetter,
  1020  	osg idutil.OfflineStatusGetter, name string, t tlf.Type) (
  1021  	*Handle, error) {
  1022  	h, err := parseHandleLoose(ctx, kbpki, idGetter, osg, name, t)
  1023  	// Return an early if there is an error, except in the case
  1024  	// where both h is not nil and it is a TlfNameNotCanonicalError.
  1025  	// In that case continue and return TlfNameNotCanonical later
  1026  	// with the right symlink target.
  1027  	if err != nil && (h == nil || !isTlfNameNotCanonical(err)) {
  1028  		return nil, err
  1029  	}
  1030  	session, err := idutil.GetCurrentSessionIfPossible(
  1031  		ctx, kbpki, h.Type() == tlf.Public)
  1032  	if err != nil {
  1033  		return nil, err
  1034  	}
  1035  	pref := h.GetPreferredFormat(session.Name)
  1036  	if string(pref) != name {
  1037  		return nil, errors.WithStack(
  1038  			idutil.TlfNameNotCanonical{
  1039  				Name:      name,
  1040  				NameToTry: string(pref),
  1041  			})
  1042  	}
  1043  	return h, nil
  1044  }
  1045  
  1046  func isTlfNameNotCanonical(err error) bool {
  1047  	_, ok := errors.Cause(err).(idutil.TlfNameNotCanonical)
  1048  	return ok
  1049  }
  1050  
  1051  type noImplicitTeamKBPKI struct {
  1052  	idutil.KBPKI
  1053  }
  1054  
  1055  // ResolveImplicitTeam implements the KBPKI interface for noImplicitTeamKBPKI.
  1056  func (nitk noImplicitTeamKBPKI) ResolveImplicitTeam(
  1057  	_ context.Context, _, _ string, _ tlf.Type,
  1058  	_ keybase1.OfflineAvailability) (idutil.ImplicitTeamInfo, error) {
  1059  	return idutil.ImplicitTeamInfo{},
  1060  		errors.New("Skipping implicit team lookup for quick handle parsing")
  1061  }
  1062  
  1063  // ParseHandlePreferredQuick parses a handle from a name, without
  1064  // doing this time consuming checks needed for implicit-team checking
  1065  // or TLF-ID-fetching.
  1066  func ParseHandlePreferredQuick(
  1067  	ctx context.Context, kbpki idutil.KBPKI, osg idutil.OfflineStatusGetter,
  1068  	name string, ty tlf.Type) (handle *Handle, err error) {
  1069  	// Override the KBPKI with one that doesn't try to resolve
  1070  	// implicit teams.
  1071  	kbpki = noImplicitTeamKBPKI{kbpki}
  1072  	return ParseHandlePreferred(ctx, kbpki, nil, osg, name, ty)
  1073  }