diff options
Diffstat (limited to 'src/backend/commands/policy.c')
-rw-r--r-- | src/backend/commands/policy.c | 988 |
1 files changed, 988 insertions, 0 deletions
diff --git a/src/backend/commands/policy.c b/src/backend/commands/policy.c new file mode 100644 index 00000000000..2f4df489021 --- /dev/null +++ b/src/backend/commands/policy.c @@ -0,0 +1,988 @@ +/*------------------------------------------------------------------------- + * + * policy.c + * Commands for manipulating policies. + * + * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/backend/commands/policy.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/genam.h" +#include "access/heapam.h" +#include "access/htup.h" +#include "access/htup_details.h" +#include "access/sysattr.h" +#include "catalog/catalog.h" +#include "catalog/dependency.h" +#include "catalog/indexing.h" +#include "catalog/namespace.h" +#include "catalog/objectaccess.h" +#include "catalog/pg_rowsecurity.h" +#include "catalog/pg_type.h" +#include "commands/policy.h" +#include "miscadmin.h" +#include "nodes/makefuncs.h" +#include "nodes/pg_list.h" +#include "optimizer/clauses.h" +#include "parser/parse_clause.h" +#include "parser/parse_node.h" +#include "parser/parse_relation.h" +#include "storage/lock.h" +#include "utils/acl.h" +#include "utils/array.h" +#include "utils/builtins.h" +#include "utils/fmgroids.h" +#include "utils/inval.h" +#include "utils/lsyscache.h" +#include "utils/memutils.h" +#include "utils/rel.h" +#include "utils/syscache.h" + +static void RangeVarCallbackForPolicy(const RangeVar *rv, + Oid relid, Oid oldrelid, void *arg); +static const char parse_row_security_command(const char *cmd_name); +static ArrayType* rls_role_list_to_array(List *roles); + +/* + * Callback to RangeVarGetRelidExtended(). + * + * Checks the following: + * - the relation specified is a table. + * - current user owns the table. + * - the table is not a system table. + * + * If any of these checks fails then an error is raised. + */ +static void +RangeVarCallbackForPolicy(const RangeVar *rv, Oid relid, Oid oldrelid, + void *arg) +{ + HeapTuple tuple; + Form_pg_class classform; + char relkind; + + tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(tuple)) + return; + + classform = (Form_pg_class) GETSTRUCT(tuple); + relkind = classform->relkind; + + /* Must own relation. */ + if (!pg_class_ownercheck(relid, GetUserId())) + aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS, rv->relname); + + /* No system table modifications unless explicitly allowed. */ + if (!allowSystemTableMods && IsSystemClass(relid, classform)) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("permission denied: \"%s\" is a system catalog", + rv->relname))); + + /* Relation type MUST be a table. */ + if (relkind != RELKIND_RELATION) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("\"%s\" is not a table", rv->relname))); + + ReleaseSysCache(tuple); +} + +/* + * parse_row_security_command - + * helper function to convert full command strings to their char + * representation. + * + * cmd_name - full string command name. Valid values are 'all', 'select', + * 'insert', 'update' and 'delete'. + * + */ +static const char +parse_row_security_command(const char *cmd_name) +{ + char cmd; + + if (!cmd_name) + elog(ERROR, "Unregonized command."); + + if (strcmp(cmd_name, "all") == 0) + cmd = 0; + else if (strcmp(cmd_name, "select") == 0) + cmd = ACL_SELECT_CHR; + else if (strcmp(cmd_name, "insert") == 0) + cmd = ACL_INSERT_CHR; + else if (strcmp(cmd_name, "update") == 0) + cmd = ACL_UPDATE_CHR; + else if (strcmp(cmd_name, "delete") == 0) + cmd = ACL_DELETE_CHR; + else + elog(ERROR, "Unregonized command."); + /* error unrecognized command */ + + return cmd; +} + +/* + * rls_role_list_to_array + * helper function to convert a list of role names in to an array of + * role ids. + * + * Note: If PUBLIC is provided as a role name, then ACL_ID_PUBLIC is + * used as the role id. + * + * roles - the list of role names to convert. + */ +static ArrayType * +rls_role_list_to_array(List *roles) +{ + ArrayType *role_ids; + Datum *temp_array; + ListCell *cell; + int num_roles; + int i = 0; + + /* Handle no roles being passed in as being for public */ + if (roles == NIL) + { + temp_array = (Datum *) palloc(sizeof(Datum)); + temp_array[0] = ObjectIdGetDatum(ACL_ID_PUBLIC); + + role_ids = construct_array(temp_array, 1, OIDOID, sizeof(Oid), true, + 'i'); + return role_ids; + } + + num_roles = list_length(roles); + temp_array = (Datum *) palloc(num_roles * sizeof(Datum)); + + foreach(cell, roles) + { + Oid roleid = get_role_oid_or_public(strVal(lfirst(cell))); + + /* + * PUBLIC covers all roles, so it only makes sense alone. + */ + if (roleid == ACL_ID_PUBLIC) + { + if (num_roles != 1) + ereport(WARNING, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("ignoring roles specified other than public"), + errhint("All roles are members of the public role."))); + + temp_array[0] = ObjectIdGetDatum(roleid); + num_roles = 1; + break; + } + else + temp_array[i++] = ObjectIdGetDatum(roleid); + } + + role_ids = construct_array(temp_array, num_roles, OIDOID, sizeof(Oid), true, + 'i'); + + return role_ids; +} + +/* + * Load row-security policy from the catalog, and keep it in + * the relation cache. + */ +void +RelationBuildRowSecurity(Relation relation) +{ + Relation catalog; + ScanKeyData skey; + SysScanDesc sscan; + HeapTuple tuple; + MemoryContext oldcxt; + MemoryContext rscxt = NULL; + RowSecurityDesc *rsdesc = NULL; + + catalog = heap_open(RowSecurityRelationId, AccessShareLock); + + ScanKeyInit(&skey, + Anum_pg_rowsecurity_rsecrelid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(RelationGetRelid(relation))); + + sscan = systable_beginscan(catalog, RowSecurityRelidPolnameIndexId, true, + NULL, 1, &skey); + PG_TRY(); + { + /* + * Set up our memory context- we will always set up some kind of + * policy here. If no explicit policies are found then an implicit + * default-deny policy is created. + */ + rscxt = AllocSetContextCreate(CacheMemoryContext, + "Row-security descriptor", + ALLOCSET_SMALL_MINSIZE, + ALLOCSET_SMALL_INITSIZE, + ALLOCSET_SMALL_MAXSIZE); + rsdesc = MemoryContextAllocZero(rscxt, sizeof(RowSecurityDesc)); + rsdesc->rscxt = rscxt; + + /* + * Loop through the row-level security entries for this relation, if + * any. + */ + while (HeapTupleIsValid(tuple = systable_getnext(sscan))) + { + Datum value_datum; + char cmd_value; + ArrayType *roles; + char *qual_value; + Expr *qual_expr; + char *with_check_value; + Expr *with_check_qual; + char *policy_name_value; + Oid policy_id; + bool isnull; + RowSecurityPolicy *policy = NULL; + + oldcxt = MemoryContextSwitchTo(rscxt); + + /* Get policy command */ + value_datum = heap_getattr(tuple, Anum_pg_rowsecurity_rseccmd, + RelationGetDescr(catalog), &isnull); + if (isnull) + cmd_value = 0; + else + cmd_value = DatumGetChar(value_datum); + + /* Get policy name */ + value_datum = heap_getattr(tuple, Anum_pg_rowsecurity_rsecpolname, + RelationGetDescr(catalog), &isnull); + Assert(!isnull); + policy_name_value = DatumGetCString(value_datum); + + /* Get policy roles */ + value_datum = heap_getattr(tuple, Anum_pg_rowsecurity_rsecroles, + RelationGetDescr(catalog), &isnull); + Assert(!isnull); + roles = DatumGetArrayTypeP(value_datum); + + /* Get policy qual */ + value_datum = heap_getattr(tuple, Anum_pg_rowsecurity_rsecqual, + RelationGetDescr(catalog), &isnull); + if (!isnull) + { + qual_value = TextDatumGetCString(value_datum); + qual_expr = (Expr *) stringToNode(qual_value); + } + else + qual_expr = NULL; + + /* Get WITH CHECK qual */ + value_datum = heap_getattr(tuple, Anum_pg_rowsecurity_rsecwithcheck, + RelationGetDescr(catalog), &isnull); + + if (!isnull) + { + with_check_value = TextDatumGetCString(value_datum); + with_check_qual = (Expr *) stringToNode(with_check_value); + } + else + with_check_qual = NULL; + + policy_id = HeapTupleGetOid(tuple); + + policy = palloc0(sizeof(RowSecurityPolicy)); + policy->policy_name = policy_name_value; + policy->rsecid = policy_id; + policy->cmd = cmd_value; + policy->roles = roles; + policy->qual = copyObject(qual_expr); + policy->with_check_qual = copyObject(with_check_qual); + policy->hassublinks = contain_subplans((Node *) qual_expr) || + contain_subplans((Node *) with_check_qual); + + rsdesc->policies = lcons(policy, rsdesc->policies); + + MemoryContextSwitchTo(oldcxt); + + if (qual_expr != NULL) + pfree(qual_expr); + + if (with_check_qual != NULL) + pfree(with_check_qual); + } + + /* + * Check if no policies were added + * + * If no policies exist in pg_rowsecurity for this relation, then we + * need to create a single default-deny policy. We use InvalidOid for + * the Oid to indicate that this is the default-deny policy (we may + * decide to ignore the default policy if an extension adds policies). + */ + if (rsdesc->policies == NIL) + { + RowSecurityPolicy *policy = NULL; + Datum role; + + oldcxt = MemoryContextSwitchTo(rscxt); + + role = ObjectIdGetDatum(ACL_ID_PUBLIC); + + policy = palloc0(sizeof(RowSecurityPolicy)); + policy->policy_name = pstrdup("default-deny policy"); + policy->rsecid = InvalidOid; + policy->cmd = '\0'; + policy->roles = construct_array(&role, 1, OIDOID, sizeof(Oid), true, + 'i'); + policy->qual = (Expr *) makeConst(BOOLOID, -1, InvalidOid, + sizeof(bool), BoolGetDatum(false), + false, true); + policy->with_check_qual = copyObject(policy->qual); + policy->hassublinks = false; + + rsdesc->policies = lcons(policy, rsdesc->policies); + + MemoryContextSwitchTo(oldcxt); + } + } + PG_CATCH(); + { + if (rscxt != NULL) + MemoryContextDelete(rscxt); + PG_RE_THROW(); + } + PG_END_TRY(); + + systable_endscan(sscan); + heap_close(catalog, AccessShareLock); + + relation->rsdesc = rsdesc; +} + +/* + * RemovePolicyById - + * remove a row-security policy by its OID. If a policy does not exist with + * the provided oid, then an error is raised. + * + * policy_id - the oid of the row-security policy. + */ +void +RemovePolicyById(Oid policy_id) +{ + Relation pg_rowsecurity_rel; + SysScanDesc sscan; + ScanKeyData skey[1]; + HeapTuple tuple; + Oid relid; + Relation rel; + + pg_rowsecurity_rel = heap_open(RowSecurityRelationId, RowExclusiveLock); + + /* + * Find the policy to delete. + */ + ScanKeyInit(&skey[0], + ObjectIdAttributeNumber, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(policy_id)); + + sscan = systable_beginscan(pg_rowsecurity_rel, RowSecurityOidIndexId, true, + NULL, 1, skey); + + tuple = systable_getnext(sscan); + + /* If the policy exists, then remove it, otherwise raise an error. */ + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "could not find tuple for row-security %u", policy_id); + + /* + * Open and exclusive-lock the relation the policy belong to. + */ + relid = ((Form_pg_rowsecurity) GETSTRUCT(tuple))->rsecrelid; + + rel = heap_open(relid, AccessExclusiveLock); + if (rel->rd_rel->relkind != RELKIND_RELATION) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("\"%s\" is not a table", + RelationGetRelationName(rel)))); + + if (!allowSystemTableMods && IsSystemRelation(rel)) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("permission denied: \"%s\" is a system catalog", + RelationGetRelationName(rel)))); + + simple_heap_delete(pg_rowsecurity_rel, &tuple->t_self); + + systable_endscan(sscan); + heap_close(rel, AccessExclusiveLock); + + /* + * Note that, unlike some of the other flags in pg_class, relhasrowsecurity + * is not just an indication of if policies exist. When relhasrowsecurity + * is set (which can be done directly by the user or indirectly by creating + * a policy on the table), then all access to the relation must be through + * a policy. If no policy is defined for the relation then a default-deny + * policy is created and all records are filtered (except for queries from + * the owner). + */ + + CacheInvalidateRelcache(rel); + + /* Clean up */ + heap_close(pg_rowsecurity_rel, RowExclusiveLock); +} + +/* + * CreatePolicy - + * handles the execution of the CREATE POLICY command. + * + * stmt - the CreatePolicyStmt that describes the policy to create. + */ +Oid +CreatePolicy(CreatePolicyStmt *stmt) +{ + Relation pg_rowsecurity_rel; + Oid rowsec_id; + Relation target_table; + Oid table_id; + char rseccmd; + ArrayType *role_ids; + ParseState *qual_pstate; + ParseState *with_check_pstate; + RangeTblEntry *rte; + Node *qual; + Node *with_check_qual; + ScanKeyData skey[2]; + SysScanDesc sscan; + HeapTuple rsec_tuple; + Datum values[Natts_pg_rowsecurity]; + bool isnull[Natts_pg_rowsecurity]; + ObjectAddress target; + ObjectAddress myself; + + /* Parse command */ + rseccmd = parse_row_security_command(stmt->cmd); + + /* + * If the command is SELECT or DELETE then WITH CHECK should be NULL. + */ + if ((rseccmd == ACL_SELECT_CHR || rseccmd == ACL_DELETE_CHR) + && stmt->with_check != NULL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("WITH CHECK cannot be applied to SELECT or DELETE"))); + + /* + * If the command is INSERT then WITH CHECK should be the only expression + * provided. + */ + if (rseccmd == ACL_INSERT_CHR && stmt->qual != NULL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("Only WITH CHECK expression allowed for INSERT"))); + + + /* Collect role ids */ + role_ids = rls_role_list_to_array(stmt->roles); + + /* Parse the supplied clause */ + qual_pstate = make_parsestate(NULL); + with_check_pstate = make_parsestate(NULL); + + /* zero-clear */ + memset(values, 0, sizeof(values)); + memset(isnull, 0, sizeof(isnull)); + + /* Get id of table. Also handles permissions checks. */ + table_id = RangeVarGetRelidExtended(stmt->table, AccessExclusiveLock, + false, false, + RangeVarCallbackForPolicy, + (void *) stmt); + + /* Open target_table to build quals. No lock is necessary.*/ + target_table = relation_open(table_id, NoLock); + + /* Add for the regular security quals */ + rte = addRangeTableEntryForRelation(qual_pstate, target_table, + NULL, false, false); + addRTEtoQuery(qual_pstate, rte, false, true, true); + + /* Add for the with-check quals */ + rte = addRangeTableEntryForRelation(with_check_pstate, target_table, + NULL, false, false); + addRTEtoQuery(with_check_pstate, rte, false, true, true); + + qual = transformWhereClause(qual_pstate, + copyObject(stmt->qual), + EXPR_KIND_WHERE, + "POLICY"); + + with_check_qual = transformWhereClause(with_check_pstate, + copyObject(stmt->with_check), + EXPR_KIND_WHERE, + "POLICY"); + + /* Open pg_rowsecurity catalog */ + pg_rowsecurity_rel = heap_open(RowSecurityRelationId, RowExclusiveLock); + + /* Set key - row security relation id. */ + ScanKeyInit(&skey[0], + Anum_pg_rowsecurity_rsecrelid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(table_id)); + + /* Set key - row security policy name. */ + ScanKeyInit(&skey[1], + Anum_pg_rowsecurity_rsecpolname, + BTEqualStrategyNumber, F_NAMEEQ, + CStringGetDatum(stmt->policy_name)); + + sscan = systable_beginscan(pg_rowsecurity_rel, + RowSecurityRelidPolnameIndexId, true, NULL, 2, + skey); + + rsec_tuple = systable_getnext(sscan); + + /* Complain if the policy name already exists for the table */ + if (HeapTupleIsValid(rsec_tuple)) + ereport(ERROR, + (errcode(ERRCODE_DUPLICATE_OBJECT), + errmsg("policy \"%s\" for relation \"%s\" already exists", + stmt->policy_name, RelationGetRelationName(target_table)))); + + values[Anum_pg_rowsecurity_rsecrelid - 1] = ObjectIdGetDatum(table_id); + values[Anum_pg_rowsecurity_rsecpolname - 1] + = CStringGetDatum(stmt->policy_name); + + if (rseccmd) + values[Anum_pg_rowsecurity_rseccmd - 1] = CharGetDatum(rseccmd); + else + isnull[Anum_pg_rowsecurity_rseccmd - 1] = true; + + values[Anum_pg_rowsecurity_rsecroles - 1] = PointerGetDatum(role_ids); + + /* Add qual if present. */ + if (qual) + values[Anum_pg_rowsecurity_rsecqual - 1] + = CStringGetTextDatum(nodeToString(qual)); + else + isnull[Anum_pg_rowsecurity_rsecqual - 1] = true; + + /* Add WITH CHECK qual if present */ + if (with_check_qual) + values[Anum_pg_rowsecurity_rsecwithcheck - 1] + = CStringGetTextDatum(nodeToString(with_check_qual)); + else + isnull[Anum_pg_rowsecurity_rsecwithcheck - 1] = true; + + rsec_tuple = heap_form_tuple(RelationGetDescr(pg_rowsecurity_rel), values, + isnull); + + rowsec_id = simple_heap_insert(pg_rowsecurity_rel, rsec_tuple); + + /* Update Indexes */ + CatalogUpdateIndexes(pg_rowsecurity_rel, rsec_tuple); + + /* Record Dependencies */ + target.classId = RelationRelationId; + target.objectId = table_id; + target.objectSubId = 0; + + myself.classId = RowSecurityRelationId; + myself.objectId = rowsec_id; + myself.objectSubId = 0; + + recordDependencyOn(&myself, &target, DEPENDENCY_AUTO); + + recordDependencyOnExpr(&myself, qual, qual_pstate->p_rtable, + DEPENDENCY_NORMAL); + + recordDependencyOnExpr(&myself, with_check_qual, + with_check_pstate->p_rtable, DEPENDENCY_NORMAL); + + /* Invalidate Relation Cache */ + CacheInvalidateRelcache(target_table); + + /* Clean up. */ + heap_freetuple(rsec_tuple); + free_parsestate(qual_pstate); + free_parsestate(with_check_pstate); + systable_endscan(sscan); + relation_close(target_table, NoLock); + heap_close(pg_rowsecurity_rel, RowExclusiveLock); + + return rowsec_id; +} + +/* + * AlterPolicy - + * handles the execution of the ALTER POLICY command. + * + * stmt - the AlterPolicyStmt that describes the policy and how to alter it. + */ +Oid +AlterPolicy(AlterPolicyStmt *stmt) +{ + Relation pg_rowsecurity_rel; + Oid rowsec_id; + Relation target_table; + Oid table_id; + ArrayType *role_ids = NULL; + List *qual_parse_rtable = NIL; + List *with_check_parse_rtable = NIL; + Node *qual = NULL; + Node *with_check_qual = NULL; + ScanKeyData skey[2]; + SysScanDesc sscan; + HeapTuple rsec_tuple; + HeapTuple new_tuple; + Datum values[Natts_pg_rowsecurity]; + bool isnull[Natts_pg_rowsecurity]; + bool replaces[Natts_pg_rowsecurity]; + ObjectAddress target; + ObjectAddress myself; + Datum cmd_datum; + char rseccmd; + bool rseccmd_isnull; + + /* Parse role_ids */ + if (stmt->roles != NULL) + role_ids = rls_role_list_to_array(stmt->roles); + + /* Get id of table. Also handles permissions checks. */ + table_id = RangeVarGetRelidExtended(stmt->table, AccessExclusiveLock, + false, false, + RangeVarCallbackForPolicy, + (void *) stmt); + + target_table = relation_open(table_id, NoLock); + + /* Parse the row-security clause */ + if (stmt->qual) + { + RangeTblEntry *rte; + ParseState *qual_pstate = make_parsestate(NULL); + + rte = addRangeTableEntryForRelation(qual_pstate, target_table, + NULL, false, false); + + addRTEtoQuery(qual_pstate, rte, false, true, true); + + qual = transformWhereClause(qual_pstate, copyObject(stmt->qual), + EXPR_KIND_WHERE, + "ROW SECURITY"); + + qual_parse_rtable = qual_pstate->p_rtable; + free_parsestate(qual_pstate); + } + + /* Parse the with-check row-security clause */ + if (stmt->with_check) + { + RangeTblEntry *rte; + ParseState *with_check_pstate = make_parsestate(NULL); + + rte = addRangeTableEntryForRelation(with_check_pstate, target_table, + NULL, false, false); + + addRTEtoQuery(with_check_pstate, rte, false, true, true); + + with_check_qual = transformWhereClause(with_check_pstate, + copyObject(stmt->with_check), + EXPR_KIND_WHERE, + "ROW SECURITY"); + + with_check_parse_rtable = with_check_pstate->p_rtable; + free_parsestate(with_check_pstate); + } + + /* zero-clear */ + memset(values, 0, sizeof(values)); + memset(replaces, 0, sizeof(replaces)); + memset(isnull, 0, sizeof(isnull)); + + /* Find policy to update. */ + pg_rowsecurity_rel = heap_open(RowSecurityRelationId, RowExclusiveLock); + + /* Set key - row security relation id. */ + ScanKeyInit(&skey[0], + Anum_pg_rowsecurity_rsecrelid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(table_id)); + + /* Set key - row security policy name. */ + ScanKeyInit(&skey[1], + Anum_pg_rowsecurity_rsecpolname, + BTEqualStrategyNumber, F_NAMEEQ, + CStringGetDatum(stmt->policy_name)); + + sscan = systable_beginscan(pg_rowsecurity_rel, + RowSecurityRelidPolnameIndexId, true, NULL, 2, + skey); + + rsec_tuple = systable_getnext(sscan); + + /* Check that the policy is found, raise an error if not. */ + if (!HeapTupleIsValid(rsec_tuple)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("policy '%s' for does not exist on table %s", + stmt->policy_name, + RelationGetRelationName(target_table)))); + + /* Get policy command */ + cmd_datum = heap_getattr(rsec_tuple, Anum_pg_rowsecurity_rseccmd, + RelationGetDescr(pg_rowsecurity_rel), + &rseccmd_isnull); + if (rseccmd_isnull) + rseccmd = 0; + else + rseccmd = DatumGetChar(cmd_datum); + + /* + * If the command is SELECT or DELETE then WITH CHECK should be NULL. + */ + if ((rseccmd == ACL_SELECT_CHR || rseccmd == ACL_DELETE_CHR) + && stmt->with_check != NULL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("only USING expression allowed for SELECT, DELETE"))); + + /* + * If the command is INSERT then WITH CHECK should be the only + * expression provided. + */ + if ((rseccmd == ACL_INSERT_CHR) + && stmt->qual != NULL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("only WITH CHECK expression allowed for INSERT"))); + + rowsec_id = HeapTupleGetOid(rsec_tuple); + + if (role_ids != NULL) + { + replaces[Anum_pg_rowsecurity_rsecroles - 1] = true; + values[Anum_pg_rowsecurity_rsecroles - 1] = PointerGetDatum(role_ids); + } + + if (qual != NULL) + { + replaces[Anum_pg_rowsecurity_rsecqual - 1] = true; + values[Anum_pg_rowsecurity_rsecqual - 1] + = CStringGetTextDatum(nodeToString(qual)); + } + + if (with_check_qual != NULL) + { + replaces[Anum_pg_rowsecurity_rsecwithcheck - 1] = true; + values[Anum_pg_rowsecurity_rsecwithcheck - 1] + = CStringGetTextDatum(nodeToString(with_check_qual)); + } + + new_tuple = heap_modify_tuple(rsec_tuple, + RelationGetDescr(pg_rowsecurity_rel), + values, isnull, replaces); + simple_heap_update(pg_rowsecurity_rel, &new_tuple->t_self, new_tuple); + + /* Update Catalog Indexes */ + CatalogUpdateIndexes(pg_rowsecurity_rel, new_tuple); + + /* Update Dependencies. */ + deleteDependencyRecordsFor(RowSecurityRelationId, rowsec_id, false); + + /* Record Dependencies */ + target.classId = RelationRelationId; + target.objectId = table_id; + target.objectSubId = 0; + + myself.classId = RowSecurityRelationId; + myself.objectId = rowsec_id; + myself.objectSubId = 0; + + recordDependencyOn(&myself, &target, DEPENDENCY_AUTO); + + recordDependencyOnExpr(&myself, qual, qual_parse_rtable, DEPENDENCY_NORMAL); + + recordDependencyOnExpr(&myself, with_check_qual, with_check_parse_rtable, + DEPENDENCY_NORMAL); + + heap_freetuple(new_tuple); + + /* Invalidate Relation Cache */ + CacheInvalidateRelcache(target_table); + + /* Clean up. */ + systable_endscan(sscan); + relation_close(target_table, NoLock); + heap_close(pg_rowsecurity_rel, RowExclusiveLock); + + return rowsec_id; +} + +/* + * rename_policy - + * change the name of a policy on a relation + */ +Oid +rename_policy(RenameStmt *stmt) +{ + Relation pg_rowsecurity_rel; + Relation target_table; + Oid table_id; + Oid opoloid; + ScanKeyData skey[2]; + SysScanDesc sscan; + HeapTuple rsec_tuple; + + /* Get id of table. Also handles permissions checks. */ + table_id = RangeVarGetRelidExtended(stmt->relation, AccessExclusiveLock, + false, false, + RangeVarCallbackForPolicy, + (void *) stmt); + + target_table = relation_open(table_id, NoLock); + + pg_rowsecurity_rel = heap_open(RowSecurityRelationId, RowExclusiveLock); + + /* First pass- check for conflict */ + + /* Add key - row security relation id. */ + ScanKeyInit(&skey[0], + Anum_pg_rowsecurity_rsecrelid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(table_id)); + + /* Add key - row security policy name. */ + ScanKeyInit(&skey[1], + Anum_pg_rowsecurity_rsecpolname, + BTEqualStrategyNumber, F_NAMEEQ, + CStringGetDatum(stmt->newname)); + + sscan = systable_beginscan(pg_rowsecurity_rel, + RowSecurityRelidPolnameIndexId, true, NULL, 2, + skey); + + if (HeapTupleIsValid(rsec_tuple = systable_getnext(sscan))) + ereport(ERROR, + (errcode(ERRCODE_DUPLICATE_OBJECT), + errmsg("row-policy \"%s\" for table \"%s\" already exists", + stmt->newname, RelationGetRelationName(target_table)))); + + systable_endscan(sscan); + + /* Second pass -- find existing policy and update */ + /* Add key - row security relation id. */ + ScanKeyInit(&skey[0], + Anum_pg_rowsecurity_rsecrelid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(table_id)); + + /* Add key - row security policy name. */ + ScanKeyInit(&skey[1], + Anum_pg_rowsecurity_rsecpolname, + BTEqualStrategyNumber, F_NAMEEQ, + CStringGetDatum(stmt->subname)); + + sscan = systable_beginscan(pg_rowsecurity_rel, + RowSecurityRelidPolnameIndexId, true, NULL, 2, + skey); + + rsec_tuple = systable_getnext(sscan); + + /* Complain if we did not find the policy */ + if (!HeapTupleIsValid(rsec_tuple)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("row-policy \"%s\" for table \"%s\" does not exist", + stmt->subname, RelationGetRelationName(target_table)))); + + opoloid = HeapTupleGetOid(rsec_tuple); + + rsec_tuple = heap_copytuple(rsec_tuple); + + namestrcpy(&((Form_pg_rowsecurity) GETSTRUCT(rsec_tuple))->rsecpolname, + stmt->newname); + + simple_heap_update(pg_rowsecurity_rel, &rsec_tuple->t_self, rsec_tuple); + + /* keep system catalog indexes current */ + CatalogUpdateIndexes(pg_rowsecurity_rel, rsec_tuple); + + InvokeObjectPostAlterHook(RowSecurityRelationId, + HeapTupleGetOid(rsec_tuple), 0); + + /* + * Invalidate relation's relcache entry so that other backends (and + * this one too!) are sent SI message to make them rebuild relcache + * entries. (Ideally this should happen automatically...) + */ + CacheInvalidateRelcache(target_table); + + /* Clean up. */ + systable_endscan(sscan); + heap_close(pg_rowsecurity_rel, RowExclusiveLock); + relation_close(target_table, NoLock); + + return opoloid; +} + +/* + * get_relation_policy_oid - Look up a policy by name to find its OID + * + * If missing_ok is false, throw an error if policy not found. If + * true, just return InvalidOid. + */ +Oid +get_relation_policy_oid(Oid relid, const char *policy_name, bool missing_ok) +{ + Relation pg_rowsecurity_rel; + ScanKeyData skey[2]; + SysScanDesc sscan; + HeapTuple rsec_tuple; + Oid policy_oid; + + pg_rowsecurity_rel = heap_open(RowSecurityRelationId, AccessShareLock); + + /* Add key - row security relation id. */ + ScanKeyInit(&skey[0], + Anum_pg_rowsecurity_rsecrelid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(relid)); + + /* Add key - row security policy name. */ + ScanKeyInit(&skey[1], + Anum_pg_rowsecurity_rsecpolname, + BTEqualStrategyNumber, F_NAMEEQ, + CStringGetDatum(policy_name)); + + sscan = systable_beginscan(pg_rowsecurity_rel, + RowSecurityRelidPolnameIndexId, true, NULL, 2, + skey); + + rsec_tuple = systable_getnext(sscan); + + if (!HeapTupleIsValid(rsec_tuple)) + { + if (!missing_ok) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("policy \"%s\" for table \"%s\" does not exist", + policy_name, get_rel_name(relid)))); + + policy_oid = InvalidOid; + } + else + policy_oid = HeapTupleGetOid(rsec_tuple); + + /* Clean up. */ + systable_endscan(sscan); + heap_close(pg_rowsecurity_rel, AccessShareLock); + + return policy_oid; +} |