github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/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 }