github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/sql/drop_role.go (about) 1 // Copyright 2017 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 sql 12 13 import ( 14 "context" 15 "fmt" 16 17 "github.com/cockroachdb/cockroach/pkg/security" 18 "github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgcode" 19 "github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgerror" 20 "github.com/cockroachdb/cockroach/pkg/sql/roleoption" 21 "github.com/cockroachdb/cockroach/pkg/sql/sem/tree" 22 "github.com/cockroachdb/cockroach/pkg/sql/sqlbase" 23 "github.com/cockroachdb/cockroach/pkg/sql/sqltelemetry" 24 "github.com/cockroachdb/cockroach/pkg/util" 25 "github.com/cockroachdb/errors" 26 ) 27 28 // DropRoleNode deletes entries from the system.users table. 29 // This is called from DROP USER and DROP ROLE. 30 type DropRoleNode struct { 31 ifExists bool 32 isRole bool 33 names func() ([]string, error) 34 } 35 36 // DropRole represents a DROP ROLE statement. 37 // Privileges: CREATEROLE privilege. 38 func (p *planner) DropRole(ctx context.Context, n *tree.DropRole) (planNode, error) { 39 return p.DropRoleNode(ctx, n.Names, n.IfExists, n.IsRole, "DROP ROLE") 40 } 41 42 // DropRoleNode creates a "drop user" plan node. This can be called from DROP USER or DROP ROLE. 43 func (p *planner) DropRoleNode( 44 ctx context.Context, namesE tree.Exprs, ifExists bool, isRole bool, opName string, 45 ) (*DropRoleNode, error) { 46 if err := p.HasRoleOption(ctx, roleoption.CREATEROLE); err != nil { 47 return nil, err 48 } 49 50 names, err := p.TypeAsStringArray(ctx, namesE, opName) 51 if err != nil { 52 return nil, err 53 } 54 55 return &DropRoleNode{ 56 ifExists: ifExists, 57 isRole: isRole, 58 names: names, 59 }, nil 60 } 61 62 func (n *DropRoleNode) startExec(params runParams) error { 63 var opName string 64 if n.isRole { 65 sqltelemetry.IncIAMDropCounter(sqltelemetry.Role) 66 opName = "drop-role" 67 } else { 68 sqltelemetry.IncIAMDropCounter(sqltelemetry.User) 69 opName = "drop-user" 70 } 71 72 names, err := n.names() 73 if err != nil { 74 return err 75 } 76 77 userNames := make(map[string]struct{}) 78 for _, name := range names { 79 normalizedUsername, err := NormalizeAndValidateUsername(name) 80 if err != nil { 81 return err 82 } 83 userNames[normalizedUsername] = struct{}{} 84 } 85 86 f := tree.NewFmtCtx(tree.FmtSimple) 87 defer f.Close() 88 89 // Now check whether the user still has permission on any object in the database. 90 91 // First check all the databases. 92 if err := forEachDatabaseDesc(params.ctx, params.p, nil /*nil prefix = all databases*/, true, /* requiresPrivileges */ 93 func(db *sqlbase.DatabaseDescriptor) error { 94 for _, u := range db.GetPrivileges().Users { 95 if _, ok := userNames[u.User]; ok { 96 if f.Len() > 0 { 97 f.WriteString(", ") 98 } 99 f.FormatNameP(&db.Name) 100 break 101 } 102 } 103 return nil 104 }); err != nil { 105 return err 106 } 107 108 // Then check all the tables. 109 // 110 // We need something like forEachTableAll here, however we can't use 111 // the predefined forEachTableAll() function because we need to look 112 // at all _visible_ descriptors, not just those on which the current 113 // user has permission. 114 descs, err := params.p.Tables().GetAllDescriptors(params.ctx, params.p.txn) 115 if err != nil { 116 return err 117 } 118 lCtx := newInternalLookupCtx(descs, nil /*prefix - we want all descriptors */) 119 for _, tbID := range lCtx.tbIDs { 120 table := lCtx.tbDescs[tbID] 121 if !tableIsVisible(table, true /*allowAdding*/) { 122 continue 123 } 124 for _, u := range table.GetPrivileges().Users { 125 if _, ok := userNames[u.User]; ok { 126 if f.Len() > 0 { 127 f.WriteString(", ") 128 } 129 parentName := lCtx.getParentName(table) 130 tn := tree.MakeTableName(tree.Name(parentName), tree.Name(table.Name)) 131 f.FormatNode(&tn) 132 break 133 } 134 } 135 } 136 137 // Was there any object depending on that user? 138 if f.Len() > 0 { 139 fnl := tree.NewFmtCtx(tree.FmtSimple) 140 defer fnl.Close() 141 for i, name := range names { 142 if i > 0 { 143 fnl.WriteString(", ") 144 } 145 fnl.FormatName(name) 146 } 147 return pgerror.Newf(pgcode.Grouping, 148 "cannot drop role%s/user%s %s: grants still exist on %s", 149 util.Pluralize(int64(len(names))), util.Pluralize(int64(len(names))), 150 fnl.String(), f.String(), 151 ) 152 } 153 154 // All safe - do the work. 155 var numRoleMembershipsDeleted int 156 for normalizedUsername := range userNames { 157 // Specifically reject special users and roles. Some (root, admin) would fail with 158 // "privileges still exist" first. 159 if normalizedUsername == sqlbase.AdminRole || normalizedUsername == sqlbase.PublicRole { 160 return pgerror.Newf( 161 pgcode.InvalidParameterValue, "cannot drop special role %s", normalizedUsername) 162 } 163 if normalizedUsername == security.RootUser { 164 return pgerror.Newf( 165 pgcode.InvalidParameterValue, "cannot drop special user %s", normalizedUsername) 166 } 167 168 numUsersDeleted, err := params.extendedEvalCtx.ExecCfg.InternalExecutor.Exec( 169 params.ctx, 170 opName, 171 params.p.txn, 172 `DELETE FROM system.users WHERE username=$1`, 173 normalizedUsername, 174 ) 175 if err != nil { 176 return err 177 } 178 179 if numUsersDeleted == 0 && !n.ifExists { 180 return errors.Errorf("role/user %s does not exist", normalizedUsername) 181 } 182 183 // Drop all role memberships involving the user/role. 184 numRoleMembershipsDeleted, err = params.extendedEvalCtx.ExecCfg.InternalExecutor.Exec( 185 params.ctx, 186 "drop-role-membership", 187 params.p.txn, 188 `DELETE FROM system.role_members WHERE "role" = $1 OR "member" = $1`, 189 normalizedUsername, 190 ) 191 if err != nil { 192 return err 193 } 194 195 _, err = params.extendedEvalCtx.ExecCfg.InternalExecutor.Exec( 196 params.ctx, 197 opName, 198 params.p.txn, 199 fmt.Sprintf( 200 `DELETE FROM %s WHERE username=$1`, 201 RoleOptionsTableName, 202 ), 203 normalizedUsername, 204 ) 205 if err != nil { 206 return err 207 } 208 } 209 210 if numRoleMembershipsDeleted > 0 { 211 // Some role memberships have been deleted, bump role_members table version to 212 // force a refresh of role membership. 213 if err := params.p.BumpRoleMembershipTableVersion(params.ctx); err != nil { 214 return err 215 } 216 } 217 218 return nil 219 } 220 221 // Next implements the planNode interface. 222 func (*DropRoleNode) Next(runParams) (bool, error) { return false, nil } 223 224 // Values implements the planNode interface. 225 func (*DropRoleNode) Values() tree.Datums { return tree.Datums{} } 226 227 // Close implements the planNode interface. 228 func (*DropRoleNode) Close(context.Context) {}