/src/postgres/src/backend/commands/policy.c
Line | Count | Source (jump to first uncovered line) |
1 | | /*------------------------------------------------------------------------- |
2 | | * |
3 | | * policy.c |
4 | | * Commands for manipulating policies. |
5 | | * |
6 | | * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group |
7 | | * Portions Copyright (c) 1994, Regents of the University of California |
8 | | * |
9 | | * src/backend/commands/policy.c |
10 | | * |
11 | | *------------------------------------------------------------------------- |
12 | | */ |
13 | | #include "postgres.h" |
14 | | |
15 | | #include "access/genam.h" |
16 | | #include "access/htup.h" |
17 | | #include "access/htup_details.h" |
18 | | #include "access/relation.h" |
19 | | #include "access/table.h" |
20 | | #include "access/xact.h" |
21 | | #include "catalog/catalog.h" |
22 | | #include "catalog/dependency.h" |
23 | | #include "catalog/indexing.h" |
24 | | #include "catalog/namespace.h" |
25 | | #include "catalog/objectaccess.h" |
26 | | #include "catalog/pg_authid.h" |
27 | | #include "catalog/pg_policy.h" |
28 | | #include "catalog/pg_type.h" |
29 | | #include "commands/policy.h" |
30 | | #include "miscadmin.h" |
31 | | #include "nodes/pg_list.h" |
32 | | #include "parser/parse_clause.h" |
33 | | #include "parser/parse_collate.h" |
34 | | #include "parser/parse_node.h" |
35 | | #include "parser/parse_relation.h" |
36 | | #include "rewrite/rewriteManip.h" |
37 | | #include "rewrite/rowsecurity.h" |
38 | | #include "utils/acl.h" |
39 | | #include "utils/array.h" |
40 | | #include "utils/builtins.h" |
41 | | #include "utils/fmgroids.h" |
42 | | #include "utils/inval.h" |
43 | | #include "utils/lsyscache.h" |
44 | | #include "utils/memutils.h" |
45 | | #include "utils/rel.h" |
46 | | #include "utils/syscache.h" |
47 | | |
48 | | static void RangeVarCallbackForPolicy(const RangeVar *rv, |
49 | | Oid relid, Oid oldrelid, void *arg); |
50 | | static char parse_policy_command(const char *cmd_name); |
51 | | static Datum *policy_role_list_to_array(List *roles, int *num_roles); |
52 | | |
53 | | /* |
54 | | * Callback to RangeVarGetRelidExtended(). |
55 | | * |
56 | | * Checks the following: |
57 | | * - the relation specified is a table. |
58 | | * - current user owns the table. |
59 | | * - the table is not a system table. |
60 | | * |
61 | | * If any of these checks fails then an error is raised. |
62 | | */ |
63 | | static void |
64 | | RangeVarCallbackForPolicy(const RangeVar *rv, Oid relid, Oid oldrelid, |
65 | | void *arg) |
66 | 0 | { |
67 | 0 | HeapTuple tuple; |
68 | 0 | Form_pg_class classform; |
69 | 0 | char relkind; |
70 | |
|
71 | 0 | tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); |
72 | 0 | if (!HeapTupleIsValid(tuple)) |
73 | 0 | return; |
74 | | |
75 | 0 | classform = (Form_pg_class) GETSTRUCT(tuple); |
76 | 0 | relkind = classform->relkind; |
77 | | |
78 | | /* Must own relation. */ |
79 | 0 | if (!object_ownercheck(RelationRelationId, relid, GetUserId())) |
80 | 0 | aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(get_rel_relkind(relid)), rv->relname); |
81 | | |
82 | | /* No system table modifications unless explicitly allowed. */ |
83 | 0 | if (!allowSystemTableMods && IsSystemClass(relid, classform)) |
84 | 0 | ereport(ERROR, |
85 | 0 | (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), |
86 | 0 | errmsg("permission denied: \"%s\" is a system catalog", |
87 | 0 | rv->relname))); |
88 | | |
89 | | /* Relation type MUST be a table. */ |
90 | 0 | if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE) |
91 | 0 | ereport(ERROR, |
92 | 0 | (errcode(ERRCODE_WRONG_OBJECT_TYPE), |
93 | 0 | errmsg("\"%s\" is not a table", rv->relname))); |
94 | | |
95 | 0 | ReleaseSysCache(tuple); |
96 | 0 | } |
97 | | |
98 | | /* |
99 | | * parse_policy_command - |
100 | | * helper function to convert full command strings to their char |
101 | | * representation. |
102 | | * |
103 | | * cmd_name - full string command name. Valid values are 'all', 'select', |
104 | | * 'insert', 'update' and 'delete'. |
105 | | * |
106 | | */ |
107 | | static char |
108 | | parse_policy_command(const char *cmd_name) |
109 | 0 | { |
110 | 0 | char polcmd; |
111 | |
|
112 | 0 | if (!cmd_name) |
113 | 0 | elog(ERROR, "unrecognized policy command"); |
114 | | |
115 | 0 | if (strcmp(cmd_name, "all") == 0) |
116 | 0 | polcmd = '*'; |
117 | 0 | else if (strcmp(cmd_name, "select") == 0) |
118 | 0 | polcmd = ACL_SELECT_CHR; |
119 | 0 | else if (strcmp(cmd_name, "insert") == 0) |
120 | 0 | polcmd = ACL_INSERT_CHR; |
121 | 0 | else if (strcmp(cmd_name, "update") == 0) |
122 | 0 | polcmd = ACL_UPDATE_CHR; |
123 | 0 | else if (strcmp(cmd_name, "delete") == 0) |
124 | 0 | polcmd = ACL_DELETE_CHR; |
125 | 0 | else |
126 | 0 | elog(ERROR, "unrecognized policy command"); |
127 | | |
128 | 0 | return polcmd; |
129 | 0 | } |
130 | | |
131 | | /* |
132 | | * policy_role_list_to_array |
133 | | * helper function to convert a list of RoleSpecs to an array of |
134 | | * role id Datums. |
135 | | */ |
136 | | static Datum * |
137 | | policy_role_list_to_array(List *roles, int *num_roles) |
138 | 0 | { |
139 | 0 | Datum *role_oids; |
140 | 0 | ListCell *cell; |
141 | 0 | int i = 0; |
142 | | |
143 | | /* Handle no roles being passed in as being for public */ |
144 | 0 | if (roles == NIL) |
145 | 0 | { |
146 | 0 | *num_roles = 1; |
147 | 0 | role_oids = (Datum *) palloc(*num_roles * sizeof(Datum)); |
148 | 0 | role_oids[0] = ObjectIdGetDatum(ACL_ID_PUBLIC); |
149 | |
|
150 | 0 | return role_oids; |
151 | 0 | } |
152 | | |
153 | 0 | *num_roles = list_length(roles); |
154 | 0 | role_oids = (Datum *) palloc(*num_roles * sizeof(Datum)); |
155 | |
|
156 | 0 | foreach(cell, roles) |
157 | 0 | { |
158 | 0 | RoleSpec *spec = lfirst(cell); |
159 | | |
160 | | /* |
161 | | * PUBLIC covers all roles, so it only makes sense alone. |
162 | | */ |
163 | 0 | if (spec->roletype == ROLESPEC_PUBLIC) |
164 | 0 | { |
165 | 0 | if (*num_roles != 1) |
166 | 0 | { |
167 | 0 | ereport(WARNING, |
168 | 0 | (errcode(ERRCODE_INVALID_PARAMETER_VALUE), |
169 | 0 | errmsg("ignoring specified roles other than PUBLIC"), |
170 | 0 | errhint("All roles are members of the PUBLIC role."))); |
171 | 0 | *num_roles = 1; |
172 | 0 | } |
173 | 0 | role_oids[0] = ObjectIdGetDatum(ACL_ID_PUBLIC); |
174 | |
|
175 | 0 | return role_oids; |
176 | 0 | } |
177 | 0 | else |
178 | 0 | role_oids[i++] = |
179 | 0 | ObjectIdGetDatum(get_rolespec_oid(spec, false)); |
180 | 0 | } |
181 | | |
182 | 0 | return role_oids; |
183 | 0 | } |
184 | | |
185 | | /* |
186 | | * Load row security policy from the catalog, and store it in |
187 | | * the relation's relcache entry. |
188 | | * |
189 | | * Note that caller should have verified that pg_class.relrowsecurity |
190 | | * is true for this relation. |
191 | | */ |
192 | | void |
193 | | RelationBuildRowSecurity(Relation relation) |
194 | 0 | { |
195 | 0 | MemoryContext rscxt; |
196 | 0 | MemoryContext oldcxt = CurrentMemoryContext; |
197 | 0 | RowSecurityDesc *rsdesc; |
198 | 0 | Relation catalog; |
199 | 0 | ScanKeyData skey; |
200 | 0 | SysScanDesc sscan; |
201 | 0 | HeapTuple tuple; |
202 | | |
203 | | /* |
204 | | * Create a memory context to hold everything associated with this |
205 | | * relation's row security policy. This makes it easy to clean up during |
206 | | * a relcache flush. However, to cover the possibility of an error |
207 | | * partway through, we don't make the context long-lived till we're done. |
208 | | */ |
209 | 0 | rscxt = AllocSetContextCreate(CurrentMemoryContext, |
210 | 0 | "row security descriptor", |
211 | 0 | ALLOCSET_SMALL_SIZES); |
212 | 0 | MemoryContextCopyAndSetIdentifier(rscxt, |
213 | 0 | RelationGetRelationName(relation)); |
214 | |
|
215 | 0 | rsdesc = MemoryContextAllocZero(rscxt, sizeof(RowSecurityDesc)); |
216 | 0 | rsdesc->rscxt = rscxt; |
217 | | |
218 | | /* |
219 | | * Now scan pg_policy for RLS policies associated with this relation. |
220 | | * Because we use the index on (polrelid, polname), we should consistently |
221 | | * visit the rel's policies in name order, at least when system indexes |
222 | | * aren't disabled. This simplifies equalRSDesc(). |
223 | | */ |
224 | 0 | catalog = table_open(PolicyRelationId, AccessShareLock); |
225 | |
|
226 | 0 | ScanKeyInit(&skey, |
227 | 0 | Anum_pg_policy_polrelid, |
228 | 0 | BTEqualStrategyNumber, F_OIDEQ, |
229 | 0 | ObjectIdGetDatum(RelationGetRelid(relation))); |
230 | |
|
231 | 0 | sscan = systable_beginscan(catalog, PolicyPolrelidPolnameIndexId, true, |
232 | 0 | NULL, 1, &skey); |
233 | |
|
234 | 0 | while (HeapTupleIsValid(tuple = systable_getnext(sscan))) |
235 | 0 | { |
236 | 0 | Form_pg_policy policy_form = (Form_pg_policy) GETSTRUCT(tuple); |
237 | 0 | RowSecurityPolicy *policy; |
238 | 0 | Datum datum; |
239 | 0 | bool isnull; |
240 | 0 | char *str_value; |
241 | |
|
242 | 0 | policy = MemoryContextAllocZero(rscxt, sizeof(RowSecurityPolicy)); |
243 | | |
244 | | /* |
245 | | * Note: we must be sure that pass-by-reference data gets copied into |
246 | | * rscxt. We avoid making that context current over wider spans than |
247 | | * we have to, though. |
248 | | */ |
249 | | |
250 | | /* Get policy command */ |
251 | 0 | policy->polcmd = policy_form->polcmd; |
252 | | |
253 | | /* Get policy, permissive or restrictive */ |
254 | 0 | policy->permissive = policy_form->polpermissive; |
255 | | |
256 | | /* Get policy name */ |
257 | 0 | policy->policy_name = |
258 | 0 | MemoryContextStrdup(rscxt, NameStr(policy_form->polname)); |
259 | | |
260 | | /* Get policy roles */ |
261 | 0 | datum = heap_getattr(tuple, Anum_pg_policy_polroles, |
262 | 0 | RelationGetDescr(catalog), &isnull); |
263 | | /* shouldn't be null, but let's check for luck */ |
264 | 0 | if (isnull) |
265 | 0 | elog(ERROR, "unexpected null value in pg_policy.polroles"); |
266 | 0 | MemoryContextSwitchTo(rscxt); |
267 | 0 | policy->roles = DatumGetArrayTypePCopy(datum); |
268 | 0 | MemoryContextSwitchTo(oldcxt); |
269 | | |
270 | | /* Get policy qual */ |
271 | 0 | datum = heap_getattr(tuple, Anum_pg_policy_polqual, |
272 | 0 | RelationGetDescr(catalog), &isnull); |
273 | 0 | if (!isnull) |
274 | 0 | { |
275 | 0 | str_value = TextDatumGetCString(datum); |
276 | 0 | MemoryContextSwitchTo(rscxt); |
277 | 0 | policy->qual = (Expr *) stringToNode(str_value); |
278 | 0 | MemoryContextSwitchTo(oldcxt); |
279 | 0 | pfree(str_value); |
280 | 0 | } |
281 | 0 | else |
282 | 0 | policy->qual = NULL; |
283 | | |
284 | | /* Get WITH CHECK qual */ |
285 | 0 | datum = heap_getattr(tuple, Anum_pg_policy_polwithcheck, |
286 | 0 | RelationGetDescr(catalog), &isnull); |
287 | 0 | if (!isnull) |
288 | 0 | { |
289 | 0 | str_value = TextDatumGetCString(datum); |
290 | 0 | MemoryContextSwitchTo(rscxt); |
291 | 0 | policy->with_check_qual = (Expr *) stringToNode(str_value); |
292 | 0 | MemoryContextSwitchTo(oldcxt); |
293 | 0 | pfree(str_value); |
294 | 0 | } |
295 | 0 | else |
296 | 0 | policy->with_check_qual = NULL; |
297 | | |
298 | | /* We want to cache whether there are SubLinks in these expressions */ |
299 | 0 | policy->hassublinks = checkExprHasSubLink((Node *) policy->qual) || |
300 | 0 | checkExprHasSubLink((Node *) policy->with_check_qual); |
301 | | |
302 | | /* |
303 | | * Add this object to list. For historical reasons, the list is built |
304 | | * in reverse order. |
305 | | */ |
306 | 0 | MemoryContextSwitchTo(rscxt); |
307 | 0 | rsdesc->policies = lcons(policy, rsdesc->policies); |
308 | 0 | MemoryContextSwitchTo(oldcxt); |
309 | 0 | } |
310 | | |
311 | 0 | systable_endscan(sscan); |
312 | 0 | table_close(catalog, AccessShareLock); |
313 | | |
314 | | /* |
315 | | * Success. Reparent the descriptor's memory context under |
316 | | * CacheMemoryContext so that it will live indefinitely, then attach the |
317 | | * policy descriptor to the relcache entry. |
318 | | */ |
319 | 0 | MemoryContextSetParent(rscxt, CacheMemoryContext); |
320 | |
|
321 | 0 | relation->rd_rsdesc = rsdesc; |
322 | 0 | } |
323 | | |
324 | | /* |
325 | | * RemovePolicyById - |
326 | | * remove a policy by its OID. If a policy does not exist with the provided |
327 | | * oid, then an error is raised. |
328 | | * |
329 | | * policy_id - the oid of the policy. |
330 | | */ |
331 | | void |
332 | | RemovePolicyById(Oid policy_id) |
333 | 0 | { |
334 | 0 | Relation pg_policy_rel; |
335 | 0 | SysScanDesc sscan; |
336 | 0 | ScanKeyData skey[1]; |
337 | 0 | HeapTuple tuple; |
338 | 0 | Oid relid; |
339 | 0 | Relation rel; |
340 | |
|
341 | 0 | pg_policy_rel = table_open(PolicyRelationId, RowExclusiveLock); |
342 | | |
343 | | /* |
344 | | * Find the policy to delete. |
345 | | */ |
346 | 0 | ScanKeyInit(&skey[0], |
347 | 0 | Anum_pg_policy_oid, |
348 | 0 | BTEqualStrategyNumber, F_OIDEQ, |
349 | 0 | ObjectIdGetDatum(policy_id)); |
350 | |
|
351 | 0 | sscan = systable_beginscan(pg_policy_rel, PolicyOidIndexId, true, |
352 | 0 | NULL, 1, skey); |
353 | |
|
354 | 0 | tuple = systable_getnext(sscan); |
355 | | |
356 | | /* If the policy exists, then remove it, otherwise raise an error. */ |
357 | 0 | if (!HeapTupleIsValid(tuple)) |
358 | 0 | elog(ERROR, "could not find tuple for policy %u", policy_id); |
359 | | |
360 | | /* |
361 | | * Open and exclusive-lock the relation the policy belongs to. (We need |
362 | | * exclusive lock to lock out queries that might otherwise depend on the |
363 | | * set of policies the rel has; furthermore we've got to hold the lock |
364 | | * till commit.) |
365 | | */ |
366 | 0 | relid = ((Form_pg_policy) GETSTRUCT(tuple))->polrelid; |
367 | |
|
368 | 0 | rel = table_open(relid, AccessExclusiveLock); |
369 | 0 | if (rel->rd_rel->relkind != RELKIND_RELATION && |
370 | 0 | rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE) |
371 | 0 | ereport(ERROR, |
372 | 0 | (errcode(ERRCODE_WRONG_OBJECT_TYPE), |
373 | 0 | errmsg("\"%s\" is not a table", |
374 | 0 | RelationGetRelationName(rel)))); |
375 | | |
376 | 0 | if (!allowSystemTableMods && IsSystemRelation(rel)) |
377 | 0 | ereport(ERROR, |
378 | 0 | (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), |
379 | 0 | errmsg("permission denied: \"%s\" is a system catalog", |
380 | 0 | RelationGetRelationName(rel)))); |
381 | | |
382 | 0 | CatalogTupleDelete(pg_policy_rel, &tuple->t_self); |
383 | |
|
384 | 0 | systable_endscan(sscan); |
385 | | |
386 | | /* |
387 | | * Note that, unlike some of the other flags in pg_class, relrowsecurity |
388 | | * is not just an indication of if policies exist. When relrowsecurity is |
389 | | * set by a user, then all access to the relation must be through a |
390 | | * policy. If no policy is defined for the relation then a default-deny |
391 | | * policy is created and all records are filtered (except for queries from |
392 | | * the owner). |
393 | | */ |
394 | 0 | CacheInvalidateRelcache(rel); |
395 | |
|
396 | 0 | table_close(rel, NoLock); |
397 | | |
398 | | /* Clean up */ |
399 | 0 | table_close(pg_policy_rel, RowExclusiveLock); |
400 | 0 | } |
401 | | |
402 | | /* |
403 | | * RemoveRoleFromObjectPolicy - |
404 | | * remove a role from a policy's applicable-roles list. |
405 | | * |
406 | | * Returns true if the role was successfully removed from the policy. |
407 | | * Returns false if the role was not removed because it would have left |
408 | | * polroles empty (which is disallowed, though perhaps it should not be). |
409 | | * On false return, the caller should instead drop the policy altogether. |
410 | | * |
411 | | * roleid - the oid of the role to remove |
412 | | * classid - should always be PolicyRelationId |
413 | | * policy_id - the oid of the policy. |
414 | | */ |
415 | | bool |
416 | | RemoveRoleFromObjectPolicy(Oid roleid, Oid classid, Oid policy_id) |
417 | 0 | { |
418 | 0 | Relation pg_policy_rel; |
419 | 0 | SysScanDesc sscan; |
420 | 0 | ScanKeyData skey[1]; |
421 | 0 | HeapTuple tuple; |
422 | 0 | Oid relid; |
423 | 0 | ArrayType *policy_roles; |
424 | 0 | Datum roles_datum; |
425 | 0 | Oid *roles; |
426 | 0 | int num_roles; |
427 | 0 | Datum *role_oids; |
428 | 0 | bool attr_isnull; |
429 | 0 | bool keep_policy = true; |
430 | 0 | int i, |
431 | 0 | j; |
432 | |
|
433 | 0 | Assert(classid == PolicyRelationId); |
434 | |
|
435 | 0 | pg_policy_rel = table_open(PolicyRelationId, RowExclusiveLock); |
436 | | |
437 | | /* |
438 | | * Find the policy to update. |
439 | | */ |
440 | 0 | ScanKeyInit(&skey[0], |
441 | 0 | Anum_pg_policy_oid, |
442 | 0 | BTEqualStrategyNumber, F_OIDEQ, |
443 | 0 | ObjectIdGetDatum(policy_id)); |
444 | |
|
445 | 0 | sscan = systable_beginscan(pg_policy_rel, PolicyOidIndexId, true, |
446 | 0 | NULL, 1, skey); |
447 | |
|
448 | 0 | tuple = systable_getnext(sscan); |
449 | | |
450 | | /* Raise an error if we don't find the policy. */ |
451 | 0 | if (!HeapTupleIsValid(tuple)) |
452 | 0 | elog(ERROR, "could not find tuple for policy %u", policy_id); |
453 | | |
454 | | /* Identify rel the policy belongs to */ |
455 | 0 | relid = ((Form_pg_policy) GETSTRUCT(tuple))->polrelid; |
456 | | |
457 | | /* Get the current set of roles */ |
458 | 0 | roles_datum = heap_getattr(tuple, |
459 | 0 | Anum_pg_policy_polroles, |
460 | 0 | RelationGetDescr(pg_policy_rel), |
461 | 0 | &attr_isnull); |
462 | |
|
463 | 0 | Assert(!attr_isnull); |
464 | |
|
465 | 0 | policy_roles = DatumGetArrayTypePCopy(roles_datum); |
466 | 0 | roles = (Oid *) ARR_DATA_PTR(policy_roles); |
467 | 0 | num_roles = ARR_DIMS(policy_roles)[0]; |
468 | | |
469 | | /* |
470 | | * Rebuild the polroles array, without any mentions of the target role. |
471 | | * Ordinarily there'd be exactly one, but we must cope with duplicate |
472 | | * mentions, since CREATE/ALTER POLICY historically have allowed that. |
473 | | */ |
474 | 0 | role_oids = (Datum *) palloc(num_roles * sizeof(Datum)); |
475 | 0 | for (i = 0, j = 0; i < num_roles; i++) |
476 | 0 | { |
477 | 0 | if (roles[i] != roleid) |
478 | 0 | role_oids[j++] = ObjectIdGetDatum(roles[i]); |
479 | 0 | } |
480 | 0 | num_roles = j; |
481 | | |
482 | | /* If any roles remain, update the policy entry. */ |
483 | 0 | if (num_roles > 0) |
484 | 0 | { |
485 | 0 | ArrayType *role_ids; |
486 | 0 | Datum values[Natts_pg_policy]; |
487 | 0 | bool isnull[Natts_pg_policy]; |
488 | 0 | bool replaces[Natts_pg_policy]; |
489 | 0 | HeapTuple new_tuple; |
490 | 0 | HeapTuple reltup; |
491 | 0 | ObjectAddress target; |
492 | 0 | ObjectAddress myself; |
493 | | |
494 | | /* zero-clear */ |
495 | 0 | memset(values, 0, sizeof(values)); |
496 | 0 | memset(replaces, 0, sizeof(replaces)); |
497 | 0 | memset(isnull, 0, sizeof(isnull)); |
498 | | |
499 | | /* This is the array for the new tuple */ |
500 | 0 | role_ids = construct_array_builtin(role_oids, num_roles, OIDOID); |
501 | |
|
502 | 0 | replaces[Anum_pg_policy_polroles - 1] = true; |
503 | 0 | values[Anum_pg_policy_polroles - 1] = PointerGetDatum(role_ids); |
504 | |
|
505 | 0 | new_tuple = heap_modify_tuple(tuple, |
506 | 0 | RelationGetDescr(pg_policy_rel), |
507 | 0 | values, isnull, replaces); |
508 | 0 | CatalogTupleUpdate(pg_policy_rel, &new_tuple->t_self, new_tuple); |
509 | | |
510 | | /* Remove all the old shared dependencies (roles) */ |
511 | 0 | deleteSharedDependencyRecordsFor(PolicyRelationId, policy_id, 0); |
512 | | |
513 | | /* Record the new shared dependencies (roles) */ |
514 | 0 | myself.classId = PolicyRelationId; |
515 | 0 | myself.objectId = policy_id; |
516 | 0 | myself.objectSubId = 0; |
517 | |
|
518 | 0 | target.classId = AuthIdRelationId; |
519 | 0 | target.objectSubId = 0; |
520 | 0 | for (i = 0; i < num_roles; i++) |
521 | 0 | { |
522 | 0 | target.objectId = DatumGetObjectId(role_oids[i]); |
523 | | /* no need for dependency on the public role */ |
524 | 0 | if (target.objectId != ACL_ID_PUBLIC) |
525 | 0 | recordSharedDependencyOn(&myself, &target, |
526 | 0 | SHARED_DEPENDENCY_POLICY); |
527 | 0 | } |
528 | |
|
529 | 0 | InvokeObjectPostAlterHook(PolicyRelationId, policy_id, 0); |
530 | |
|
531 | 0 | heap_freetuple(new_tuple); |
532 | | |
533 | | /* Make updates visible */ |
534 | 0 | CommandCounterIncrement(); |
535 | | |
536 | | /* |
537 | | * Invalidate relcache entry for rel the policy belongs to, to force |
538 | | * redoing any dependent plans. In case of a race condition where the |
539 | | * rel was just dropped, we need do nothing. |
540 | | */ |
541 | 0 | reltup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); |
542 | 0 | if (HeapTupleIsValid(reltup)) |
543 | 0 | { |
544 | 0 | CacheInvalidateRelcacheByTuple(reltup); |
545 | 0 | ReleaseSysCache(reltup); |
546 | 0 | } |
547 | 0 | } |
548 | 0 | else |
549 | 0 | { |
550 | | /* No roles would remain, so drop the policy instead. */ |
551 | 0 | keep_policy = false; |
552 | 0 | } |
553 | | |
554 | | /* Clean up. */ |
555 | 0 | systable_endscan(sscan); |
556 | |
|
557 | 0 | table_close(pg_policy_rel, RowExclusiveLock); |
558 | |
|
559 | 0 | return keep_policy; |
560 | 0 | } |
561 | | |
562 | | /* |
563 | | * CreatePolicy - |
564 | | * handles the execution of the CREATE POLICY command. |
565 | | * |
566 | | * stmt - the CreatePolicyStmt that describes the policy to create. |
567 | | */ |
568 | | ObjectAddress |
569 | | CreatePolicy(CreatePolicyStmt *stmt) |
570 | 0 | { |
571 | 0 | Relation pg_policy_rel; |
572 | 0 | Oid policy_id; |
573 | 0 | Relation target_table; |
574 | 0 | Oid table_id; |
575 | 0 | char polcmd; |
576 | 0 | Datum *role_oids; |
577 | 0 | int nitems = 0; |
578 | 0 | ArrayType *role_ids; |
579 | 0 | ParseState *qual_pstate; |
580 | 0 | ParseState *with_check_pstate; |
581 | 0 | ParseNamespaceItem *nsitem; |
582 | 0 | Node *qual; |
583 | 0 | Node *with_check_qual; |
584 | 0 | ScanKeyData skey[2]; |
585 | 0 | SysScanDesc sscan; |
586 | 0 | HeapTuple policy_tuple; |
587 | 0 | Datum values[Natts_pg_policy]; |
588 | 0 | bool isnull[Natts_pg_policy]; |
589 | 0 | ObjectAddress target; |
590 | 0 | ObjectAddress myself; |
591 | 0 | int i; |
592 | | |
593 | | /* Parse command */ |
594 | 0 | polcmd = parse_policy_command(stmt->cmd_name); |
595 | | |
596 | | /* |
597 | | * If the command is SELECT or DELETE then WITH CHECK should be NULL. |
598 | | */ |
599 | 0 | if ((polcmd == ACL_SELECT_CHR || polcmd == ACL_DELETE_CHR) |
600 | 0 | && stmt->with_check != NULL) |
601 | 0 | ereport(ERROR, |
602 | 0 | (errcode(ERRCODE_SYNTAX_ERROR), |
603 | 0 | errmsg("WITH CHECK cannot be applied to SELECT or DELETE"))); |
604 | | |
605 | | /* |
606 | | * If the command is INSERT then WITH CHECK should be the only expression |
607 | | * provided. |
608 | | */ |
609 | 0 | if (polcmd == ACL_INSERT_CHR && stmt->qual != NULL) |
610 | 0 | ereport(ERROR, |
611 | 0 | (errcode(ERRCODE_SYNTAX_ERROR), |
612 | 0 | errmsg("only WITH CHECK expression allowed for INSERT"))); |
613 | | |
614 | | /* Collect role ids */ |
615 | 0 | role_oids = policy_role_list_to_array(stmt->roles, &nitems); |
616 | 0 | role_ids = construct_array_builtin(role_oids, nitems, OIDOID); |
617 | | |
618 | | /* Parse the supplied clause */ |
619 | 0 | qual_pstate = make_parsestate(NULL); |
620 | 0 | with_check_pstate = make_parsestate(NULL); |
621 | | |
622 | | /* zero-clear */ |
623 | 0 | memset(values, 0, sizeof(values)); |
624 | 0 | memset(isnull, 0, sizeof(isnull)); |
625 | | |
626 | | /* Get id of table. Also handles permissions checks. */ |
627 | 0 | table_id = RangeVarGetRelidExtended(stmt->table, AccessExclusiveLock, |
628 | 0 | 0, |
629 | 0 | RangeVarCallbackForPolicy, |
630 | 0 | stmt); |
631 | | |
632 | | /* Open target_table to build quals. No additional lock is necessary. */ |
633 | 0 | target_table = relation_open(table_id, NoLock); |
634 | | |
635 | | /* Add for the regular security quals */ |
636 | 0 | nsitem = addRangeTableEntryForRelation(qual_pstate, target_table, |
637 | 0 | AccessShareLock, |
638 | 0 | NULL, false, false); |
639 | 0 | addNSItemToQuery(qual_pstate, nsitem, false, true, true); |
640 | | |
641 | | /* Add for the with-check quals */ |
642 | 0 | nsitem = addRangeTableEntryForRelation(with_check_pstate, target_table, |
643 | 0 | AccessShareLock, |
644 | 0 | NULL, false, false); |
645 | 0 | addNSItemToQuery(with_check_pstate, nsitem, false, true, true); |
646 | |
|
647 | 0 | qual = transformWhereClause(qual_pstate, |
648 | 0 | stmt->qual, |
649 | 0 | EXPR_KIND_POLICY, |
650 | 0 | "POLICY"); |
651 | |
|
652 | 0 | with_check_qual = transformWhereClause(with_check_pstate, |
653 | 0 | stmt->with_check, |
654 | 0 | EXPR_KIND_POLICY, |
655 | 0 | "POLICY"); |
656 | | |
657 | | /* Fix up collation information */ |
658 | 0 | assign_expr_collations(qual_pstate, qual); |
659 | 0 | assign_expr_collations(with_check_pstate, with_check_qual); |
660 | | |
661 | | /* Open pg_policy catalog */ |
662 | 0 | pg_policy_rel = table_open(PolicyRelationId, RowExclusiveLock); |
663 | | |
664 | | /* Set key - policy's relation id. */ |
665 | 0 | ScanKeyInit(&skey[0], |
666 | 0 | Anum_pg_policy_polrelid, |
667 | 0 | BTEqualStrategyNumber, F_OIDEQ, |
668 | 0 | ObjectIdGetDatum(table_id)); |
669 | | |
670 | | /* Set key - policy's name. */ |
671 | 0 | ScanKeyInit(&skey[1], |
672 | 0 | Anum_pg_policy_polname, |
673 | 0 | BTEqualStrategyNumber, F_NAMEEQ, |
674 | 0 | CStringGetDatum(stmt->policy_name)); |
675 | |
|
676 | 0 | sscan = systable_beginscan(pg_policy_rel, |
677 | 0 | PolicyPolrelidPolnameIndexId, true, NULL, 2, |
678 | 0 | skey); |
679 | |
|
680 | 0 | policy_tuple = systable_getnext(sscan); |
681 | | |
682 | | /* Complain if the policy name already exists for the table */ |
683 | 0 | if (HeapTupleIsValid(policy_tuple)) |
684 | 0 | ereport(ERROR, |
685 | 0 | (errcode(ERRCODE_DUPLICATE_OBJECT), |
686 | 0 | errmsg("policy \"%s\" for table \"%s\" already exists", |
687 | 0 | stmt->policy_name, RelationGetRelationName(target_table)))); |
688 | | |
689 | 0 | policy_id = GetNewOidWithIndex(pg_policy_rel, PolicyOidIndexId, |
690 | 0 | Anum_pg_policy_oid); |
691 | 0 | values[Anum_pg_policy_oid - 1] = ObjectIdGetDatum(policy_id); |
692 | 0 | values[Anum_pg_policy_polrelid - 1] = ObjectIdGetDatum(table_id); |
693 | 0 | values[Anum_pg_policy_polname - 1] = DirectFunctionCall1(namein, |
694 | 0 | CStringGetDatum(stmt->policy_name)); |
695 | 0 | values[Anum_pg_policy_polcmd - 1] = CharGetDatum(polcmd); |
696 | 0 | values[Anum_pg_policy_polpermissive - 1] = BoolGetDatum(stmt->permissive); |
697 | 0 | values[Anum_pg_policy_polroles - 1] = PointerGetDatum(role_ids); |
698 | | |
699 | | /* Add qual if present. */ |
700 | 0 | if (qual) |
701 | 0 | values[Anum_pg_policy_polqual - 1] = CStringGetTextDatum(nodeToString(qual)); |
702 | 0 | else |
703 | 0 | isnull[Anum_pg_policy_polqual - 1] = true; |
704 | | |
705 | | /* Add WITH CHECK qual if present */ |
706 | 0 | if (with_check_qual) |
707 | 0 | values[Anum_pg_policy_polwithcheck - 1] = CStringGetTextDatum(nodeToString(with_check_qual)); |
708 | 0 | else |
709 | 0 | isnull[Anum_pg_policy_polwithcheck - 1] = true; |
710 | |
|
711 | 0 | policy_tuple = heap_form_tuple(RelationGetDescr(pg_policy_rel), values, |
712 | 0 | isnull); |
713 | |
|
714 | 0 | CatalogTupleInsert(pg_policy_rel, policy_tuple); |
715 | | |
716 | | /* Record Dependencies */ |
717 | 0 | target.classId = RelationRelationId; |
718 | 0 | target.objectId = table_id; |
719 | 0 | target.objectSubId = 0; |
720 | |
|
721 | 0 | myself.classId = PolicyRelationId; |
722 | 0 | myself.objectId = policy_id; |
723 | 0 | myself.objectSubId = 0; |
724 | |
|
725 | 0 | recordDependencyOn(&myself, &target, DEPENDENCY_AUTO); |
726 | |
|
727 | 0 | recordDependencyOnExpr(&myself, qual, qual_pstate->p_rtable, |
728 | 0 | DEPENDENCY_NORMAL); |
729 | |
|
730 | 0 | recordDependencyOnExpr(&myself, with_check_qual, |
731 | 0 | with_check_pstate->p_rtable, DEPENDENCY_NORMAL); |
732 | | |
733 | | /* Register role dependencies */ |
734 | 0 | target.classId = AuthIdRelationId; |
735 | 0 | target.objectSubId = 0; |
736 | 0 | for (i = 0; i < nitems; i++) |
737 | 0 | { |
738 | 0 | target.objectId = DatumGetObjectId(role_oids[i]); |
739 | | /* no dependency if public */ |
740 | 0 | if (target.objectId != ACL_ID_PUBLIC) |
741 | 0 | recordSharedDependencyOn(&myself, &target, |
742 | 0 | SHARED_DEPENDENCY_POLICY); |
743 | 0 | } |
744 | |
|
745 | 0 | InvokeObjectPostCreateHook(PolicyRelationId, policy_id, 0); |
746 | | |
747 | | /* Invalidate Relation Cache */ |
748 | 0 | CacheInvalidateRelcache(target_table); |
749 | | |
750 | | /* Clean up. */ |
751 | 0 | heap_freetuple(policy_tuple); |
752 | 0 | free_parsestate(qual_pstate); |
753 | 0 | free_parsestate(with_check_pstate); |
754 | 0 | systable_endscan(sscan); |
755 | 0 | relation_close(target_table, NoLock); |
756 | 0 | table_close(pg_policy_rel, RowExclusiveLock); |
757 | |
|
758 | 0 | return myself; |
759 | 0 | } |
760 | | |
761 | | /* |
762 | | * AlterPolicy - |
763 | | * handles the execution of the ALTER POLICY command. |
764 | | * |
765 | | * stmt - the AlterPolicyStmt that describes the policy and how to alter it. |
766 | | */ |
767 | | ObjectAddress |
768 | | AlterPolicy(AlterPolicyStmt *stmt) |
769 | 0 | { |
770 | 0 | Relation pg_policy_rel; |
771 | 0 | Oid policy_id; |
772 | 0 | Relation target_table; |
773 | 0 | Oid table_id; |
774 | 0 | Datum *role_oids = NULL; |
775 | 0 | int nitems = 0; |
776 | 0 | ArrayType *role_ids = NULL; |
777 | 0 | List *qual_parse_rtable = NIL; |
778 | 0 | List *with_check_parse_rtable = NIL; |
779 | 0 | Node *qual = NULL; |
780 | 0 | Node *with_check_qual = NULL; |
781 | 0 | ScanKeyData skey[2]; |
782 | 0 | SysScanDesc sscan; |
783 | 0 | HeapTuple policy_tuple; |
784 | 0 | HeapTuple new_tuple; |
785 | 0 | Datum values[Natts_pg_policy]; |
786 | 0 | bool isnull[Natts_pg_policy]; |
787 | 0 | bool replaces[Natts_pg_policy]; |
788 | 0 | ObjectAddress target; |
789 | 0 | ObjectAddress myself; |
790 | 0 | Datum polcmd_datum; |
791 | 0 | char polcmd; |
792 | 0 | bool polcmd_isnull; |
793 | 0 | int i; |
794 | | |
795 | | /* Parse role_ids */ |
796 | 0 | if (stmt->roles != NULL) |
797 | 0 | { |
798 | 0 | role_oids = policy_role_list_to_array(stmt->roles, &nitems); |
799 | 0 | role_ids = construct_array_builtin(role_oids, nitems, OIDOID); |
800 | 0 | } |
801 | | |
802 | | /* Get id of table. Also handles permissions checks. */ |
803 | 0 | table_id = RangeVarGetRelidExtended(stmt->table, AccessExclusiveLock, |
804 | 0 | 0, |
805 | 0 | RangeVarCallbackForPolicy, |
806 | 0 | stmt); |
807 | |
|
808 | 0 | target_table = relation_open(table_id, NoLock); |
809 | | |
810 | | /* Parse the using policy clause */ |
811 | 0 | if (stmt->qual) |
812 | 0 | { |
813 | 0 | ParseNamespaceItem *nsitem; |
814 | 0 | ParseState *qual_pstate = make_parsestate(NULL); |
815 | |
|
816 | 0 | nsitem = addRangeTableEntryForRelation(qual_pstate, target_table, |
817 | 0 | AccessShareLock, |
818 | 0 | NULL, false, false); |
819 | |
|
820 | 0 | addNSItemToQuery(qual_pstate, nsitem, false, true, true); |
821 | |
|
822 | 0 | qual = transformWhereClause(qual_pstate, stmt->qual, |
823 | 0 | EXPR_KIND_POLICY, |
824 | 0 | "POLICY"); |
825 | | |
826 | | /* Fix up collation information */ |
827 | 0 | assign_expr_collations(qual_pstate, qual); |
828 | |
|
829 | 0 | qual_parse_rtable = qual_pstate->p_rtable; |
830 | 0 | free_parsestate(qual_pstate); |
831 | 0 | } |
832 | | |
833 | | /* Parse the with-check policy clause */ |
834 | 0 | if (stmt->with_check) |
835 | 0 | { |
836 | 0 | ParseNamespaceItem *nsitem; |
837 | 0 | ParseState *with_check_pstate = make_parsestate(NULL); |
838 | |
|
839 | 0 | nsitem = addRangeTableEntryForRelation(with_check_pstate, target_table, |
840 | 0 | AccessShareLock, |
841 | 0 | NULL, false, false); |
842 | |
|
843 | 0 | addNSItemToQuery(with_check_pstate, nsitem, false, true, true); |
844 | |
|
845 | 0 | with_check_qual = transformWhereClause(with_check_pstate, |
846 | 0 | stmt->with_check, |
847 | 0 | EXPR_KIND_POLICY, |
848 | 0 | "POLICY"); |
849 | | |
850 | | /* Fix up collation information */ |
851 | 0 | assign_expr_collations(with_check_pstate, with_check_qual); |
852 | |
|
853 | 0 | with_check_parse_rtable = with_check_pstate->p_rtable; |
854 | 0 | free_parsestate(with_check_pstate); |
855 | 0 | } |
856 | | |
857 | | /* zero-clear */ |
858 | 0 | memset(values, 0, sizeof(values)); |
859 | 0 | memset(replaces, 0, sizeof(replaces)); |
860 | 0 | memset(isnull, 0, sizeof(isnull)); |
861 | | |
862 | | /* Find policy to update. */ |
863 | 0 | pg_policy_rel = table_open(PolicyRelationId, RowExclusiveLock); |
864 | | |
865 | | /* Set key - policy's relation id. */ |
866 | 0 | ScanKeyInit(&skey[0], |
867 | 0 | Anum_pg_policy_polrelid, |
868 | 0 | BTEqualStrategyNumber, F_OIDEQ, |
869 | 0 | ObjectIdGetDatum(table_id)); |
870 | | |
871 | | /* Set key - policy's name. */ |
872 | 0 | ScanKeyInit(&skey[1], |
873 | 0 | Anum_pg_policy_polname, |
874 | 0 | BTEqualStrategyNumber, F_NAMEEQ, |
875 | 0 | CStringGetDatum(stmt->policy_name)); |
876 | |
|
877 | 0 | sscan = systable_beginscan(pg_policy_rel, |
878 | 0 | PolicyPolrelidPolnameIndexId, true, NULL, 2, |
879 | 0 | skey); |
880 | |
|
881 | 0 | policy_tuple = systable_getnext(sscan); |
882 | | |
883 | | /* Check that the policy is found, raise an error if not. */ |
884 | 0 | if (!HeapTupleIsValid(policy_tuple)) |
885 | 0 | ereport(ERROR, |
886 | 0 | (errcode(ERRCODE_UNDEFINED_OBJECT), |
887 | 0 | errmsg("policy \"%s\" for table \"%s\" does not exist", |
888 | 0 | stmt->policy_name, |
889 | 0 | RelationGetRelationName(target_table)))); |
890 | | |
891 | | /* Get policy command */ |
892 | 0 | polcmd_datum = heap_getattr(policy_tuple, Anum_pg_policy_polcmd, |
893 | 0 | RelationGetDescr(pg_policy_rel), |
894 | 0 | &polcmd_isnull); |
895 | 0 | Assert(!polcmd_isnull); |
896 | 0 | polcmd = DatumGetChar(polcmd_datum); |
897 | | |
898 | | /* |
899 | | * If the command is SELECT or DELETE then WITH CHECK should be NULL. |
900 | | */ |
901 | 0 | if ((polcmd == ACL_SELECT_CHR || polcmd == ACL_DELETE_CHR) |
902 | 0 | && stmt->with_check != NULL) |
903 | 0 | ereport(ERROR, |
904 | 0 | (errcode(ERRCODE_SYNTAX_ERROR), |
905 | 0 | errmsg("only USING expression allowed for SELECT, DELETE"))); |
906 | | |
907 | | /* |
908 | | * If the command is INSERT then WITH CHECK should be the only expression |
909 | | * provided. |
910 | | */ |
911 | 0 | if ((polcmd == ACL_INSERT_CHR) |
912 | 0 | && stmt->qual != NULL) |
913 | 0 | ereport(ERROR, |
914 | 0 | (errcode(ERRCODE_SYNTAX_ERROR), |
915 | 0 | errmsg("only WITH CHECK expression allowed for INSERT"))); |
916 | | |
917 | 0 | policy_id = ((Form_pg_policy) GETSTRUCT(policy_tuple))->oid; |
918 | |
|
919 | 0 | if (role_ids != NULL) |
920 | 0 | { |
921 | 0 | replaces[Anum_pg_policy_polroles - 1] = true; |
922 | 0 | values[Anum_pg_policy_polroles - 1] = PointerGetDatum(role_ids); |
923 | 0 | } |
924 | 0 | else |
925 | 0 | { |
926 | 0 | Oid *roles; |
927 | 0 | Datum roles_datum; |
928 | 0 | bool attr_isnull; |
929 | 0 | ArrayType *policy_roles; |
930 | | |
931 | | /* |
932 | | * We need to pull the set of roles this policy applies to from what's |
933 | | * in the catalog, so that we can recreate the dependencies correctly |
934 | | * for the policy. |
935 | | */ |
936 | |
|
937 | 0 | roles_datum = heap_getattr(policy_tuple, Anum_pg_policy_polroles, |
938 | 0 | RelationGetDescr(pg_policy_rel), |
939 | 0 | &attr_isnull); |
940 | 0 | Assert(!attr_isnull); |
941 | |
|
942 | 0 | policy_roles = DatumGetArrayTypePCopy(roles_datum); |
943 | |
|
944 | 0 | roles = (Oid *) ARR_DATA_PTR(policy_roles); |
945 | |
|
946 | 0 | nitems = ARR_DIMS(policy_roles)[0]; |
947 | |
|
948 | 0 | role_oids = (Datum *) palloc(nitems * sizeof(Datum)); |
949 | |
|
950 | 0 | for (i = 0; i < nitems; i++) |
951 | 0 | role_oids[i] = ObjectIdGetDatum(roles[i]); |
952 | 0 | } |
953 | |
|
954 | 0 | if (qual != NULL) |
955 | 0 | { |
956 | 0 | replaces[Anum_pg_policy_polqual - 1] = true; |
957 | 0 | values[Anum_pg_policy_polqual - 1] |
958 | 0 | = CStringGetTextDatum(nodeToString(qual)); |
959 | 0 | } |
960 | 0 | else |
961 | 0 | { |
962 | 0 | Datum value_datum; |
963 | 0 | bool attr_isnull; |
964 | | |
965 | | /* |
966 | | * We need to pull the USING expression and build the range table for |
967 | | * the policy from what's in the catalog, so that we can recreate the |
968 | | * dependencies correctly for the policy. |
969 | | */ |
970 | | |
971 | | /* Check if the policy has a USING expr */ |
972 | 0 | value_datum = heap_getattr(policy_tuple, Anum_pg_policy_polqual, |
973 | 0 | RelationGetDescr(pg_policy_rel), |
974 | 0 | &attr_isnull); |
975 | 0 | if (!attr_isnull) |
976 | 0 | { |
977 | 0 | char *qual_value; |
978 | 0 | ParseState *qual_pstate; |
979 | | |
980 | | /* parsestate is built just to build the range table */ |
981 | 0 | qual_pstate = make_parsestate(NULL); |
982 | |
|
983 | 0 | qual_value = TextDatumGetCString(value_datum); |
984 | 0 | qual = stringToNode(qual_value); |
985 | | |
986 | | /* Add this rel to the parsestate's rangetable, for dependencies */ |
987 | 0 | (void) addRangeTableEntryForRelation(qual_pstate, target_table, |
988 | 0 | AccessShareLock, |
989 | 0 | NULL, false, false); |
990 | |
|
991 | 0 | qual_parse_rtable = qual_pstate->p_rtable; |
992 | 0 | free_parsestate(qual_pstate); |
993 | 0 | } |
994 | 0 | } |
995 | |
|
996 | 0 | if (with_check_qual != NULL) |
997 | 0 | { |
998 | 0 | replaces[Anum_pg_policy_polwithcheck - 1] = true; |
999 | 0 | values[Anum_pg_policy_polwithcheck - 1] |
1000 | 0 | = CStringGetTextDatum(nodeToString(with_check_qual)); |
1001 | 0 | } |
1002 | 0 | else |
1003 | 0 | { |
1004 | 0 | Datum value_datum; |
1005 | 0 | bool attr_isnull; |
1006 | | |
1007 | | /* |
1008 | | * We need to pull the WITH CHECK expression and build the range table |
1009 | | * for the policy from what's in the catalog, so that we can recreate |
1010 | | * the dependencies correctly for the policy. |
1011 | | */ |
1012 | | |
1013 | | /* Check if the policy has a WITH CHECK expr */ |
1014 | 0 | value_datum = heap_getattr(policy_tuple, Anum_pg_policy_polwithcheck, |
1015 | 0 | RelationGetDescr(pg_policy_rel), |
1016 | 0 | &attr_isnull); |
1017 | 0 | if (!attr_isnull) |
1018 | 0 | { |
1019 | 0 | char *with_check_value; |
1020 | 0 | ParseState *with_check_pstate; |
1021 | | |
1022 | | /* parsestate is built just to build the range table */ |
1023 | 0 | with_check_pstate = make_parsestate(NULL); |
1024 | |
|
1025 | 0 | with_check_value = TextDatumGetCString(value_datum); |
1026 | 0 | with_check_qual = stringToNode(with_check_value); |
1027 | | |
1028 | | /* Add this rel to the parsestate's rangetable, for dependencies */ |
1029 | 0 | (void) addRangeTableEntryForRelation(with_check_pstate, |
1030 | 0 | target_table, |
1031 | 0 | AccessShareLock, |
1032 | 0 | NULL, false, false); |
1033 | |
|
1034 | 0 | with_check_parse_rtable = with_check_pstate->p_rtable; |
1035 | 0 | free_parsestate(with_check_pstate); |
1036 | 0 | } |
1037 | 0 | } |
1038 | |
|
1039 | 0 | new_tuple = heap_modify_tuple(policy_tuple, |
1040 | 0 | RelationGetDescr(pg_policy_rel), |
1041 | 0 | values, isnull, replaces); |
1042 | 0 | CatalogTupleUpdate(pg_policy_rel, &new_tuple->t_self, new_tuple); |
1043 | | |
1044 | | /* Update Dependencies. */ |
1045 | 0 | deleteDependencyRecordsFor(PolicyRelationId, policy_id, false); |
1046 | | |
1047 | | /* Record Dependencies */ |
1048 | 0 | target.classId = RelationRelationId; |
1049 | 0 | target.objectId = table_id; |
1050 | 0 | target.objectSubId = 0; |
1051 | |
|
1052 | 0 | myself.classId = PolicyRelationId; |
1053 | 0 | myself.objectId = policy_id; |
1054 | 0 | myself.objectSubId = 0; |
1055 | |
|
1056 | 0 | recordDependencyOn(&myself, &target, DEPENDENCY_AUTO); |
1057 | |
|
1058 | 0 | recordDependencyOnExpr(&myself, qual, qual_parse_rtable, DEPENDENCY_NORMAL); |
1059 | |
|
1060 | 0 | recordDependencyOnExpr(&myself, with_check_qual, with_check_parse_rtable, |
1061 | 0 | DEPENDENCY_NORMAL); |
1062 | | |
1063 | | /* Register role dependencies */ |
1064 | 0 | deleteSharedDependencyRecordsFor(PolicyRelationId, policy_id, 0); |
1065 | 0 | target.classId = AuthIdRelationId; |
1066 | 0 | target.objectSubId = 0; |
1067 | 0 | for (i = 0; i < nitems; i++) |
1068 | 0 | { |
1069 | 0 | target.objectId = DatumGetObjectId(role_oids[i]); |
1070 | | /* no dependency if public */ |
1071 | 0 | if (target.objectId != ACL_ID_PUBLIC) |
1072 | 0 | recordSharedDependencyOn(&myself, &target, |
1073 | 0 | SHARED_DEPENDENCY_POLICY); |
1074 | 0 | } |
1075 | |
|
1076 | 0 | InvokeObjectPostAlterHook(PolicyRelationId, policy_id, 0); |
1077 | |
|
1078 | 0 | heap_freetuple(new_tuple); |
1079 | | |
1080 | | /* Invalidate Relation Cache */ |
1081 | 0 | CacheInvalidateRelcache(target_table); |
1082 | | |
1083 | | /* Clean up. */ |
1084 | 0 | systable_endscan(sscan); |
1085 | 0 | relation_close(target_table, NoLock); |
1086 | 0 | table_close(pg_policy_rel, RowExclusiveLock); |
1087 | |
|
1088 | 0 | return myself; |
1089 | 0 | } |
1090 | | |
1091 | | /* |
1092 | | * rename_policy - |
1093 | | * change the name of a policy on a relation |
1094 | | */ |
1095 | | ObjectAddress |
1096 | | rename_policy(RenameStmt *stmt) |
1097 | 0 | { |
1098 | 0 | Relation pg_policy_rel; |
1099 | 0 | Relation target_table; |
1100 | 0 | Oid table_id; |
1101 | 0 | Oid opoloid; |
1102 | 0 | ScanKeyData skey[2]; |
1103 | 0 | SysScanDesc sscan; |
1104 | 0 | HeapTuple policy_tuple; |
1105 | 0 | ObjectAddress address; |
1106 | | |
1107 | | /* Get id of table. Also handles permissions checks. */ |
1108 | 0 | table_id = RangeVarGetRelidExtended(stmt->relation, AccessExclusiveLock, |
1109 | 0 | 0, |
1110 | 0 | RangeVarCallbackForPolicy, |
1111 | 0 | stmt); |
1112 | |
|
1113 | 0 | target_table = relation_open(table_id, NoLock); |
1114 | |
|
1115 | 0 | pg_policy_rel = table_open(PolicyRelationId, RowExclusiveLock); |
1116 | | |
1117 | | /* First pass -- check for conflict */ |
1118 | | |
1119 | | /* Add key - policy's relation id. */ |
1120 | 0 | ScanKeyInit(&skey[0], |
1121 | 0 | Anum_pg_policy_polrelid, |
1122 | 0 | BTEqualStrategyNumber, F_OIDEQ, |
1123 | 0 | ObjectIdGetDatum(table_id)); |
1124 | | |
1125 | | /* Add key - policy's name. */ |
1126 | 0 | ScanKeyInit(&skey[1], |
1127 | 0 | Anum_pg_policy_polname, |
1128 | 0 | BTEqualStrategyNumber, F_NAMEEQ, |
1129 | 0 | CStringGetDatum(stmt->newname)); |
1130 | |
|
1131 | 0 | sscan = systable_beginscan(pg_policy_rel, |
1132 | 0 | PolicyPolrelidPolnameIndexId, true, NULL, 2, |
1133 | 0 | skey); |
1134 | |
|
1135 | 0 | if (HeapTupleIsValid(systable_getnext(sscan))) |
1136 | 0 | ereport(ERROR, |
1137 | 0 | (errcode(ERRCODE_DUPLICATE_OBJECT), |
1138 | 0 | errmsg("policy \"%s\" for table \"%s\" already exists", |
1139 | 0 | stmt->newname, RelationGetRelationName(target_table)))); |
1140 | | |
1141 | 0 | systable_endscan(sscan); |
1142 | | |
1143 | | /* Second pass -- find existing policy and update */ |
1144 | | /* Add key - policy's relation id. */ |
1145 | 0 | ScanKeyInit(&skey[0], |
1146 | 0 | Anum_pg_policy_polrelid, |
1147 | 0 | BTEqualStrategyNumber, F_OIDEQ, |
1148 | 0 | ObjectIdGetDatum(table_id)); |
1149 | | |
1150 | | /* Add key - policy's name. */ |
1151 | 0 | ScanKeyInit(&skey[1], |
1152 | 0 | Anum_pg_policy_polname, |
1153 | 0 | BTEqualStrategyNumber, F_NAMEEQ, |
1154 | 0 | CStringGetDatum(stmt->subname)); |
1155 | |
|
1156 | 0 | sscan = systable_beginscan(pg_policy_rel, |
1157 | 0 | PolicyPolrelidPolnameIndexId, true, NULL, 2, |
1158 | 0 | skey); |
1159 | |
|
1160 | 0 | policy_tuple = systable_getnext(sscan); |
1161 | | |
1162 | | /* Complain if we did not find the policy */ |
1163 | 0 | if (!HeapTupleIsValid(policy_tuple)) |
1164 | 0 | ereport(ERROR, |
1165 | 0 | (errcode(ERRCODE_UNDEFINED_OBJECT), |
1166 | 0 | errmsg("policy \"%s\" for table \"%s\" does not exist", |
1167 | 0 | stmt->subname, RelationGetRelationName(target_table)))); |
1168 | | |
1169 | 0 | opoloid = ((Form_pg_policy) GETSTRUCT(policy_tuple))->oid; |
1170 | |
|
1171 | 0 | policy_tuple = heap_copytuple(policy_tuple); |
1172 | |
|
1173 | 0 | namestrcpy(&((Form_pg_policy) GETSTRUCT(policy_tuple))->polname, |
1174 | 0 | stmt->newname); |
1175 | |
|
1176 | 0 | CatalogTupleUpdate(pg_policy_rel, &policy_tuple->t_self, policy_tuple); |
1177 | |
|
1178 | 0 | InvokeObjectPostAlterHook(PolicyRelationId, opoloid, 0); |
1179 | |
|
1180 | 0 | ObjectAddressSet(address, PolicyRelationId, opoloid); |
1181 | | |
1182 | | /* |
1183 | | * Invalidate relation's relcache entry so that other backends (and this |
1184 | | * one too!) are sent SI message to make them rebuild relcache entries. |
1185 | | * (Ideally this should happen automatically...) |
1186 | | */ |
1187 | 0 | CacheInvalidateRelcache(target_table); |
1188 | | |
1189 | | /* Clean up. */ |
1190 | 0 | systable_endscan(sscan); |
1191 | 0 | table_close(pg_policy_rel, RowExclusiveLock); |
1192 | 0 | relation_close(target_table, NoLock); |
1193 | |
|
1194 | 0 | return address; |
1195 | 0 | } |
1196 | | |
1197 | | /* |
1198 | | * get_relation_policy_oid - Look up a policy by name to find its OID |
1199 | | * |
1200 | | * If missing_ok is false, throw an error if policy not found. If |
1201 | | * true, just return InvalidOid. |
1202 | | */ |
1203 | | Oid |
1204 | | get_relation_policy_oid(Oid relid, const char *policy_name, bool missing_ok) |
1205 | 0 | { |
1206 | 0 | Relation pg_policy_rel; |
1207 | 0 | ScanKeyData skey[2]; |
1208 | 0 | SysScanDesc sscan; |
1209 | 0 | HeapTuple policy_tuple; |
1210 | 0 | Oid policy_oid; |
1211 | |
|
1212 | 0 | pg_policy_rel = table_open(PolicyRelationId, AccessShareLock); |
1213 | | |
1214 | | /* Add key - policy's relation id. */ |
1215 | 0 | ScanKeyInit(&skey[0], |
1216 | 0 | Anum_pg_policy_polrelid, |
1217 | 0 | BTEqualStrategyNumber, F_OIDEQ, |
1218 | 0 | ObjectIdGetDatum(relid)); |
1219 | | |
1220 | | /* Add key - policy's name. */ |
1221 | 0 | ScanKeyInit(&skey[1], |
1222 | 0 | Anum_pg_policy_polname, |
1223 | 0 | BTEqualStrategyNumber, F_NAMEEQ, |
1224 | 0 | CStringGetDatum(policy_name)); |
1225 | |
|
1226 | 0 | sscan = systable_beginscan(pg_policy_rel, |
1227 | 0 | PolicyPolrelidPolnameIndexId, true, NULL, 2, |
1228 | 0 | skey); |
1229 | |
|
1230 | 0 | policy_tuple = systable_getnext(sscan); |
1231 | |
|
1232 | 0 | if (!HeapTupleIsValid(policy_tuple)) |
1233 | 0 | { |
1234 | 0 | if (!missing_ok) |
1235 | 0 | ereport(ERROR, |
1236 | 0 | (errcode(ERRCODE_UNDEFINED_OBJECT), |
1237 | 0 | errmsg("policy \"%s\" for table \"%s\" does not exist", |
1238 | 0 | policy_name, get_rel_name(relid)))); |
1239 | | |
1240 | 0 | policy_oid = InvalidOid; |
1241 | 0 | } |
1242 | 0 | else |
1243 | 0 | policy_oid = ((Form_pg_policy) GETSTRUCT(policy_tuple))->oid; |
1244 | | |
1245 | | /* Clean up. */ |
1246 | 0 | systable_endscan(sscan); |
1247 | 0 | table_close(pg_policy_rel, AccessShareLock); |
1248 | |
|
1249 | 0 | return policy_oid; |
1250 | 0 | } |
1251 | | |
1252 | | /* |
1253 | | * relation_has_policies - Determine if relation has any policies |
1254 | | */ |
1255 | | bool |
1256 | | relation_has_policies(Relation rel) |
1257 | 0 | { |
1258 | 0 | Relation catalog; |
1259 | 0 | ScanKeyData skey; |
1260 | 0 | SysScanDesc sscan; |
1261 | 0 | HeapTuple policy_tuple; |
1262 | 0 | bool ret = false; |
1263 | |
|
1264 | 0 | catalog = table_open(PolicyRelationId, AccessShareLock); |
1265 | 0 | ScanKeyInit(&skey, |
1266 | 0 | Anum_pg_policy_polrelid, |
1267 | 0 | BTEqualStrategyNumber, F_OIDEQ, |
1268 | 0 | ObjectIdGetDatum(RelationGetRelid(rel))); |
1269 | 0 | sscan = systable_beginscan(catalog, PolicyPolrelidPolnameIndexId, true, |
1270 | 0 | NULL, 1, &skey); |
1271 | 0 | policy_tuple = systable_getnext(sscan); |
1272 | 0 | if (HeapTupleIsValid(policy_tuple)) |
1273 | 0 | ret = true; |
1274 | |
|
1275 | 0 | systable_endscan(sscan); |
1276 | 0 | table_close(catalog, AccessShareLock); |
1277 | |
|
1278 | 0 | return ret; |
1279 | 0 | } |