github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/kbfs/tlfhandle/handle.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 type for TlfHandles and offline functionality.
     8  
     9  import (
    10  	"fmt"
    11  	"reflect"
    12  	"sort"
    13  	"strings"
    14  
    15  	"github.com/keybase/client/go/kbfs/favorites"
    16  	"github.com/keybase/client/go/kbfs/idutil"
    17  	"github.com/keybase/client/go/kbfs/kbfscodec"
    18  	"github.com/keybase/client/go/kbfs/tlf"
    19  	kbname "github.com/keybase/client/go/kbun"
    20  	"github.com/keybase/client/go/protocol/keybase1"
    21  	"github.com/pkg/errors"
    22  	"golang.org/x/net/context"
    23  )
    24  
    25  // Handle contains all the info in a tlf.Handle as well as
    26  // additional info. This doesn't embed tlf.Handle to avoid having to
    27  // keep track of data in multiple places.
    28  type Handle struct {
    29  	// If this is not Private, resolvedReaders and unresolvedReaders
    30  	// should both be nil.
    31  	tlfType         tlf.Type
    32  	resolvedWriters map[keybase1.UserOrTeamID]kbname.NormalizedUsername
    33  	resolvedReaders map[keybase1.UserOrTeamID]kbname.NormalizedUsername
    34  	// Both unresolvedWriters and unresolvedReaders are stored in
    35  	// sorted order.
    36  	unresolvedWriters []keybase1.SocialAssertion
    37  	unresolvedReaders []keybase1.SocialAssertion
    38  	conflictInfo      *tlf.HandleExtension
    39  	finalizedInfo     *tlf.HandleExtension
    40  	// name can be computed from the other fields, but is cached
    41  	// for speed.
    42  	name tlf.CanonicalName
    43  
    44  	// If we know the TLF ID at the time this handle is constructed
    45  	// (e.g., because this handle is backed by an implicit team), we
    46  	// store the TLF ID here so that we can look the TLF up from the
    47  	// mdserver using the ID, instead of the handle.
    48  	tlfID tlf.ID
    49  }
    50  
    51  // NewHandle returns a simple new Handle based on the given fields.
    52  // This is probably most useful for testing.
    53  func NewHandle(
    54  	ty tlf.Type,
    55  	resolvedWriters map[keybase1.UserOrTeamID]kbname.NormalizedUsername,
    56  	unresolvedWriters, unresolvedReaders []keybase1.SocialAssertion,
    57  	name tlf.CanonicalName, tlfID tlf.ID) *Handle {
    58  	return &Handle{
    59  		tlfType:           ty,
    60  		resolvedWriters:   resolvedWriters,
    61  		unresolvedWriters: unresolvedWriters,
    62  		unresolvedReaders: unresolvedReaders,
    63  		name:              name,
    64  		tlfID:             tlfID,
    65  	}
    66  }
    67  
    68  // Type returns the type of the TLF this TlfHandle represents.
    69  func (h Handle) Type() tlf.Type {
    70  	return h.tlfType
    71  }
    72  
    73  // IsBackedByTeam returns true if h represents a TLF backed by a team. It could
    74  // be either a SingleTeam TLF or a private/public TLF backed by an implicit
    75  // team.
    76  func (h Handle) IsBackedByTeam() bool {
    77  	if len(h.resolvedWriters) != 1 ||
    78  		len(h.resolvedReaders) != 0 ||
    79  		len(h.unresolvedReaders) != 0 ||
    80  		len(h.unresolvedWriters) != 0 {
    81  		return false
    82  	}
    83  	return h.FirstResolvedWriter().IsTeamOrSubteam()
    84  }
    85  
    86  // TypeForKeying returns the keying type for the handle h.
    87  func (h Handle) TypeForKeying() tlf.KeyingType {
    88  	if h.IsBackedByTeam() {
    89  		return tlf.TeamKeying
    90  	}
    91  	return h.Type().ToKeyingType()
    92  }
    93  
    94  // TlfID returns the TLF ID corresponding to this handle, if it's
    95  // known.  If it's wasn't known at the time the handle was
    96  // constructed, tlf.NullID is returned.
    97  func (h Handle) TlfID() tlf.ID {
    98  	return h.tlfID
    99  }
   100  
   101  // IsWriter returns whether or not the given user is a writer for the
   102  // top-level folder represented by this TlfHandle.
   103  func (h Handle) IsWriter(user keybase1.UID) bool {
   104  	// TODO(KBFS-2185) relax this?
   105  	if h.TypeForKeying() == tlf.TeamKeying {
   106  		panic("Can't check whether a user is a writer on a team TLF")
   107  	}
   108  	_, ok := h.resolvedWriters[user.AsUserOrTeam()]
   109  	return ok
   110  }
   111  
   112  // IsReader returns whether or not the given user is a reader for the
   113  // top-level folder represented by this TlfHandle.
   114  func (h Handle) IsReader(user keybase1.UID) bool {
   115  	// TODO(KBFS-2185) relax this?
   116  	if h.TypeForKeying() == tlf.TeamKeying {
   117  		panic("Can't check whether a user is a reader on a team TLF")
   118  	}
   119  	if h.TypeForKeying() == tlf.PublicKeying || h.IsWriter(user) {
   120  		return true
   121  	}
   122  	_, ok := h.resolvedReaders[user.AsUserOrTeam()]
   123  	return ok
   124  }
   125  
   126  // ResolvedUsersMap returns a map of resolved users from uid to usernames.
   127  func (h Handle) ResolvedUsersMap() map[keybase1.UserOrTeamID]kbname.NormalizedUsername {
   128  	m := make(map[keybase1.UserOrTeamID]kbname.NormalizedUsername,
   129  		len(h.resolvedReaders)+len(h.resolvedWriters))
   130  	for k, v := range h.resolvedReaders {
   131  		m[k] = v
   132  	}
   133  	for k, v := range h.resolvedWriters {
   134  		m[k] = v
   135  	}
   136  	return m
   137  }
   138  
   139  func (h Handle) unsortedResolvedWriters() []keybase1.UserOrTeamID {
   140  	if len(h.resolvedWriters) == 0 {
   141  		return nil
   142  	}
   143  	writers := make([]keybase1.UserOrTeamID, 0, len(h.resolvedWriters))
   144  	for r := range h.resolvedWriters {
   145  		writers = append(writers, r)
   146  	}
   147  	return writers
   148  }
   149  
   150  // ResolvedWriters returns the handle's resolved writer IDs in sorted
   151  // order.
   152  func (h Handle) ResolvedWriters() []keybase1.UserOrTeamID {
   153  	writers := h.unsortedResolvedWriters()
   154  	sort.Sort(tlf.UIDList(writers))
   155  	return writers
   156  }
   157  
   158  // FirstResolvedWriter returns the handle's first resolved writer ID
   159  // (when sorted).  For SingleTeam handles, this returns the team to
   160  // which the TLF belongs.
   161  func (h Handle) FirstResolvedWriter() keybase1.UserOrTeamID {
   162  	return h.ResolvedWriters()[0]
   163  }
   164  
   165  func (h Handle) unsortedResolvedReaders() []keybase1.UserOrTeamID {
   166  	if len(h.resolvedReaders) == 0 {
   167  		return nil
   168  	}
   169  	readers := make([]keybase1.UserOrTeamID, 0, len(h.resolvedReaders))
   170  	for r := range h.resolvedReaders {
   171  		readers = append(readers, r)
   172  	}
   173  	return readers
   174  }
   175  
   176  // ResolvedReaders returns the handle's resolved reader IDs in sorted
   177  // order. If the handle is public, nil will be returned.
   178  func (h Handle) ResolvedReaders() []keybase1.UserOrTeamID {
   179  	readers := h.unsortedResolvedReaders()
   180  	sort.Sort(tlf.UIDList(readers))
   181  	return readers
   182  }
   183  
   184  // UnresolvedWriters returns the handle's unresolved writers in sorted
   185  // order.
   186  func (h Handle) UnresolvedWriters() []keybase1.SocialAssertion {
   187  	if len(h.unresolvedWriters) == 0 {
   188  		return nil
   189  	}
   190  	unresolvedWriters := make([]keybase1.SocialAssertion, len(h.unresolvedWriters))
   191  	copy(unresolvedWriters, h.unresolvedWriters)
   192  	return unresolvedWriters
   193  }
   194  
   195  // UnresolvedReaders returns the handle's unresolved readers in sorted
   196  // order. If the handle is public, nil will be returned.
   197  func (h Handle) UnresolvedReaders() []keybase1.SocialAssertion {
   198  	if len(h.unresolvedReaders) == 0 {
   199  		return nil
   200  	}
   201  	unresolvedReaders := make([]keybase1.SocialAssertion, len(h.unresolvedReaders))
   202  	copy(unresolvedReaders, h.unresolvedReaders)
   203  	return unresolvedReaders
   204  }
   205  
   206  // ConflictInfo returns the handle's conflict info, if any.
   207  func (h Handle) ConflictInfo() *tlf.HandleExtension {
   208  	if h.conflictInfo == nil {
   209  		return nil
   210  	}
   211  	conflictInfoCopy := *h.conflictInfo
   212  	return &conflictInfoCopy
   213  }
   214  
   215  func (h Handle) recomputeNameWithExtensions() tlf.CanonicalName {
   216  	components := strings.Split(string(h.name), tlf.HandleExtensionSep)
   217  	newName := components[0]
   218  	extensionList := tlf.HandleExtensionList(h.Extensions())
   219  	sort.Sort(extensionList)
   220  	if h.IsBackedByTeam() {
   221  		newName += extensionList.SuffixForTeamHandle()
   222  	} else {
   223  		newName += extensionList.Suffix()
   224  	}
   225  	return tlf.CanonicalName(newName)
   226  }
   227  
   228  // WithUpdatedConflictInfo returns a new handle with the conflict info set to
   229  // the given one, if the existing one is nil. (In this case, the given one may
   230  // also be nil.) Otherwise, the given conflict info must match the existing
   231  // one.
   232  func (h Handle) WithUpdatedConflictInfo(
   233  	codec kbfscodec.Codec, info *tlf.HandleExtension) (*Handle, error) {
   234  	newHandle := h.DeepCopy()
   235  	if newHandle.conflictInfo == nil {
   236  		if info == nil {
   237  			// Nothing to do.
   238  			return newHandle, nil
   239  		}
   240  		conflictInfoCopy := *info
   241  		newHandle.conflictInfo = &conflictInfoCopy
   242  		newHandle.name = newHandle.recomputeNameWithExtensions()
   243  		return newHandle, nil
   244  	}
   245  	// Make sure conflict info is the same; the conflict info for
   246  	// a TLF, once set, is immutable and should never change.
   247  	equal, err := kbfscodec.Equal(codec, newHandle.conflictInfo, info)
   248  	if err != nil {
   249  		return newHandle, err
   250  	}
   251  	if !equal {
   252  		return newHandle, tlf.HandleExtensionMismatchError{
   253  			Expected: *newHandle.ConflictInfo(),
   254  			Actual:   info,
   255  		}
   256  	}
   257  	return newHandle, nil
   258  }
   259  
   260  // FinalizedInfo returns the handle's finalized info, if any.
   261  func (h Handle) FinalizedInfo() *tlf.HandleExtension {
   262  	if h.finalizedInfo == nil {
   263  		return nil
   264  	}
   265  	finalizedInfoCopy := *h.finalizedInfo
   266  	return &finalizedInfoCopy
   267  }
   268  
   269  // SetFinalizedInfo sets the handle's finalized info to the given one,
   270  // which may be nil.
   271  // TODO: remove this to make TlfHandle fully immutable
   272  func (h *Handle) SetFinalizedInfo(info *tlf.HandleExtension) {
   273  	if info == nil {
   274  		h.finalizedInfo = nil
   275  	} else {
   276  		finalizedInfoCopy := *info
   277  		h.finalizedInfo = &finalizedInfoCopy
   278  	}
   279  	h.name = h.recomputeNameWithExtensions()
   280  }
   281  
   282  // SetName sets the TLF name associated with this Handle.  Useful for
   283  // testing.
   284  func (h *Handle) SetName(name tlf.CanonicalName) {
   285  	h.name = name
   286  }
   287  
   288  // SetResolvedWriter resolves the given `id` to the given `name`.
   289  // Useful for testing.
   290  func (h *Handle) SetResolvedWriter(
   291  	id keybase1.UserOrTeamID, name kbname.NormalizedUsername) {
   292  	h.resolvedWriters[id] = name
   293  }
   294  
   295  // ClearResolvedReaders forgets all the resolved reader.  Useful for
   296  // testing.
   297  func (h *Handle) ClearResolvedReaders() {
   298  	h.resolvedReaders = nil
   299  }
   300  
   301  // SetTlfID sets the TLF ID associated with this handle.
   302  func (h *Handle) SetTlfID(id tlf.ID) {
   303  	h.tlfID = id
   304  }
   305  
   306  // Extensions returns a list of extensions for the given handle.
   307  func (h Handle) Extensions() (extensions []tlf.HandleExtension) {
   308  	if h.ConflictInfo() != nil {
   309  		extensions = append(extensions, *h.ConflictInfo())
   310  	}
   311  	if h.FinalizedInfo() != nil {
   312  		extensions = append(extensions, *h.FinalizedInfo())
   313  	}
   314  	return extensions
   315  }
   316  
   317  func init() {
   318  	if reflect.ValueOf(Handle{}).NumField() != 9 {
   319  		panic(errors.New(
   320  			"Unexpected number of fields in TlfHandle; " +
   321  				"please update TlfHandle.Equals() for your " +
   322  				"new or removed field"))
   323  	}
   324  }
   325  
   326  // EqualsIgnoreName returns whether h and other contain the same info ignoring the name.
   327  func (h Handle) EqualsIgnoreName(
   328  	codec kbfscodec.Codec, other Handle) (bool, error) {
   329  	if h.tlfType != other.tlfType {
   330  		return false, nil
   331  	}
   332  	if h.tlfID != other.tlfID {
   333  		return false, nil
   334  	}
   335  
   336  	if !reflect.DeepEqual(h.resolvedWriters, other.resolvedWriters) {
   337  		return false, nil
   338  	}
   339  
   340  	if !reflect.DeepEqual(h.resolvedReaders, other.resolvedReaders) {
   341  		return false, nil
   342  	}
   343  
   344  	if !reflect.DeepEqual(h.unresolvedWriters, other.unresolvedWriters) {
   345  		return false, nil
   346  	}
   347  
   348  	if !reflect.DeepEqual(h.unresolvedReaders, other.unresolvedReaders) {
   349  		return false, nil
   350  	}
   351  
   352  	eq, err := kbfscodec.Equal(codec, h.conflictInfo, other.conflictInfo)
   353  	if err != nil {
   354  		return false, err
   355  	}
   356  	if !eq {
   357  		return false, nil
   358  	}
   359  
   360  	eq, err = kbfscodec.Equal(codec, h.finalizedInfo, other.finalizedInfo)
   361  	if err != nil {
   362  		return false, err
   363  	}
   364  	return eq, nil
   365  }
   366  
   367  // Equals returns whether h and other contain the same info.
   368  func (h Handle) Equals(
   369  	codec kbfscodec.Codec, other Handle) (bool, error) {
   370  	eq, err := h.EqualsIgnoreName(codec, other)
   371  	if err != nil {
   372  		return false, err
   373  	}
   374  
   375  	if eq && h.name != other.name {
   376  		return false, nil
   377  	}
   378  
   379  	return eq, nil
   380  }
   381  
   382  // ToBareHandle returns a tlf.Handle corresponding to this handle.
   383  func (h Handle) ToBareHandle() (tlf.Handle, error) {
   384  	var readers []keybase1.UserOrTeamID
   385  	switch h.TypeForKeying() {
   386  	case tlf.PublicKeying:
   387  		readers = []keybase1.UserOrTeamID{
   388  			keybase1.UserOrTeamID(keybase1.PUBLIC_UID)}
   389  	case tlf.TeamKeying:
   390  		// Leave readers blank.
   391  	default:
   392  		readers = h.unsortedResolvedReaders()
   393  	}
   394  	return tlf.MakeHandle(
   395  		h.unsortedResolvedWriters(), readers,
   396  		h.unresolvedWriters, h.unresolvedReaders,
   397  		h.Extensions())
   398  }
   399  
   400  // ToBareHandleOrBust returns a tlf.Handle corresponding to this
   401  // handle, and panics if there's an error. Used by tests.
   402  func (h Handle) ToBareHandleOrBust() tlf.Handle {
   403  	bh, err := h.ToBareHandle()
   404  	if err != nil {
   405  		panic(err)
   406  	}
   407  	return bh
   408  }
   409  
   410  // DeepCopy makes a deep copy of this `Handle`.
   411  func (h Handle) DeepCopy() *Handle {
   412  	hCopy := Handle{
   413  		tlfType:           h.tlfType,
   414  		name:              h.name,
   415  		unresolvedWriters: h.UnresolvedWriters(),
   416  		unresolvedReaders: h.UnresolvedReaders(),
   417  		conflictInfo:      h.ConflictInfo(),
   418  		finalizedInfo:     h.FinalizedInfo(),
   419  		tlfID:             h.tlfID,
   420  	}
   421  
   422  	hCopy.resolvedWriters = make(map[keybase1.UserOrTeamID]kbname.NormalizedUsername, len(h.resolvedWriters))
   423  	for k, v := range h.resolvedWriters {
   424  		hCopy.resolvedWriters[k] = v
   425  	}
   426  
   427  	hCopy.resolvedReaders = make(map[keybase1.UserOrTeamID]kbname.NormalizedUsername, len(h.resolvedReaders))
   428  	for k, v := range h.resolvedReaders {
   429  		hCopy.resolvedReaders[k] = v
   430  	}
   431  
   432  	return &hCopy
   433  }
   434  
   435  // GetCanonicalName returns the canonical name of this TLF.
   436  func (h *Handle) GetCanonicalName() tlf.CanonicalName {
   437  	if h.name == "" {
   438  		panic(fmt.Sprintf("TlfHandle %v with no name", h))
   439  	}
   440  
   441  	return h.name
   442  }
   443  
   444  // GetCanonicalPath returns the full canonical path of this TLF.
   445  func (h *Handle) GetCanonicalPath() string {
   446  	return BuildCanonicalPathForTlfName(h.Type(), h.GetCanonicalName())
   447  }
   448  
   449  // GetProtocolPath returns the `keybase1.Path` representing this
   450  // handle.
   451  func (h *Handle) GetProtocolPath() keybase1.Path {
   452  	return BuildProtocolPathForTlfName(h.Type(), h.GetCanonicalName())
   453  }
   454  
   455  // ToFavorite converts a TlfHandle into a Favorite, suitable for
   456  // Favorites calls.
   457  func (h *Handle) ToFavorite() favorites.Folder {
   458  	return favorites.Folder{
   459  		Name: string(h.GetCanonicalName()),
   460  		Type: h.Type(),
   461  	}
   462  }
   463  
   464  // FavoriteData converts a TlfHandle into FavoriteData, suitable for
   465  // Favorites calls.
   466  func (h *Handle) FavoriteData() favorites.Data {
   467  	fd := favorites.Data{
   468  		Name:         string(h.GetCanonicalName()),
   469  		FolderType:   h.Type().FolderType(),
   470  		Private:      h.Type() != tlf.Public,
   471  		ResetMembers: []keybase1.User{},
   472  	}
   473  	if h.IsBackedByTeam() {
   474  		teamID := h.FirstResolvedWriter().AsTeamOrBust()
   475  		fd.TeamID = &teamID
   476  	}
   477  	return fd
   478  }
   479  
   480  // ToFavToAdd converts a TlfHandle into a Favorite to be added, and
   481  // sets internal state about whether the corresponding folder was just
   482  // created or not.
   483  func (h *Handle) ToFavToAdd(created bool) favorites.ToAdd {
   484  	return favorites.ToAdd{
   485  		Folder:  h.ToFavorite(),
   486  		Data:    h.FavoriteData(),
   487  		Created: created,
   488  	}
   489  }
   490  
   491  func getSortedUnresolved(unresolved map[keybase1.SocialAssertion]bool) []keybase1.SocialAssertion {
   492  	var assertions []keybase1.SocialAssertion
   493  	for sa := range unresolved {
   494  		assertions = append(assertions, sa)
   495  	}
   496  	sort.Sort(tlf.SocialAssertionList(assertions))
   497  	return assertions
   498  }
   499  
   500  // CheckHandleOffline does light checks whether a TLF handle looks ok,
   501  // it avoids all network calls.
   502  func CheckHandleOffline(
   503  	ctx context.Context, name string, t tlf.Type) error {
   504  	_, _, _, err := idutil.SplitAndNormalizeTLFName(name, t)
   505  	return err
   506  }
   507  
   508  // IsFinal returns whether or not this TlfHandle represents a finalized
   509  // top-level folder.
   510  func (h Handle) IsFinal() bool {
   511  	return h.finalizedInfo != nil
   512  }
   513  
   514  // IsConflict returns whether or not this TlfHandle represents a conflicted
   515  // top-level folder.
   516  func (h Handle) IsConflict() bool {
   517  	return h.conflictInfo != nil &&
   518  		h.conflictInfo.Type == tlf.HandleExtensionConflict
   519  }
   520  
   521  // IsLocalConflict returns whether or not this TlfHandle represents a
   522  // locally conflict branch for a top-level folder.
   523  func (h Handle) IsLocalConflict() bool {
   524  	return h.conflictInfo != nil &&
   525  		h.conflictInfo.Type == tlf.HandleExtensionLocalConflict
   526  }
   527  
   528  // GetPreferredFormat returns a TLF name formatted with the username given
   529  // as the parameter first.
   530  // This calls tlf.CanonicalToPreferredName with the canonical
   531  // tlf name which will be reordered into the preferred format.
   532  // An empty username is allowed here and results in the canonical ordering.
   533  func (h Handle) GetPreferredFormat(
   534  	username kbname.NormalizedUsername) tlf.PreferredName {
   535  	s, err := tlf.CanonicalToPreferredName(username, h.GetCanonicalName())
   536  	if err != nil {
   537  		panic("TlfHandle.GetPreferredFormat: Parsing canonical username failed!")
   538  	}
   539  	return s
   540  }