github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/kbfs/tlf/name.go (about)

     1  // Copyright 2017 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 tlf
     6  
     7  import (
     8  	"fmt"
     9  	"sort"
    10  	"strings"
    11  
    12  	kbname "github.com/keybase/client/go/kbun"
    13  	"github.com/keybase/client/go/protocol/keybase1"
    14  )
    15  
    16  const (
    17  	// ReaderSep is the string that separates readers from writers in a
    18  	// TLF name.
    19  	ReaderSep = "#"
    20  )
    21  
    22  // SplitExtension separates any extension suffix from the assertions.
    23  func SplitExtension(name string) (
    24  	assertions, extensionSuffix string, err error) {
    25  	names := strings.SplitN(name, HandleExtensionSep, 2)
    26  	if len(names) > 2 {
    27  		return "", "", BadNameError{name}
    28  	}
    29  	if len(names) > 1 {
    30  		extensionSuffix = names[1]
    31  	}
    32  	return names[0], extensionSuffix, nil
    33  }
    34  
    35  // SplitName splits a TLF name into components.
    36  func SplitName(name string) (writerNames, readerNames []string,
    37  	extensionSuffix string, err error) {
    38  	assertions, extensionSuffix, err := SplitExtension(name)
    39  	if err != nil {
    40  		return nil, nil, "", err
    41  	}
    42  
    43  	splitNames := strings.SplitN(assertions, ReaderSep, 3)
    44  	if len(splitNames) > 2 {
    45  		return nil, nil, "", BadNameError{name}
    46  	}
    47  	writerNames = strings.Split(splitNames[0], ",")
    48  	if len(splitNames) > 1 {
    49  		readerNames = strings.Split(splitNames[1], ",")
    50  	}
    51  
    52  	return writerNames, readerNames, extensionSuffix, nil
    53  }
    54  
    55  // CanonicalName is a string containing the canonical name of a TLF.
    56  type CanonicalName string
    57  
    58  func getSortedNames(
    59  	resolved []kbname.NormalizedUsername,
    60  	unresolved []keybase1.SocialAssertion) []string {
    61  	var names []string
    62  	for _, name := range resolved {
    63  		names = append(names, name.String())
    64  	}
    65  	for _, sa := range unresolved {
    66  		names = append(names, sa.String())
    67  	}
    68  	sort.Strings(names)
    69  	return names
    70  }
    71  
    72  func makeCanonicalName(resolvedWriters []kbname.NormalizedUsername,
    73  	unresolvedWriters []keybase1.SocialAssertion,
    74  	resolvedReaders []kbname.NormalizedUsername,
    75  	unresolvedReaders []keybase1.SocialAssertion,
    76  	extensions []HandleExtension, isBackedByTeam bool) CanonicalName {
    77  	writerNames := getSortedNames(resolvedWriters, unresolvedWriters)
    78  	canonicalName := strings.Join(writerNames, ",")
    79  	if len(resolvedReaders)+len(unresolvedReaders) > 0 {
    80  		readerNames := getSortedNames(resolvedReaders, unresolvedReaders)
    81  		canonicalName += ReaderSep + strings.Join(readerNames, ",")
    82  	}
    83  
    84  	extensionList := make(HandleExtensionList, len(extensions))
    85  	copy(extensionList, extensions)
    86  	sort.Sort(extensionList)
    87  	if isBackedByTeam {
    88  		canonicalName += extensionList.SuffixForTeamHandle()
    89  	} else {
    90  		canonicalName += extensionList.Suffix()
    91  	}
    92  	return CanonicalName(canonicalName)
    93  }
    94  
    95  // MakeCanonicalName makes a CanonicalName from components.
    96  func MakeCanonicalName(resolvedWriters []kbname.NormalizedUsername,
    97  	unresolvedWriters []keybase1.SocialAssertion,
    98  	resolvedReaders []kbname.NormalizedUsername,
    99  	unresolvedReaders []keybase1.SocialAssertion,
   100  	extensions []HandleExtension) CanonicalName {
   101  	return makeCanonicalName(
   102  		resolvedWriters, unresolvedWriters, resolvedReaders, unresolvedReaders,
   103  		extensions, false)
   104  }
   105  
   106  // MakeCanonicalNameForTeam makes a CanonicalName from components for a team.
   107  func MakeCanonicalNameForTeam(resolvedWriters []kbname.NormalizedUsername,
   108  	unresolvedWriters []keybase1.SocialAssertion,
   109  	resolvedReaders []kbname.NormalizedUsername,
   110  	unresolvedReaders []keybase1.SocialAssertion,
   111  	extensions []HandleExtension) CanonicalName {
   112  	return makeCanonicalName(
   113  		resolvedWriters, unresolvedWriters, resolvedReaders, unresolvedReaders,
   114  		extensions, true)
   115  }
   116  
   117  // PreferredName is a preferred TLF name.
   118  type PreferredName string
   119  
   120  func putUserFirst(uname string, users []string) []string {
   121  	for i, w := range users {
   122  		if w == uname {
   123  			if i != 0 {
   124  				copy(users[1:i+1], users[0:i])
   125  				users[0] = w
   126  				return users
   127  			}
   128  		}
   129  	}
   130  	return users
   131  }
   132  
   133  // CanonicalToPreferredName returns the preferred TLF name, given a
   134  // canonical name and a username. The username may be empty, and
   135  // results in the canonical name being being returned unmodified.
   136  func CanonicalToPreferredName(username kbname.NormalizedUsername,
   137  	canon CanonicalName) (PreferredName, error) {
   138  	tlfname := string(canon)
   139  	if len(username) == 0 {
   140  		return PreferredName(tlfname), nil
   141  	}
   142  	ws, rs, ext, err := SplitName(tlfname)
   143  	if err != nil {
   144  		return "", err
   145  	}
   146  	if len(ws) == 0 {
   147  		return "", fmt.Errorf("TLF name %q with no writers", tlfname)
   148  	}
   149  	uname := username.String()
   150  	ws = putUserFirst(uname, ws)
   151  	rs = putUserFirst(uname, rs)
   152  	tlfname = strings.Join(ws, ",")
   153  	if len(rs) > 0 {
   154  		tlfname += ReaderSep + strings.Join(rs, ",")
   155  	}
   156  	if len(ext) > 0 {
   157  		tlfname += HandleExtensionSep + ext
   158  	}
   159  	return PreferredName(tlfname), nil
   160  }
   161  
   162  // UserIsOnlyWriter returns true if and only if username is the only writer in
   163  // a TLF represented by canon. In any error case, false is returned. This
   164  // function only naively looks at the TLF name, so it should only be used on
   165  // non-team TLFs.
   166  func UserIsOnlyWriter(username kbname.NormalizedUsername, canon CanonicalName) bool {
   167  	tlfname := string(canon)
   168  	if len(username) == 0 {
   169  		return false
   170  	}
   171  	ws, _, _, err := SplitName(tlfname)
   172  	if err != nil {
   173  		return false
   174  	}
   175  	return len(ws) == 1 && ws[0] == string(username)
   176  }