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) }