github.com/cockroachdb/cockroachdb-parser@v0.23.3-0.20240213214944-911057d40c9a/pkg/security/username/username.go (about)

     1  // Copyright 2020 The Cockroach Authors.
     2  //
     3  // Use of this software is governed by the Business Source License
     4  // included in the file licenses/BSL.txt.
     5  //
     6  // As of the Change Date specified in that file, in accordance with
     7  // the Business Source License, use of this software will be governed
     8  // by the Apache License, Version 2.0, included in the file
     9  // licenses/APL.txt.
    10  
    11  package username
    12  
    13  import (
    14  	"bytes"
    15  	"fmt"
    16  	"regexp"
    17  	"strings"
    18  
    19  	"github.com/cockroachdb/cockroachdb-parser/pkg/sql/lexbase"
    20  	"github.com/cockroachdb/cockroachdb-parser/pkg/sql/sem/catid"
    21  	"github.com/cockroachdb/errors"
    22  	"github.com/cockroachdb/redact"
    23  )
    24  
    25  // SQLUsername represents a username valid inside SQL.
    26  //
    27  // Note that SQL usernames are not just ASCII names: they can start
    28  // with digits or contain only digits; they can contain certain
    29  // punctuation, and they can contain non-ASCII unicode letters.
    30  // For example, "123.-456" is a valid username.
    31  // Therefore, care must be taken when assembling a string from a
    32  // username for use in other contexts, e.g. to generate filenames:
    33  // some escaping and/or quoting is likely necessary.
    34  //
    35  // Additionally, beware that usernames as manipulated client-side (in
    36  // client drivers, in CLI commands) may not be the same as
    37  // server-side; this is because usernames can be substituted during
    38  // authentication. Additional care must be taken when deriving
    39  // server-side strings in client code. It is always better to add an
    40  // API server-side to assemble the string safely on the client's
    41  // behalf.
    42  //
    43  // This datatype is more complex to a simple string so as to force
    44  // usages to clarify when it is converted to/from strings.
    45  // This complexity is necessary because in CockroachDB SQL, unlike in
    46  // PostgreSQL, SQL usernames are case-folded and NFC-normalized when a
    47  // user logs in, or when used as input to certain CLI commands or SQL
    48  // statements. Then, "inside" CockroachDB, username strings are
    49  // considered pre-normalized and can be used directly for comparisons,
    50  // lookup etc.
    51  //
    52  //   - The constructor MakeSQLUsernameFromUserInput() creates
    53  //     a username from "external input".
    54  //
    55  //   - The constructor MakeSQLUsernameFromPreNormalizedString()
    56  //     creates a username when the caller can guarantee that
    57  //     the input is already pre-normalized.
    58  //
    59  // For convenience, the SQLIdentifier() method also represents a
    60  // username in the form suitable for input back by the SQL parser.
    61  type SQLUsername struct {
    62  	u string
    63  }
    64  
    65  // EmptyRole is a pseudo-role that's used in system tables.
    66  const EmptyRole = ""
    67  
    68  // EmptyRoleID is the ID for EmptyRole.
    69  const EmptyRoleID = 0
    70  
    71  // EmptyRoleName is the SQLUsername for EmptyRole.
    72  func EmptyRoleName() SQLUsername { return SQLUsername{EmptyRole} }
    73  
    74  // IsEmptyRole is true iff the username designates the empty user.
    75  func (s SQLUsername) IsEmptyRole() bool { return s.u == EmptyRole }
    76  
    77  // NodeUser is used by nodes for intra-cluster traffic.
    78  const NodeUser = "node"
    79  
    80  // NodeUserID is the ID for NodeUser.
    81  const NodeUserID = 3
    82  
    83  // NodeUserName is the SQLUsername for NodeUser.
    84  func NodeUserName() SQLUsername { return SQLUsername{NodeUser} }
    85  
    86  // IsNodeUser is true iff the username designates the node user.
    87  func (s SQLUsername) IsNodeUser() bool { return s.u == NodeUser }
    88  
    89  // RootUser is the default cluster administrator.
    90  const RootUser = "root"
    91  
    92  // RootUserID is the ID for RootUser.
    93  const RootUserID = 1
    94  
    95  // RootUserName is the SQLUsername for RootUser.
    96  func RootUserName() SQLUsername { return SQLUsername{RootUser} }
    97  
    98  // IsRootUser is true iff the username designates the root user.
    99  func (s SQLUsername) IsRootUser() bool { return s.u == RootUser }
   100  
   101  // AdminRole is the default (and non-droppable) role with superuser privileges.
   102  const AdminRole = "admin"
   103  
   104  // AdminRoleID is the ID for admin.
   105  const AdminRoleID = 2
   106  
   107  // AdminRoleName is the SQLUsername for AdminRole.
   108  func AdminRoleName() SQLUsername { return SQLUsername{AdminRole} }
   109  
   110  // IsAdminRole is true iff the username designates the admin role.
   111  func (s SQLUsername) IsAdminRole() bool { return s.u == AdminRole }
   112  
   113  // PublicRole is the special "public" pseudo-role.
   114  // All users are implicit members of "public". The role cannot be created,
   115  // dropped, assigned to another role, and is generally not listed.
   116  // It can be granted privileges, implicitly granting them to all users (current and future).
   117  const PublicRole = "public"
   118  
   119  // PublicRoleID is the ID for public role.
   120  const PublicRoleID = 4
   121  
   122  // PublicRoleName is the SQLUsername for PublicRole.
   123  func PublicRoleName() SQLUsername { return SQLUsername{PublicRole} }
   124  
   125  // IsPublicRole is true iff the username designates the public role.
   126  func (s SQLUsername) IsPublicRole() bool { return s.u == PublicRole }
   127  
   128  // This map is immutable and should always hold.
   129  // Right now this should always hold as we cannot rename any of the
   130  // roles defined in this map.
   131  // TODO(richardjcai): Add checks to ensure that this mapping always holds.
   132  var roleNameToID = map[SQLUsername]catid.RoleID{
   133  	RootUserName():   RootUserID,
   134  	AdminRoleName():  AdminRoleID,
   135  	NodeUserName():   NodeUserID,
   136  	PublicRoleName(): PublicRoleID,
   137  }
   138  
   139  // GetDefaultRoleNameToID returns a role id for default roles.
   140  func GetDefaultRoleNameToID(username SQLUsername) catid.RoleID {
   141  	return roleNameToID[username]
   142  }
   143  
   144  // NoneRole is a special role.
   145  // It is primarily used in SET ROLE, where "none" symbolizes a reset.
   146  const NoneRole = "none"
   147  
   148  // IsNoneRole is true iff the username designates the none role.
   149  func (s SQLUsername) IsNoneRole() bool { return s.u == NoneRole }
   150  
   151  // IsReserved is true if the given username is reserved.
   152  // Matches Postgres and also includes crdb_internal_.
   153  func (s SQLUsername) IsReserved() bool {
   154  	return s.IsPublicRole() || s.u == NoneRole || s.IsNodeUser() ||
   155  		strings.HasPrefix(s.u, "pg_") ||
   156  		strings.HasPrefix(s.u, "crdb_internal_")
   157  }
   158  
   159  // Undefined is true iff the username is an empty string.
   160  func (s SQLUsername) Undefined() bool { return len(s.u) == 0 }
   161  
   162  // TestUser is used in tests.
   163  const TestUser = "testuser"
   164  
   165  // TestUserName is the SQLUsername for testuser.
   166  func TestUserName() SQLUsername { return SQLUsername{TestUser} }
   167  
   168  // MakeSQLUsernameFromUserInput normalizes a username string as
   169  // entered in an ambiguous context into a SQL username (performs case
   170  // folding and unicode normalization form C - NFC).
   171  // If the purpose if PurposeCreation, the structure of the username
   172  // is also checked. An error is returned if the validation fails.
   173  // If the purpose is PurposeValidation, no error is returned.
   174  func MakeSQLUsernameFromUserInput(u string, purpose Purpose) (res SQLUsername, err error) {
   175  	// Perform case folding and NFC normalization.
   176  	res.u = lexbase.NormalizeName(u)
   177  	if purpose == PurposeCreation {
   178  		err = res.ValidateForCreation()
   179  	}
   180  	return res, err
   181  }
   182  
   183  // Purpose indicates the purpose of the resulting
   184  // SQLUsername in MakeSQLUsernameFromUserInput.
   185  type Purpose bool
   186  
   187  const (
   188  	// PurposeCreation indicates that the SQLUsername is being
   189  	// input for the purpose of creating a user account.
   190  	// This causes MakeSQLUsernameFromUserInput to also enforce
   191  	// structural restrictions on the username: which characters
   192  	// are allowed and a maximum length.
   193  	PurposeCreation Purpose = false
   194  
   195  	// PurposeValidation indicates that the SQLUsername is
   196  	// being input for the purpose of looking up an existing
   197  	// user, or to compare with an existing username.
   198  	// This skips the structural restrictions imposed
   199  	// for the purpose PurposeCreation.
   200  	PurposeValidation Purpose = true
   201  )
   202  
   203  const usernameHelp = "Usernames are case insensitive, must start with a letter, " +
   204  	"digit or underscore, may contain letters, digits, dashes, periods, or underscores, and must not exceed 63 characters."
   205  
   206  const maxUsernameLengthForCreation = 63
   207  
   208  var validUsernameCreationRE = regexp.MustCompile(`^[\p{Ll}0-9_][---\p{Ll}0-9_.]*$`)
   209  
   210  // ValidateForCreation checks that a username matches the structural
   211  // restrictions for creation of a user account with that name.
   212  func (s SQLUsername) ValidateForCreation() error {
   213  	if len(s.u) == 0 {
   214  		return ErrUsernameEmpty
   215  	}
   216  	if len(s.u) > maxUsernameLengthForCreation {
   217  		return ErrUsernameTooLong
   218  	}
   219  	if !validUsernameCreationRE.MatchString(s.u) {
   220  		return ErrUsernameInvalid
   221  	}
   222  	return nil
   223  }
   224  
   225  // ErrUsernameTooLong indicates that a username string was too
   226  // long. It is returned by ValidateForCreation() or
   227  // MakeSQLUserFromUserInput() with purpose PurposeCreation.
   228  var ErrUsernameTooLong = errors.WithHint(errors.New("username is too long"), usernameHelp)
   229  
   230  // ErrUsernameInvalid indicates that a username string contained
   231  // invalid characters. It is returned by ValidateForCreation() or
   232  // MakeSQLUserFromUserInput() with purpose PurposeCreation.
   233  var ErrUsernameInvalid = errors.WithHint(errors.New("username is invalid"), usernameHelp)
   234  
   235  // ErrUsernameEmpty indicates that an empty string was used as
   236  // username. It is returned by ValidateForCreation() or
   237  // MakeSQLUserFromUserInput() with purpose PurposeCreation.
   238  var ErrUsernameEmpty = errors.WithHint(errors.New("username is empty"), usernameHelp)
   239  
   240  // ErrUsernameNotNormalized indicates that a username
   241  // was not pre-normalized during a conversion.
   242  var ErrUsernameNotNormalized = errors.WithHint(errors.New("username is not normalized"),
   243  	"The username should be converted to lowercase and unicode characters normalized to NFC.")
   244  
   245  // MakeSQLUsernameFromPreNormalizedString takes a string containing a
   246  // canonical username and converts it to a SQLUsername. The caller of
   247  // this promises that the argument is pre-normalized. This conversion
   248  // is cheap.
   249  // Note: avoid using this function when processing strings
   250  // in requests from external APIs.
   251  // See also: MakeSQLUsernameFromPreNormalizedStringChecked().
   252  func MakeSQLUsernameFromPreNormalizedString(u string) SQLUsername {
   253  	return SQLUsername{u: u}
   254  }
   255  
   256  // MakeSQLUsernameFromPreNormalizedStringChecked takes a string,
   257  // validates it is a prenormalized username, then converts it to
   258  // a SQLUsername.
   259  // See also: MakeSQLUsernameFromPreNormalizedString().
   260  func MakeSQLUsernameFromPreNormalizedStringChecked(u string) (SQLUsername, error) {
   261  	res := SQLUsername{u: lexbase.NormalizeName(u)}
   262  	if res.u != u {
   263  		return res, ErrUsernameNotNormalized
   264  	}
   265  	return res, nil
   266  }
   267  
   268  // Normalized returns the normalized username, suitable for equality
   269  // comparison and lookups. The username is unquoted.
   270  func (s SQLUsername) Normalized() string { return s.u }
   271  
   272  // SQLIdentifier returns the normalized username in a form
   273  // suitable for parsing as a SQL identifier.
   274  // The identifier is quoted if it contains special characters
   275  // or it is a reserved keyword.
   276  func (s SQLUsername) SQLIdentifier() string {
   277  	var buf bytes.Buffer
   278  	lexbase.EncodeRestrictedSQLIdent(&buf, s.u, lexbase.EncNoFlags)
   279  	return buf.String()
   280  }
   281  
   282  // Format implements the fmt.Formatter interface. It renders
   283  // the username in normalized form.
   284  func (s SQLUsername) Format(fs fmt.State, verb rune) {
   285  	_, f := redact.MakeFormat(fs, verb)
   286  	fmt.Fprintf(fs, f, s.u)
   287  }
   288  
   289  // LessThan is true iff the receiver sorts strictly before
   290  // the given argument. This can be used e.g. in sort.Sort().
   291  func (s SQLUsername) LessThan(u SQLUsername) bool {
   292  	return s.u < u.u
   293  }
   294  
   295  // SQLUsernameProto is the wire representation of a SQLUsername.
   296  type SQLUsernameProto string
   297  
   298  // Decode turns the proto representation of a username back into its
   299  // legitimate form.
   300  func (s SQLUsernameProto) Decode() SQLUsername { return SQLUsername{u: string(s)} }
   301  
   302  // EncodeProto turns a username into its proto representation.
   303  func (s SQLUsername) EncodeProto() SQLUsernameProto { return SQLUsernameProto(s.u) }