diff --git a/pkg/ccl/importccl/import_table_creation.go b/pkg/ccl/importccl/import_table_creation.go index ab3b018b601f..2dfec9aca408 100644 --- a/pkg/ccl/importccl/import_table_creation.go +++ b/pkg/ccl/importccl/import_table_creation.go @@ -329,14 +329,14 @@ func (so *importSequenceOperators) IsTypeVisible( return false, false, errors.WithStack(errSequenceOperators) } -// HasPrivilege is part of the tree.EvalDatabase interface. -func (so *importSequenceOperators) HasPrivilege( +// HasAnyPrivilege is part of the tree.EvalDatabase interface. +func (so *importSequenceOperators) HasAnyPrivilege( ctx context.Context, specifier tree.HasPrivilegeSpecifier, user security.SQLUsername, - priv privilege.Privilege, -) (bool, error) { - return false, errors.WithStack(errSequenceOperators) + privs []privilege.Privilege, +) (tree.HasAnyPrivilegeResult, error) { + return tree.HasNoPrivilege, errors.WithStack(errSequenceOperators) } // IncrementSequenceByID implements the tree.SequenceOperators interface. diff --git a/pkg/sql/faketreeeval/evalctx.go b/pkg/sql/faketreeeval/evalctx.go index e6fb3a8b736b..c7efcdb626b9 100644 --- a/pkg/sql/faketreeeval/evalctx.go +++ b/pkg/sql/faketreeeval/evalctx.go @@ -80,14 +80,14 @@ func (so *DummySequenceOperators) IsTypeVisible( return false, false, errors.WithStack(errEvalPlanner) } -// HasPrivilege is part of the tree.EvalDatabase interface. -func (so *DummySequenceOperators) HasPrivilege( +// HasAnyPrivilege is part of the tree.EvalDatabase interface. +func (so *DummySequenceOperators) HasAnyPrivilege( ctx context.Context, specifier tree.HasPrivilegeSpecifier, user security.SQLUsername, - priv privilege.Privilege, -) (bool, error) { - return false, errors.WithStack(errEvalPlanner) + privs []privilege.Privilege, +) (tree.HasAnyPrivilegeResult, error) { + return tree.HasNoPrivilege, errors.WithStack(errEvalPlanner) } // IncrementSequenceByID is part of the tree.SequenceOperators interface. @@ -317,14 +317,14 @@ func (ep *DummyEvalPlanner) IsTypeVisible( return false, false, errors.WithStack(errEvalPlanner) } -// HasPrivilege is part of the tree.EvalDatabase interface. -func (ep *DummyEvalPlanner) HasPrivilege( +// HasAnyPrivilege is part of the tree.EvalDatabase interface. +func (ep *DummyEvalPlanner) HasAnyPrivilege( ctx context.Context, specifier tree.HasPrivilegeSpecifier, user security.SQLUsername, - priv privilege.Privilege, -) (bool, error) { - return false, errors.WithStack(errEvalPlanner) + privs []privilege.Privilege, +) (tree.HasAnyPrivilegeResult, error) { + return tree.HasNoPrivilege, errors.WithStack(errEvalPlanner) } // ResolveTableName is part of the tree.EvalDatabase interface. diff --git a/pkg/sql/resolver.go b/pkg/sql/resolver.go index b069f8cde202..d39fdb9bf16e 100644 --- a/pkg/sql/resolver.go +++ b/pkg/sql/resolver.go @@ -299,69 +299,61 @@ func (p *planner) IsTypeVisible( return false, true, nil } -// HasPrivilege is part of the tree.EvalDatabase interface. -func (p *planner) HasPrivilege( +// HasAnyPrivilege is part of the tree.EvalDatabase interface. +func (p *planner) HasAnyPrivilege( ctx context.Context, specifier tree.HasPrivilegeSpecifier, user security.SQLUsername, - priv privilege.Privilege, -) (bool, error) { + privs []privilege.Privilege, +) (tree.HasAnyPrivilegeResult, error) { desc, err := p.ResolveDescriptorForPrivilegeSpecifier( ctx, specifier, ) if err != nil { - return false, err + return tree.HasNoPrivilege, err + } + if desc == nil { + return tree.ObjectNotFound, nil } - // hasPrivilegeFunc checks whether any role has the given privilege. - hasPrivilegeFunc := func(priv privilege.Privilege) (bool, error) { - err := p.CheckPrivilegeForUser(ctx, desc, priv.Kind, user) - if err != nil { + for _, priv := range privs { + // RULE was only added for compatibility with Postgres, and Postgres + // never allows RULE to be granted, even if the user has ALL privileges. + // See https://www.postgresql.org/docs/8.1/sql-grant.html + // and https://www.postgresql.org/docs/release/8.2.0/. + if priv.Kind == privilege.RULE { + continue + } + + if err := p.CheckPrivilegeForUser(ctx, desc, priv.Kind, user); err != nil { if pgerror.GetPGCode(err) == pgcode.InsufficientPrivilege { - return false, nil + continue } - return false, err + return tree.HasNoPrivilege, err } if priv.GrantOption { if !p.ExecCfg().Settings.Version.IsActive(ctx, clusterversion.ValidateGrantOption) { - err := p.CheckPrivilegeForUser(ctx, desc, privilege.GRANT, user) - if err != nil { + if err := p.CheckPrivilegeForUser(ctx, desc, privilege.GRANT, user); err != nil { if pgerror.GetPGCode(err) == pgcode.InsufficientPrivilege { - return false, nil + continue } - return false, err + return tree.HasNoPrivilege, err } } else { - err := p.CheckGrantOptionsForUser(ctx, desc, []privilege.Kind{priv.Kind}, user, true /* isGrant */) - if err != nil { + if err := p.CheckGrantOptionsForUser(ctx, desc, []privilege.Kind{priv.Kind}, user, true /* isGrant */); err != nil { if pgerror.GetPGCode(err) == pgcode.WarningPrivilegeNotGranted { - return false, nil + continue } - return false, err + return tree.HasNoPrivilege, err } } } - - return true, nil + return tree.HasPrivilege, nil } - if priv.Kind == privilege.RULE { - // RULE was only added for compatibility with Postgres, and Postgres - // never allows RULE to be granted, even if the user has ALL privileges. - // See https://www.postgresql.org/docs/8.1/sql-grant.html - // and https://www.postgresql.org/docs/release/8.2.0/. - return false, nil - } - hasPrivilege, err := hasPrivilegeFunc(privilege.Privilege{Kind: privilege.ALL}) - if err != nil { - return false, err - } - if hasPrivilege { - return true, nil - } - return hasPrivilegeFunc(priv) + return tree.HasNoPrivilege, nil } // ResolveDescriptorForPrivilegeSpecifier resolves a tree.HasPrivilegeSpecifier @@ -375,7 +367,7 @@ func (p *planner) ResolveDescriptorForPrivilegeSpecifier( ) } else if specifier.DatabaseOID != nil { _, database, err := p.Descriptors().GetImmutableDatabaseByID( - ctx, p.txn, descpb.ID(*specifier.DatabaseOID), tree.DatabaseLookupFlags{Required: true}, + ctx, p.txn, descpb.ID(*specifier.DatabaseOID), tree.DatabaseLookupFlags{}, ) return database, err } else if specifier.SchemaName != nil { @@ -386,7 +378,7 @@ func (p *planner) ResolveDescriptorForPrivilegeSpecifier( return nil, err } return p.Descriptors().GetImmutableSchemaByName( - ctx, p.txn, database, *specifier.SchemaName, tree.SchemaLookupFlags{Required: true}, + ctx, p.txn, database, *specifier.SchemaName, tree.SchemaLookupFlags{Required: *specifier.SchemaIsRequired}, ) } else if specifier.TableName != nil || specifier.TableOID != nil { var table catalog.TableDescriptor @@ -408,21 +400,17 @@ func (p *planner) ResolveDescriptorForPrivilegeSpecifier( "cross-database references are not implemented: %s", tn) } _, table, err = p.Descriptors().GetImmutableTableByName( - ctx, p.txn, tn, tree.ObjectLookupFlags{ - CommonLookupFlags: tree.CommonLookupFlags{ - Required: true, - }, - }, + ctx, p.txn, tn, tree.ObjectLookupFlags{}, ) } else { table, err = p.Descriptors().GetImmutableTableByID( - ctx, p.txn, descpb.ID(*specifier.TableOID), - tree.ObjectLookupFlags{ - CommonLookupFlags: tree.CommonLookupFlags{ - Required: true, - }, - }, + ctx, p.txn, descpb.ID(*specifier.TableOID), tree.ObjectLookupFlags{}, ) + // When a TableOID is specified and the relation is not found, we return NULL. + if err != nil && sqlerrors.IsUndefinedRelationError(err) { + // nolint:returnerrcheck + return nil, nil + } } if err != nil { return nil, err diff --git a/pkg/sql/sem/builtins/pg_builtins.go b/pkg/sql/sem/builtins/pg_builtins.go index 9307214485d7..523859266b83 100644 --- a/pkg/sql/sem/builtins/pg_builtins.go +++ b/pkg/sql/sem/builtins/pg_builtins.go @@ -340,7 +340,7 @@ var strOrOidTypes = []*types.T{types.String, types.Oid} func makePGPrivilegeInquiryDef( infoDetail string, objSpecArgs argTypeOpts, - fn func(ctx *tree.EvalContext, args tree.Datums, user security.SQLUsername) (tree.Datum, error), + fn func(ctx *tree.EvalContext, args tree.Datums, user security.SQLUsername) (tree.HasAnyPrivilegeResult, error), ) builtinDefinition { // Collect the different argument type variations. // @@ -416,7 +416,20 @@ func makePGPrivilegeInquiryDef( } user = ctx.SessionData().User() } - return fn(ctx, args, user) + ret, err := fn(ctx, args, user) + if err != nil { + return nil, err + } + switch ret { + case tree.HasPrivilege: + return tree.DBoolTrue, nil + case tree.HasNoPrivilege: + return tree.DBoolFalse, nil + case tree.ObjectNotFound: + return tree.DNull, nil + default: + panic(fmt.Sprintf("unrecognized HasAnyPrivilegeResult %d", ret)) + } }, Info: fmt.Sprintf(infoFmt, infoDetail), Volatility: tree.VolatilityStable, @@ -454,6 +467,20 @@ func getNameForArg(ctx *tree.EvalContext, arg tree.Datum, pgTable, pgCol string) // privMap maps a privilege string to a Privilege. type privMap map[string]privilege.Privilege +func normalizePrivilegeStr(arg tree.Datum) []string { + argStr := string(tree.MustBeDString(arg)) + privStrs := strings.Split(argStr, ",") + res := make([]string, len(privStrs)) + for i, privStr := range privStrs { + // Privileges are case-insensitive. + privStr = strings.ToUpper(privStr) + // Extra whitespace is allowed between but not within privilege names. + privStr = strings.TrimSpace(privStr) + res[i] = privStr + } + return res +} + // parsePrivilegeStr recognizes privilege strings for has_foo_privilege // builtins, which are known as Access Privilege Inquiry Functions. // @@ -462,14 +489,9 @@ type privMap map[string]privilege.Privilege // items, not so much about whitespace within items. The allowed privilege names // and their corresponding privileges are given as a privMap. func parsePrivilegeStr(arg tree.Datum, m privMap) ([]privilege.Privilege, error) { - argStr := string(tree.MustBeDString(arg)) - privStrs := strings.Split(argStr, ",") + privStrs := normalizePrivilegeStr(arg) res := make([]privilege.Privilege, len(privStrs)) for i, privStr := range privStrs { - // Privileges are case-insensitive. - privStr = strings.ToUpper(privStr) - // Extra whitespace is allowed between but not within privilege names. - privStr = strings.TrimSpace(privStr) // Check the privilege map. p, ok := m[privStr] if !ok { @@ -481,28 +503,6 @@ func parsePrivilegeStr(arg tree.Datum, m privMap) ([]privilege.Privilege, error) return res, nil } -// runPrivilegeChecks runs the provided function for each privilege in the list. -// If any of the checks return True or NULL, the function short-circuits with -// that result. Otherwise, it returns False. -func runPrivilegeChecks( - privs []privilege.Privilege, check func(privilege.Privilege) (tree.Datum, error), -) (tree.Datum, error) { - for _, p := range privs { - d, err := check(p) - if err != nil { - return nil, err - } - switch d { - case tree.DBoolFalse: - case tree.DBoolTrue, tree.DNull: - return d, nil - default: - return nil, errors.AssertionFailedf("unexpected privilege check result %v", d) - } - } - return tree.DBoolFalse, nil -} - func makeCreateRegDef(typ *types.T) builtinDefinition { return makeBuiltin(defProps(), tree.Overload{ @@ -1326,11 +1326,11 @@ SELECT description "has_any_column_privilege": makePGPrivilegeInquiryDef( "any column of table", argTypeOpts{{"table", strOrOidTypes}}, - func(ctx *tree.EvalContext, args tree.Datums, user security.SQLUsername) (tree.Datum, error) { + func(ctx *tree.EvalContext, args tree.Datums, user security.SQLUsername) (tree.HasAnyPrivilegeResult, error) { tableArg := tree.UnwrapDatum(ctx, args[0]) specifier, err := tableHasPrivilegeSpecifier(tableArg, false /* isSequence */) if err != nil { - return nil, err + return tree.HasNoPrivilege, err } privs, err := parsePrivilegeStr(args[1], privMap{ @@ -1344,24 +1344,21 @@ SELECT description "REFERENCES WITH GRANT OPTION": {Kind: privilege.SELECT, GrantOption: true}, }) if err != nil { - return nil, err + return tree.HasNoPrivilege, err } - return runPrivilegeChecks(privs, func(priv privilege.Privilege) (tree.Datum, error) { - ret, err := ctx.Planner.HasPrivilege(ctx.Context, specifier, user, priv) - return handleTableHasPrivilegeError(specifier, ret, err) - }) + return ctx.Planner.HasAnyPrivilege(ctx.Context, specifier, user, privs) }, ), "has_column_privilege": makePGPrivilegeInquiryDef( "column", argTypeOpts{{"table", strOrOidTypes}, {"column", []*types.T{types.String, types.Int}}}, - func(ctx *tree.EvalContext, args tree.Datums, user security.SQLUsername) (tree.Datum, error) { + func(ctx *tree.EvalContext, args tree.Datums, user security.SQLUsername) (tree.HasAnyPrivilegeResult, error) { tableArg := tree.UnwrapDatum(ctx, args[0]) colArg := tree.UnwrapDatum(ctx, args[1]) specifier, err := columnHasPrivilegeSpecifier(tableArg, colArg) if err != nil { - return nil, err + return tree.HasNoPrivilege, err } privs, err := parsePrivilegeStr(args[2], privMap{ @@ -1375,24 +1372,21 @@ SELECT description "REFERENCES WITH GRANT OPTION": {Kind: privilege.SELECT, GrantOption: true}, }) if err != nil { - return nil, err + return tree.HasNoPrivilege, err } - return runPrivilegeChecks(privs, func(priv privilege.Privilege) (tree.Datum, error) { - ret, err := ctx.Planner.HasPrivilege(ctx.Context, specifier, user, priv) - return handleTableHasPrivilegeError(specifier, ret, err) - }) + return ctx.Planner.HasAnyPrivilege(ctx.Context, specifier, user, privs) }, ), "has_database_privilege": makePGPrivilegeInquiryDef( "database", argTypeOpts{{"database", strOrOidTypes}}, - func(ctx *tree.EvalContext, args tree.Datums, user security.SQLUsername) (tree.Datum, error) { + func(ctx *tree.EvalContext, args tree.Datums, user security.SQLUsername) (tree.HasAnyPrivilegeResult, error) { databaseArg := tree.UnwrapDatum(ctx, args[0]) specifier, err := databaseHasPrivilegeSpecifier(databaseArg) if err != nil { - return nil, err + return tree.HasNoPrivilege, err } privs, err := parsePrivilegeStr(args[1], privMap{ @@ -1406,30 +1400,27 @@ SELECT description "TEMP WITH GRANT OPTION": {Kind: privilege.CREATE, GrantOption: true}, }) if err != nil { - return nil, err + return tree.HasNoPrivilege, err } - return runPrivilegeChecks(privs, func(priv privilege.Privilege) (tree.Datum, error) { - ret, err := ctx.Planner.HasPrivilege(ctx.Context, specifier, user, priv) - return handleDatabaseHasPrivilegeError(specifier, ret, err) - }) + return ctx.Planner.HasAnyPrivilege(ctx.Context, specifier, user, privs) }, ), "has_foreign_data_wrapper_privilege": makePGPrivilegeInquiryDef( "foreign-data wrapper", argTypeOpts{{"fdw", strOrOidTypes}}, - func(ctx *tree.EvalContext, args tree.Datums, user security.SQLUsername) (tree.Datum, error) { + func(ctx *tree.EvalContext, args tree.Datums, user security.SQLUsername) (tree.HasAnyPrivilegeResult, error) { fdwArg := tree.UnwrapDatum(ctx, args[0]) fdw, err := getNameForArg(ctx, fdwArg, "pg_foreign_data_wrapper", "fdwname") if err != nil { - return nil, err + return tree.HasNoPrivilege, err } retNull := false if fdw == "" { switch fdwArg.(type) { case *tree.DString: - return nil, pgerror.Newf(pgcode.UndefinedObject, + return tree.HasNoPrivilege, pgerror.Newf(pgcode.UndefinedObject, "foreign-data wrapper %s does not exist", fdwArg) case *tree.DOid: // Postgres returns NULL if no matching foreign data wrapper is found @@ -1443,21 +1434,21 @@ SELECT description "USAGE WITH GRANT OPTION": {Kind: privilege.USAGE, GrantOption: true}, }) if err != nil { - return nil, err + return tree.HasNoPrivilege, err } if retNull { - return tree.DNull, nil + return tree.ObjectNotFound, nil } // All users have USAGE privileges for all foreign-data wrappers. _ = privs - return tree.DBoolTrue, nil + return tree.HasPrivilege, nil }, ), "has_function_privilege": makePGPrivilegeInquiryDef( "function", argTypeOpts{{"function", strOrOidTypes}}, - func(ctx *tree.EvalContext, args tree.Datums, user security.SQLUsername) (tree.Datum, error) { + func(ctx *tree.EvalContext, args tree.Datums, user security.SQLUsername) (tree.HasAnyPrivilegeResult, error) { oidArg := tree.UnwrapDatum(ctx, args[0]) // When specifying a function by a text string rather than by OID, // the allowed input is the same as for the regprocedure data type. @@ -1467,7 +1458,7 @@ SELECT description var err error oid, err = tree.ParseDOid(ctx, string(*t), types.RegProcedure) if err != nil { - return nil, err + return tree.HasNoPrivilege, err } case *tree.DOid: oid = t @@ -1475,7 +1466,7 @@ SELECT description fn, err := getNameForArg(ctx, oid, "pg_proc", "proname") if err != nil { - return nil, err + return tree.HasNoPrivilege, err } retNull := false if fn == "" { @@ -1492,31 +1483,31 @@ SELECT description "EXECUTE WITH GRANT OPTION": {Kind: privilege.USAGE, GrantOption: true}, }) if err != nil { - return nil, err + return tree.HasNoPrivilege, err } if retNull { - return tree.DNull, nil + return tree.ObjectNotFound, nil } // All users have EXECUTE privileges for all functions. _ = privs - return tree.DBoolTrue, nil + return tree.HasPrivilege, nil }, ), "has_language_privilege": makePGPrivilegeInquiryDef( "language", argTypeOpts{{"language", strOrOidTypes}}, - func(ctx *tree.EvalContext, args tree.Datums, user security.SQLUsername) (tree.Datum, error) { + func(ctx *tree.EvalContext, args tree.Datums, user security.SQLUsername) (tree.HasAnyPrivilegeResult, error) { langArg := tree.UnwrapDatum(ctx, args[0]) lang, err := getNameForArg(ctx, langArg, "pg_language", "lanname") if err != nil { - return nil, err + return tree.HasNoPrivilege, err } retNull := false if lang == "" { switch langArg.(type) { case *tree.DString: - return nil, pgerror.Newf(pgcode.UndefinedObject, + return tree.HasNoPrivilege, pgerror.Newf(pgcode.UndefinedObject, "language %s does not exist", langArg) case *tree.DOid: // Postgres returns NULL if no matching language is found @@ -1530,26 +1521,26 @@ SELECT description "USAGE WITH GRANT OPTION": {Kind: privilege.USAGE, GrantOption: true}, }) if err != nil { - return nil, err + return tree.HasNoPrivilege, err } if retNull { - return tree.DNull, nil + return tree.ObjectNotFound, nil } // All users have USAGE privileges for all languages. _ = privs - return tree.DBoolTrue, nil + return tree.HasPrivilege, nil }, ), "has_schema_privilege": makePGPrivilegeInquiryDef( "schema", argTypeOpts{{"schema", strOrOidTypes}}, - func(ctx *tree.EvalContext, args tree.Datums, user security.SQLUsername) (tree.Datum, error) { + func(ctx *tree.EvalContext, args tree.Datums, user security.SQLUsername) (tree.HasAnyPrivilegeResult, error) { schemaArg := tree.UnwrapDatum(ctx, args[0]) databaseName := ctx.SessionData().Database - specifier, isOidSpecified, err := schemaHasPrivilegeSpecifier(ctx, schemaArg, databaseName) + specifier, err := schemaHasPrivilegeSpecifier(ctx, schemaArg, databaseName) if err != nil { - return nil, err + return tree.HasNoPrivilege, err } privs, err := parsePrivilegeStr(args[1], privMap{ @@ -1559,28 +1550,25 @@ SELECT description "USAGE WITH GRANT OPTION": {Kind: privilege.USAGE, GrantOption: true}, }) if err != nil { - return nil, err + return tree.HasNoPrivilege, err } if len(databaseName) == 0 { // If no database is set, return NULL. - return tree.DNull, nil + return tree.ObjectNotFound, nil } - return runPrivilegeChecks(privs, func(priv privilege.Privilege) (tree.Datum, error) { - ret, err := ctx.Planner.HasPrivilege(ctx.Context, specifier, user, priv) - return handleSchemaHasPrivilegeError(isOidSpecified, ret, err) - }) + return ctx.Planner.HasAnyPrivilege(ctx.Context, specifier, user, privs) }, ), "has_sequence_privilege": makePGPrivilegeInquiryDef( "sequence", argTypeOpts{{"sequence", strOrOidTypes}}, - func(ctx *tree.EvalContext, args tree.Datums, user security.SQLUsername) (tree.Datum, error) { + func(ctx *tree.EvalContext, args tree.Datums, user security.SQLUsername) (tree.HasAnyPrivilegeResult, error) { seqArg := tree.UnwrapDatum(ctx, args[0]) specifier, err := tableHasPrivilegeSpecifier(seqArg, true /* isSequence */) if err != nil { - return nil, err + return tree.HasNoPrivilege, err } privs, err := parsePrivilegeStr(args[1], privMap{ // Sequences and other table objects cannot be given a USAGE privilege, @@ -1593,29 +1581,26 @@ SELECT description "UPDATE WITH GRANT OPTION": {Kind: privilege.UPDATE, GrantOption: true}, }) if err != nil { - return nil, err + return tree.HasPrivilege, err } - return runPrivilegeChecks(privs, func(priv privilege.Privilege) (tree.Datum, error) { - ret, err := ctx.Planner.HasPrivilege(ctx.Context, specifier, user, priv) - return handleTableHasPrivilegeError(specifier, ret, err) - }) + return ctx.Planner.HasAnyPrivilege(ctx.Context, specifier, user, privs) }, ), "has_server_privilege": makePGPrivilegeInquiryDef( "foreign server", argTypeOpts{{"server", strOrOidTypes}}, - func(ctx *tree.EvalContext, args tree.Datums, user security.SQLUsername) (tree.Datum, error) { + func(ctx *tree.EvalContext, args tree.Datums, user security.SQLUsername) (tree.HasAnyPrivilegeResult, error) { serverArg := tree.UnwrapDatum(ctx, args[0]) server, err := getNameForArg(ctx, serverArg, "pg_foreign_server", "srvname") if err != nil { - return nil, err + return tree.HasNoPrivilege, err } retNull := false if server == "" { switch serverArg.(type) { case *tree.DString: - return nil, pgerror.Newf(pgcode.UndefinedObject, + return tree.HasNoPrivilege, pgerror.Newf(pgcode.UndefinedObject, "server %s does not exist", serverArg) case *tree.DOid: // Postgres returns NULL if no matching foreign server is found when @@ -1629,25 +1614,25 @@ SELECT description "USAGE WITH GRANT OPTION": {Kind: privilege.USAGE, GrantOption: true}, }) if err != nil { - return nil, err + return tree.HasNoPrivilege, err } if retNull { - return tree.DNull, nil + return tree.ObjectNotFound, nil } // All users have USAGE privileges for all foreign servers. _ = privs - return tree.DBoolTrue, nil + return tree.HasPrivilege, nil }, ), "has_table_privilege": makePGPrivilegeInquiryDef( "table", argTypeOpts{{"table", strOrOidTypes}}, - func(ctx *tree.EvalContext, args tree.Datums, user security.SQLUsername) (tree.Datum, error) { + func(ctx *tree.EvalContext, args tree.Datums, user security.SQLUsername) (tree.HasAnyPrivilegeResult, error) { tableArg := tree.UnwrapDatum(ctx, args[0]) specifier, err := tableHasPrivilegeSpecifier(tableArg, false /* isSequence */) if err != nil { - return nil, err + return tree.HasNoPrivilege, err } privs, err := parsePrivilegeStr(args[1], privMap{ @@ -1669,29 +1654,26 @@ SELECT description "RULE WITH GRANT OPTION": {Kind: privilege.RULE, GrantOption: true}, }) if err != nil { - return nil, err + return tree.HasNoPrivilege, err } - return runPrivilegeChecks(privs, func(priv privilege.Privilege) (tree.Datum, error) { - ret, err := ctx.Planner.HasPrivilege(ctx.Context, specifier, user, priv) - return handleTableHasPrivilegeError(specifier, ret, err) - }) + return ctx.Planner.HasAnyPrivilege(ctx.Context, specifier, user, privs) }, ), "has_tablespace_privilege": makePGPrivilegeInquiryDef( "tablespace", argTypeOpts{{"tablespace", strOrOidTypes}}, - func(ctx *tree.EvalContext, args tree.Datums, user security.SQLUsername) (tree.Datum, error) { + func(ctx *tree.EvalContext, args tree.Datums, user security.SQLUsername) (tree.HasAnyPrivilegeResult, error) { tablespaceArg := tree.UnwrapDatum(ctx, args[0]) tablespace, err := getNameForArg(ctx, tablespaceArg, "pg_tablespace", "spcname") if err != nil { - return nil, err + return tree.HasNoPrivilege, err } retNull := false if tablespace == "" { switch tablespaceArg.(type) { case *tree.DString: - return nil, pgerror.Newf(pgcode.UndefinedObject, + return tree.HasNoPrivilege, pgerror.Newf(pgcode.UndefinedObject, "tablespace %s does not exist", tablespaceArg) case *tree.DOid: // Postgres returns NULL if no matching tablespace is found when given @@ -1705,21 +1687,21 @@ SELECT description "CREATE WITH GRANT OPTION": {Kind: privilege.CREATE, GrantOption: true}, }) if err != nil { - return nil, err + return tree.HasNoPrivilege, err } if retNull { - return tree.DNull, nil + return tree.ObjectNotFound, nil } // All users have CREATE privileges in all tablespaces. _ = privs - return tree.DBoolTrue, nil + return tree.HasPrivilege, nil }, ), "has_type_privilege": makePGPrivilegeInquiryDef( "type", argTypeOpts{{"type", strOrOidTypes}}, - func(ctx *tree.EvalContext, args tree.Datums, user security.SQLUsername) (tree.Datum, error) { + func(ctx *tree.EvalContext, args tree.Datums, user security.SQLUsername) (tree.HasAnyPrivilegeResult, error) { oidArg := tree.UnwrapDatum(ctx, args[0]) // When specifying a type by a text string rather than by OID, the // allowed input is the same as for the regtype data type. @@ -1729,7 +1711,7 @@ SELECT description var err error oid, err = tree.ParseDOid(ctx, string(*t), types.RegType) if err != nil { - return nil, err + return tree.HasNoPrivilege, err } case *tree.DOid: oid = t @@ -1737,7 +1719,7 @@ SELECT description typ, err := getNameForArg(ctx, oid, "pg_type", "typname") if err != nil { - return nil, err + return tree.HasNoPrivilege, err } retNull := false if typ == "" { @@ -1751,74 +1733,68 @@ SELECT description "USAGE WITH GRANT OPTION": {Kind: privilege.USAGE, GrantOption: true}, }) if err != nil { - return nil, err + return tree.HasNoPrivilege, err } if retNull { - return tree.DNull, nil + return tree.ObjectNotFound, nil } // All users have USAGE privileges to all types. _ = privs - return tree.DBoolTrue, nil + return tree.HasPrivilege, nil }, ), "pg_has_role": makePGPrivilegeInquiryDef( "role", argTypeOpts{{"role", strOrOidTypes}}, - func(ctx *tree.EvalContext, args tree.Datums, user security.SQLUsername) (tree.Datum, error) { + func(ctx *tree.EvalContext, args tree.Datums, user security.SQLUsername) (tree.HasAnyPrivilegeResult, error) { roleArg := tree.UnwrapDatum(ctx, args[0]) roleS, err := getNameForArg(ctx, roleArg, "pg_roles", "rolname") if err != nil { - return nil, err + return tree.HasNoPrivilege, err } // Note: the username in pg_roles is already normalized, so we can safely // turn it into a SQLUsername without re-normalization. role := security.MakeSQLUsernameFromPreNormalizedString(roleS) - retNull := false if role.Undefined() { switch roleArg.(type) { case *tree.DString: - return nil, pgerror.Newf(pgcode.UndefinedObject, + return tree.HasNoPrivilege, pgerror.Newf(pgcode.UndefinedObject, "role %s does not exist", roleArg) case *tree.DOid: // Postgres returns NULL if no matching role is found when given an // OID. - retNull = true + return tree.ObjectNotFound, nil } } - privs, err := parsePrivilegeStr(args[1], privMap{ - // This privMap is handled a little differently than in other cases - // (but similar to in PostgreSQL, see convert_role_priv_string and - // pg_role_aclcheck). We use USAGE to denote whether the privileges of - // the role are accessible (hasPrivsOfRole), CREATE to denote whether - // the user is a member of the role (isMemberOfRole), and GRANT to - // denote whether the user is an admin of the role (isAdminOfRole). - "USAGE": {Kind: privilege.USAGE}, - "MEMBER": {Kind: privilege.CREATE}, - "USAGE WITH GRANT OPTION": {Kind: privilege.GRANT}, - "USAGE WITH ADMIN OPTION": {Kind: privilege.GRANT}, - "MEMBER WITH GRANT OPTION": {Kind: privilege.GRANT}, - "MEMBER WITH ADMIN OPTION": {Kind: privilege.GRANT}, - }) - if err != nil { - return nil, err - } - if retNull { - return tree.DNull, nil - } - return runPrivilegeChecks(privs, func(priv privilege.Privilege) (tree.Datum, error) { - switch priv.Kind { - case privilege.USAGE: - return hasPrivsOfRole(ctx, user, role) - case privilege.CREATE: - return isMemberOfRole(ctx, user, role) - case privilege.GRANT: - return isAdminOfRole(ctx, user, role) + privStrs := normalizePrivilegeStr(args[1]) + for _, privStr := range privStrs { + var hasAnyPrivilegeResult tree.HasAnyPrivilegeResult + var err error + switch privStr { + case "USAGE": + hasAnyPrivilegeResult, err = hasPrivsOfRole(ctx, user, role) + case "MEMBER": + hasAnyPrivilegeResult, err = isMemberOfRole(ctx, user, role) + case + "USAGE WITH GRANT OPTION", + "USAGE WITH ADMIN OPTION", + "MEMBER WITH GRANT OPTION", + "MEMBER WITH ADMIN OPTION": + hasAnyPrivilegeResult, err = isAdminOfRole(ctx, user, role) default: - panic("unexpected") + return tree.HasNoPrivilege, pgerror.Newf(pgcode.InvalidParameterValue, + "unrecognized privilege type: %q", privStr) } - }) + if err != nil { + return tree.HasNoPrivilege, err + } + if hasAnyPrivilegeResult == tree.HasPrivilege { + return hasAnyPrivilegeResult, nil + } + } + return tree.HasNoPrivilege, nil }, ), @@ -2292,62 +2268,27 @@ func columnHasPrivilegeSpecifier( func schemaHasPrivilegeSpecifier( ctx *tree.EvalContext, schemaArg tree.Datum, databaseName string, -) (tree.HasPrivilegeSpecifier, bool, error) { +) (tree.HasPrivilegeSpecifier, error) { specifier := tree.HasPrivilegeSpecifier{ SchemaDatabaseName: &databaseName, } + var schemaIsRequired bool switch t := schemaArg.(type) { case *tree.DString: s := string(*t) specifier.SchemaName = &s - return specifier, false, nil + schemaIsRequired = true case *tree.DOid: schemaName, err := getNameForArg(ctx, schemaArg, "pg_namespace", "nspname") if err != nil { - return specifier, true, err + return specifier, err } specifier.SchemaName = &schemaName - return specifier, true, nil default: - return specifier, false, errors.AssertionFailedf("unknown privilege specifier: %#v", schemaArg) + return specifier, errors.AssertionFailedf("unknown privilege specifier: %#v", schemaArg) } -} - -func handleDatabaseHasPrivilegeError( - specifier tree.HasPrivilegeSpecifier, ret bool, err error, -) (tree.Datum, error) { - if err != nil { - // When a DatabaseOID is specified and the relation is not found, we return NULL. - if specifier.DatabaseOID != nil && sqlerrors.IsUndefinedDatabaseError(err) { - return tree.DNull, nil - } - return nil, err - } - return tree.MakeDBool(tree.DBool(ret)), nil -} - -func handleSchemaHasPrivilegeError(isOidSpecified bool, ret bool, err error) (tree.Datum, error) { - if err != nil { - // When a Schema OID is specified and the relation is not found, we return NULL. - if isOidSpecified && sqlerrors.IsUndefinedSchemaError(err) { - return tree.DNull, nil - } - return nil, err - } - return tree.MakeDBool(tree.DBool(ret)), nil -} - -func handleTableHasPrivilegeError( - specifier tree.HasPrivilegeSpecifier, ret bool, err error, -) (tree.Datum, error) { - if err != nil { - // When a TableOID is specified and the relation is not found, we return NULL. - if specifier.TableOID != nil && sqlerrors.IsUndefinedRelationError(err) { - return tree.DNull, nil - } - return nil, err - } - return tree.MakeDBool(tree.DBool(ret)), nil + specifier.SchemaIsRequired = &schemaIsRequired + return specifier, nil } func pgTrueTypImpl(attrField, typField string, retType *types.T) builtinDefinition { @@ -2410,7 +2351,9 @@ func pgTrueTypImpl(attrField, typField string, retType *types.T) builtinDefiniti // member of a role is equivalent to a user having the privileges of that // role, so this is currently equivalent to isMemberOfRole. // See https://github.com/cockroachdb/cockroach/issues/69583. -func hasPrivsOfRole(ctx *tree.EvalContext, user, role security.SQLUsername) (tree.Datum, error) { +func hasPrivsOfRole( + ctx *tree.EvalContext, user, role security.SQLUsername, +) (tree.HasAnyPrivilegeResult, error) { return isMemberOfRole(ctx, user, role) } @@ -2418,39 +2361,46 @@ func hasPrivsOfRole(ctx *tree.EvalContext, user, role security.SQLUsername) (tre // (directly or indirectly). // // This is defined to recurse through roles regardless of rolinherit. -func isMemberOfRole(ctx *tree.EvalContext, user, role security.SQLUsername) (tree.Datum, error) { +func isMemberOfRole( + ctx *tree.EvalContext, user, role security.SQLUsername, +) (tree.HasAnyPrivilegeResult, error) { // Fast path for simple case. if user == role { - return tree.DBoolTrue, nil + return tree.HasPrivilege, nil } // Superusers have every privilege and are part of every role. if isSuper, err := ctx.Planner.UserHasAdminRole(ctx.Context, user); err != nil { - return nil, err + return tree.HasNoPrivilege, err } else if isSuper { - return tree.DBoolTrue, nil + return tree.HasPrivilege, nil } allRoleMemberships, err := ctx.Planner.MemberOfWithAdminOption(ctx.Context, user) if err != nil { - return nil, err + return tree.HasNoPrivilege, err } _, member := allRoleMemberships[role] - return tree.MakeDBool(tree.DBool(member)), nil + if member { + return tree.HasPrivilege, nil + } + return tree.HasNoPrivilege, nil } // isAdminOfRole returns whether the user is an admin of the specified role. // // That is, is member the role itself (subject to restrictions below), a // member (directly or indirectly) WITH ADMIN OPTION, or a superuser? -func isAdminOfRole(ctx *tree.EvalContext, user, role security.SQLUsername) (tree.Datum, error) { +func isAdminOfRole( + ctx *tree.EvalContext, user, role security.SQLUsername, +) (tree.HasAnyPrivilegeResult, error) { // Superusers are an admin of every role. // // NB: this is intentionally before the user == role check here. if isSuper, err := ctx.Planner.UserHasAdminRole(ctx.Context, user); err != nil { - return nil, err + return tree.HasNoPrivilege, err } else if isSuper { - return tree.DBoolTrue, nil + return tree.HasPrivilege, nil } // Fast path for simple case. @@ -2487,14 +2437,18 @@ func isAdminOfRole(ctx *tree.EvalContext, user, role security.SQLUsername) (tree // Because CockroachDB does not have "security-restricted operation", so // for compatibility, we just need to check whether the user matches the // session user. - isSessionUser := user == ctx.SessionData().SessionUser() - return tree.MakeDBool(tree.DBool(isSessionUser)), nil + if isSessionUser := user == ctx.SessionData().SessionUser(); isSessionUser { + return tree.HasPrivilege, nil + } + return tree.HasNoPrivilege, nil } allRoleMemberships, err := ctx.Planner.MemberOfWithAdminOption(ctx.Context, user) if err != nil { - return nil, err + return tree.HasNoPrivilege, err + } + if isAdmin := allRoleMemberships[role]; isAdmin { + return tree.HasPrivilege, nil } - isAdmin := allRoleMemberships[role] - return tree.MakeDBool(tree.DBool(isAdmin)), nil + return tree.HasNoPrivilege, nil } diff --git a/pkg/sql/sem/tree/eval.go b/pkg/sql/sem/tree/eval.go index 6ddd0052fcfa..77895294974c 100644 --- a/pkg/sql/sem/tree/eval.go +++ b/pkg/sql/sem/tree/eval.go @@ -3077,6 +3077,18 @@ type DatabaseRegionConfig interface { PrimaryRegionString() string } +// HasAnyPrivilegeResult represents the non-error results of calling HasAnyPrivilege +type HasAnyPrivilegeResult = int8 + +const ( + // HasPrivilege means at least one of the specified privileges is granted. + HasPrivilege HasAnyPrivilegeResult = 1 + // HasNoPrivilege means no privileges are granted. + HasNoPrivilege HasAnyPrivilegeResult = 0 + // ObjectNotFound means the object that privileges are being checked on was not found. + ObjectNotFound HasAnyPrivilegeResult = -1 +) + // EvalDatabase consists of functions that reference the session database // and is to be used from EvalContext. type EvalDatabase interface { @@ -3109,14 +3121,9 @@ type EvalDatabase interface { ctx context.Context, curDB string, searchPath sessiondata.SearchPath, typeID oid.Oid, ) (isVisible bool, exists bool, err error) - // HasPrivilege returns whether the current user has privilege to access + // HasAnyPrivilege returns whether the current user has privilege to access // the given object. - HasPrivilege( - ctx context.Context, - specifier HasPrivilegeSpecifier, - user security.SQLUsername, - priv privilege.Privilege, - ) (bool, error) + HasAnyPrivilege(ctx context.Context, specifier HasPrivilegeSpecifier, user security.SQLUsername, privs []privilege.Privilege) (HasAnyPrivilegeResult, error) } // HasPrivilegeSpecifier specifies an object to lookup privilege for. @@ -3130,13 +3137,16 @@ type HasPrivilegeSpecifier struct { // Schema privilege // Schema OID must be converted to name before using HasPrivilegeSpecifier. SchemaName *string - // SchemaDatabaseName is required when SchemaName is used + // SchemaDatabaseName is required when SchemaName is used. SchemaDatabaseName *string + // Because schemas cannot be looked up by OID directly, + // this controls whether the result is nil (originally queried by OID) or an error (originally queried by name). + SchemaIsRequired *bool // Table privilege TableName *string TableOID *oid.Oid - // Sequences are stored internally as a table + // Sequences are stored internally as a table. IsSequence *bool // Column privilege