/Users/deen/code/yugabyte-db/src/postgres/src/backend/commands/user.c
Line | Count | Source (jump to first uncovered line) |
1 | | /*------------------------------------------------------------------------- |
2 | | * |
3 | | * user.c |
4 | | * Commands for manipulating roles (formerly called users). |
5 | | * |
6 | | * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group |
7 | | * Portions Copyright (c) 1994, Regents of the University of California |
8 | | * |
9 | | * src/backend/commands/user.c |
10 | | * |
11 | | *------------------------------------------------------------------------- |
12 | | */ |
13 | | #include <pg_yb_utils.h> |
14 | | #include "postgres.h" |
15 | | |
16 | | #include "access/genam.h" |
17 | | #include "access/heapam.h" |
18 | | #include "access/htup_details.h" |
19 | | #include "access/xact.h" |
20 | | #include "catalog/binary_upgrade.h" |
21 | | #include "catalog/catalog.h" |
22 | | #include "catalog/dependency.h" |
23 | | #include "catalog/indexing.h" |
24 | | #include "catalog/objectaccess.h" |
25 | | #include "catalog/pg_auth_members.h" |
26 | | #include "catalog/pg_authid.h" |
27 | | #include "catalog/pg_database.h" |
28 | | #include "catalog/pg_db_role_setting.h" |
29 | | #include "commands/comment.h" |
30 | | #include "commands/dbcommands.h" |
31 | | #include "commands/seclabel.h" |
32 | | #include "commands/user.h" |
33 | | #include "libpq/crypt.h" |
34 | | #include "miscadmin.h" |
35 | | #include "storage/lmgr.h" |
36 | | #include "utils/acl.h" |
37 | | #include "utils/builtins.h" |
38 | | #include "utils/fmgroids.h" |
39 | | #include "utils/syscache.h" |
40 | | #include "utils/timestamp.h" |
41 | | #include "utils/tqual.h" |
42 | | |
43 | | /* Potentially set by pg_upgrade_support functions */ |
44 | | Oid binary_upgrade_next_pg_authid_oid = InvalidOid; |
45 | | |
46 | | |
47 | | /* GUC parameter */ |
48 | | int Password_encryption = PASSWORD_TYPE_MD5; |
49 | | |
50 | | /* Hook to check passwords in CreateRole() and AlterRole() */ |
51 | | check_password_hook_type check_password_hook = NULL; |
52 | | |
53 | | static void AddRoleMems(const char *rolename, Oid roleid, |
54 | | List *memberSpecs, List *memberIds, |
55 | | Oid grantorId, bool admin_opt); |
56 | | static void DelRoleMems(const char *rolename, Oid roleid, |
57 | | List *memberSpecs, List *memberIds, |
58 | | bool admin_opt); |
59 | | |
60 | | |
61 | | /* Check if current user has createrole privileges */ |
62 | | static bool |
63 | | have_createrole_privilege(void) |
64 | 502 | { |
65 | 502 | return has_createrole_privilege(GetUserId()); |
66 | 502 | } |
67 | | |
68 | | |
69 | | /* |
70 | | * CREATE ROLE |
71 | | */ |
72 | | Oid |
73 | | CreateRole(ParseState *pstate, CreateRoleStmt *stmt) |
74 | 479 | { |
75 | 479 | Relation pg_authid_rel; |
76 | 479 | TupleDesc pg_authid_dsc; |
77 | 479 | HeapTuple tuple; |
78 | 479 | Datum new_record[Natts_pg_authid]; |
79 | 479 | bool new_record_nulls[Natts_pg_authid]; |
80 | 479 | Oid roleid; |
81 | 479 | ListCell *item; |
82 | 479 | ListCell *option; |
83 | 479 | char *password = NULL; /* user password */ |
84 | 479 | bool issuper = false; /* Make the user a superuser? */ |
85 | 479 | bool inherit = true; /* Auto inherit privileges? */ |
86 | 479 | bool createrole = false; /* Can this user create roles? */ |
87 | 479 | bool createdb = false; /* Can the user create databases? */ |
88 | 479 | bool canlogin = false; /* Can this user login? */ |
89 | 479 | bool isreplication = false; /* Is this a replication role? */ |
90 | 479 | bool bypassrls = false; /* Is this a row security enabled role? */ |
91 | 479 | int connlimit = -1; /* maximum connections allowed */ |
92 | 479 | List *addroleto = NIL; /* roles to make this a member of */ |
93 | 479 | List *rolemembers = NIL; /* roles to be members of this role */ |
94 | 479 | List *adminmembers = NIL; /* roles to be admins of this role */ |
95 | 479 | char *validUntil = NULL; /* time the login is valid until */ |
96 | 479 | Datum validUntil_datum; /* same, as timestamptz Datum */ |
97 | 479 | bool validUntil_null; |
98 | 479 | DefElem *dpassword = NULL; |
99 | 479 | DefElem *dissuper = NULL; |
100 | 479 | DefElem *dinherit = NULL; |
101 | 479 | DefElem *dcreaterole = NULL; |
102 | 479 | DefElem *dcreatedb = NULL; |
103 | 479 | DefElem *dcanlogin = NULL; |
104 | 479 | DefElem *disreplication = NULL; |
105 | 479 | DefElem *dconnlimit = NULL; |
106 | 479 | DefElem *daddroleto = NULL; |
107 | 479 | DefElem *drolemembers = NULL; |
108 | 479 | DefElem *dadminmembers = NULL; |
109 | 479 | DefElem *dvalidUntil = NULL; |
110 | 479 | DefElem *dbypassRLS = NULL; |
111 | | |
112 | | /* The defaults can vary depending on the original statement type */ |
113 | 479 | switch (stmt->stmt_type) |
114 | 479 | { |
115 | 457 | case ROLESTMT_ROLE: |
116 | 457 | break; |
117 | 19 | case ROLESTMT_USER: |
118 | 19 | canlogin = true; |
119 | | /* may eventually want inherit to default to false here */ |
120 | 19 | break; |
121 | 3 | case ROLESTMT_GROUP: |
122 | 3 | break; |
123 | 479 | } |
124 | | |
125 | | /* Extract options from the statement node tree */ |
126 | 479 | foreach(option, stmt->options) |
127 | 1.49k | { |
128 | 1.49k | DefElem *defel = (DefElem *) lfirst(option); |
129 | | |
130 | 1.49k | if (strcmp(defel->defname, "password") == 0) |
131 | 16 | { |
132 | 16 | if (dpassword) |
133 | 16 | ereport(ERROR, |
134 | 16 | (errcode(ERRCODE_SYNTAX_ERROR), |
135 | 16 | errmsg("conflicting or redundant options"), |
136 | 16 | parser_errposition(pstate, defel->location))); |
137 | 16 | dpassword = defel; |
138 | 16 | } |
139 | 1.47k | else if (strcmp(defel->defname, "sysid") == 0) |
140 | 1 | { |
141 | 1 | ereport(NOTICE, |
142 | 1 | (errmsg("SYSID can no longer be specified"))); |
143 | 1 | } |
144 | 1.47k | else if (strcmp(defel->defname, "superuser") == 0) |
145 | 293 | { |
146 | 293 | if (dissuper) |
147 | 293 | ereport(ERROR, |
148 | 293 | (errcode(ERRCODE_SYNTAX_ERROR), |
149 | 293 | errmsg("conflicting or redundant options"), |
150 | 293 | parser_errposition(pstate, defel->location))); |
151 | 293 | dissuper = defel; |
152 | 293 | } |
153 | 1.18k | else if (strcmp(defel->defname, "inherit") == 0) |
154 | 10 | { |
155 | 10 | if (dinherit) |
156 | 10 | ereport(ERROR, |
157 | 10 | (errcode(ERRCODE_SYNTAX_ERROR), |
158 | 10 | errmsg("conflicting or redundant options"), |
159 | 10 | parser_errposition(pstate, defel->location))); |
160 | 10 | dinherit = defel; |
161 | 10 | } |
162 | 1.17k | else if (strcmp(defel->defname, "createrole") == 0) |
163 | 270 | { |
164 | 270 | if (dcreaterole) |
165 | 270 | ereport(ERROR, |
166 | 270 | (errcode(ERRCODE_SYNTAX_ERROR), |
167 | 270 | errmsg("conflicting or redundant options"), |
168 | 270 | parser_errposition(pstate, defel->location))); |
169 | 270 | dcreaterole = defel; |
170 | 270 | } |
171 | 903 | else if (strcmp(defel->defname, "createdb") == 0) |
172 | 270 | { |
173 | 270 | if (dcreatedb) |
174 | 270 | ereport(ERROR, |
175 | 270 | (errcode(ERRCODE_SYNTAX_ERROR), |
176 | 270 | errmsg("conflicting or redundant options"), |
177 | 270 | parser_errposition(pstate, defel->location))); |
178 | 270 | dcreatedb = defel; |
179 | 270 | } |
180 | 633 | else if (strcmp(defel->defname, "canlogin") == 0) |
181 | 327 | { |
182 | 327 | if (dcanlogin) |
183 | 327 | ereport(ERROR, |
184 | 327 | (errcode(ERRCODE_SYNTAX_ERROR), |
185 | 327 | errmsg("conflicting or redundant options"), |
186 | 327 | parser_errposition(pstate, defel->location))); |
187 | 327 | dcanlogin = defel; |
188 | 327 | } |
189 | 306 | else if (strcmp(defel->defname, "isreplication") == 0) |
190 | 2 | { |
191 | 2 | if (disreplication) |
192 | 2 | ereport(ERROR, |
193 | 2 | (errcode(ERRCODE_SYNTAX_ERROR), |
194 | 2 | errmsg("conflicting or redundant options"), |
195 | 2 | parser_errposition(pstate, defel->location))); |
196 | 2 | disreplication = defel; |
197 | 2 | } |
198 | 304 | else if (strcmp(defel->defname, "connectionlimit") == 0) |
199 | 2 | { |
200 | 2 | if (dconnlimit) |
201 | 2 | ereport(ERROR, |
202 | 2 | (errcode(ERRCODE_SYNTAX_ERROR), |
203 | 2 | errmsg("conflicting or redundant options"), |
204 | 2 | parser_errposition(pstate, defel->location))); |
205 | 2 | dconnlimit = defel; |
206 | 2 | } |
207 | 302 | else if (strcmp(defel->defname, "addroleto") == 0) |
208 | 3 | { |
209 | 3 | if (daddroleto) |
210 | 3 | ereport(ERROR, |
211 | 3 | (errcode(ERRCODE_SYNTAX_ERROR), |
212 | 3 | errmsg("conflicting or redundant options"), |
213 | 3 | parser_errposition(pstate, defel->location))); |
214 | 3 | daddroleto = defel; |
215 | 3 | } |
216 | 299 | else if (strcmp(defel->defname, "rolemembers") == 0) |
217 | 27 | { |
218 | 27 | if (drolemembers) |
219 | 27 | ereport(ERROR, |
220 | 27 | (errcode(ERRCODE_SYNTAX_ERROR), |
221 | 27 | errmsg("conflicting or redundant options"), |
222 | 27 | parser_errposition(pstate, defel->location))); |
223 | 27 | drolemembers = defel; |
224 | 27 | } |
225 | 272 | else if (strcmp(defel->defname, "adminmembers") == 0) |
226 | 2 | { |
227 | 2 | if (dadminmembers) |
228 | 2 | ereport(ERROR, |
229 | 2 | (errcode(ERRCODE_SYNTAX_ERROR), |
230 | 2 | errmsg("conflicting or redundant options"), |
231 | 2 | parser_errposition(pstate, defel->location))); |
232 | 2 | dadminmembers = defel; |
233 | 2 | } |
234 | 270 | else if (strcmp(defel->defname, "validUntil") == 0) |
235 | 1 | { |
236 | 1 | if (dvalidUntil) |
237 | 1 | ereport(ERROR, |
238 | 1 | (errcode(ERRCODE_SYNTAX_ERROR), |
239 | 1 | errmsg("conflicting or redundant options"), |
240 | 1 | parser_errposition(pstate, defel->location))); |
241 | 1 | dvalidUntil = defel; |
242 | 1 | } |
243 | 269 | else if (strcmp(defel->defname, "bypassrls") == 0) |
244 | 269 | { |
245 | 269 | if (dbypassRLS) |
246 | 269 | ereport(ERROR, |
247 | 269 | (errcode(ERRCODE_SYNTAX_ERROR), |
248 | 269 | errmsg("conflicting or redundant options"), |
249 | 269 | parser_errposition(pstate, defel->location))); |
250 | 269 | dbypassRLS = defel; |
251 | 269 | } |
252 | 269 | else |
253 | 0 | elog(ERROR, "option \"%s\" not recognized", |
254 | 1.49k | defel->defname); |
255 | 1.49k | } |
256 | | |
257 | 479 | if (dpassword && dpassword->arg) |
258 | 15 | password = strVal(dpassword->arg); |
259 | 479 | if (dissuper) |
260 | 293 | issuper = intVal(dissuper->arg) != 0; |
261 | 479 | if (dinherit) |
262 | 10 | inherit = intVal(dinherit->arg) != 0; |
263 | 479 | if (dcreaterole) |
264 | 270 | createrole = intVal(dcreaterole->arg) != 0; |
265 | 479 | if (dcreatedb) |
266 | 270 | createdb = intVal(dcreatedb->arg) != 0; |
267 | 479 | if (dcanlogin) |
268 | 327 | canlogin = intVal(dcanlogin->arg) != 0; |
269 | 479 | if (disreplication) |
270 | 2 | isreplication = intVal(disreplication->arg) != 0; |
271 | 479 | if (dconnlimit) |
272 | 2 | { |
273 | 2 | connlimit = intVal(dconnlimit->arg); |
274 | 2 | if (connlimit < -1) |
275 | 2 | ereport(ERROR, |
276 | 2 | (errcode(ERRCODE_INVALID_PARAMETER_VALUE), |
277 | 2 | errmsg("invalid connection limit: %d", connlimit))); |
278 | 2 | } |
279 | 479 | if (daddroleto) |
280 | 3 | addroleto = (List *) daddroleto->arg; |
281 | 479 | if (drolemembers) |
282 | 27 | rolemembers = (List *) drolemembers->arg; |
283 | 479 | if (dadminmembers) |
284 | 2 | adminmembers = (List *) dadminmembers->arg; |
285 | 479 | if (dvalidUntil) |
286 | 1 | validUntil = strVal(dvalidUntil->arg); |
287 | 479 | if (dbypassRLS) |
288 | 269 | bypassrls = intVal(dbypassRLS->arg) != 0; |
289 | | |
290 | | /* Check some permissions first */ |
291 | 479 | if (issuper) |
292 | 293 | { |
293 | 293 | if (!superuser()) |
294 | 293 | ereport(ERROR, |
295 | 293 | (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), |
296 | 293 | errmsg("must be superuser to create superusers"))); |
297 | 293 | } |
298 | 186 | else if (isreplication) |
299 | 2 | { |
300 | 2 | if (!superuser()) |
301 | 2 | ereport(ERROR, |
302 | 2 | (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), |
303 | 2 | errmsg("must be superuser to create replication users"))); |
304 | 2 | } |
305 | 184 | else if (bypassrls) |
306 | 4 | { |
307 | 4 | if (!superuser()) |
308 | 4 | ereport(ERROR, |
309 | 4 | (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), |
310 | 4 | errmsg("must be superuser to change bypassrls attribute"))); |
311 | 4 | } |
312 | 180 | else |
313 | 180 | { |
314 | 180 | if (!have_createrole_privilege()) |
315 | 180 | ereport(ERROR, |
316 | 180 | (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), |
317 | 180 | errmsg("permission denied to create role"))); |
318 | 180 | } |
319 | | |
320 | | /* |
321 | | * Check that the user is not trying to create a role in the reserved |
322 | | * "pg_" namespace. |
323 | | */ |
324 | 479 | if (IsReservedName(stmt->role)) |
325 | 479 | ereport(ERROR, |
326 | 479 | (errcode(ERRCODE_RESERVED_NAME), |
327 | 479 | errmsg("role name \"%s\" is reserved", |
328 | 479 | stmt->role), |
329 | 479 | errdetail("Role names starting with \"pg_\" are reserved."))); |
330 | | |
331 | | /* |
332 | | * Check the pg_authid relation to be certain the role doesn't already |
333 | | * exist. |
334 | | */ |
335 | 479 | pg_authid_rel = heap_open(AuthIdRelationId, RowExclusiveLock); |
336 | 479 | pg_authid_dsc = RelationGetDescr(pg_authid_rel); |
337 | | |
338 | 479 | if (OidIsValid(get_role_oid(stmt->role, true))) |
339 | 479 | ereport(ERROR, |
340 | 479 | (errcode(ERRCODE_DUPLICATE_OBJECT), |
341 | 479 | errmsg("role \"%s\" already exists", |
342 | 479 | stmt->role))); |
343 | | |
344 | | /* Convert validuntil to internal form */ |
345 | 479 | if (validUntil) |
346 | 1 | { |
347 | 1 | validUntil_datum = DirectFunctionCall3(timestamptz_in, |
348 | 1 | CStringGetDatum(validUntil), |
349 | 1 | ObjectIdGetDatum(InvalidOid), |
350 | 1 | Int32GetDatum(-1)); |
351 | 1 | validUntil_null = false; |
352 | 1 | } |
353 | 478 | else |
354 | 478 | { |
355 | 478 | validUntil_datum = (Datum) 0; |
356 | 478 | validUntil_null = true; |
357 | 478 | } |
358 | | |
359 | | /* |
360 | | * Call the password checking hook if there is one defined |
361 | | */ |
362 | 479 | if (check_password_hook && password) |
363 | 0 | (*check_password_hook) (stmt->role, |
364 | 0 | password, |
365 | 0 | get_password_type(password), |
366 | 0 | validUntil_datum, |
367 | 0 | validUntil_null); |
368 | | |
369 | | /* |
370 | | * Build a tuple to insert |
371 | | */ |
372 | 479 | MemSet(new_record, 0, sizeof(new_record)); |
373 | 479 | MemSet(new_record_nulls, false, sizeof(new_record_nulls)); |
374 | | |
375 | 479 | new_record[Anum_pg_authid_rolname - 1] = |
376 | 479 | DirectFunctionCall1(namein, CStringGetDatum(stmt->role)); |
377 | | |
378 | 479 | new_record[Anum_pg_authid_rolsuper - 1] = BoolGetDatum(issuper); |
379 | 479 | new_record[Anum_pg_authid_rolinherit - 1] = BoolGetDatum(inherit); |
380 | 479 | new_record[Anum_pg_authid_rolcreaterole - 1] = BoolGetDatum(createrole); |
381 | 479 | new_record[Anum_pg_authid_rolcreatedb - 1] = BoolGetDatum(createdb); |
382 | 479 | new_record[Anum_pg_authid_rolcanlogin - 1] = BoolGetDatum(canlogin); |
383 | 479 | new_record[Anum_pg_authid_rolreplication - 1] = BoolGetDatum(isreplication); |
384 | 479 | new_record[Anum_pg_authid_rolconnlimit - 1] = Int32GetDatum(connlimit); |
385 | | |
386 | 479 | if (password) |
387 | 15 | { |
388 | 15 | char *shadow_pass; |
389 | 15 | char *logdetail; |
390 | | |
391 | | /* |
392 | | * Don't allow an empty password. Libpq treats an empty password the |
393 | | * same as no password at all, and won't even try to authenticate. But |
394 | | * other clients might, so allowing it would be confusing. By clearing |
395 | | * the password when an empty string is specified, the account is |
396 | | * consistently locked for all clients. |
397 | | * |
398 | | * Note that this only covers passwords stored in the database itself. |
399 | | * There are also checks in the authentication code, to forbid an |
400 | | * empty password from being used with authentication methods that |
401 | | * fetch the password from an external system, like LDAP or PAM. |
402 | | */ |
403 | 15 | if (password[0] == '\0' || |
404 | 14 | plain_crypt_verify(stmt->role, password, "", &logdetail) == STATUS_OK) |
405 | 1 | { |
406 | 1 | ereport(NOTICE, |
407 | 1 | (errmsg("empty string is not a valid password, clearing password"))); |
408 | 1 | new_record_nulls[Anum_pg_authid_rolpassword - 1] = true; |
409 | 1 | } |
410 | 14 | else |
411 | 14 | { |
412 | | /* Encrypt the password to the requested format. */ |
413 | 14 | shadow_pass = encrypt_password(Password_encryption, stmt->role, |
414 | 14 | password); |
415 | 14 | new_record[Anum_pg_authid_rolpassword - 1] = |
416 | 14 | CStringGetTextDatum(shadow_pass); |
417 | 14 | } |
418 | 15 | } |
419 | 464 | else |
420 | 464 | new_record_nulls[Anum_pg_authid_rolpassword - 1] = true; |
421 | | |
422 | 479 | new_record[Anum_pg_authid_rolvaliduntil - 1] = validUntil_datum; |
423 | 479 | new_record_nulls[Anum_pg_authid_rolvaliduntil - 1] = validUntil_null; |
424 | | |
425 | 479 | new_record[Anum_pg_authid_rolbypassrls - 1] = BoolGetDatum(bypassrls); |
426 | | |
427 | 479 | tuple = heap_form_tuple(pg_authid_dsc, new_record, new_record_nulls); |
428 | | |
429 | | /* |
430 | | * pg_largeobject_metadata contains pg_authid.oid's, so we use the |
431 | | * binary-upgrade override. |
432 | | */ |
433 | 479 | if (IsBinaryUpgrade && !yb_binary_restore) |
434 | 0 | { |
435 | 0 | if (!OidIsValid(binary_upgrade_next_pg_authid_oid)) |
436 | 0 | ereport(ERROR, |
437 | 0 | (errcode(ERRCODE_INVALID_PARAMETER_VALUE), |
438 | 0 | errmsg("pg_authid OID value not set when in binary upgrade mode"))); |
439 | |
|
440 | 0 | HeapTupleSetOid(tuple, binary_upgrade_next_pg_authid_oid); |
441 | 0 | binary_upgrade_next_pg_authid_oid = InvalidOid; |
442 | 0 | } |
443 | | |
444 | | /* |
445 | | * Insert new record in the pg_authid table |
446 | | */ |
447 | 479 | roleid = CatalogTupleInsert(pg_authid_rel, tuple); |
448 | | |
449 | | /* |
450 | | * Advance command counter so we can see new record; else tests in |
451 | | * AddRoleMems may fail. |
452 | | */ |
453 | 479 | if (addroleto || adminmembers || rolemembers) |
454 | 32 | CommandCounterIncrement(); |
455 | | |
456 | | /* |
457 | | * Add the new role to the specified existing roles. |
458 | | */ |
459 | 479 | foreach(item, addroleto) |
460 | 3 | { |
461 | 3 | RoleSpec *oldrole = lfirst(item); |
462 | 3 | HeapTuple oldroletup = get_rolespec_tuple(oldrole); |
463 | 3 | Oid oldroleid = HeapTupleGetOid(oldroletup); |
464 | 3 | char *oldrolename = NameStr(((Form_pg_authid) GETSTRUCT(oldroletup))->rolname); |
465 | | |
466 | 3 | AddRoleMems(oldrolename, oldroleid, |
467 | 3 | list_make1(makeString(stmt->role)), |
468 | 3 | list_make1_oid(roleid), |
469 | 3 | GetUserId(), false); |
470 | | |
471 | 3 | ReleaseSysCache(oldroletup); |
472 | 3 | } |
473 | | |
474 | | /* |
475 | | * Add the specified members to this new role. adminmembers get the admin |
476 | | * option, rolemembers don't. |
477 | | */ |
478 | 479 | AddRoleMems(stmt->role, roleid, |
479 | 479 | adminmembers, roleSpecsToIds(adminmembers), |
480 | 479 | GetUserId(), true); |
481 | 479 | AddRoleMems(stmt->role, roleid, |
482 | 479 | rolemembers, roleSpecsToIds(rolemembers), |
483 | 479 | GetUserId(), false); |
484 | | |
485 | | /* Post creation hook for new role */ |
486 | 479 | InvokeObjectPostCreateHook(AuthIdRelationId, roleid, 0); |
487 | | |
488 | | /* |
489 | | * Close pg_authid, but keep lock till commit. |
490 | | */ |
491 | 479 | heap_close(pg_authid_rel, NoLock); |
492 | | |
493 | 479 | return roleid; |
494 | 479 | } |
495 | | |
496 | | |
497 | | /* |
498 | | * ALTER ROLE |
499 | | * |
500 | | * Note: the rolemembers option accepted here is intended to support the |
501 | | * backwards-compatible ALTER GROUP syntax. Although it will work to say |
502 | | * "ALTER ROLE role ROLE rolenames", we don't document it. |
503 | | */ |
504 | | Oid |
505 | | AlterRole(AlterRoleStmt *stmt) |
506 | 72 | { |
507 | 72 | Datum new_record[Natts_pg_authid]; |
508 | 72 | bool new_record_nulls[Natts_pg_authid]; |
509 | 72 | bool new_record_repl[Natts_pg_authid]; |
510 | 72 | Relation pg_authid_rel; |
511 | 72 | TupleDesc pg_authid_dsc; |
512 | 72 | HeapTuple tuple, |
513 | 72 | new_tuple; |
514 | 72 | Form_pg_authid authform; |
515 | 72 | ListCell *option; |
516 | 72 | char *rolename = NULL; |
517 | 72 | char *password = NULL; /* user password */ |
518 | 72 | int issuper = -1; /* Make the user a superuser? */ |
519 | 72 | int inherit = -1; /* Auto inherit privileges? */ |
520 | 72 | int createrole = -1; /* Can this user create roles? */ |
521 | 72 | int createdb = -1; /* Can the user create databases? */ |
522 | 72 | int canlogin = -1; /* Can this user login? */ |
523 | 72 | int isreplication = -1; /* Is this a replication role? */ |
524 | 72 | int connlimit = -1; /* maximum connections allowed */ |
525 | 72 | List *rolemembers = NIL; /* roles to be added/removed */ |
526 | 72 | char *validUntil = NULL; /* time the login is valid until */ |
527 | 72 | Datum validUntil_datum; /* same, as timestamptz Datum */ |
528 | 72 | bool validUntil_null; |
529 | 72 | int bypassrls = -1; |
530 | 72 | DefElem *dpassword = NULL; |
531 | 72 | DefElem *dissuper = NULL; |
532 | 72 | DefElem *dinherit = NULL; |
533 | 72 | DefElem *dcreaterole = NULL; |
534 | 72 | DefElem *dcreatedb = NULL; |
535 | 72 | DefElem *dcanlogin = NULL; |
536 | 72 | DefElem *disreplication = NULL; |
537 | 72 | DefElem *dconnlimit = NULL; |
538 | 72 | DefElem *drolemembers = NULL; |
539 | 72 | DefElem *dvalidUntil = NULL; |
540 | 72 | DefElem *dbypassRLS = NULL; |
541 | 72 | Oid roleid; |
542 | | |
543 | 72 | check_rolespec_name(stmt->role, |
544 | 72 | "Cannot alter reserved roles."); |
545 | | |
546 | | /* Extract options from the statement node tree */ |
547 | 72 | foreach(option, stmt->options) |
548 | 72 | { |
549 | 72 | DefElem *defel = (DefElem *) lfirst(option); |
550 | | |
551 | 72 | if (strcmp(defel->defname, "password") == 0) |
552 | 7 | { |
553 | 7 | if (dpassword) |
554 | 7 | ereport(ERROR, |
555 | 7 | (errcode(ERRCODE_SYNTAX_ERROR), |
556 | 7 | errmsg("conflicting or redundant options"))); |
557 | 7 | dpassword = defel; |
558 | 7 | } |
559 | 65 | else if (strcmp(defel->defname, "superuser") == 0) |
560 | 5 | { |
561 | 5 | if (dissuper) |
562 | 5 | ereport(ERROR, |
563 | 5 | (errcode(ERRCODE_SYNTAX_ERROR), |
564 | 5 | errmsg("conflicting or redundant options"))); |
565 | 5 | dissuper = defel; |
566 | 5 | } |
567 | 60 | else if (strcmp(defel->defname, "inherit") == 0) |
568 | 6 | { |
569 | 6 | if (dinherit) |
570 | 6 | ereport(ERROR, |
571 | 6 | (errcode(ERRCODE_SYNTAX_ERROR), |
572 | 6 | errmsg("conflicting or redundant options"))); |
573 | 6 | dinherit = defel; |
574 | 6 | } |
575 | 54 | else if (strcmp(defel->defname, "createrole") == 0) |
576 | 4 | { |
577 | 4 | if (dcreaterole) |
578 | 4 | ereport(ERROR, |
579 | 4 | (errcode(ERRCODE_SYNTAX_ERROR), |
580 | 4 | errmsg("conflicting or redundant options"))); |
581 | 4 | dcreaterole = defel; |
582 | 4 | } |
583 | 50 | else if (strcmp(defel->defname, "createdb") == 0) |
584 | 4 | { |
585 | 4 | if (dcreatedb) |
586 | 4 | ereport(ERROR, |
587 | 4 | (errcode(ERRCODE_SYNTAX_ERROR), |
588 | 4 | errmsg("conflicting or redundant options"))); |
589 | 4 | dcreatedb = defel; |
590 | 4 | } |
591 | 46 | else if (strcmp(defel->defname, "canlogin") == 0) |
592 | 10 | { |
593 | 10 | if (dcanlogin) |
594 | 10 | ereport(ERROR, |
595 | 10 | (errcode(ERRCODE_SYNTAX_ERROR), |
596 | 10 | errmsg("conflicting or redundant options"))); |
597 | 10 | dcanlogin = defel; |
598 | 10 | } |
599 | 36 | else if (strcmp(defel->defname, "isreplication") == 0) |
600 | 30 | { |
601 | 30 | if (disreplication) |
602 | 30 | ereport(ERROR, |
603 | 30 | (errcode(ERRCODE_SYNTAX_ERROR), |
604 | 30 | errmsg("conflicting or redundant options"))); |
605 | 30 | disreplication = defel; |
606 | 30 | } |
607 | 6 | else if (strcmp(defel->defname, "connectionlimit") == 0) |
608 | 1 | { |
609 | 1 | if (dconnlimit) |
610 | 1 | ereport(ERROR, |
611 | 1 | (errcode(ERRCODE_SYNTAX_ERROR), |
612 | 1 | errmsg("conflicting or redundant options"))); |
613 | 1 | dconnlimit = defel; |
614 | 1 | } |
615 | 5 | else if (strcmp(defel->defname, "rolemembers") == 0 && |
616 | 3 | stmt->action != 0) |
617 | 3 | { |
618 | 3 | if (drolemembers) |
619 | 3 | ereport(ERROR, |
620 | 3 | (errcode(ERRCODE_SYNTAX_ERROR), |
621 | 3 | errmsg("conflicting or redundant options"))); |
622 | 3 | drolemembers = defel; |
623 | 3 | } |
624 | 2 | else if (strcmp(defel->defname, "validUntil") == 0) |
625 | 0 | { |
626 | 0 | if (dvalidUntil) |
627 | 0 | ereport(ERROR, |
628 | 0 | (errcode(ERRCODE_SYNTAX_ERROR), |
629 | 0 | errmsg("conflicting or redundant options"))); |
630 | 0 | dvalidUntil = defel; |
631 | 0 | } |
632 | 2 | else if (strcmp(defel->defname, "bypassrls") == 0) |
633 | 2 | { |
634 | 2 | if (dbypassRLS) |
635 | 2 | ereport(ERROR, |
636 | 2 | (errcode(ERRCODE_SYNTAX_ERROR), |
637 | 2 | errmsg("conflicting or redundant options"))); |
638 | 2 | dbypassRLS = defel; |
639 | 2 | } |
640 | 2 | else |
641 | 0 | elog(ERROR, "option \"%s\" not recognized", |
642 | 72 | defel->defname); |
643 | 72 | } |
644 | | |
645 | 72 | if (dpassword && dpassword->arg) |
646 | 7 | password = strVal(dpassword->arg); |
647 | 72 | if (dissuper) |
648 | 5 | issuper = intVal(dissuper->arg); |
649 | 72 | if (dinherit) |
650 | 6 | inherit = intVal(dinherit->arg); |
651 | 72 | if (dcreaterole) |
652 | 4 | createrole = intVal(dcreaterole->arg); |
653 | 72 | if (dcreatedb) |
654 | 4 | createdb = intVal(dcreatedb->arg); |
655 | 72 | if (dcanlogin) |
656 | 10 | canlogin = intVal(dcanlogin->arg); |
657 | 72 | if (disreplication) |
658 | 30 | isreplication = intVal(disreplication->arg); |
659 | 72 | if (dconnlimit) |
660 | 1 | { |
661 | 1 | connlimit = intVal(dconnlimit->arg); |
662 | 1 | if (connlimit < -1) |
663 | 1 | ereport(ERROR, |
664 | 1 | (errcode(ERRCODE_INVALID_PARAMETER_VALUE), |
665 | 1 | errmsg("invalid connection limit: %d", connlimit))); |
666 | 1 | } |
667 | 72 | if (drolemembers) |
668 | 3 | rolemembers = (List *) drolemembers->arg; |
669 | 72 | if (dvalidUntil) |
670 | 0 | validUntil = strVal(dvalidUntil->arg); |
671 | 72 | if (dbypassRLS) |
672 | 2 | bypassrls = intVal(dbypassRLS->arg); |
673 | | |
674 | | /* |
675 | | * Scan the pg_authid relation to be certain the user exists. |
676 | | */ |
677 | 72 | pg_authid_rel = heap_open(AuthIdRelationId, RowExclusiveLock); |
678 | 72 | pg_authid_dsc = RelationGetDescr(pg_authid_rel); |
679 | | |
680 | 72 | tuple = get_rolespec_tuple(stmt->role); |
681 | 72 | authform = (Form_pg_authid) GETSTRUCT(tuple); |
682 | 72 | rolename = pstrdup(NameStr(authform->rolname)); |
683 | 72 | roleid = HeapTupleGetOid(tuple); |
684 | | |
685 | | /* |
686 | | * To mess with a superuser or replication role in any way you gotta be |
687 | | * superuser. We also insist on superuser to change the BYPASSRLS |
688 | | * property. Otherwise, if you don't have createrole, you're only allowed |
689 | | * to change your own password. |
690 | | */ |
691 | 72 | if (authform->rolsuper || issuper >= 0) |
692 | 16 | { |
693 | 16 | if (!superuser()) |
694 | 16 | ereport(ERROR, |
695 | 16 | (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), |
696 | 16 | errmsg("must be superuser to alter superusers"))); |
697 | 16 | } |
698 | 56 | else if (authform->rolreplication || isreplication >= 0) |
699 | 14 | { |
700 | 14 | if (!superuser()) |
701 | 14 | ereport(ERROR, |
702 | 14 | (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), |
703 | 14 | errmsg("must be superuser to alter replication users"))); |
704 | 14 | } |
705 | 42 | else if (bypassrls >= 0) |
706 | 2 | { |
707 | 2 | if (!superuser()) |
708 | 2 | ereport(ERROR, |
709 | 2 | (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), |
710 | 2 | errmsg("must be superuser to change bypassrls attribute"))); |
711 | 2 | } |
712 | 40 | else if (!have_createrole_privilege()) |
713 | 0 | { |
714 | | /* We already checked issuper, isreplication, and bypassrls */ |
715 | 0 | if (!(inherit < 0 && |
716 | 0 | createrole < 0 && |
717 | 0 | createdb < 0 && |
718 | 0 | canlogin < 0 && |
719 | 0 | !dconnlimit && |
720 | 0 | !rolemembers && |
721 | 0 | !validUntil && |
722 | 0 | dpassword && |
723 | 0 | roleid == GetUserId())) |
724 | 0 | ereport(ERROR, |
725 | 0 | (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), |
726 | 0 | errmsg("permission denied"))); |
727 | 0 | } |
728 | | |
729 | | /* Convert validuntil to internal form */ |
730 | 72 | if (validUntil) |
731 | 0 | { |
732 | 0 | validUntil_datum = DirectFunctionCall3(timestamptz_in, |
733 | 0 | CStringGetDatum(validUntil), |
734 | 0 | ObjectIdGetDatum(InvalidOid), |
735 | 0 | Int32GetDatum(-1)); |
736 | 0 | validUntil_null = false; |
737 | 0 | } |
738 | 72 | else |
739 | 72 | { |
740 | | /* fetch existing setting in case hook needs it */ |
741 | 72 | validUntil_datum = SysCacheGetAttr(AUTHNAME, tuple, |
742 | 72 | Anum_pg_authid_rolvaliduntil, |
743 | 72 | &validUntil_null); |
744 | 72 | } |
745 | | |
746 | | /* |
747 | | * Call the password checking hook if there is one defined |
748 | | */ |
749 | 72 | if (check_password_hook && password) |
750 | 0 | (*check_password_hook) (rolename, |
751 | 0 | password, |
752 | 0 | get_password_type(password), |
753 | 0 | validUntil_datum, |
754 | 0 | validUntil_null); |
755 | | |
756 | | /* |
757 | | * Build an updated tuple, perusing the information just obtained |
758 | | */ |
759 | 72 | MemSet(new_record, 0, sizeof(new_record)); |
760 | 72 | MemSet(new_record_nulls, false, sizeof(new_record_nulls)); |
761 | 72 | MemSet(new_record_repl, false, sizeof(new_record_repl)); |
762 | | |
763 | | /* |
764 | | * issuper/createrole/etc |
765 | | */ |
766 | 72 | if (issuper >= 0) |
767 | 5 | { |
768 | 5 | new_record[Anum_pg_authid_rolsuper - 1] = BoolGetDatum(issuper > 0); |
769 | 5 | new_record_repl[Anum_pg_authid_rolsuper - 1] = true; |
770 | 5 | } |
771 | | |
772 | 72 | if (inherit >= 0) |
773 | 6 | { |
774 | 6 | new_record[Anum_pg_authid_rolinherit - 1] = BoolGetDatum(inherit > 0); |
775 | 6 | new_record_repl[Anum_pg_authid_rolinherit - 1] = true; |
776 | 6 | } |
777 | | |
778 | 72 | if (createrole >= 0) |
779 | 4 | { |
780 | 4 | new_record[Anum_pg_authid_rolcreaterole - 1] = BoolGetDatum(createrole > 0); |
781 | 4 | new_record_repl[Anum_pg_authid_rolcreaterole - 1] = true; |
782 | 4 | } |
783 | | |
784 | 72 | if (createdb >= 0) |
785 | 4 | { |
786 | 4 | new_record[Anum_pg_authid_rolcreatedb - 1] = BoolGetDatum(createdb > 0); |
787 | 4 | new_record_repl[Anum_pg_authid_rolcreatedb - 1] = true; |
788 | 4 | } |
789 | | |
790 | 72 | if (canlogin >= 0) |
791 | 9 | { |
792 | 9 | new_record[Anum_pg_authid_rolcanlogin - 1] = BoolGetDatum(canlogin > 0); |
793 | 9 | new_record_repl[Anum_pg_authid_rolcanlogin - 1] = true; |
794 | 9 | } |
795 | | |
796 | 72 | if (isreplication >= 0) |
797 | 22 | { |
798 | 22 | new_record[Anum_pg_authid_rolreplication - 1] = BoolGetDatum(isreplication > 0); |
799 | 22 | new_record_repl[Anum_pg_authid_rolreplication - 1] = true; |
800 | 22 | } |
801 | | |
802 | 72 | if (dconnlimit) |
803 | 1 | { |
804 | | /* Check connection limit for postgres. */ |
805 | 1 | if (roleid == 10 && connlimit != -1) |
806 | 1 | ereport(ERROR, |
807 | 1 | (errcode(ERRCODE_SYNTAX_ERROR), |
808 | 1 | errmsg("cannot set connection limit for postgres"), |
809 | 1 | errhint("did you mean ALTER ROLE %s CONNECTION LIMIT -1", rolename))); |
810 | 1 | new_record[Anum_pg_authid_rolconnlimit - 1] = Int32GetDatum(connlimit); |
811 | 1 | new_record_repl[Anum_pg_authid_rolconnlimit - 1] = true; |
812 | 1 | } |
813 | | |
814 | | /* password */ |
815 | 72 | if (password) |
816 | 7 | { |
817 | 7 | char *shadow_pass; |
818 | 7 | char *logdetail; |
819 | | |
820 | | /* Like in CREATE USER, don't allow an empty password. */ |
821 | 7 | if (password[0] == '\0' || |
822 | 7 | plain_crypt_verify(rolename, password, "", &logdetail) == STATUS_OK) |
823 | 2 | { |
824 | 2 | ereport(NOTICE, |
825 | 2 | (errmsg("empty string is not a valid password, clearing password"))); |
826 | 2 | new_record_nulls[Anum_pg_authid_rolpassword - 1] = true; |
827 | 2 | } |
828 | 5 | else |
829 | 5 | { |
830 | | /* Encrypt the password to the requested format. */ |
831 | 5 | shadow_pass = encrypt_password(Password_encryption, rolename, |
832 | 5 | password); |
833 | 5 | new_record[Anum_pg_authid_rolpassword - 1] = |
834 | 5 | CStringGetTextDatum(shadow_pass); |
835 | 5 | } |
836 | 7 | new_record_repl[Anum_pg_authid_rolpassword - 1] = true; |
837 | 7 | } |
838 | | |
839 | | /* unset password */ |
840 | 72 | if (dpassword && dpassword->arg == NULL) |
841 | 0 | { |
842 | 0 | new_record_repl[Anum_pg_authid_rolpassword - 1] = true; |
843 | 0 | new_record_nulls[Anum_pg_authid_rolpassword - 1] = true; |
844 | 0 | } |
845 | | |
846 | | /* valid until */ |
847 | 72 | new_record[Anum_pg_authid_rolvaliduntil - 1] = validUntil_datum; |
848 | 72 | new_record_nulls[Anum_pg_authid_rolvaliduntil - 1] = validUntil_null; |
849 | 72 | new_record_repl[Anum_pg_authid_rolvaliduntil - 1] = true; |
850 | | |
851 | 72 | if (bypassrls >= 0) |
852 | 2 | { |
853 | 2 | new_record[Anum_pg_authid_rolbypassrls - 1] = BoolGetDatum(bypassrls > 0); |
854 | 2 | new_record_repl[Anum_pg_authid_rolbypassrls - 1] = true; |
855 | 2 | } |
856 | | |
857 | 72 | new_tuple = heap_modify_tuple(tuple, pg_authid_dsc, new_record, |
858 | 72 | new_record_nulls, new_record_repl); |
859 | 72 | CatalogTupleUpdate(pg_authid_rel, &tuple->t_self, new_tuple); |
860 | | |
861 | 72 | InvokeObjectPostAlterHook(AuthIdRelationId, roleid, 0); |
862 | | |
863 | 72 | ReleaseSysCache(tuple); |
864 | 72 | heap_freetuple(new_tuple); |
865 | | |
866 | | /* |
867 | | * Advance command counter so we can see new record; else tests in |
868 | | * AddRoleMems may fail. |
869 | | */ |
870 | 72 | if (rolemembers) |
871 | 3 | CommandCounterIncrement(); |
872 | | |
873 | 72 | if (stmt->action == +1) /* add members to role */ |
874 | 62 | AddRoleMems(rolename, roleid, |
875 | 62 | rolemembers, roleSpecsToIds(rolemembers), |
876 | 62 | GetUserId(), false); |
877 | 10 | else if (stmt->action == -1) /* drop members from role */ |
878 | 1 | DelRoleMems(rolename, roleid, |
879 | 1 | rolemembers, roleSpecsToIds(rolemembers), |
880 | 1 | false); |
881 | | |
882 | | /* |
883 | | * Close pg_authid, but keep lock till commit. |
884 | | */ |
885 | 72 | heap_close(pg_authid_rel, NoLock); |
886 | | |
887 | 72 | return roleid; |
888 | 72 | } |
889 | | |
890 | | |
891 | | /* |
892 | | * ALTER ROLE ... SET |
893 | | */ |
894 | | Oid |
895 | | AlterRoleSet(AlterRoleSetStmt *stmt) |
896 | 32 | { |
897 | 32 | HeapTuple roletuple; |
898 | 32 | Oid databaseid = InvalidOid; |
899 | 32 | Oid roleid = InvalidOid; |
900 | | |
901 | 32 | if (stmt->role) |
902 | 27 | { |
903 | 27 | check_rolespec_name(stmt->role, |
904 | 27 | "Cannot alter reserved roles."); |
905 | | |
906 | 27 | roletuple = get_rolespec_tuple(stmt->role); |
907 | 27 | roleid = HeapTupleGetOid(roletuple); |
908 | | |
909 | | /* |
910 | | * Obtain a lock on the role and make sure it didn't go away in the |
911 | | * meantime. |
912 | | */ |
913 | 27 | shdepLockAndCheckObject(AuthIdRelationId, HeapTupleGetOid(roletuple)); |
914 | | |
915 | | /* |
916 | | * To mess with a superuser you gotta be superuser; else you need |
917 | | * createrole, or just want to change your own settings |
918 | | */ |
919 | 27 | if (((Form_pg_authid) GETSTRUCT(roletuple))->rolsuper) |
920 | 11 | { |
921 | 11 | if (!superuser()) |
922 | 11 | ereport(ERROR, |
923 | 11 | (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), |
924 | 11 | errmsg("must be superuser to alter superusers"))); |
925 | 11 | } |
926 | 16 | else |
927 | 16 | { |
928 | 16 | if (!have_createrole_privilege() && |
929 | 0 | HeapTupleGetOid(roletuple) != GetUserId()) |
930 | 16 | ereport(ERROR, |
931 | 16 | (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), |
932 | 16 | errmsg("permission denied"))); |
933 | 16 | } |
934 | | |
935 | 27 | ReleaseSysCache(roletuple); |
936 | 27 | } |
937 | | |
938 | | /* look up and lock the database, if specified */ |
939 | 32 | if (stmt->database != NULL) |
940 | 1 | { |
941 | 1 | databaseid = get_database_oid(stmt->database, false); |
942 | 1 | shdepLockAndCheckObject(DatabaseRelationId, databaseid); |
943 | | |
944 | 1 | if (!stmt->role) |
945 | 0 | { |
946 | | /* |
947 | | * If no role is specified, then this is effectively the same as |
948 | | * ALTER DATABASE ... SET, so use the same permission check. |
949 | | */ |
950 | 0 | if (!pg_database_ownercheck(databaseid, GetUserId())) |
951 | 0 | aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_DATABASE, |
952 | 0 | stmt->database); |
953 | 0 | } |
954 | 1 | } |
955 | | |
956 | 32 | if (!stmt->role && !stmt->database) |
957 | 5 | { |
958 | | /* Must be superuser to alter settings globally. */ |
959 | 5 | if (!superuser()) |
960 | 5 | ereport(ERROR, |
961 | 5 | (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), |
962 | 5 | errmsg("must be superuser to alter settings globally"))); |
963 | 5 | } |
964 | | |
965 | 32 | AlterSetting(databaseid, roleid, stmt->setstmt); |
966 | | |
967 | 32 | return roleid; |
968 | 32 | } |
969 | | |
970 | | |
971 | | /* |
972 | | * DROP ROLE |
973 | | */ |
974 | | void |
975 | | DropRole(DropRoleStmt *stmt) |
976 | 211 | { |
977 | 211 | Relation pg_authid_rel, |
978 | 211 | pg_auth_members_rel; |
979 | 211 | ListCell *item; |
980 | | |
981 | 211 | if (!have_createrole_privilege()) |
982 | 211 | ereport(ERROR, |
983 | 211 | (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), |
984 | 211 | errmsg("permission denied to drop role"))); |
985 | | |
986 | | /* |
987 | | * Scan the pg_authid relation to find the Oid of the role(s) to be |
988 | | * deleted. |
989 | | */ |
990 | 211 | pg_authid_rel = heap_open(AuthIdRelationId, RowExclusiveLock); |
991 | 211 | pg_auth_members_rel = heap_open(AuthMemRelationId, RowExclusiveLock); |
992 | | |
993 | 211 | foreach(item, stmt->roles) |
994 | 222 | { |
995 | 222 | RoleSpec *rolspec = lfirst(item); |
996 | 222 | char *role; |
997 | 222 | HeapTuple tuple, |
998 | 222 | tmp_tuple; |
999 | 222 | ScanKeyData scankey; |
1000 | 222 | char *detail; |
1001 | 222 | char *detail_log; |
1002 | 222 | SysScanDesc sscan; |
1003 | 222 | Oid roleid; |
1004 | | |
1005 | 222 | if (rolspec->roletype != ROLESPEC_CSTRING) |
1006 | 222 | ereport(ERROR, |
1007 | 222 | (errcode(ERRCODE_INVALID_PARAMETER_VALUE), |
1008 | 222 | errmsg("cannot use special role specifier in DROP ROLE"))); |
1009 | 222 | role = rolspec->rolename; |
1010 | | |
1011 | 222 | tuple = SearchSysCache1(AUTHNAME, PointerGetDatum(role)); |
1012 | 222 | if (!HeapTupleIsValid(tuple)) |
1013 | 21 | { |
1014 | 21 | if (!stmt->missing_ok) |
1015 | 1 | { |
1016 | 1 | ereport(ERROR, |
1017 | 1 | (errcode(ERRCODE_UNDEFINED_OBJECT), |
1018 | 1 | errmsg("role \"%s\" does not exist", role))); |
1019 | 1 | } |
1020 | 20 | else |
1021 | 20 | { |
1022 | 20 | ereport(NOTICE, |
1023 | 20 | (errmsg("role \"%s\" does not exist, skipping", |
1024 | 20 | role))); |
1025 | 20 | } |
1026 | | |
1027 | 21 | continue; |
1028 | 201 | } |
1029 | | |
1030 | 201 | roleid = HeapTupleGetOid(tuple); |
1031 | | |
1032 | 201 | if (roleid == GetUserId()) |
1033 | 201 | ereport(ERROR, |
1034 | 201 | (errcode(ERRCODE_OBJECT_IN_USE), |
1035 | 201 | errmsg("current user cannot be dropped"))); |
1036 | 201 | if (roleid == GetOuterUserId()) |
1037 | 201 | ereport(ERROR, |
1038 | 201 | (errcode(ERRCODE_OBJECT_IN_USE), |
1039 | 201 | errmsg("current user cannot be dropped"))); |
1040 | 201 | if (roleid == GetSessionUserId()) |
1041 | 201 | ereport(ERROR, |
1042 | 201 | (errcode(ERRCODE_OBJECT_IN_USE), |
1043 | 201 | errmsg("session user cannot be dropped"))); |
1044 | | |
1045 | | /* |
1046 | | * For safety's sake, we allow createrole holders to drop ordinary |
1047 | | * roles but not superuser roles. This is mainly to avoid the |
1048 | | * scenario where you accidentally drop the last superuser. |
1049 | | */ |
1050 | 201 | if (((Form_pg_authid) GETSTRUCT(tuple))->rolsuper && |
1051 | 20 | !superuser()) |
1052 | 201 | ereport(ERROR, |
1053 | 201 | (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), |
1054 | 201 | errmsg("must be superuser to drop superusers"))); |
1055 | | |
1056 | | /* DROP hook for the role being removed */ |
1057 | 201 | InvokeObjectDropHook(AuthIdRelationId, roleid, 0); |
1058 | | |
1059 | | /* |
1060 | | * Lock the role, so nobody can add dependencies to her while we drop |
1061 | | * her. We keep the lock until the end of transaction. |
1062 | | */ |
1063 | 201 | LockSharedObject(AuthIdRelationId, roleid, 0, AccessExclusiveLock); |
1064 | | |
1065 | | /* Check for pg_shdepend entries depending on this role */ |
1066 | 201 | if (checkSharedDependencies(AuthIdRelationId, roleid, |
1067 | 201 | &detail, &detail_log)) |
1068 | 4 | { |
1069 | 4 | if (IsYugaByteEnabled() && detail != NULL) |
1070 | 4 | { |
1071 | 4 | detail = YBDetailSorted(detail); |
1072 | 4 | } |
1073 | 4 | ereport(ERROR, |
1074 | 4 | (errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST), |
1075 | 4 | errmsg("role \"%s\" cannot be dropped because some objects depend on it", |
1076 | 4 | role), |
1077 | 4 | errdetail_internal("%s", detail), |
1078 | 4 | errdetail_log("%s", detail_log))); |
1079 | 4 | } |
1080 | | |
1081 | | /* |
1082 | | * Remove the role from the pg_authid table |
1083 | | */ |
1084 | 201 | CatalogTupleDelete(pg_authid_rel, tuple); |
1085 | | |
1086 | 201 | ReleaseSysCache(tuple); |
1087 | | |
1088 | | /* |
1089 | | * Remove role from the pg_auth_members table. We have to remove all |
1090 | | * tuples that show it as either a role or a member. |
1091 | | * |
1092 | | * XXX what about grantor entries? Maybe we should do one heap scan. |
1093 | | */ |
1094 | 201 | ScanKeyInit(&scankey, |
1095 | 201 | Anum_pg_auth_members_roleid, |
1096 | 201 | BTEqualStrategyNumber, F_OIDEQ, |
1097 | 201 | ObjectIdGetDatum(roleid)); |
1098 | | |
1099 | 201 | sscan = systable_beginscan(pg_auth_members_rel, AuthMemRoleMemIndexId, |
1100 | 201 | true, NULL, 1, &scankey); |
1101 | | |
1102 | 201 | while (HeapTupleIsValid(tmp_tuple = systable_getnext(sscan))) |
1103 | 7 | { |
1104 | 7 | CatalogTupleDelete(pg_auth_members_rel, tmp_tuple); |
1105 | 7 | } |
1106 | | |
1107 | 201 | systable_endscan(sscan); |
1108 | | |
1109 | 201 | ScanKeyInit(&scankey, |
1110 | 201 | Anum_pg_auth_members_member, |
1111 | 201 | BTEqualStrategyNumber, F_OIDEQ, |
1112 | 201 | ObjectIdGetDatum(roleid)); |
1113 | | |
1114 | 201 | sscan = systable_beginscan(pg_auth_members_rel, AuthMemMemRoleIndexId, |
1115 | 201 | true, NULL, 1, &scankey); |
1116 | | |
1117 | 201 | while (HeapTupleIsValid(tmp_tuple = systable_getnext(sscan))) |
1118 | 37 | { |
1119 | 37 | CatalogTupleDelete(pg_auth_members_rel, tmp_tuple); |
1120 | 37 | } |
1121 | | |
1122 | 201 | systable_endscan(sscan); |
1123 | | |
1124 | | /* |
1125 | | * Remove any comments or security labels on this role. |
1126 | | */ |
1127 | 201 | DeleteSharedComments(roleid, AuthIdRelationId); |
1128 | 201 | DeleteSharedSecurityLabel(roleid, AuthIdRelationId); |
1129 | | |
1130 | | /* |
1131 | | * Remove settings for this role. |
1132 | | */ |
1133 | 201 | DropSetting(InvalidOid, roleid); |
1134 | | |
1135 | | /* |
1136 | | * Advance command counter so that later iterations of this loop will |
1137 | | * see the changes already made. This is essential if, for example, |
1138 | | * we are trying to drop both a role and one of its direct members --- |
1139 | | * we'll get an error if we try to delete the linking pg_auth_members |
1140 | | * tuple twice. (We do not need a CCI between the two delete loops |
1141 | | * above, because it's not allowed for a role to directly contain |
1142 | | * itself.) |
1143 | | */ |
1144 | 201 | CommandCounterIncrement(); |
1145 | 201 | } |
1146 | | |
1147 | | /* |
1148 | | * Now we can clean up; but keep locks until commit. |
1149 | | */ |
1150 | 211 | heap_close(pg_auth_members_rel, NoLock); |
1151 | 211 | heap_close(pg_authid_rel, NoLock); |
1152 | 211 | } |
1153 | | |
1154 | | /* |
1155 | | * Rename role |
1156 | | */ |
1157 | | ObjectAddress |
1158 | | RenameRole(const char *oldname, const char *newname) |
1159 | 6 | { |
1160 | 6 | HeapTuple oldtuple, |
1161 | 6 | newtuple; |
1162 | 6 | TupleDesc dsc; |
1163 | 6 | Relation rel; |
1164 | 6 | Datum datum; |
1165 | 6 | bool isnull; |
1166 | 6 | Datum repl_val[Natts_pg_authid]; |
1167 | 6 | bool repl_null[Natts_pg_authid]; |
1168 | 6 | bool repl_repl[Natts_pg_authid]; |
1169 | 6 | int i; |
1170 | 6 | Oid roleid; |
1171 | 6 | ObjectAddress address; |
1172 | 6 | Form_pg_authid authform; |
1173 | | |
1174 | 6 | rel = heap_open(AuthIdRelationId, RowExclusiveLock); |
1175 | 6 | dsc = RelationGetDescr(rel); |
1176 | | |
1177 | 6 | oldtuple = SearchSysCache1(AUTHNAME, CStringGetDatum(oldname)); |
1178 | 6 | if (!HeapTupleIsValid(oldtuple)) |
1179 | 6 | ereport(ERROR, |
1180 | 6 | (errcode(ERRCODE_UNDEFINED_OBJECT), |
1181 | 6 | errmsg("role \"%s\" does not exist", oldname))); |
1182 | | |
1183 | | /* |
1184 | | * XXX Client applications probably store the session user somewhere, so |
1185 | | * renaming it could cause confusion. On the other hand, there may not be |
1186 | | * an actual problem besides a little confusion, so think about this and |
1187 | | * decide. Same for SET ROLE ... we don't restrict renaming the current |
1188 | | * effective userid, though. |
1189 | | */ |
1190 | | |
1191 | 6 | roleid = HeapTupleGetOid(oldtuple); |
1192 | 6 | authform = (Form_pg_authid) GETSTRUCT(oldtuple); |
1193 | | |
1194 | 6 | if (roleid == GetSessionUserId()) |
1195 | 6 | ereport(ERROR, |
1196 | 6 | (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
1197 | 6 | errmsg("session user cannot be renamed"))); |
1198 | 6 | if (roleid == GetOuterUserId()) |
1199 | 6 | ereport(ERROR, |
1200 | 6 | (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
1201 | 6 | errmsg("current user cannot be renamed"))); |
1202 | | |
1203 | | /* |
1204 | | * Check that the user is not trying to rename a system role and not |
1205 | | * trying to rename a role into the reserved "pg_" namespace. |
1206 | | */ |
1207 | 6 | if (IsReservedName(NameStr(authform->rolname))) |
1208 | 6 | ereport(ERROR, |
1209 | 6 | (errcode(ERRCODE_RESERVED_NAME), |
1210 | 6 | errmsg("role name \"%s\" is reserved", |
1211 | 6 | NameStr(authform->rolname)), |
1212 | 6 | errdetail("Role names starting with \"pg_\" are reserved."))); |
1213 | | |
1214 | 6 | if (IsReservedName(newname)) |
1215 | 6 | ereport(ERROR, |
1216 | 6 | (errcode(ERRCODE_RESERVED_NAME), |
1217 | 6 | errmsg("role name \"%s\" is reserved", |
1218 | 6 | newname), |
1219 | 6 | errdetail("Role names starting with \"pg_\" are reserved."))); |
1220 | | |
1221 | | /* Check whether postgres is being renamed. */ |
1222 | 6 | if (roleid == 10 && strcmp(newname, "postgres") != 0) |
1223 | 6 | ereport(ERROR, |
1224 | 6 | (errcode(ERRCODE_SYNTAX_ERROR), |
1225 | 6 | errmsg("cannot rename postgres"), |
1226 | 6 | strcmp(oldname, "postgres") != 0 ? |
1227 | 6 | errhint("ALTER ROLE %s RENAME TO postgres", oldname) : 0)); |
1228 | | |
1229 | | /* make sure the new name doesn't exist */ |
1230 | 6 | if (SearchSysCacheExists1(AUTHNAME, CStringGetDatum(newname))) |
1231 | 6 | ereport(ERROR, |
1232 | 6 | (errcode(ERRCODE_DUPLICATE_OBJECT), |
1233 | 6 | errmsg("role \"%s\" already exists", newname))); |
1234 | | |
1235 | | /* |
1236 | | * createrole is enough privilege unless you want to mess with a superuser |
1237 | | */ |
1238 | 6 | if (((Form_pg_authid) GETSTRUCT(oldtuple))->rolsuper) |
1239 | 1 | { |
1240 | 1 | if (!superuser()) |
1241 | 1 | ereport(ERROR, |
1242 | 1 | (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), |
1243 | 1 | errmsg("must be superuser to rename superusers"))); |
1244 | 1 | } |
1245 | 5 | else |
1246 | 5 | { |
1247 | 5 | if (!have_createrole_privilege()) |
1248 | 5 | ereport(ERROR, |
1249 | 5 | (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), |
1250 | 5 | errmsg("permission denied to rename role"))); |
1251 | 5 | } |
1252 | | |
1253 | | /* OK, construct the modified tuple */ |
1254 | 72 | for (i = 0; i < Natts_pg_authid; i++) |
1255 | 66 | repl_repl[i] = false; |
1256 | | |
1257 | 6 | repl_repl[Anum_pg_authid_rolname - 1] = true; |
1258 | 6 | repl_val[Anum_pg_authid_rolname - 1] = DirectFunctionCall1(namein, |
1259 | 6 | CStringGetDatum(newname)); |
1260 | 6 | repl_null[Anum_pg_authid_rolname - 1] = false; |
1261 | | |
1262 | 6 | datum = heap_getattr(oldtuple, Anum_pg_authid_rolpassword, dsc, &isnull); |
1263 | | |
1264 | 6 | if (!isnull && get_password_type(TextDatumGetCString(datum)) == PASSWORD_TYPE_MD5) |
1265 | 1 | { |
1266 | | /* MD5 uses the username as salt, so just clear it on a rename */ |
1267 | 1 | repl_repl[Anum_pg_authid_rolpassword - 1] = true; |
1268 | 1 | repl_null[Anum_pg_authid_rolpassword - 1] = true; |
1269 | | |
1270 | 1 | ereport(NOTICE, |
1271 | 1 | (errmsg("MD5 password cleared because of role rename"))); |
1272 | 1 | } |
1273 | | |
1274 | 6 | newtuple = heap_modify_tuple(oldtuple, dsc, repl_val, repl_null, repl_repl); |
1275 | 6 | CatalogTupleUpdate(rel, &oldtuple->t_self, newtuple); |
1276 | | |
1277 | 6 | InvokeObjectPostAlterHook(AuthIdRelationId, roleid, 0); |
1278 | | |
1279 | 6 | ObjectAddressSet(address, AuthIdRelationId, roleid); |
1280 | | |
1281 | 6 | ReleaseSysCache(oldtuple); |
1282 | | |
1283 | | /* |
1284 | | * Close pg_authid, but keep lock till commit. |
1285 | | */ |
1286 | 6 | heap_close(rel, NoLock); |
1287 | | |
1288 | 6 | return address; |
1289 | 6 | } |
1290 | | |
1291 | | /* |
1292 | | * GrantRoleStmt |
1293 | | * |
1294 | | * Grant/Revoke roles to/from roles |
1295 | | */ |
1296 | | void |
1297 | | GrantRole(GrantRoleStmt *stmt) |
1298 | 29 | { |
1299 | 29 | Relation pg_authid_rel; |
1300 | 29 | Oid grantor; |
1301 | 29 | List *grantee_ids; |
1302 | 29 | ListCell *item; |
1303 | | |
1304 | 29 | if (stmt->grantor) |
1305 | 0 | grantor = get_rolespec_oid(stmt->grantor, false); |
1306 | 29 | else |
1307 | 29 | grantor = GetUserId(); |
1308 | | |
1309 | 29 | grantee_ids = roleSpecsToIds(stmt->grantee_roles); |
1310 | | |
1311 | | /* AccessShareLock is enough since we aren't modifying pg_authid */ |
1312 | 29 | pg_authid_rel = heap_open(AuthIdRelationId, AccessShareLock); |
1313 | | |
1314 | | /* |
1315 | | * Step through all of the granted roles and add/remove entries for the |
1316 | | * grantees, or, if admin_opt is set, then just add/remove the admin |
1317 | | * option. |
1318 | | * |
1319 | | * Note: Permissions checking is done by AddRoleMems/DelRoleMems |
1320 | | */ |
1321 | 29 | foreach(item, stmt->granted_roles) |
1322 | 29 | { |
1323 | 29 | AccessPriv *priv = (AccessPriv *) lfirst(item); |
1324 | 29 | char *rolename = priv->priv_name; |
1325 | 29 | Oid roleid; |
1326 | | |
1327 | | /* Must reject priv(columns) and ALL PRIVILEGES(columns) */ |
1328 | 29 | if (rolename == NULL || priv->cols != NIL) |
1329 | 29 | ereport(ERROR, |
1330 | 29 | (errcode(ERRCODE_INVALID_GRANT_OPERATION), |
1331 | 29 | errmsg("column names cannot be included in GRANT/REVOKE ROLE"))); |
1332 | | |
1333 | 29 | roleid = get_role_oid(rolename, false); |
1334 | 29 | if (stmt->is_grant) |
1335 | 23 | AddRoleMems(rolename, roleid, |
1336 | 23 | stmt->grantee_roles, grantee_ids, |
1337 | 23 | grantor, stmt->admin_opt); |
1338 | 6 | else |
1339 | 6 | DelRoleMems(rolename, roleid, |
1340 | 6 | stmt->grantee_roles, grantee_ids, |
1341 | 6 | stmt->admin_opt); |
1342 | 29 | } |
1343 | | |
1344 | | /* |
1345 | | * Close pg_authid, but keep lock till commit. |
1346 | | */ |
1347 | 29 | heap_close(pg_authid_rel, NoLock); |
1348 | 29 | } |
1349 | | |
1350 | | /* |
1351 | | * DropOwnedObjects |
1352 | | * |
1353 | | * Drop the objects owned by a given list of roles. |
1354 | | */ |
1355 | | void |
1356 | | DropOwnedObjects(DropOwnedStmt *stmt) |
1357 | 1.16k | { |
1358 | 1.16k | List *role_ids = roleSpecsToIds(stmt->roles); |
1359 | 1.16k | ListCell *cell; |
1360 | | |
1361 | | /* Check privileges */ |
1362 | 1.16k | foreach(cell, role_ids) |
1363 | 1.17k | { |
1364 | 1.17k | Oid roleid = lfirst_oid(cell); |
1365 | | |
1366 | 1.17k | if (!has_privs_of_role(GetUserId(), roleid)) |
1367 | 1.17k | ereport(ERROR, |
1368 | 1.17k | (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), |
1369 | 1.17k | errmsg("permission denied to drop objects"))); |
1370 | 1.17k | } |
1371 | | |
1372 | | /* Ok, do it */ |
1373 | 1.16k | shdepDropOwned(role_ids, stmt->behavior); |
1374 | 1.16k | } |
1375 | | |
1376 | | /* |
1377 | | * ReassignOwnedObjects |
1378 | | * |
1379 | | * Give the objects owned by a given list of roles away to another user. |
1380 | | */ |
1381 | | void |
1382 | | ReassignOwnedObjects(ReassignOwnedStmt *stmt) |
1383 | 8 | { |
1384 | 8 | List *role_ids = roleSpecsToIds(stmt->roles); |
1385 | 8 | ListCell *cell; |
1386 | 8 | Oid newrole; |
1387 | | |
1388 | | /* Check privileges */ |
1389 | 8 | foreach(cell, role_ids) |
1390 | 9 | { |
1391 | 9 | Oid roleid = lfirst_oid(cell); |
1392 | | |
1393 | 9 | if (!has_privs_of_role(GetUserId(), roleid)) |
1394 | 9 | ereport(ERROR, |
1395 | 9 | (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), |
1396 | 9 | errmsg("permission denied to reassign objects"))); |
1397 | 9 | } |
1398 | | |
1399 | | /* Must have privileges on the receiving side too */ |
1400 | 8 | newrole = get_rolespec_oid(stmt->newrole, false); |
1401 | | |
1402 | 8 | if (!has_privs_of_role(GetUserId(), newrole)) |
1403 | 8 | ereport(ERROR, |
1404 | 8 | (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), |
1405 | 8 | errmsg("permission denied to reassign objects"))); |
1406 | | |
1407 | | /* Ok, do it */ |
1408 | 8 | shdepReassignOwned(role_ids, newrole); |
1409 | 8 | } |
1410 | | |
1411 | | /* |
1412 | | * roleSpecsToIds |
1413 | | * |
1414 | | * Given a list of RoleSpecs, generate a list of role OIDs in the same order. |
1415 | | * |
1416 | | * ROLESPEC_PUBLIC is not allowed. |
1417 | | */ |
1418 | | List * |
1419 | | roleSpecsToIds(List *memberNames) |
1420 | 2.19k | { |
1421 | 2.19k | List *result = NIL; |
1422 | 2.19k | ListCell *l; |
1423 | | |
1424 | 2.19k | foreach(l, memberNames) |
1425 | 1.24k | { |
1426 | 1.24k | RoleSpec *rolespec = lfirst_node(RoleSpec, l); |
1427 | 1.24k | Oid roleid; |
1428 | | |
1429 | 1.24k | roleid = get_rolespec_oid(rolespec, false); |
1430 | 1.24k | result = lappend_oid(result, roleid); |
1431 | 1.24k | } |
1432 | 2.19k | return result; |
1433 | 2.19k | } |
1434 | | |
1435 | | /* |
1436 | | * AddRoleMems -- Add given members to the specified role |
1437 | | * |
1438 | | * rolename: name of role to add to (used only for error messages) |
1439 | | * roleid: OID of role to add to |
1440 | | * memberSpecs: list of RoleSpec of roles to add (used only for error messages) |
1441 | | * memberIds: OIDs of roles to add |
1442 | | * grantorId: who is granting the membership |
1443 | | * admin_opt: granting admin option? |
1444 | | * |
1445 | | * Note: caller is responsible for calling auth_file_update_needed(). |
1446 | | */ |
1447 | | static void |
1448 | | AddRoleMems(const char *rolename, Oid roleid, |
1449 | | List *memberSpecs, List *memberIds, |
1450 | | Oid grantorId, bool admin_opt) |
1451 | 1.01k | { |
1452 | 1.01k | Relation pg_authmem_rel; |
1453 | 1.01k | TupleDesc pg_authmem_dsc; |
1454 | 1.01k | ListCell *specitem; |
1455 | 1.01k | ListCell *iditem; |
1456 | | |
1457 | 1.01k | Assert(list_length(memberSpecs) == list_length(memberIds)); |
1458 | | |
1459 | | /* Skip permission check if nothing to do */ |
1460 | 1.01k | if (!memberIds) |
1461 | 955 | return; |
1462 | | |
1463 | | /* |
1464 | | * Check permissions: must have createrole or admin option on the role to |
1465 | | * be changed. To mess with a superuser role, you gotta be superuser. |
1466 | | */ |
1467 | 57 | if (superuser_arg(roleid)) |
1468 | 2 | { |
1469 | 2 | if (!superuser()) |
1470 | 2 | ereport(ERROR, |
1471 | 2 | (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), |
1472 | 2 | errmsg("must be superuser to alter superusers"))); |
1473 | 2 | } |
1474 | 55 | else |
1475 | 55 | { |
1476 | 55 | if (!have_createrole_privilege() && |
1477 | 13 | !is_admin_of_role(grantorId, roleid)) |
1478 | 55 | ereport(ERROR, |
1479 | 55 | (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), |
1480 | 55 | errmsg("must have admin option on role \"%s\"", |
1481 | 55 | rolename))); |
1482 | 55 | } |
1483 | | |
1484 | | /* |
1485 | | * The role membership grantor of record has little significance at |
1486 | | * present. Nonetheless, inasmuch as users might look to it for a crude |
1487 | | * audit trail, let only superusers impute the grant to a third party. |
1488 | | * |
1489 | | * Before lifting this restriction, give the member == role case of |
1490 | | * is_admin_of_role() a fresh look. Ensure that the current role cannot |
1491 | | * use an explicit grantor specification to take advantage of the session |
1492 | | * user's self-admin right. |
1493 | | */ |
1494 | 57 | if (grantorId != GetUserId() && !superuser()) |
1495 | 57 | ereport(ERROR, |
1496 | 57 | (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), |
1497 | 57 | errmsg("must be superuser to set grantor"))); |
1498 | | |
1499 | 57 | pg_authmem_rel = heap_open(AuthMemRelationId, RowExclusiveLock); |
1500 | 57 | pg_authmem_dsc = RelationGetDescr(pg_authmem_rel); |
1501 | | |
1502 | 57 | forboth(specitem, memberSpecs, iditem, memberIds) |
1503 | 54 | { |
1504 | 54 | RoleSpec *memberRole = lfirst(specitem); |
1505 | 54 | Oid memberid = lfirst_oid(iditem); |
1506 | 54 | HeapTuple authmem_tuple; |
1507 | 54 | HeapTuple tuple; |
1508 | 54 | Datum new_record[Natts_pg_auth_members]; |
1509 | 54 | bool new_record_nulls[Natts_pg_auth_members]; |
1510 | 54 | bool new_record_repl[Natts_pg_auth_members]; |
1511 | | |
1512 | | /* |
1513 | | * Refuse creation of membership loops, including the trivial case |
1514 | | * where a role is made a member of itself. We do this by checking to |
1515 | | * see if the target role is already a member of the proposed member |
1516 | | * role. We have to ignore possible superuserness, however, else we |
1517 | | * could never grant membership in a superuser-privileged role. |
1518 | | */ |
1519 | 54 | if (is_member_of_role_nosuper(roleid, memberid)) |
1520 | 54 | ereport(ERROR, |
1521 | 54 | (errcode(ERRCODE_INVALID_GRANT_OPERATION), |
1522 | 54 | (errmsg("role \"%s\" is a member of role \"%s\"", |
1523 | 54 | rolename, get_rolespec_name(memberRole))))); |
1524 | | |
1525 | | /* |
1526 | | * Check if entry for this role/member already exists; if so, give |
1527 | | * warning unless we are adding admin option. |
1528 | | */ |
1529 | 54 | authmem_tuple = SearchSysCache2(AUTHMEMROLEMEM, |
1530 | 54 | ObjectIdGetDatum(roleid), |
1531 | 54 | ObjectIdGetDatum(memberid)); |
1532 | 54 | if (HeapTupleIsValid(authmem_tuple) && |
1533 | 3 | (!admin_opt || |
1534 | 0 | ((Form_pg_auth_members) GETSTRUCT(authmem_tuple))->admin_option)) |
1535 | 3 | { |
1536 | 3 | ereport(NOTICE, |
1537 | 3 | (errmsg("role \"%s\" is already a member of role \"%s\"", |
1538 | 3 | get_rolespec_name(memberRole), rolename))); |
1539 | 3 | ReleaseSysCache(authmem_tuple); |
1540 | 3 | continue; |
1541 | 51 | } |
1542 | | |
1543 | | /* Build a tuple to insert or update */ |
1544 | 51 | MemSet(new_record, 0, sizeof(new_record)); |
1545 | 51 | MemSet(new_record_nulls, false, sizeof(new_record_nulls)); |
1546 | 51 | MemSet(new_record_repl, false, sizeof(new_record_repl)); |
1547 | | |
1548 | 51 | new_record[Anum_pg_auth_members_roleid - 1] = ObjectIdGetDatum(roleid); |
1549 | 51 | new_record[Anum_pg_auth_members_member - 1] = ObjectIdGetDatum(memberid); |
1550 | 51 | new_record[Anum_pg_auth_members_grantor - 1] = ObjectIdGetDatum(grantorId); |
1551 | 51 | new_record[Anum_pg_auth_members_admin_option - 1] = BoolGetDatum(admin_opt); |
1552 | | |
1553 | 51 | if (HeapTupleIsValid(authmem_tuple)) |
1554 | 0 | { |
1555 | 0 | new_record_repl[Anum_pg_auth_members_grantor - 1] = true; |
1556 | 0 | new_record_repl[Anum_pg_auth_members_admin_option - 1] = true; |
1557 | 0 | tuple = heap_modify_tuple(authmem_tuple, pg_authmem_dsc, |
1558 | 0 | new_record, |
1559 | 0 | new_record_nulls, new_record_repl); |
1560 | 0 | CatalogTupleUpdate(pg_authmem_rel, &tuple->t_self, tuple); |
1561 | 0 | ReleaseSysCache(authmem_tuple); |
1562 | 0 | } |
1563 | 51 | else |
1564 | 51 | { |
1565 | 51 | tuple = heap_form_tuple(pg_authmem_dsc, |
1566 | 51 | new_record, new_record_nulls); |
1567 | 51 | CatalogTupleInsert(pg_authmem_rel, tuple); |
1568 | 51 | } |
1569 | | |
1570 | | /* CCI after each change, in case there are duplicates in list */ |
1571 | 51 | CommandCounterIncrement(); |
1572 | 51 | } |
1573 | | |
1574 | | /* |
1575 | | * Close pg_authmem, but keep lock till commit. |
1576 | | */ |
1577 | 57 | heap_close(pg_authmem_rel, NoLock); |
1578 | 57 | } |
1579 | | |
1580 | | /* |
1581 | | * DelRoleMems -- Remove given members from the specified role |
1582 | | * |
1583 | | * rolename: name of role to del from (used only for error messages) |
1584 | | * roleid: OID of role to del from |
1585 | | * memberSpecs: list of RoleSpec of roles to del (used only for error messages) |
1586 | | * memberIds: OIDs of roles to del |
1587 | | * admin_opt: remove admin option only? |
1588 | | * |
1589 | | * Note: caller is responsible for calling auth_file_update_needed(). |
1590 | | */ |
1591 | | static void |
1592 | | DelRoleMems(const char *rolename, Oid roleid, |
1593 | | List *memberSpecs, List *memberIds, |
1594 | | bool admin_opt) |
1595 | 7 | { |
1596 | 7 | Relation pg_authmem_rel; |
1597 | 7 | TupleDesc pg_authmem_dsc; |
1598 | 7 | ListCell *specitem; |
1599 | 7 | ListCell *iditem; |
1600 | | |
1601 | 7 | Assert(list_length(memberSpecs) == list_length(memberIds)); |
1602 | | |
1603 | | /* Skip permission check if nothing to do */ |
1604 | 7 | if (!memberIds) |
1605 | 0 | return; |
1606 | | |
1607 | | /* |
1608 | | * Check permissions: must have createrole or admin option on the role to |
1609 | | * be changed. To mess with a superuser role, you gotta be superuser. |
1610 | | */ |
1611 | 7 | if (superuser_arg(roleid)) |
1612 | 0 | { |
1613 | 0 | if (!superuser()) |
1614 | 0 | ereport(ERROR, |
1615 | 0 | (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), |
1616 | 0 | errmsg("must be superuser to alter superusers"))); |
1617 | 0 | } |
1618 | 7 | else |
1619 | 7 | { |
1620 | 7 | if (!have_createrole_privilege() && |
1621 | 1 | !is_admin_of_role(GetUserId(), roleid)) |
1622 | 7 | ereport(ERROR, |
1623 | 7 | (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), |
1624 | 7 | errmsg("must have admin option on role \"%s\"", |
1625 | 7 | rolename))); |
1626 | 7 | } |
1627 | | |
1628 | 7 | pg_authmem_rel = heap_open(AuthMemRelationId, RowExclusiveLock); |
1629 | 7 | pg_authmem_dsc = RelationGetDescr(pg_authmem_rel); |
1630 | | |
1631 | 7 | forboth(specitem, memberSpecs, iditem, memberIds) |
1632 | 7 | { |
1633 | 7 | RoleSpec *memberRole = lfirst(specitem); |
1634 | 7 | Oid memberid = lfirst_oid(iditem); |
1635 | 7 | HeapTuple authmem_tuple; |
1636 | | |
1637 | | /* |
1638 | | * Find entry for this role/member |
1639 | | */ |
1640 | 7 | authmem_tuple = SearchSysCache2(AUTHMEMROLEMEM, |
1641 | 7 | ObjectIdGetDatum(roleid), |
1642 | 7 | ObjectIdGetDatum(memberid)); |
1643 | 7 | if (!HeapTupleIsValid(authmem_tuple)) |
1644 | 0 | { |
1645 | 0 | ereport(WARNING, |
1646 | 0 | (errmsg("role \"%s\" is not a member of role \"%s\"", |
1647 | 0 | get_rolespec_name(memberRole), rolename))); |
1648 | 0 | continue; |
1649 | 7 | } |
1650 | | |
1651 | 7 | if (!admin_opt) |
1652 | 6 | { |
1653 | | /* Remove the entry altogether */ |
1654 | 6 | CatalogTupleDelete(pg_authmem_rel, authmem_tuple); |
1655 | 6 | } |
1656 | 1 | else |
1657 | 1 | { |
1658 | | /* Just turn off the admin option */ |
1659 | 1 | HeapTuple tuple; |
1660 | 1 | Datum new_record[Natts_pg_auth_members]; |
1661 | 1 | bool new_record_nulls[Natts_pg_auth_members]; |
1662 | 1 | bool new_record_repl[Natts_pg_auth_members]; |
1663 | | |
1664 | | /* Build a tuple to update with */ |
1665 | 1 | MemSet(new_record, 0, sizeof(new_record)); |
1666 | 1 | MemSet(new_record_nulls, false, sizeof(new_record_nulls)); |
1667 | 1 | MemSet(new_record_repl, false, sizeof(new_record_repl)); |
1668 | | |
1669 | 1 | new_record[Anum_pg_auth_members_admin_option - 1] = BoolGetDatum(false); |
1670 | 1 | new_record_repl[Anum_pg_auth_members_admin_option - 1] = true; |
1671 | | |
1672 | 1 | tuple = heap_modify_tuple(authmem_tuple, pg_authmem_dsc, |
1673 | 1 | new_record, |
1674 | 1 | new_record_nulls, new_record_repl); |
1675 | 1 | CatalogTupleUpdate(pg_authmem_rel, &tuple->t_self, tuple); |
1676 | 1 | } |
1677 | | |
1678 | 7 | ReleaseSysCache(authmem_tuple); |
1679 | | |
1680 | | /* CCI after each change, in case there are duplicates in list */ |
1681 | 7 | CommandCounterIncrement(); |
1682 | 7 | } |
1683 | | |
1684 | | /* |
1685 | | * Close pg_authmem, but keep lock till commit. |
1686 | | */ |
1687 | 7 | heap_close(pg_authmem_rel, NoLock); |
1688 | 7 | } |