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