*** a/src/backend/commands/dbcommands.c --- b/src/backend/commands/dbcommands.c *************** *** 42,47 **** --- 42,48 ---- #include "commands/dbcommands.h" #include "commands/dbcommands_xlog.h" #include "commands/defrem.h" + #include "commands/policy.h" #include "commands/seclabel.h" #include "commands/tablespace.h" #include "mb/pg_wchar.h" *************** *** 226,231 **** createdb(const CreatedbStmt *stmt) --- 227,239 ---- errmsg("LOCATION is not supported anymore"), errhint("Consider using tablespaces instead."))); } + else if (strcmp(defel->defname, "catalog_security") == 0) + { + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("catalog security is not supported with create database command."), + errdetail("Enable catalog security using Alter database command."))); + } else ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), *************** *** 519,524 **** createdb(const CreatedbStmt *stmt) --- 527,533 ---- new_record[Anum_pg_database_datfrozenxid - 1] = TransactionIdGetDatum(src_frozenxid); new_record[Anum_pg_database_datminmxid - 1] = TransactionIdGetDatum(src_minmxid); new_record[Anum_pg_database_dattablespace - 1] = ObjectIdGetDatum(dst_deftablespace); + new_record[Anum_pg_database_datcatalogsecurity - 1] = BoolGetDatum(false); /* * We deliberately set datacl to default (NULL), rather than copying it *************** *** 1375,1385 **** AlterDatabase(AlterDatabaseStmt *stmt, bool isTopLevel) --- 1384,1397 ---- ListCell *option; bool dbistemplate = false; bool dballowconnections = true; + bool dbcatalogsecurity = false; int dbconnlimit = -1; DefElem *distemplate = NULL; DefElem *dallowconnections = NULL; DefElem *dconnlimit = NULL; DefElem *dtablespace = NULL; + DefElem *dcatalogsecurity = NULL; + Form_pg_database pg_database_tuple; Datum new_record[Natts_pg_database]; bool new_record_nulls[Natts_pg_database]; bool new_record_repl[Natts_pg_database]; *************** *** 1421,1426 **** AlterDatabase(AlterDatabaseStmt *stmt, bool isTopLevel) --- 1433,1447 ---- errmsg("conflicting or redundant options"))); dtablespace = defel; } + else if (strcmp(defel->defname, "catalog_security") == 0) + { + if (dcatalogsecurity) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("conflicting or redundant options"))); + + dcatalogsecurity = defel; + } else ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), *************** *** 1457,1462 **** AlterDatabase(AlterDatabaseStmt *stmt, bool isTopLevel) --- 1478,1485 ---- (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("invalid connection limit: %d", dbconnlimit))); } + if (dcatalogsecurity && dcatalogsecurity->arg) + dbcatalogsecurity = defGetBoolean(dcatalogsecurity); /* * Get the old tuple. We don't need a lock on the database per se, *************** *** 1476,1487 **** AlterDatabase(AlterDatabaseStmt *stmt, bool isTopLevel) --- 1499,1517 ---- (errcode(ERRCODE_UNDEFINED_DATABASE), errmsg("database \"%s\" does not exist", stmt->dbname))); + pg_database_tuple = (Form_pg_database)GETSTRUCT(tuple); dboid = HeapTupleGetOid(tuple); if (!pg_database_ownercheck(HeapTupleGetOid(tuple), GetUserId())) aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_DATABASE, stmt->dbname); + if (dcatalogsecurity && (dboid != MyDatabaseId)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("Enabling/disabling catalog security can be done" + " only to the connected database \"%s\"", stmt->dbname))); + /* * In order to avoid getting locked out and having to go through * standalone mode, we refuse to disallow connections to the database *************** *** 1493,1498 **** AlterDatabase(AlterDatabaseStmt *stmt, bool isTopLevel) --- 1523,1539 ---- (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("cannot disallow connections for current database"))); + if (dcatalogsecurity && BoolGetDatum(dbcatalogsecurity) && !pg_database_tuple->datcatalogsecurity) + { + CreateCatalogPolicy(); + CommandCounterIncrement(); + } + else if (pg_database_tuple->datcatalogsecurity) + { + RemoveCatalogPolicy(); + CommandCounterIncrement(); + } + /* * Build an updated tuple, perusing the information just obtained */ *************** *** 1515,1520 **** AlterDatabase(AlterDatabaseStmt *stmt, bool isTopLevel) --- 1556,1566 ---- new_record[Anum_pg_database_datconnlimit - 1] = Int32GetDatum(dbconnlimit); new_record_repl[Anum_pg_database_datconnlimit - 1] = true; } + if (dcatalogsecurity) + { + new_record[Anum_pg_database_datcatalogsecurity - 1] = BoolGetDatum(dbcatalogsecurity); + new_record_repl[Anum_pg_database_datcatalogsecurity - 1] = true; + } newtuple = heap_modify_tuple(tuple, RelationGetDescr(rel), new_record, new_record_nulls, new_record_repl); *** a/src/backend/commands/policy.c --- b/src/backend/commands/policy.c *************** *** 22,31 **** --- 22,83 ---- #include "catalog/indexing.h" #include "catalog/namespace.h" #include "catalog/objectaccess.h" + #include "catalog/pg_aggregate.h" + #include "catalog/pg_am.h" + #include "catalog/pg_amop.h" + #include "catalog/pg_amproc.h" + #include "catalog/pg_attrdef.h" + #include "catalog/pg_attribute.h" + #include "catalog/pg_auth_members.h" #include "catalog/pg_authid.h" + #include "catalog/pg_cast.h" + #include "catalog/pg_class.h" + #include "catalog/pg_collation.h" + #include "catalog/pg_constraint.h" + #include "catalog/pg_conversion.h" + #include "catalog/pg_database.h" + #include "catalog/pg_db_role_setting.h" + #include "catalog/pg_default_acl.h" + #include "catalog/pg_depend.h" + #include "catalog/pg_description.h" + #include "catalog/pg_enum.h" + #include "catalog/pg_event_trigger.h" + #include "catalog/pg_extension.h" + #include "catalog/pg_foreign_data_wrapper.h" + #include "catalog/pg_foreign_server.h" + #include "catalog/pg_foreign_table.h" + #include "catalog/pg_index.h" + #include "catalog/pg_inherits.h" + #include "catalog/pg_language.h" + #include "catalog/pg_largeobject.h" + #include "catalog/pg_largeobject_metadata.h" + #include "catalog/pg_namespace.h" + #include "catalog/pg_opclass.h" + #include "catalog/pg_operator.h" + #include "catalog/pg_opfamily.h" + #include "catalog/pg_pltemplate.h" #include "catalog/pg_policy.h" + #include "catalog/pg_proc.h" + #include "catalog/pg_range.h" + #include "catalog/pg_replication_origin.h" + #include "catalog/pg_rewrite.h" + #include "catalog/pg_seclabel.h" + #include "catalog/pg_shdepend.h" + #include "catalog/pg_shdescription.h" + #include "catalog/pg_shseclabel.h" + #include "catalog/pg_statistic.h" + #include "catalog/pg_tablespace.h" + #include "catalog/pg_transform.h" + #include "catalog/pg_trigger.h" + #include "catalog/pg_ts_config.h" + #include "catalog/pg_ts_config_map.h" + #include "catalog/pg_ts_dict.h" + #include "catalog/pg_ts_parser.h" + #include "catalog/pg_ts_template.h" #include "catalog/pg_type.h" + #include "catalog/pg_user_mapping.h" #include "commands/policy.h" + #include "executor/spi.h" #include "miscadmin.h" #include "nodes/makefuncs.h" #include "nodes/pg_list.h" *************** *** 44,56 **** --- 96,117 ---- #include "utils/lsyscache.h" #include "utils/memutils.h" #include "utils/rel.h" + #include "utils/snapmgr.h" #include "utils/syscache.h" + #define CATALOG_POLICY_STRING_SIZE 2000 + static void RangeVarCallbackForPolicy(const RangeVar *rv, Oid relid, Oid oldrelid, void *arg); static char parse_policy_command(const char *cmd_name); static Datum *policy_role_list_to_array(List *roles, int *num_roles); + static bool generate_catalog_create_policy_string(HeapTuple cache_tuple, char *buf); + static bool generate_catalog_drop_policy_string(HeapTuple cache_tuple, char *buf); + + /* variable to identify whether pg_policy relcache is built or not? */ + bool policyRelcacheBuilt = false; + /* * Callback to RangeVarGetRelidExtended(). * *************** *** 194,199 **** RelationBuildRowSecurity(Relation relation) --- 255,272 ---- MemoryContext oldcxt = CurrentMemoryContext; RowSecurityDesc *volatile rsdesc = NULL; + /* */ + if (!criticalRelcachesBuilt || !criticalSharedRelcachesBuilt) + return; + + if (relation->rd_id == PolicyRelationId) + { + if (policyRelcacheBuilt) + return; + else + policyRelcacheBuilt = true; + } + /* * Create a memory context to hold everything associated with this * relation's row security policy. This makes it easy to clean up during *************** *** 325,330 **** RelationBuildRowSecurity(Relation relation) --- 398,410 ---- /* Delete rscxt, first making sure it isn't active */ MemoryContextSwitchTo(oldcxt); MemoryContextDelete(rscxt); + + if (relation->rd_id == PolicyRelationId) + { + Assert(policyRelcacheBuilt == true); + policyRelcacheBuilt = false; + } + PG_RE_THROW(); } PG_END_TRY(); *************** *** 407,412 **** RemovePolicyById(Oid policy_id) --- 487,1040 ---- heap_close(pg_policy_rel, RowExclusiveLock); } + static bool + generate_catalog_create_policy_string(HeapTuple tuple, char *buf) + { + bool can_create_policy = false; + Form_pg_class pg_class_tuple; + Oid relationid; + + pg_class_tuple = (Form_pg_class)GETSTRUCT(tuple); + relationid = HeapTupleGetOid(tuple); + + if (IsSharedRelation(relationid)) + return can_create_policy; + + switch (relationid) + { + /* + * Following catalog tables data is accessible to all roles. + * So they doesn't need any speicific RLS policies on them. + */ + case AggregateRelationId: + case AccessMethodRelationId: + case AccessMethodOperatorRelationId: + case AccessMethodProcedureRelationId: + break; + case AttrDefaultRelationId: + sprintf(buf, "create policy %s_read_own_data on %s for select using" + " (has_column_privilege(adrelid, adnum,'any'))", + pg_class_tuple->relname.data, pg_class_tuple->relname.data); + can_create_policy = true; + break; + case AttributeRelationId: + sprintf(buf, "create policy %s_read_own_data on %s for select using" + " (has_column_privilege(attrelid, attnum,'any'))", + pg_class_tuple->relname.data, pg_class_tuple->relname.data); + can_create_policy = true; + break; + case CastRelationId: + sprintf(buf, "create policy %s_read_own_data on %s for select using" + " ((oid < 16384) OR has_cast_privilege(oid, 'any'))", + pg_class_tuple->relname.data, pg_class_tuple->relname.data); + can_create_policy = true; + break; + case RelationRelationId: + sprintf(buf, "create policy %s_read_own_data on %s for select using" + " ((oid < 16384) OR has_table_privilege(oid,'any'))", + pg_class_tuple->relname.data, pg_class_tuple->relname.data); + can_create_policy = true; + break; + case CollationRelationId: + /*sprintf(buf, "create policy %s_read_own_data on %s for select using" + " ((oid < 16384) OR (pg_get_userbyid(collowner) = current_user))", + pg_class_tuple->relname.data, pg_class_tuple->relname.data); + can_create_policy = true;*/ + break; + case ConstraintRelationId: + sprintf(buf, "create policy %s_read_own_data on %s for select using" + " ((oid < 16384) OR has_constraint_privilege(oid,'any'))", + pg_class_tuple->relname.data, pg_class_tuple->relname.data); + can_create_policy = true; + break; + case ConversionRelationId: + /*sprintf(buf, "create policy %s_read_own_data on %s for select using" + " ((oid < 16384) OR (pg_get_userbyid(conowner) = current_user))", + pg_class_tuple->relname.data, pg_class_tuple->relname.data); + can_create_policy = true;*/ + break; + case DefaultAclRelationId: + sprintf(buf, "create policy %s_read_own_data on %s for select using" + " (pg_get_userbyid(defaclrole) = current_user)", + pg_class_tuple->relname.data, pg_class_tuple->relname.data); + can_create_policy = true; + break; + case DependRelationId: + sprintf(buf, "create policy %s_read_own_data on %s for select using" + " ((classid = 2600)" + " OR (classid = 2605 AND has_cast_privilege(objid, 'any'))" + " OR (classid = 3456)" + " OR (classid = 1249 AND has_column_privilege(objid, objsubid::smallint, 'any'))" + " OR (classid = 2606 AND has_constraint_privilege(objid, 'any'))" + " OR (classid = 2607)" + " OR (classid = 1262 AND has_database_privilege(objid, 'any'))" + " OR (classid = 1247 AND has_type_privilege(objid, 'any'))" + " OR (classid = 3079)" + " OR (classid = 3466)" + " OR (classid = 2328 AND has_foreign_data_wrapper_privilege(objid, 'any'))" + " OR (classid = 1259 AND has_table_privilege(objid,'any'))" + " OR (classid = 1255 AND has_function_privilege(objid, 'any'))" + " OR (classid = 2601)" + " OR (classid = 2602)" + " OR (classid = 2603)" + " OR (classid = 2604 AND has_column_default_privilege(objid, 'any'))" + " OR (classid = 2612 AND has_language_privilege(objid, 'any'))" + " OR (classid = 2613)" + " OR (classid = 2616)" + " OR (classid = 2617)" + " OR (classid = 2753)" + " OR (classid = 3501)" + " OR (classid = 3466)" + " OR (classid = 3079)" + " OR (classid = 3256 AND has_policy_privilege(objid, 'any'))" + " OR (classid = 1260 AND pg_has_role(objid, 'any'))" + " OR (classid = 2618)" + " OR (classid = 2615 AND has_schema_privilege(objid, 'any'))" + " OR (classid = 1417 AND has_server_privilege(objid, 'any'))" + " OR (classid = 1213 AND has_tablespace_privilege(objid, 'any'))" + " OR (classid = 3600)" + " OR (classid = 3601)" + " OR (classid = 3602)" + " OR (classid = 3764)" + " OR (classid = 3576)" + " OR (classid = 2620 AND has_trigger_privilege(objid, 'any'))" + " OR (classid = 1418 AND has_user_mapping_privilege(objid, 'any')))", + pg_class_tuple->relname.data, pg_class_tuple->relname.data); + can_create_policy = true; + break; + case DescriptionRelationId: + sprintf(buf, "create policy %s_read_own_data on %s for select using" + " ((classoid = 2600)" + " OR (classoid = 2605 AND has_cast_privilege(objoid, 'any'))" + " OR (classoid = 3456)" + " OR (classoid = 1249 AND has_column_privilege(objoid, objsubid::smallint, 'any'))" + " OR (classoid = 2606 AND has_constraint_privilege(objoid, 'any'))" + " OR (classoid = 2607)" + " OR (classoid = 1262 AND has_database_privilege(objoid, 'any'))" + " OR (classoid = 1247 AND has_type_privilege(objoid, 'any'))" + " OR (classoid = 3079)" + " OR (classoid = 3466)" + " OR (classoid = 2328 AND has_foreign_data_wrapper_privilege(objoid, 'any'))" + " OR (classoid = 1259 AND has_table_privilege(objoid,'any'))" + " OR (classoid = 1255 AND has_function_privilege(objoid, 'any'))" + " OR (classoid = 2613)" + " OR (classoid = 2616)" + " OR (classoid = 2617)" + " OR (classoid = 2753)" + " OR (classoid = 2995)" + " OR (classoid = 3256 AND has_policy_privilege(objoid, 'any'))" + " OR (classoid = 1260 AND pg_has_role(objoid, 'any'))" + " OR (classoid = 2618)" + " OR (classoid = 2615 AND has_schema_privilege(objoid, 'any'))" + " OR (classoid = 1417 AND has_server_privilege(objoid, 'any'))" + " OR (classoid = 1213 AND has_tablespace_privilege(objoid, 'any'))" + " OR (classoid = 3541)" + " OR (classoid = 3600)" + " OR (classoid = 3601)" + " OR (classoid = 3602)" + " OR (classoid = 3764)" + " OR (classoid = 3576)" + " OR (classoid = 2620 AND has_trigger_privilege(objoid, 'any')))", + pg_class_tuple->relname.data, pg_class_tuple->relname.data); + can_create_policy = true; + break; + case EnumRelationId: + break; + case EventTriggerRelationId: + break; + case ExtensionRelationId: + break; + case ForeignDataWrapperRelationId: + sprintf(buf, "create policy %s_read_own_data on %s for select using" + " ((oid < 16384) OR has_foreign_data_wrapper_privilege(oid,'any'))", + pg_class_tuple->relname.data, pg_class_tuple->relname.data); + can_create_policy = true; + break; + case ForeignServerRelationId: + sprintf(buf, "create policy %s_read_own_data on %s for select using" + " ((oid < 16384) OR has_server_privilege(oid,'any'))", + pg_class_tuple->relname.data, pg_class_tuple->relname.data); + can_create_policy = true; + break; + case ForeignTableRelationId: + sprintf(buf, "create policy %s_read_own_data on %s for select using" + " (has_table_privilege(ftrelid, 'any'))", + pg_class_tuple->relname.data, pg_class_tuple->relname.data); + can_create_policy = true; + break; + case IndexRelationId: + sprintf(buf, "create policy %s_read_own_data on %s for select using" + " (has_table_privilege(indrelid, 'any'))", + pg_class_tuple->relname.data, pg_class_tuple->relname.data); + can_create_policy = true; + break; + case InheritsRelationId: + sprintf(buf, "create policy %s_read_own_data on %s for select using" + " (has_table_privilege(inhrelid, 'any'))", + pg_class_tuple->relname.data, pg_class_tuple->relname.data); + can_create_policy = true; + break; + case LanguageRelationId: + sprintf(buf, "create policy %s_read_own_data on %s for select using" + " ((oid < 16384) OR has_language_privilege(oid, 'any'))", + pg_class_tuple->relname.data, pg_class_tuple->relname.data); + can_create_policy = true; + break; + case LargeObjectRelationId: + break; + case LargeObjectMetadataRelationId: + break; + case NamespaceRelationId: + sprintf(buf, "create policy %s_read_own_data on %s for select using" + " ((oid < 16384) OR has_schema_privilege(oid, 'any'))", + pg_class_tuple->relname.data, pg_class_tuple->relname.data); + can_create_policy = true; + break; + case OperatorClassRelationId: + /*sprintf(buf, "create policy %s_read_own_data on %s for select using" + " ((oid < 16384) OR (pg_get_userbyid(opcowner) = current_user))", + pg_class_tuple->relname.data, pg_class_tuple->relname.data); + can_create_policy = true;*/ + break; + case OperatorRelationId: + /*sprintf(buf, "create policy %s_read_own_data on %s for select using" + " ((oid < 16384) OR (pg_get_userbyid(oprowner) = current_user))", + pg_class_tuple->relname.data, pg_class_tuple->relname.data); + can_create_policy = true;*/ + break; + case OperatorFamilyRelationId: + /*sprintf(buf, "create policy %s_read_own_data on %s for select using" + " ((oid < 16384) OR (pg_get_userbyid(opfowner) = current_user))", + pg_class_tuple->relname.data, pg_class_tuple->relname.data); + can_create_policy = true;*/ + break; + case PolicyRelationId: + sprintf(buf, "create policy %s_read_own_data on %s for select using" + " ((oid < 16384) OR has_table_privilege(polrelid, 'any'))", + pg_class_tuple->relname.data, pg_class_tuple->relname.data); + can_create_policy = true; + break; + case ProcedureRelationId: + sprintf(buf, "create policy %s_read_own_data on %s for select using" + " ((oid < 16384) OR has_function_privilege(oid, 'any'))", + pg_class_tuple->relname.data, pg_class_tuple->relname.data); + can_create_policy = true; + break; + case RangeRelationId: + break; + case RewriteRelationId: + break; + case SecLabelRelationId: + sprintf(buf, "create policy %s_read_own_data on %s for select using" + " ((classoid = 2600)" + " OR (classoid = 1249 AND has_column_privilege(objoid, objsubid::smallint, 'any'))" + " OR (classoid = 1262 AND has_database_privilege(objoid, 'any'))" + " OR (classoid = 1247 AND has_type_privilege(objoid, 'any'))" + " OR (classoid = 1259 AND has_table_privilege(objoid,'any'))" + " OR (classoid = 1255 AND has_function_privilege(objoid, 'any'))" + " OR (classoid = 2612 AND has_language_privilege(objoid, 'any'))" + " OR (classoid = 1260 AND pg_has_role(objoid, 'any'))" + " OR (classoid = 2613)" + " OR (classoid = 3466)" + " OR (classoid = 1213 AND has_tablespace_privilege(objoid, 'any')))", + pg_class_tuple->relname.data, pg_class_tuple->relname.data); + can_create_policy = true; + break; + case StatisticRelationId: + sprintf(buf, "create policy %s_read_own_data on %s for select using" + " ((starelid < 16384) OR has_column_privilege(starelid, staattnum, 'any'))", + pg_class_tuple->relname.data, pg_class_tuple->relname.data); + can_create_policy = true; + break; + case TransformRelationId: + break; + case TriggerRelationId: + sprintf(buf, "create policy %s_read_own_data on %s for select using" + " ((oid < 16384) OR has_table_privilege(tgrelid, 'any'))", + pg_class_tuple->relname.data, pg_class_tuple->relname.data); + can_create_policy = true; + break; + case TSConfigRelationId: + break; + case TSConfigMapRelationId: + break; + case TSDictionaryRelationId: + break; + case TSParserRelationId: + break; + case TSTemplateRelationId: + break; + case TypeRelationId: + sprintf(buf, "create policy %s_read_own_data on %s for select using" + " ((oid < 16384) OR has_type_privilege(oid, 'any'))", + pg_class_tuple->relname.data, pg_class_tuple->relname.data); + can_create_policy = true; + break; + case UserMappingRelationId: + sprintf(buf, "create policy %s_read_own_data on %s for select using" + " ((oid < 16384) OR pg_has_role(umuser, 'any')" + " OR has_server_privilege(umserver, 'any'))", + pg_class_tuple->relname.data, pg_class_tuple->relname.data); + can_create_policy = true; + break; + default: + can_create_policy = false; + break; + } + + return can_create_policy; + } + + static bool + generate_catalog_drop_policy_string(HeapTuple tuple, char *buf) + { + bool can_create_policy = false; + Form_pg_class pg_class_tuple; + Oid relationid; + + pg_class_tuple = (Form_pg_class)GETSTRUCT(tuple); + relationid = HeapTupleGetOid(tuple); + + if (IsSharedRelation(relationid)) + return can_create_policy; + + switch (relationid) + { + /* + * Following catalog tables data is accessible to all roles. + * So they doesn't need any speicific RLS policies on them. + */ + case AggregateRelationId: + case AccessMethodRelationId: + case AccessMethodOperatorRelationId: + case AccessMethodProcedureRelationId: + case CollationRelationId: + case ConversionRelationId: + case EnumRelationId: + case EventTriggerRelationId: + case ExtensionRelationId: + case LargeObjectRelationId: + case LargeObjectMetadataRelationId: + case OperatorClassRelationId: + case OperatorRelationId: + case OperatorFamilyRelationId: + case RangeRelationId: + case RewriteRelationId: + case TransformRelationId: + case TSConfigRelationId: + case TSConfigMapRelationId: + case TSDictionaryRelationId: + case TSParserRelationId: + case TSTemplateRelationId: + break; + + case AttrDefaultRelationId: + case AttributeRelationId: + case CastRelationId: + case RelationRelationId: + case ConstraintRelationId: + case DefaultAclRelationId: + case DependRelationId: + case DescriptionRelationId: + case ForeignDataWrapperRelationId: + case ForeignServerRelationId: + case ForeignTableRelationId: + case IndexRelationId: + case InheritsRelationId: + case LanguageRelationId: + case NamespaceRelationId: + case PolicyRelationId: + case ProcedureRelationId: + case SecLabelRelationId: + case StatisticRelationId: + case TriggerRelationId: + case TypeRelationId: + case UserMappingRelationId: + sprintf(buf, "drop policy %s_read_own_data on %s", + pg_class_tuple->relname.data, pg_class_tuple->relname.data); + can_create_policy = true; + break; + + default: + can_create_policy = false; + break; + } + + return can_create_policy; + } + + /* + * CreateCatalogPolicy - + * handles the execution of the ALTER DATBASE ROW LEVEL SECUTIRY = TRUE command. + */ + void + CreateCatalogPolicy() + { + Relation rel; + ScanKeyData scankey; + SysScanDesc scan; + HeapTuple tuple; + bool allow_sytem_table_mods_old; + char *buf; + + /* + * Get all catalog relations from pg_class system table and + * enable the row level security along with the catalog policy + * command. + */ + SPI_connect(); + PushActiveSnapshot(GetTransactionSnapshot()); + + rel = heap_open(RelationRelationId, RowExclusiveLock); + ScanKeyInit(&scankey, + ObjectIdAttributeNumber, + BTLessStrategyNumber, F_OIDLT, + ObjectIdGetDatum(FirstNormalObjectId)); + scan = systable_beginscan(rel, ClassOidIndexId, true, + NULL, 1, &scankey); + + buf = palloc(CATALOG_POLICY_STRING_SIZE); + allow_sytem_table_mods_old = allowSystemTableMods; + allowSystemTableMods = true; + + PG_TRY(); + { + while ((tuple = systable_getnext(scan)) != NULL) + { + int ret; + HeapTuple cache_tuple; + Form_pg_class pg_class_tuple; + bool can_create_policy; + + pg_class_tuple = (Form_pg_class)GETSTRUCT(tuple); + if (pg_class_tuple->relkind != RELKIND_RELATION) + continue; + + cache_tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(HeapTupleGetOid(tuple))); + + if (!HeapTupleIsValid(cache_tuple)) + elog(ERROR, "cache lookup failed for relation %u", HeapTupleGetOid(tuple)); + + can_create_policy = generate_catalog_create_policy_string(cache_tuple, buf); + if (!can_create_policy) + { + heap_freetuple(cache_tuple); + continue; + } + + ret = SPI_execute(buf, false, 0); + if (ret != SPI_OK_UTILITY) + elog(ERROR, "Creating policy failed : error code %d", ret); + + ((Form_pg_class)GETSTRUCT(cache_tuple))->relrowsecurity = true; + heap_inplace_update(rel, cache_tuple); + + heap_freetuple(cache_tuple); + } + } + PG_CATCH(); + { + allowSystemTableMods = allow_sytem_table_mods_old; + PG_RE_THROW(); + } + PG_END_TRY(); + + allowSystemTableMods = allow_sytem_table_mods_old; + pfree(buf); + + systable_endscan(scan); + heap_close(rel, NoLock); + + SPI_finish(); + PopActiveSnapshot(); + } + + /* + * RemoveCatalogPolicy - + * handles the execution of the ALTER DATBASE ROW LEVEL SECUTIRY = FALSE command. + */ + void + RemoveCatalogPolicy() + { + Relation rel; + ScanKeyData scankey; + SysScanDesc scan; + HeapTuple tuple; + Form_pg_class pg_class_tuple; + bool allow_sytem_table_mods_old; + char *buf; + + /* + * Get all catalog relations from pg_class system table and + * enable the row level security along with the catalog policy + * command. + */ + SPI_connect(); + PushActiveSnapshot(GetTransactionSnapshot()); + + rel = heap_open(RelationRelationId, RowExclusiveLock); + ScanKeyInit(&scankey, + ObjectIdAttributeNumber, + BTLessStrategyNumber, F_OIDLT, + ObjectIdGetDatum(FirstNormalObjectId)); + scan = systable_beginscan(rel, ClassOidIndexId, true, + NULL, 1, &scankey); + + buf = palloc(CATALOG_POLICY_STRING_SIZE); + + allow_sytem_table_mods_old = allowSystemTableMods; + allowSystemTableMods = true; + + PG_TRY(); + { + while ((tuple = systable_getnext(scan)) != NULL) + { + int ret; + HeapTuple cache_tuple; + bool can_drop_policy; + + pg_class_tuple = (Form_pg_class)GETSTRUCT(tuple); + if (pg_class_tuple->relkind != RELKIND_RELATION) + continue; + + cache_tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(HeapTupleGetOid(tuple))); + if (!HeapTupleIsValid(cache_tuple)) + elog(ERROR, "cache lookup failed for relation %u", HeapTupleGetOid(tuple)); + + can_drop_policy = generate_catalog_drop_policy_string(cache_tuple, buf); + if (!can_drop_policy) + { + heap_freetuple(cache_tuple); + continue; + } + + ret = SPI_execute(buf, false, 0); + if (ret != SPI_OK_UTILITY) + elog(ERROR, "Creating policy failed : error code %d", ret); + + ((Form_pg_class)GETSTRUCT(cache_tuple))->relrowsecurity = false; + heap_inplace_update(rel, cache_tuple); + + heap_freetuple(cache_tuple); + } + } + PG_CATCH(); + { + allowSystemTableMods = allow_sytem_table_mods_old; + PG_RE_THROW(); + } + PG_END_TRY(); + + allowSystemTableMods = allow_sytem_table_mods_old; + + pfree(buf); + systable_endscan(scan); + heap_close(rel, NoLock); + + SPI_finish(); + PopActiveSnapshot(); + } + /* * CreatePolicy - * handles the execution of the CREATE POLICY command. *** a/src/backend/commands/tablecmds.c --- b/src/backend/commands/tablecmds.c *************** *** 3436,3441 **** ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode) --- 3436,3450 ---- { AlteredTableInfo *tab = (AlteredTableInfo *) lfirst(ltab); + /* + * ALTER table on sytem catalog tables is possible only when user specifies + * CATALOG SECURITY on system catalog tables. To avoid the error in the + * AlterTableCreateToastTable function for system catalog tables, the system + * catalog tables are ignored for the toast table creation. + */ + if (!IsUnderPostmaster && IsSharedRelation(tab->relid)) + continue; + if (tab->relkind == RELKIND_RELATION || tab->relkind == RELKIND_MATVIEW) AlterTableCreateToastTable(tab->relid, (Datum) 0, lockmode); *** a/src/backend/parser/gram.y --- b/src/backend/parser/gram.y *************** *** 8881,8886 **** createdb_opt_name: --- 8881,8887 ---- | OWNER { $$ = pstrdup($1); } | TABLESPACE { $$ = pstrdup($1); } | TEMPLATE { $$ = pstrdup($1); } + | CATALOG_P SECURITY { $$ = pstrdup("catalog_security"); } ; /* *** a/src/backend/utils/adt/acl.c --- b/src/backend/utils/adt/acl.c *************** *** 17,27 **** #include #include "access/htup_details.h" #include "catalog/namespace.h" #include "catalog/pg_authid.h" #include "catalog/pg_auth_members.h" ! #include "catalog/pg_type.h" #include "catalog/pg_class.h" #include "commands/dbcommands.h" #include "commands/proclang.h" #include "commands/tablespace.h" --- 17,35 ---- #include #include "access/htup_details.h" + #include "access/sysattr.h" + #include "catalog/indexing.h" #include "catalog/namespace.h" #include "catalog/pg_authid.h" #include "catalog/pg_auth_members.h" ! #include "catalog/pg_attrdef.h" ! #include "catalog/pg_cast.h" #include "catalog/pg_class.h" + #include "catalog/pg_constraint.h" + #include "catalog/pg_policy.h" + #include "catalog/pg_trigger.h" + #include "catalog/pg_type.h" + #include "catalog/pg_user_mapping.h" #include "commands/dbcommands.h" #include "commands/proclang.h" #include "commands/tablespace.h" *************** *** 31,36 **** --- 39,45 ---- #include "utils/acl.h" #include "utils/builtins.h" #include "utils/catcache.h" + #include "utils/fmgroids.h" #include "utils/inval.h" #include "utils/lsyscache.h" #include "utils/memutils.h" *************** *** 2031,2036 **** convert_table_priv_string(text *priv_type_text) --- 2040,2050 ---- {"REFERENCES WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_REFERENCES)}, {"TRIGGER", ACL_TRIGGER}, {"TRIGGER WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_TRIGGER)}, + {"ANY", ACL_SELECT | ACL_GRANT_OPTION_FOR(ACL_SELECT) | + ACL_INSERT | ACL_GRANT_OPTION_FOR(ACL_INSERT) | ACL_UPDATE | + ACL_GRANT_OPTION_FOR(ACL_UPDATE) | ACL_DELETE | ACL_GRANT_OPTION_FOR(ACL_DELETE) | + ACL_TRUNCATE | ACL_GRANT_OPTION_FOR(ACL_TRUNCATE) | ACL_REFERENCES | + ACL_GRANT_OPTION_FOR(ACL_REFERENCES) | ACL_TRIGGER | ACL_GRANT_OPTION_FOR(ACL_TRIGGER)}, {"RULE", 0}, /* ignore old RULE privileges */ {"RULE WITH GRANT OPTION", 0}, {NULL, 0} *************** *** 2243,2248 **** convert_sequence_priv_string(text *priv_type_text) --- 2257,2263 ---- {"USAGE", ACL_USAGE}, {"SELECT", ACL_SELECT}, {"UPDATE", ACL_UPDATE}, + {"ANY", ACL_USAGE | ACL_SELECT | ACL_UPDATE}, {NULL, 0} }; *************** *** 2858,2863 **** convert_column_priv_string(text *priv_type_text) --- 2873,2881 ---- {"UPDATE WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_UPDATE)}, {"REFERENCES", ACL_REFERENCES}, {"REFERENCES WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_REFERENCES)}, + {"ANY", ACL_SELECT | ACL_GRANT_OPTION_FOR(ACL_SELECT) | ACL_INSERT | + ACL_GRANT_OPTION_FOR(ACL_INSERT) | ACL_GRANT_OPTION_FOR(ACL_INSERT) | ACL_UPDATE | + ACL_GRANT_OPTION_FOR(ACL_UPDATE) | ACL_REFERENCES | ACL_GRANT_OPTION_FOR(ACL_REFERENCES)}, {NULL, 0} }; *************** *** 3055,3060 **** convert_database_priv_string(text *priv_type_text) --- 3073,3081 ---- {"TEMP WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_CREATE_TEMP)}, {"CONNECT", ACL_CONNECT}, {"CONNECT WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_CONNECT)}, + {"ANY", ACL_CREATE | ACL_GRANT_OPTION_FOR(ACL_CREATE) | + ACL_CREATE_TEMP | ACL_GRANT_OPTION_FOR(ACL_CREATE_TEMP) | ACL_CREATE_TEMP | + ACL_GRANT_OPTION_FOR(ACL_CREATE_TEMP) | ACL_CONNECT | ACL_GRANT_OPTION_FOR(ACL_CONNECT)}, {NULL, 0} }; *************** *** 3238,3243 **** convert_foreign_data_wrapper_priv_string(text *priv_type_text) --- 3259,3265 ---- static const priv_map foreign_data_wrapper_priv_map[] = { {"USAGE", ACL_USAGE}, {"USAGE WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_USAGE)}, + {"ANY", ACL_USAGE | ACL_GRANT_OPTION_FOR(ACL_USAGE)}, {NULL, 0} }; *************** *** 3438,3443 **** convert_function_priv_string(text *priv_type_text) --- 3460,3466 ---- static const priv_map function_priv_map[] = { {"EXECUTE", ACL_EXECUTE}, {"EXECUTE WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_EXECUTE)}, + {"ANY", ACL_EXECUTE | ACL_GRANT_OPTION_FOR(ACL_EXECUTE)}, {NULL, 0} }; *************** *** 3629,3634 **** convert_language_priv_string(text *priv_type_text) --- 3652,3658 ---- static const priv_map language_priv_map[] = { {"USAGE", ACL_USAGE}, {"USAGE WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_USAGE)}, + {"ANY", ACL_USAGE | ACL_GRANT_OPTION_FOR(ACL_USAGE)}, {NULL, 0} }; *************** *** 3822,3827 **** convert_schema_priv_string(text *priv_type_text) --- 3846,3852 ---- {"CREATE WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_CREATE)}, {"USAGE", ACL_USAGE}, {"USAGE WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_USAGE)}, + {"ANY", ACL_CREATE | ACL_GRANT_OPTION_FOR(ACL_CREATE) | ACL_USAGE | ACL_GRANT_OPTION_FOR(ACL_USAGE)}, {NULL, 0} }; *************** *** 4004,4009 **** convert_server_priv_string(text *priv_type_text) --- 4029,4035 ---- static const priv_map server_priv_map[] = { {"USAGE", ACL_USAGE}, {"USAGE WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_USAGE)}, + {"ANY", ACL_USAGE | ACL_GRANT_OPTION_FOR(ACL_USAGE)}, {NULL, 0} }; *************** *** 4186,4191 **** convert_tablespace_priv_string(text *priv_type_text) --- 4212,4218 ---- static const priv_map tablespace_priv_map[] = { {"CREATE", ACL_CREATE}, {"CREATE WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_CREATE)}, + {"ANY", ACL_CREATE | ACL_GRANT_OPTION_FOR(ACL_CREATE)}, {NULL, 0} }; *************** *** 4385,4390 **** convert_type_priv_string(text *priv_type_text) --- 4412,4418 ---- static const priv_map type_priv_map[] = { {"USAGE", ACL_USAGE}, {"USAGE WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_USAGE)}, + {"ANY", ACL_USAGE | ACL_GRANT_OPTION_FOR(ACL_USAGE)}, {NULL, 0} }; *************** *** 4566,4571 **** convert_role_priv_string(text *priv_type_text) --- 4594,4600 ---- {"USAGE WITH ADMIN OPTION", ACL_GRANT_OPTION_FOR(ACL_CREATE)}, {"MEMBER WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_CREATE)}, {"MEMBER WITH ADMIN OPTION", ACL_GRANT_OPTION_FOR(ACL_CREATE)}, + {"ANY", ACL_USAGE | ACL_CREATE | ACL_GRANT_OPTION_FOR(ACL_CREATE)}, {NULL, 0} }; *************** *** 5247,5249 **** get_rolespec_name(const Node *node) --- 5276,5568 ---- return rolename; } + + /* + * has_cast_privilege_id + * Check user privileges on a cast given + * cast oid, and text priv name. + */ + Datum + has_cast_privilege_id(PG_FUNCTION_ARGS) + { + Oid castoid = PG_GETARG_OID(0); + text *priv_type_text = PG_GETARG_TEXT_P(1); + Oid roleid; + AclMode mode; + AclResult aclresult1; + AclResult aclresult2; + Relation castDesc; + ScanKeyData skey[1]; + SysScanDesc rcscan; + HeapTuple tup; + Form_pg_cast castForm; + + roleid = GetUserId(); + mode = convert_server_priv_string(priv_type_text); + + castDesc = heap_open(CastRelationId, AccessShareLock); + + ScanKeyInit(&skey[0], + ObjectIdAttributeNumber, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(castoid)); + + rcscan = systable_beginscan(castDesc, CastOidIndexId, true, + NULL, 1, skey); + + tup = systable_getnext(rcscan); + + if (!HeapTupleIsValid(tup)) + { + systable_endscan(rcscan); + heap_close(castDesc, AccessShareLock); + PG_RETURN_NULL(); + } + + castForm = (Form_pg_cast)GETSTRUCT(tup); + + aclresult1 = pg_type_aclcheck(castForm->castsource, roleid, mode); + aclresult2 = pg_type_aclcheck(castForm->casttarget, roleid, mode); + + systable_endscan(rcscan); + heap_close(castDesc, AccessShareLock); + PG_RETURN_BOOL((aclresult1 == ACLCHECK_OK) || (aclresult2 == ACLCHECK_OK)); + } + + /* + * has_constraint_privilege_id + * Check user privileges on a constraint given + * constraint oid, and text priv name. + */ + Datum + has_constraint_privilege_id(PG_FUNCTION_ARGS) + { + Oid constraintoid = PG_GETARG_OID(0); + text *priv_type_text = PG_GETARG_TEXT_P(1); + Oid roleid; + AclMode mode; + AclResult aclresult; + Relation constraintDesc; + ScanKeyData skey[1]; + SysScanDesc rcscan; + HeapTuple tup; + Form_pg_constraint constraintForm; + + roleid = GetUserId(); + mode = convert_server_priv_string(priv_type_text); + + constraintDesc = heap_open(ConstraintRelationId, AccessShareLock); + + ScanKeyInit(&skey[0], + ObjectIdAttributeNumber, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(constraintoid)); + + rcscan = systable_beginscan(constraintDesc, ConstraintOidIndexId, true, + NULL, 1, skey); + + tup = systable_getnext(rcscan); + + if (!HeapTupleIsValid(tup)) + { + systable_endscan(rcscan); + heap_close(constraintDesc, AccessShareLock); + PG_RETURN_NULL(); + } + + constraintForm = (Form_pg_constraint)GETSTRUCT(tup); + + if (constraintForm->contypid) + aclresult = pg_type_aclcheck(constraintForm->contypid, roleid, mode); + else + aclresult = pg_class_aclcheck(constraintForm->conrelid, roleid, mode); + + systable_endscan(rcscan); + heap_close(constraintDesc, AccessShareLock); + PG_RETURN_BOOL(aclresult == ACLCHECK_OK); + } + + /* + * has_column_default_privilege_id + * Check user privileges on a column default given + * attrdefault oid, and text priv name. + */ + Datum + has_column_default_privilege_id(PG_FUNCTION_ARGS) + { + Oid attrdefaulttoid = PG_GETARG_OID(0); + text *priv_type_text = PG_GETARG_TEXT_P(1); + Oid roleid; + AclMode mode; + int privresult; + Relation attrDefaultDesc; + ScanKeyData skey[1]; + SysScanDesc rcscan; + HeapTuple tup; + Form_pg_attrdef attrDefForm; + + roleid = GetUserId(); + mode = convert_server_priv_string(priv_type_text); + + attrDefaultDesc = heap_open(AttrDefaultRelationId, AccessShareLock); + + ScanKeyInit(&skey[0], + ObjectIdAttributeNumber, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(attrdefaulttoid)); + + rcscan = systable_beginscan(attrDefaultDesc, AttrDefaultOidIndexId, true, + NULL, 1, skey); + + tup = systable_getnext(rcscan); + + if (!HeapTupleIsValid(tup)) + { + systable_endscan(rcscan); + heap_close(attrDefaultDesc, AccessShareLock); + PG_RETURN_NULL(); + } + + attrDefForm = (Form_pg_attrdef)GETSTRUCT(tup); + + privresult = column_privilege_check(attrDefForm->adrelid, attrDefForm->adnum, roleid, mode); + + systable_endscan(rcscan); + heap_close(attrDefaultDesc, AccessShareLock); + + if (privresult < 0) + PG_RETURN_NULL(); + PG_RETURN_BOOL(privresult); + } + + /* + * has_policy_privilege_id + * Check user privileges on a policy given + * policy oid, and text priv name. + */ + Datum + has_policy_privilege_id(PG_FUNCTION_ARGS) + { + Oid policyoid = PG_GETARG_OID(0); + text *priv_type_text = PG_GETARG_TEXT_P(1); + Oid roleid; + AclMode mode; + AclResult aclresult; + Relation policyDesc; + ScanKeyData skey[1]; + SysScanDesc rcscan; + HeapTuple tup; + Form_pg_policy policyForm; + + roleid = GetUserId(); + mode = convert_server_priv_string(priv_type_text); + + policyDesc = heap_open(PolicyRelationId, AccessShareLock); + + ScanKeyInit(&skey[0], + ObjectIdAttributeNumber, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(policyoid)); + + rcscan = systable_beginscan(policyDesc, PolicyOidIndexId, true, + NULL, 1, skey); + + tup = systable_getnext(rcscan); + + if (!HeapTupleIsValid(tup)) + { + systable_endscan(rcscan); + heap_close(policyDesc, AccessShareLock); + PG_RETURN_NULL(); + } + + policyForm = (Form_pg_policy)GETSTRUCT(tup); + + aclresult = pg_class_aclcheck(policyForm->polrelid, roleid, mode); + + systable_endscan(rcscan); + heap_close(policyDesc, AccessShareLock); + PG_RETURN_BOOL(aclresult == ACLCHECK_OK); + } + + /* + * has_trigger_privilege_id + * Check user privileges on a trigger given + * trigger oid, and text priv name. + */ + Datum + has_trigger_privilege_id(PG_FUNCTION_ARGS) + { + Oid triggeroid = PG_GETARG_OID(0); + text *priv_type_text = PG_GETARG_TEXT_P(1); + Oid roleid; + AclMode mode; + AclResult aclresult; + Relation triggerDesc; + ScanKeyData skey[1]; + SysScanDesc rcscan; + HeapTuple tup; + Form_pg_trigger triggerForm; + + roleid = GetUserId(); + mode = convert_server_priv_string(priv_type_text); + + triggerDesc = heap_open(PolicyRelationId, AccessShareLock); + + ScanKeyInit(&skey[0], + ObjectIdAttributeNumber, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(triggeroid)); + + rcscan = systable_beginscan(triggerDesc, PolicyOidIndexId, true, + NULL, 1, skey); + + tup = systable_getnext(rcscan); + + if (!HeapTupleIsValid(tup)) + { + systable_endscan(rcscan); + heap_close(triggerDesc, AccessShareLock); + PG_RETURN_NULL(); + } + + triggerForm = (Form_pg_trigger)GETSTRUCT(tup); + + aclresult = pg_class_aclcheck(triggerForm->tgrelid, roleid, mode); + + systable_endscan(rcscan); + heap_close(triggerDesc, AccessShareLock); + PG_RETURN_BOOL(aclresult == ACLCHECK_OK); + } + + /* + * has_user_mapping_privilege_id + * Check user privileges on a user mapping given + * user_mapping oid, and text priv name. + */ + Datum + has_user_mapping_privilege_id(PG_FUNCTION_ARGS) + { + Oid usermapoid = PG_GETARG_OID(0); + text *priv_type_text = PG_GETARG_TEXT_P(1); + Oid roleid; + AclMode mode; + AclResult aclresult1; + AclResult aclresult2; + HeapTuple tup; + Form_pg_user_mapping usermapForm; + + roleid = GetUserId(); + mode = convert_server_priv_string(priv_type_text); + + tup = SearchSysCache1(USERMAPPINGOID, usermapoid); + if (!HeapTupleIsValid(tup)) + PG_RETURN_NULL(); + + usermapForm = (Form_pg_user_mapping)GETSTRUCT(tup); + + aclresult1 = pg_role_aclcheck(usermapForm->umuser, roleid, mode); + aclresult2 = pg_foreign_server_aclcheck(usermapForm->umserver, roleid, mode); + + PG_RETURN_BOOL((aclresult1 == ACLCHECK_OK) || (aclresult2 == ACLCHECK_OK)); + } *** a/src/backend/utils/cache/relcache.c --- b/src/backend/utils/cache/relcache.c *************** *** 2076,2081 **** RelationClearRelation(Relation relation, bool rebuild) --- 2076,2083 ---- */ if (relation->rd_isnailed) { + HeapTuple pg_class_tuple; + RelationInitPhysicalAddr(relation); if (relation->rd_rel->relkind == RELKIND_INDEX) *************** *** 2084,2089 **** RelationClearRelation(Relation relation, bool rebuild) --- 2086,2112 ---- if (relation->rd_refcnt > 1 && IsTransactionState()) RelationReloadIndexInfo(relation); } + + /* + * A nailed-in system relation never ever blow away from rel cache, because + * we'd be unable to recover. So for such relations, we will update the + * row security descriptor if it is enabled. Usually this happens during + * RelationBuildDesc function, but for nailed-in system relations, we will + * do it here. + */ + if (criticalRelcachesBuilt + && criticalSharedRelcachesBuilt + && IsTransactionState()) + { + /* + * find the tuple in pg_class corresponding to the given relation id + */ + pg_class_tuple = ScanPgRelation(RelationGetRelid(relation), true, false); + + if (((Form_pg_class)GETSTRUCT(pg_class_tuple))->relrowsecurity) + RelationBuildRowSecurity(relation); + heap_freetuple(pg_class_tuple); + } return; } *** a/src/backend/utils/misc/rls.c --- b/src/backend/utils/misc/rls.c *************** *** 58,67 **** check_enable_rls(Oid relid, Oid checkAsUser, bool noError) bool relforcerowsecurity; Oid user_id = checkAsUser ? checkAsUser : GetUserId(); - /* Nothing to do for built-in relations */ - if (relid < FirstNormalObjectId) - return RLS_NONE; - tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); if (!HeapTupleIsValid(tuple)) return RLS_NONE; --- 58,63 ---- *** a/src/bin/initdb/initdb.c --- b/src/bin/initdb/initdb.c *************** *** 132,137 **** static bool do_sync = true; --- 132,138 ---- static bool sync_only = false; static bool show_setting = false; static bool data_checksums = false; + static bool shared_catalog_security = false; static char *xlog_dir = ""; *************** *** 186,191 **** static char *authwarning = NULL; --- 187,193 ---- */ static const char *boot_options = "-F"; static const char *backend_options = "--single -F -O -c search_path=pg_catalog -c exit_on_error=true"; + static const char *catalog_security_options = "--single -F -O -c search_path=pg_catalog -c exit_on_error=true -c allow_system_table_mods=true"; static const char *subdirs[] = { "global", *************** *** 255,260 **** static void setup_dictionary(void); --- 257,263 ---- static void setup_privileges(void); static void set_info_version(void); static void setup_schema(void); + static void setup_shared_catalog_security(void); static void load_plpgsql(void); static void vacuum_db(void); static void make_template0(void); *************** *** 2252,2257 **** setup_schema(void) --- 2255,2357 ---- } /* + * setup shared catalog security by defining policies + */ + static void + setup_shared_catalog_security(void) + { + PG_CMD_DECL; + char **line; + char **policy_lines; + static const char *pg_shared_catalog_security_setup[] = { + /* AuthMemRelationId */ + /* + * Currently there is no policy needed for this table, so + * leave it as it is. + */ + + /* AuthIdRelationId */ + "create policy pg_authid_read_own_data on pg_authid for select using" + " ((oid < 16384) OR pg_has_role(oid, 'any'));\n", + + "alter table pg_authid enable row level security;\n", + + /* DatabaseRelationId */ + "create policy pg_database_read_own_data on pg_database for select using" + " ((oid < 16384) OR has_database_privilege(oid,'any'));\n", + + "alter table pg_database enable row level security;\n", + + /* DbRoleSettingRelationId */ + "create policy pg_db_role_setting_read_own_data on pg_db_role_setting for select using" + " (pg_get_userbyid(setrole) = current_user);\n", + + "alter table pg_database enable row level security;\n", + + /* PLTemplateRelationId */ + /* + * Currently there is no policy needed for this table, so + * leave it as it is. + */ + + /* ReplicationOriginRelationId */ + "create policy pg_replication_origin_read_own_data on pg_replication_origin for select using" + " (pg_has_role(roident, 'any'));\n", + + "alter table pg_replication_origin enable row level security;\n", + + /* SharedDependRelationId */ + "create policy pg_shdepend_read_own_data on pg_shdepend for select using" + " ((classid = 1262 AND has_database_privilege(objid, 'any'))" + " OR (classid = 1260 AND pg_has_role(objid, 'any'))" + " OR (classid = 1213 AND has_tablespace_privilege(objid, 'any')));\n", + + "alter table pg_shdepend enable row level security;\n", + + /* SharedDescriptionRelationId */ + "create policy pg_shdescription_read_own_data on pg_shdescription for select using" + " ((classoid = 1262 AND has_database_privilege(objoid, 'any'))" + " OR (classoid = 1260 AND pg_has_role(objoid, 'any'))" + " OR (classoid = 1213 AND has_tablespace_privilege(objoid, 'any')));\n", + + "alter table pg_shdescription enable row level security;\n", + + /* SharedSecLabelRelationId */ + "create policy pg_shseclabel_read_own_data on pg_shseclabel for select using" + " ((classoid = 1262 AND has_database_privilege(objoid, 'any'))" + " OR (classoid = 1260 AND pg_has_role(objoid, 'any'))" + " OR (classoid = 1213 AND has_tablespace_privilege(objoid, 'any')));\n", + + "alter table pg_shseclabel enable row level security;\n", + + /* TableSpaceRelationId */ + "create policy pg_tablespace_read_own_data on pg_tablespace for select using" + " ((oid < 16384) OR has_tablespace_privilege(oid, 'any'));\n", + + "alter table pg_tablespace enable row level security;\n", + + NULL + }; + + fputs(_("creating shared catalog security policy ... "), stdout); + fflush(stdout); + + snprintf(cmd, sizeof(cmd), + "\"%s\" %s template1 >%s", + backend_exec, catalog_security_options, + DEVNULL); + + PG_CMD_OPEN; + + for (line = pg_shared_catalog_security_setup; *line != NULL; line++) + PG_CMD_PUTS(*line); + + PG_CMD_CLOSE; + + check_ok(); + } + + /* * load PL/pgsql server-side language */ static void *************** *** 2768,2773 **** usage(const char *progname) --- 2868,2875 ---- printf(_("\nLess commonly used options:\n")); printf(_(" -d, --debug generate lots of debugging output\n")); printf(_(" -k, --data-checksums use data page checksums\n")); + printf(_(" -C, --shared-catalog-security\n" + " use shared catalog security\n")); printf(_(" -L DIRECTORY where to find the input files\n")); printf(_(" -n, --noclean do not clean up after errors\n")); printf(_(" -N, --nosync do not wait for changes to be written safely to disk\n")); *************** *** 3365,3377 **** initialize_data_directory(void) setup_schema(); load_plpgsql(); vacuum_db(); make_template0(); ! make_postgres(); } --- 3467,3482 ---- setup_schema(); + if (shared_catalog_security) + setup_shared_catalog_security(); + load_plpgsql(); vacuum_db(); make_template0(); ! make_postgres(); } *************** *** 3405,3410 **** main(int argc, char *argv[]) --- 3510,3516 ---- {"sync-only", no_argument, NULL, 'S'}, {"xlogdir", required_argument, NULL, 'X'}, {"data-checksums", no_argument, NULL, 'k'}, + {"shared-catalog-security", no_argument, NULL, 'C' }, {NULL, 0, NULL, 0} }; *************** *** 3445,3451 **** main(int argc, char *argv[]) /* process command-line options */ ! while ((c = getopt_long(argc, argv, "dD:E:kL:nNU:WA:sST:X:", long_options, &option_index)) != -1) { switch (c) { --- 3551,3557 ---- /* process command-line options */ ! while ((c = getopt_long(argc, argv, "dD:E:kCL:nNU:WA:sST:X:", long_options, &option_index)) != -1) { switch (c) { *************** *** 3497,3502 **** main(int argc, char *argv[]) --- 3603,3611 ---- case 'k': data_checksums = true; break; + case 'C': + shared_catalog_security = true; + break; case 'L': share_path = pg_strdup(optarg); break; *** a/src/include/catalog/pg_database.h --- b/src/include/catalog/pg_database.h *************** *** 43,49 **** CATALOG(pg_database,1262) BKI_SHARED_RELATION BKI_ROWTYPE_OID(1248) BKI_SCHEMA_M TransactionId datfrozenxid; /* all Xids < this are frozen in this DB */ TransactionId datminmxid; /* all multixacts in the DB are >= this */ Oid dattablespace; /* default table space for this DB */ ! #ifdef CATALOG_VARLEN /* variable-length fields start here */ aclitem datacl[1]; /* access permissions */ #endif --- 43,49 ---- TransactionId datfrozenxid; /* all Xids < this are frozen in this DB */ TransactionId datminmxid; /* all multixacts in the DB are >= this */ Oid dattablespace; /* default table space for this DB */ ! bool datcatalogsecurity; /* catalog security is enabled? */ #ifdef CATALOG_VARLEN /* variable-length fields start here */ aclitem datacl[1]; /* access permissions */ #endif *************** *** 60,81 **** typedef FormData_pg_database *Form_pg_database; * compiler constants for pg_database * ---------------- */ ! #define Natts_pg_database 13 ! #define Anum_pg_database_datname 1 ! #define Anum_pg_database_datdba 2 ! #define Anum_pg_database_encoding 3 ! #define Anum_pg_database_datcollate 4 ! #define Anum_pg_database_datctype 5 ! #define Anum_pg_database_datistemplate 6 ! #define Anum_pg_database_datallowconn 7 ! #define Anum_pg_database_datconnlimit 8 ! #define Anum_pg_database_datlastsysoid 9 ! #define Anum_pg_database_datfrozenxid 10 ! #define Anum_pg_database_datminmxid 11 ! #define Anum_pg_database_dattablespace 12 ! #define Anum_pg_database_datacl 13 ! DATA(insert OID = 1 ( template1 PGUID ENCODING "LC_COLLATE" "LC_CTYPE" t t -1 0 0 1 1663 _null_)); SHDESCR("default template for new databases"); #define TemplateDbOid 1 --- 60,82 ---- * compiler constants for pg_database * ---------------- */ ! #define Natts_pg_database 14 ! #define Anum_pg_database_datname 1 ! #define Anum_pg_database_datdba 2 ! #define Anum_pg_database_encoding 3 ! #define Anum_pg_database_datcollate 4 ! #define Anum_pg_database_datctype 5 ! #define Anum_pg_database_datistemplate 6 ! #define Anum_pg_database_datallowconn 7 ! #define Anum_pg_database_datconnlimit 8 ! #define Anum_pg_database_datlastsysoid 9 ! #define Anum_pg_database_datfrozenxid 10 ! #define Anum_pg_database_datminmxid 11 ! #define Anum_pg_database_dattablespace 12 ! #define Anum_pg_database_datcatalogsecurity 13 ! #define Anum_pg_database_datacl 14 ! DATA(insert OID = 1 ( template1 PGUID ENCODING "LC_COLLATE" "LC_CTYPE" t t -1 0 0 1 1663 f _null_)); SHDESCR("default template for new databases"); #define TemplateDbOid 1 *** a/src/include/catalog/pg_proc.h --- b/src/include/catalog/pg_proc.h *************** *** 3642,3647 **** DESCR("current user privilege on role by role name"); --- 3642,3665 ---- DATA(insert OID = 2710 ( pg_has_role PGNSP PGUID 12 1 0 0 0 f f f f t f s s 2 0 16 "26 25" _null_ _null_ _null_ _null_ _null_ pg_has_role_id _null_ _null_ _null_ )); DESCR("current user privilege on role by role oid"); + DATA(insert OID = 3315 (has_cast_privilege PGNSP PGUID 12 1 0 0 0 f f f f t f s s 2 0 16 "26 25" _null_ _null_ _null_ _null_ _null_ has_cast_privilege_id _null_ _null_ _null_)); + DESCR("current user privilege on cast by cast oid"); + + DATA(insert OID = 3316 (has_constraint_privilege PGNSP PGUID 12 1 0 0 0 f f f f t f s s 2 0 16 "26 25" _null_ _null_ _null_ _null_ _null_ has_constraint_privilege_id _null_ _null_ _null_)); + DESCR("current user privilege on contrainst by constraint oid"); + + DATA(insert OID = 3317 (has_column_default_privilege PGNSP PGUID 12 1 0 0 0 f f f f t f s s 2 0 16 "26 25" _null_ _null_ _null_ _null_ _null_ has_column_default_privilege_id _null_ _null_ _null_)); + DESCR("current user privilege on attrdefault by attrdefault oid"); + + DATA(insert OID = 3318 (has_policy_privilege PGNSP PGUID 12 1 0 0 0 f f f f t f s s 2 0 16 "26 25" _null_ _null_ _null_ _null_ _null_ has_policy_privilege_id _null_ _null_ _null_)); + DESCR("current user privilege on policy by policy oid"); + + DATA(insert OID = 3319 (has_trigger_privilege PGNSP PGUID 12 1 0 0 0 f f f f t f s s 2 0 16 "26 25" _null_ _null_ _null_ _null_ _null_ has_trigger_privilege_id _null_ _null_ _null_)); + DESCR("current user privilege on trigger by trigger oid"); + + DATA(insert OID = 3320 (has_user_mapping_privilege PGNSP PGUID 12 1 0 0 0 f f f f t f s s 2 0 16 "26 25" _null_ _null_ _null_ _null_ _null_ has_user_mapping_privilege_id _null_ _null_ _null_)); + DESCR("current user privilege on user mapping by user_mapping oid"); + DATA(insert OID = 1269 ( pg_column_size PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 23 "2276" _null_ _null_ _null_ _null_ _null_ pg_column_size _null_ _null_ _null_ )); DESCR("bytes required to store the value, perhaps with compression"); DATA(insert OID = 2322 ( pg_tablespace_size PGNSP PGUID 12 1 0 0 0 f f f f t f v s 1 0 20 "26" _null_ _null_ _null_ _null_ _null_ pg_tablespace_size_oid _null_ _null_ _null_ )); *** a/src/include/commands/policy.h --- b/src/include/commands/policy.h *************** *** 33,36 **** extern ObjectAddress rename_policy(RenameStmt *stmt); --- 33,39 ---- extern bool relation_has_policies(Relation rel); + extern void CreateCatalogPolicy(void); + extern void RemoveCatalogPolicy(void); + #endif /* POLICY_H */ *** a/src/include/utils/builtins.h --- b/src/include/utils/builtins.h *************** *** 106,111 **** extern Datum pg_has_role_id_name(PG_FUNCTION_ARGS); --- 106,117 ---- extern Datum pg_has_role_id_id(PG_FUNCTION_ARGS); extern Datum pg_has_role_name(PG_FUNCTION_ARGS); extern Datum pg_has_role_id(PG_FUNCTION_ARGS); + extern Datum has_cast_privilege_id(PG_FUNCTION_ARGS); + extern Datum has_constraint_privilege_id(PG_FUNCTION_ARGS); + extern Datum has_column_default_privilege_id(PG_FUNCTION_ARGS); + extern Datum has_policy_privilege_id(PG_FUNCTION_ARGS); + extern Datum has_trigger_privilege_id(PG_FUNCTION_ARGS); + extern Datum has_user_mapping_privilege_id(PG_FUNCTION_ARGS); /* bool.c */ extern Datum boolin(PG_FUNCTION_ARGS); *** /dev/null --- b/src/test/regress/expected/multitenancy.out *************** *** 0 **** --- 1,322 ---- + -- Create roles that are used by the following tests + create role tenancy_user1 login createdb; + create role tenancy_user2 login createdb; + create schema tenancy_user1; + grant all on schema tenancy_user1 to tenancy_user1; + create schema tenancy_user2; + grant all on schema tenancy_user2 to tenancy_user2; + alter database regression with catalog security = true; + -- create objects realted to tenacy_user1 + SET SESSION ROLE tenancy_user1; + create table public.tenancy_user1_tbl1 (tenancy_user1_tbl1_column1 serial, + tenancy_user1_tbl1_column2 char(10) default 'FUJITSU', + tenancy_user1_tbl1_column3 int); + create index tenancy_user1_tbl1_idx on tenancy_user1_tbl1 (tenancy_user1_tbl1_column3); + insert into tenancy_user1_tbl1(tenancy_user1_tbl1_column3) values(1); + create table tenancy_user1.tenancy_user1_tbl2 (tenancy_user1_tbl2_column1 serial, + tenancy_user1_tbl2_column2 char(10) default 'FUJITSU', + tenancy_user1_tbl2_column3 int); + create index tenancy_user1_tbl2_idx on tenancy_user1.tenancy_user1_tbl2 (tenancy_user1_tbl2_column3); + create view public.tenancy_user1_view1 as select * from tenancy_user1_tbl1; + create materialized view public.tenancy_user1_matview1 as select * from tenancy_user1_tbl1; + select * from tenancy_user1_tbl1; + tenancy_user1_tbl1_column1 | tenancy_user1_tbl1_column2 | tenancy_user1_tbl1_column3 + ----------------------------+----------------------------+---------------------------- + 1 | FUJITSU | 1 + (1 row) + + select * from tenancy_user1_view1; + tenancy_user1_tbl1_column1 | tenancy_user1_tbl1_column2 | tenancy_user1_tbl1_column3 + ----------------------------+----------------------------+---------------------------- + 1 | FUJITSU | 1 + (1 row) + + select * from tenancy_user1_matview1; + tenancy_user1_tbl1_column1 | tenancy_user1_tbl1_column2 | tenancy_user1_tbl1_column3 + ----------------------------+----------------------------+---------------------------- + 1 | FUJITSU | 1 + (1 row) + + + -- verify all system catalogs related to the objects created by tenancy_user1 + select relname from pg_class where relname = 'tenancy_user1_tbl1'; + relname + -------------------- + tenancy_user1_tbl1 + (1 row) + + select relname from pg_class where relname = 'tenancy_user1_tbl1_idx'; + relname + ------------------------ + tenancy_user1_tbl1_idx + (1 row) + + select relname from pg_class where relname = 'tenancy_user1_view1'; + relname + --------------------- + tenancy_user1_view1 + (1 row) + + select relname from pg_class where relname = 'tenancy_user1_matview1'; + relname + ------------------------ + tenancy_user1_matview1 + (1 row) + + select attname from pg_attribute where attname = 'tenancy_user1_tbl1_column1'; + attname + ---------------------------- + tenancy_user1_tbl1_column1 + tenancy_user1_tbl1_column1 + tenancy_user1_tbl1_column1 + (3 rows) + + select adnum, adsrc from pg_attrdef where adrelid in (select oid from pg_class where relname = 'tenancy_user1_tbl1'); + adnum | adsrc + -------+------------------------------------------------------------------------ + 1 | nextval('tenancy_user1_tbl1_tenancy_user1_tbl1_column1_seq'::regclass) + 2 | 'FUJITSU'::bpchar + (2 rows) + + select indnatts, indkey from pg_index where indrelid in (select oid from pg_class where relname = 'tenancy_user1_tbl1'); + indnatts | indkey + ----------+-------- + 1 | 3 + (1 row) + + select nspname, nspowner from pg_namespace where nspname = 'tenancy_user1'; + nspname | nspowner + ---------------+---------- + tenancy_user1 | 10 + (1 row) + + + -- verify all system views related to the objects created by tenancy_user1 + --select schemaname, relname from pg_stat_all_tables where relname = 'tenancy_user1_tbl1'; + --select schemaname, relname from pg_stat_all_tables where relname = 'tenancy_user1_tbl2' and schemaname = 'tenancy_user1'; + + RESET ROLE; + -- create objects realted to tenacy_user2 + SET SESSION ROLE tenancy_user2; + create table public.tenancy_user2_tbl1 (tenancy_user2_tbl1_column1 serial, + tenancy_user2_tbl1_column2 char(10) default 'FUJITSU', + tenancy_user2_tbl1_column3 int); + create index tenancy_user2_tbl1_idx on tenancy_user2_tbl1 (tenancy_user2_tbl1_column3); + insert into tenancy_user2_tbl1(tenancy_user2_tbl1_column3) values(1); + create table tenancy_user2.tenancy_user2_tbl2 (tenancy_user2_tbl2_column1 serial, + tenancy_user2_tbl2_column2 char(10) default 'FUJITSU', + tenancy_user2_tbl2_column3 int); + create index tenancy_user2_tbl2_idx on tenancy_user2.tenancy_user2_tbl2 (tenancy_user2_tbl2_column3); + create view public.tenancy_user2_view1 as select * from tenancy_user2_tbl1; + create materialized view public.tenancy_user2_matview1 as select * from tenancy_user2_tbl1; + select * from tenancy_user2_tbl1; + tenancy_user2_tbl1_column1 | tenancy_user2_tbl1_column2 | tenancy_user2_tbl1_column3 + ----------------------------+----------------------------+---------------------------- + 1 | FUJITSU | 1 + (1 row) + + select * from tenancy_user2_view1; + tenancy_user2_tbl1_column1 | tenancy_user2_tbl1_column2 | tenancy_user2_tbl1_column3 + ----------------------------+----------------------------+---------------------------- + 1 | FUJITSU | 1 + (1 row) + + select * from tenancy_user2_matview1; + tenancy_user2_tbl1_column1 | tenancy_user2_tbl1_column2 | tenancy_user2_tbl1_column3 + ----------------------------+----------------------------+---------------------------- + 1 | FUJITSU | 1 + (1 row) + + -- verify all system catalogs related to the objects created by tenancy_user2 + select relname from pg_class where relname = 'tenancy_user2_tbl1'; + relname + -------------------- + tenancy_user2_tbl1 + (1 row) + + select relname from pg_class where relname = 'tenancy_user2_tbl1_idx'; + relname + ------------------------ + tenancy_user2_tbl1_idx + (1 row) + + select relname from pg_class where relname = 'tenancy_user2_view1'; + relname + --------------------- + tenancy_user2_view1 + (1 row) + + select relname from pg_class where relname = 'tenancy_user2_matview1'; + relname + ------------------------ + tenancy_user2_matview1 + (1 row) + + select attname from pg_attribute where attname = 'tenancy_user2_tbl1_column1'; + attname + ---------------------------- + tenancy_user2_tbl1_column1 + tenancy_user2_tbl1_column1 + tenancy_user2_tbl1_column1 + (3 rows) + + select adnum, adsrc from pg_attrdef where adrelid in (select oid from pg_class where relname = 'tenancy_user2_tbl1'); + adnum | adsrc + -------+------------------------------------------------------------------------ + 1 | nextval('tenancy_user2_tbl1_tenancy_user2_tbl1_column1_seq'::regclass) + 2 | 'FUJITSU'::bpchar + (2 rows) + + select indnatts, indkey from pg_index where indrelid in (select oid from pg_class where relname = 'tenancy_user2_tbl1'); + indnatts | indkey + ----------+-------- + 1 | 3 + (1 row) + + select nspname, nspowner from pg_namespace where nspname = 'tenancy_user2'; + nspname | nspowner + ---------------+---------- + tenancy_user2 | 10 + (1 row) + + + -- verify all system views related to the objects created by tenancy_user1 + --select schemaname, relname from pg_stat_all_tables where relname = 'tenancy_user2_tbl1'; + --select schemaname, relname from pg_stat_all_tables where relname = 'tenancy_user2_tbl2' and schemaname = 'tenancy_user2'; + RESET ROLE; + -- Try to get the objects created by tenancy_user2 from tenancy_user1 + SET SESSION ROLE tenancy_user1; + select * from tenancy_user2_tbl1; + ERROR: permission denied for relation tenancy_user2_tbl1 + select * from tenancy_user2_view1; + ERROR: permission denied for relation tenancy_user2_view1 + select * from tenancy_user2_matview1; + ERROR: permission denied for relation tenancy_user2_matview1 + select * from tenancy_user2.tenancy_user2.tbl2; + ERROR: cross-database references are not implemented: "tenancy_user2.tenancy_user2.tbl2" + LINE 1: select * from tenancy_user2.tenancy_user2.tbl2; + ^ + -- verify all system catalogs related to the objects created by tenancy_user2 + select relname from pg_class where relname = 'tenancy_user2_tbl1'; + relname + --------- + (0 rows) + + select relname from pg_class where relname = 'tenancy_user2_tbl1_idx'; + relname + --------- + (0 rows) + + select relname from pg_class where relname = 'tenancy_user2_view1'; + relname + --------- + (0 rows) + + select relname from pg_class where relname = 'tenancy_user2_matview1'; + relname + --------- + (0 rows) + + select attname from pg_attribute where attname = 'tenancy_user2_tbl1_column1'; + attname + --------- + (0 rows) + + select adnum, adsrc from pg_attrdef where adrelid in (select oid from pg_class where relname = 'tenancy_user2_tbl1'); + adnum | adsrc + -------+------- + (0 rows) + + select indnatts, indkey from pg_index where indrelid in (select oid from pg_class where relname = 'tenancy_user2_tbl1'); + indnatts | indkey + ----------+-------- + (0 rows) + + select nspname, nspowner from pg_namespace where nspname = 'tenancy_user2'; + nspname | nspowner + ---------+---------- + (0 rows) + + + -- verify all system views related to the objects created by tenancy_user1 + --select schemaname, relname from pg_stat_all_tables where relname = 'tenancy_user2_tbl1'; + --select schemaname, relname from pg_stat_all_tables where relname = 'tenancy_user2_tbl2' and schemaname = 'tenancy_user2'; + RESET ROLE; + -- Try to get the objects created by tenancy_user1 from tenancy_user2 + SET SESSION ROLE tenancy_user2; + select * from tenancy_user1_tbl1; + ERROR: permission denied for relation tenancy_user1_tbl1 + select * from tenancy_user1_view1; + ERROR: permission denied for relation tenancy_user1_view1 + select * from tenancy_user1_matview1; + ERROR: permission denied for relation tenancy_user1_matview1 + select * from tenancy_user1.tenancy_user1.tbl2; + ERROR: cross-database references are not implemented: "tenancy_user1.tenancy_user1.tbl2" + LINE 1: select * from tenancy_user1.tenancy_user1.tbl2; + ^ + -- verify all system catalogs related to the objects created by tenancy_user1 + select relname from pg_class where relname = 'tenancy_user1_tbl1'; + relname + --------- + (0 rows) + + select relname from pg_class where relname = 'tenancy_user1_tbl1_idx'; + relname + --------- + (0 rows) + + select relname from pg_class where relname = 'tenancy_user1_view1'; + relname + --------- + (0 rows) + + select relname from pg_class where relname = 'tenancy_user1_matview1'; + relname + --------- + (0 rows) + + select attname from pg_attribute where attname = 'tenancy_user1_tbl1_column1'; + attname + --------- + (0 rows) + + select adnum, adsrc from pg_attrdef where adrelid in (select oid from pg_class where relname = 'tenancy_user1_tbl1'); + adnum | adsrc + -------+------- + (0 rows) + + select indnatts, indkey from pg_index where indrelid in (select oid from pg_class where relname = 'tenancy_user1_tbl1'); + indnatts | indkey + ----------+-------- + (0 rows) + + select nspname, nspowner from pg_namespace where nspname = 'tenancy_user1'; + nspname | nspowner + ---------+---------- + (0 rows) + + + -- verify all system views related to the objects created by tenancy_user1 + --select schemaname, relname from pg_stat_all_tables where relname = 'tenancy_user1_tbl1'; + --select schemaname, relname from pg_stat_all_tables where relname = 'tenancy_user1_tbl2' and schemaname = 'tenancy_user1'; + RESET ROLE; + -- Delete the roles and it's associated objects. + SET SESSION ROLE tenancy_user1; + DROP TABLE tenancy_user1_tbl1 cascade; + NOTICE: drop cascades to 2 other objects + DETAIL: drop cascades to view tenancy_user1_view1 + drop cascades to materialized view tenancy_user1_matview1 + DROP TABLE tenancy_user1_tbl2 cascade; + RESET ROLE; + SET SESSION ROLE tenancy_user2; + DROP TABLE tenancy_user2_tbl1 cascade; + NOTICE: drop cascades to 2 other objects + DETAIL: drop cascades to view tenancy_user2_view1 + drop cascades to materialized view tenancy_user2_matview1 + DROP TABLE tenancy_user2_tbl2 cascade; + RESET ROLE; + alter database regression with catalog security = false; + drop schema tenancy_user1; + drop schema tenancy_user2; + drop role tenancy_user1; + drop role tenancy_user2; *** a/src/test/regress/parallel_schedule --- b/src/test/regress/parallel_schedule *************** *** 60,66 **** test: create_index create_view # ---------- # Another group of parallel tests # ---------- ! test: create_aggregate create_function_3 create_cast constraints triggers inherit create_table_like typed_table vacuum drop_if_exists updatable_views rolenames roleattributes # ---------- # sanity_check does a vacuum, affecting the sort order of SELECT * --- 60,66 ---- # ---------- # Another group of parallel tests # ---------- ! test: create_aggregate create_function_3 create_cast constraints triggers inherit create_table_like typed_table vacuum drop_if_exists updatable_views rolenames roleattributes multitenancy # ---------- # sanity_check does a vacuum, affecting the sort order of SELECT * *** a/src/test/regress/serial_schedule --- b/src/test/regress/serial_schedule *************** *** 157,160 **** test: largeobject --- 157,161 ---- test: with test: xml test: event_trigger + test: multitenancy test: stats *** /dev/null --- b/src/test/regress/sql/multitenancy.sql *************** *** 0 **** --- 1,169 ---- + -- Create roles that are used by the following tests + create role tenancy_user1 login createdb; + create role tenancy_user2 login createdb; + + create schema tenancy_user1; + grant all on schema tenancy_user1 to tenancy_user1; + + create schema tenancy_user2; + grant all on schema tenancy_user2 to tenancy_user2; + + alter database regression with catalog security = true; + + -- create objects realted to tenacy_user1 + SET SESSION ROLE tenancy_user1; + + create table public.tenancy_user1_tbl1 (tenancy_user1_tbl1_column1 serial, + tenancy_user1_tbl1_column2 char(10) default 'FUJITSU', + tenancy_user1_tbl1_column3 int); + create index tenancy_user1_tbl1_idx on tenancy_user1_tbl1 (tenancy_user1_tbl1_column3); + + insert into tenancy_user1_tbl1(tenancy_user1_tbl1_column3) values(1); + + create table tenancy_user1.tenancy_user1_tbl2 (tenancy_user1_tbl2_column1 serial, + tenancy_user1_tbl2_column2 char(10) default 'FUJITSU', + tenancy_user1_tbl2_column3 int); + create index tenancy_user1_tbl2_idx on tenancy_user1.tenancy_user1_tbl2 (tenancy_user1_tbl2_column3); + + + create view public.tenancy_user1_view1 as select * from tenancy_user1_tbl1; + create materialized view public.tenancy_user1_matview1 as select * from tenancy_user1_tbl1; + + select * from tenancy_user1_tbl1; + select * from tenancy_user1_view1; + select * from tenancy_user1_matview1; + + + -- verify all system catalogs related to the objects created by tenancy_user1 + select relname from pg_class where relname = 'tenancy_user1_tbl1'; + select relname from pg_class where relname = 'tenancy_user1_tbl1_idx'; + select relname from pg_class where relname = 'tenancy_user1_view1'; + select relname from pg_class where relname = 'tenancy_user1_matview1'; + + select attname from pg_attribute where attname = 'tenancy_user1_tbl1_column1'; + select adnum, adsrc from pg_attrdef where adrelid in (select oid from pg_class where relname = 'tenancy_user1_tbl1'); + select indnatts, indkey from pg_index where indrelid in (select oid from pg_class where relname = 'tenancy_user1_tbl1'); + select nspname, nspowner from pg_namespace where nspname = 'tenancy_user1'; + + -- verify all system views related to the objects created by tenancy_user1 + --select schemaname, relname from pg_stat_all_tables where relname = 'tenancy_user1_tbl1'; + --select schemaname, relname from pg_stat_all_tables where relname = 'tenancy_user1_tbl2' and schemaname = 'tenancy_user1'; + + + RESET ROLE; + + + -- create objects realted to tenacy_user2 + SET SESSION ROLE tenancy_user2; + + create table public.tenancy_user2_tbl1 (tenancy_user2_tbl1_column1 serial, + tenancy_user2_tbl1_column2 char(10) default 'FUJITSU', + tenancy_user2_tbl1_column3 int); + create index tenancy_user2_tbl1_idx on tenancy_user2_tbl1 (tenancy_user2_tbl1_column3); + + insert into tenancy_user2_tbl1(tenancy_user2_tbl1_column3) values(1); + + create table tenancy_user2.tenancy_user2_tbl2 (tenancy_user2_tbl2_column1 serial, + tenancy_user2_tbl2_column2 char(10) default 'FUJITSU', + tenancy_user2_tbl2_column3 int); + create index tenancy_user2_tbl2_idx on tenancy_user2.tenancy_user2_tbl2 (tenancy_user2_tbl2_column3); + + create view public.tenancy_user2_view1 as select * from tenancy_user2_tbl1; + create materialized view public.tenancy_user2_matview1 as select * from tenancy_user2_tbl1; + + select * from tenancy_user2_tbl1; + select * from tenancy_user2_view1; + select * from tenancy_user2_matview1; + + -- verify all system catalogs related to the objects created by tenancy_user2 + select relname from pg_class where relname = 'tenancy_user2_tbl1'; + select relname from pg_class where relname = 'tenancy_user2_tbl1_idx'; + select relname from pg_class where relname = 'tenancy_user2_view1'; + select relname from pg_class where relname = 'tenancy_user2_matview1'; + + select attname from pg_attribute where attname = 'tenancy_user2_tbl1_column1'; + select adnum, adsrc from pg_attrdef where adrelid in (select oid from pg_class where relname = 'tenancy_user2_tbl1'); + select indnatts, indkey from pg_index where indrelid in (select oid from pg_class where relname = 'tenancy_user2_tbl1'); + select nspname, nspowner from pg_namespace where nspname = 'tenancy_user2'; + + -- verify all system views related to the objects created by tenancy_user1 + --select schemaname, relname from pg_stat_all_tables where relname = 'tenancy_user2_tbl1'; + --select schemaname, relname from pg_stat_all_tables where relname = 'tenancy_user2_tbl2' and schemaname = 'tenancy_user2'; + + RESET ROLE; + + -- Try to get the objects created by tenancy_user2 from tenancy_user1 + SET SESSION ROLE tenancy_user1; + + select * from tenancy_user2_tbl1; + select * from tenancy_user2_view1; + select * from tenancy_user2_matview1; + + select * from tenancy_user2.tenancy_user2.tbl2; + + -- verify all system catalogs related to the objects created by tenancy_user2 + select relname from pg_class where relname = 'tenancy_user2_tbl1'; + select relname from pg_class where relname = 'tenancy_user2_tbl1_idx'; + select relname from pg_class where relname = 'tenancy_user2_view1'; + select relname from pg_class where relname = 'tenancy_user2_matview1'; + + select attname from pg_attribute where attname = 'tenancy_user2_tbl1_column1'; + select adnum, adsrc from pg_attrdef where adrelid in (select oid from pg_class where relname = 'tenancy_user2_tbl1'); + select indnatts, indkey from pg_index where indrelid in (select oid from pg_class where relname = 'tenancy_user2_tbl1'); + select nspname, nspowner from pg_namespace where nspname = 'tenancy_user2'; + + -- verify all system views related to the objects created by tenancy_user1 + --select schemaname, relname from pg_stat_all_tables where relname = 'tenancy_user2_tbl1'; + --select schemaname, relname from pg_stat_all_tables where relname = 'tenancy_user2_tbl2' and schemaname = 'tenancy_user2'; + + RESET ROLE; + + + -- Try to get the objects created by tenancy_user1 from tenancy_user2 + SET SESSION ROLE tenancy_user2; + + select * from tenancy_user1_tbl1; + select * from tenancy_user1_view1; + select * from tenancy_user1_matview1; + + select * from tenancy_user1.tenancy_user1.tbl2; + + -- verify all system catalogs related to the objects created by tenancy_user1 + select relname from pg_class where relname = 'tenancy_user1_tbl1'; + select relname from pg_class where relname = 'tenancy_user1_tbl1_idx'; + select relname from pg_class where relname = 'tenancy_user1_view1'; + select relname from pg_class where relname = 'tenancy_user1_matview1'; + + select attname from pg_attribute where attname = 'tenancy_user1_tbl1_column1'; + select adnum, adsrc from pg_attrdef where adrelid in (select oid from pg_class where relname = 'tenancy_user1_tbl1'); + select indnatts, indkey from pg_index where indrelid in (select oid from pg_class where relname = 'tenancy_user1_tbl1'); + select nspname, nspowner from pg_namespace where nspname = 'tenancy_user1'; + + -- verify all system views related to the objects created by tenancy_user1 + --select schemaname, relname from pg_stat_all_tables where relname = 'tenancy_user1_tbl1'; + --select schemaname, relname from pg_stat_all_tables where relname = 'tenancy_user1_tbl2' and schemaname = 'tenancy_user1'; + + RESET ROLE; + + -- Delete the roles and it's associated objects. + SET SESSION ROLE tenancy_user1; + + DROP TABLE tenancy_user1_tbl1 cascade; + DROP TABLE tenancy_user1_tbl2 cascade; + + RESET ROLE; + + SET SESSION ROLE tenancy_user2; + + DROP TABLE tenancy_user2_tbl1 cascade; + DROP TABLE tenancy_user2_tbl2 cascade; + + RESET ROLE; + + alter database regression with catalog security = false; + + drop schema tenancy_user1; + drop schema tenancy_user2; + + drop role tenancy_user1; + drop role tenancy_user2;