github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/kbfs/idutil/tlf_names.go (about)

     1  // Copyright 2019 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 idutil
     6  
     7  import (
     8  	"context"
     9  	"fmt"
    10  	"sort"
    11  	"strings"
    12  
    13  	"github.com/keybase/client/go/externals"
    14  	"github.com/keybase/client/go/kbfs/tlf"
    15  	kbname "github.com/keybase/client/go/kbun"
    16  	"github.com/pkg/errors"
    17  )
    18  
    19  // TODO: this function can likely be replaced with a call to
    20  // AssertionParseAndOnly when CORE-2967 and CORE-2968 are fixed.
    21  func normalizeAssertionOrName(s string, t tlf.Type) (string, error) {
    22  	if kbname.CheckUsername(s) {
    23  		return kbname.NewNormalizedUsername(s).String(), nil
    24  	}
    25  
    26  	// TODO: this fails for http and https right now (see CORE-2968).
    27  	socialAssertion, isSocialAssertion := externals.NormalizeSocialAssertionStatic(context.TODO(), s)
    28  	if isSocialAssertion {
    29  		if t == tlf.SingleTeam {
    30  			return "", fmt.Errorf(
    31  				"No social assertions allowed for team TLF: %s", s)
    32  		}
    33  		return socialAssertion.String(), nil
    34  	}
    35  
    36  	sAssertion := s
    37  	if t == tlf.SingleTeam {
    38  		sAssertion = "team:" + s
    39  	}
    40  	if expr, err := externals.AssertionParseAndOnlyStatic(context.TODO(), sAssertion); err == nil {
    41  		// If the expression only contains a single url, make sure
    42  		// it's not a just considered a single keybase username.  If
    43  		// it is, then some non-username slipped into the default
    44  		// "keybase" case and should be considered an error.
    45  		urls := expr.CollectUrls(nil)
    46  		if len(urls) == 1 && urls[0].IsKeybase() {
    47  			return "", NoSuchUserError{s}
    48  		}
    49  
    50  		// Normalize and return.  Ideally `AssertionParseAndOnly`
    51  		// would normalize for us, but that doesn't work yet, so for
    52  		// now we'll just lower-case.  This will incorrectly lower
    53  		// case http/https/web assertions, as well as case-sensitive
    54  		// social assertions in AND expressions.  TODO: see CORE-2967.
    55  		return strings.ToLower(s), nil
    56  	}
    57  
    58  	return "", BadTLFNameError{Name: s}
    59  }
    60  
    61  // normalizeNames normalizes a slice of names and returns
    62  // whether any of them changed.
    63  func normalizeNames(names []string, t tlf.Type) (changesMade bool, err error) {
    64  	for i, name := range names {
    65  		x, err := normalizeAssertionOrName(name, t)
    66  		if err != nil {
    67  			return false, err
    68  		}
    69  		if x != name {
    70  			names[i] = x
    71  			changesMade = true
    72  		}
    73  	}
    74  	return changesMade, nil
    75  }
    76  
    77  // NormalizeNamesInTLF takes a split TLF name and, without doing any
    78  // resolutions or identify calls, normalizes all elements of the
    79  // name. It then returns the normalized name and a boolean flag
    80  // whether any names were modified.
    81  // This modifies the slices passed as arguments.
    82  func NormalizeNamesInTLF(writerNames, readerNames []string,
    83  	t tlf.Type, extensionSuffix string) (normalizedName string,
    84  	changesMade bool, err error) {
    85  	changesMade, err = normalizeNames(writerNames, t)
    86  	if err != nil {
    87  		return "", false, err
    88  	}
    89  	sort.Strings(writerNames)
    90  	normalizedName = strings.Join(writerNames, ",")
    91  	if len(readerNames) > 0 {
    92  		rchanges, err := normalizeNames(readerNames, t)
    93  		if err != nil {
    94  			return "", false, err
    95  		}
    96  		changesMade = changesMade || rchanges
    97  		sort.Strings(readerNames)
    98  		normalizedName += tlf.ReaderSep + strings.Join(readerNames, ",")
    99  	}
   100  	if len(extensionSuffix) != 0 {
   101  		// This *should* be normalized already but make sure.  I can see not
   102  		// doing so might surprise a caller.
   103  		nExt := strings.ToLower(extensionSuffix)
   104  		normalizedName += tlf.HandleExtensionSep + nExt
   105  		changesMade = changesMade || nExt != extensionSuffix
   106  	}
   107  
   108  	return normalizedName, changesMade, nil
   109  }
   110  
   111  // SplitAndNormalizeTLFName takes a tlf name as a string
   112  // and tries to normalize it offline. In addition to other
   113  // checks it returns TlfNameNotCanonical if it does not
   114  // look canonical.
   115  // Note that ordering differences do not result in TlfNameNotCanonical
   116  // being returned.
   117  func SplitAndNormalizeTLFName(name string, t tlf.Type) (
   118  	writerNames, readerNames []string,
   119  	extensionSuffix string, err error) {
   120  	writerNames, readerNames, extensionSuffix, err = tlf.SplitName(name)
   121  	if err != nil {
   122  		return nil, nil, "", err
   123  	}
   124  	if t == tlf.SingleTeam && len(writerNames) != 1 {
   125  		// No team folder can have more than one writer.
   126  		return nil, nil, "", NoSuchNameError{Name: name}
   127  	}
   128  
   129  	hasReaders := len(readerNames) != 0
   130  	if t != tlf.Private && hasReaders {
   131  		// No public/team folder can have readers.
   132  		return nil, nil, "", NoSuchNameError{Name: name}
   133  	}
   134  
   135  	normalizedName, changes, err := NormalizeNamesInTLF(
   136  		writerNames, readerNames, t, extensionSuffix)
   137  	if err != nil {
   138  		return nil, nil, "", err
   139  	}
   140  	// Check for changes - not just ordering differences here.
   141  	if changes {
   142  		return nil, nil, "", errors.WithStack(TlfNameNotCanonical{name, normalizedName})
   143  	}
   144  
   145  	return writerNames, readerNames, strings.ToLower(extensionSuffix), nil
   146  }