github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/sql/roleoption/role_option.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 roleoption 12 13 import ( 14 "strings" 15 16 "github.com/cockroachdb/cockroach/pkg/security" 17 "github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgcode" 18 "github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgerror" 19 "github.com/cockroachdb/cockroach/pkg/sql/sqltelemetry" 20 "github.com/cockroachdb/errors" 21 ) 22 23 //go:generate stringer -type=Option 24 25 // Option defines a role option. This is output by the parser 26 type Option uint32 27 28 // RoleOption represents an Option with a value. 29 type RoleOption struct { 30 Option 31 HasValue bool 32 // Need to resolve value in Exec for the case of placeholders. 33 Value func() (bool, string, error) 34 } 35 36 // KindList of role options. 37 const ( 38 _ Option = iota 39 CREATEROLE 40 NOCREATEROLE 41 PASSWORD 42 LOGIN 43 NOLOGIN 44 VALIDUNTIL 45 ) 46 47 // toSQLStmts is a map of Kind -> SQL statement string for applying the 48 // option to the role. 49 var toSQLStmts = map[Option]string{ 50 CREATEROLE: `UPSERT INTO system.role_options (username, option) VALUES ($1, 'CREATEROLE')`, 51 NOCREATEROLE: `DELETE FROM system.role_options WHERE username = $1 AND option = 'CREATEROLE'`, 52 LOGIN: `DELETE FROM system.role_options WHERE username = $1 AND option = 'NOLOGIN'`, 53 NOLOGIN: `UPSERT INTO system.role_options (username, option) VALUES ($1, 'NOLOGIN')`, 54 VALIDUNTIL: `UPSERT INTO system.role_options (username, option, value) VALUES ($1, 'VALID UNTIL', $2::timestamptz::string)`, 55 } 56 57 // Mask returns the bitmask for a given role option. 58 func (o Option) Mask() uint32 { 59 return 1 << o 60 } 61 62 // ByName is a map of string -> kind value. 63 var ByName = map[string]Option{ 64 "CREATEROLE": CREATEROLE, 65 "NOCREATEROLE": NOCREATEROLE, 66 "PASSWORD": PASSWORD, 67 "LOGIN": LOGIN, 68 "NOLOGIN": NOLOGIN, 69 "VALID_UNTIL": VALIDUNTIL, 70 } 71 72 // ToOption takes a string and returns the corresponding Option. 73 func ToOption(str string) (Option, error) { 74 ret := ByName[strings.ToUpper(str)] 75 if ret == 0 { 76 return 0, pgerror.New(pgcode.Syntax, "option does not exist") 77 } 78 79 return ret, nil 80 } 81 82 // List is a list of role options. 83 type List []RoleOption 84 85 // GetSQLStmts returns a map of SQL stmts to apply each role option. 86 // Maps stmts to values (value of the role option). 87 func (rol List) GetSQLStmts(op string) (map[string]func() (bool, string, error), error) { 88 if len(rol) <= 0 { 89 return nil, nil 90 } 91 92 stmts := make(map[string]func() (bool, string, error), len(rol)) 93 94 err := rol.CheckRoleOptionConflicts() 95 if err != nil { 96 return stmts, err 97 } 98 99 for _, ro := range rol { 100 sqltelemetry.IncIAMOptionCounter( 101 op, 102 strings.ToLower(ro.Option.String()), 103 ) 104 // Skip PASSWORD option. 105 // Since PASSWORD still resides in system.users, we handle setting PASSWORD 106 // outside of this set stmt. 107 // TODO(richardjcai): migrate password to system.role_options 108 if ro.Option == PASSWORD { 109 continue 110 } 111 112 stmt := toSQLStmts[ro.Option] 113 if ro.HasValue { 114 stmts[stmt] = ro.Value 115 } else { 116 stmts[stmt] = nil 117 } 118 } 119 120 return stmts, nil 121 } 122 123 // ToBitField returns the bitfield representation of 124 // a list of role options. 125 func (rol List) ToBitField() (uint32, error) { 126 var ret uint32 127 for _, p := range rol { 128 if ret&p.Option.Mask() != 0 { 129 return 0, pgerror.Newf(pgcode.Syntax, "redundant role options") 130 } 131 ret |= p.Option.Mask() 132 } 133 return ret, nil 134 } 135 136 // Contains returns true if List contains option, false otherwise. 137 func (rol List) Contains(p Option) bool { 138 for _, ro := range rol { 139 if ro.Option == p { 140 return true 141 } 142 } 143 144 return false 145 } 146 147 // CheckRoleOptionConflicts returns an error if two or more options conflict with each other. 148 func (rol List) CheckRoleOptionConflicts() error { 149 roleOptionBits, err := rol.ToBitField() 150 151 if err != nil { 152 return err 153 } 154 155 if (roleOptionBits&CREATEROLE.Mask() != 0 && 156 roleOptionBits&NOCREATEROLE.Mask() != 0) || 157 (roleOptionBits&LOGIN.Mask() != 0 && 158 roleOptionBits&NOLOGIN.Mask() != 0) { 159 return pgerror.Newf(pgcode.Syntax, "conflicting role options") 160 } 161 return nil 162 } 163 164 // GetHashedPassword returns the value of the password after hashing it. 165 // Returns error if no password option is found or if password is invalid. 166 func (rol List) GetHashedPassword() ([]byte, error) { 167 var hashedPassword []byte 168 for _, ro := range rol { 169 if ro.Option == PASSWORD { 170 isNull, password, err := ro.Value() 171 if isNull { 172 // Use empty byte array for hashedPassword. 173 return hashedPassword, nil 174 } 175 if err != nil { 176 return hashedPassword, err 177 } 178 if password == "" { 179 return hashedPassword, security.ErrEmptyPassword 180 } 181 hashedPassword, err = security.HashPassword(password) 182 if err != nil { 183 return hashedPassword, err 184 } 185 186 return hashedPassword, nil 187 } 188 } 189 // Password option not found. 190 return hashedPassword, errors.New("password not found in role options") 191 }