/*------------------------------------------------------------------------- * * acl.c * Basic access control list data structures manipulation routines. * * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * * IDENTIFICATION * src/backend/utils/adt/acl.c * *------------------------------------------------------------------------- */ #include "postgres.h" #include #include "access/htup_details.h" #include "catalog/catalog.h" #include "catalog/namespace.h" #include "catalog/pg_auth_members.h" #include "catalog/pg_authid.h" #include "catalog/pg_class.h" #include "catalog/pg_database.h" #include "catalog/pg_type.h" #include "commands/dbcommands.h" #include "commands/proclang.h" #include "commands/tablespace.h" #include "common/hashfn.h" #include "foreign/foreign.h" #include "funcapi.h" #include "lib/qunique.h" #include "miscadmin.h" #include "utils/acl.h" #include "utils/array.h" #include "utils/builtins.h" #include "utils/catcache.h" #include "utils/inval.h" #include "utils/lsyscache.h" #include "utils/memutils.h" #include "utils/syscache.h" #include "utils/varlena.h" typedef struct { const char *name; AclMode value; } priv_map; /* * We frequently need to test whether a given role is a member of some other * role. In most of these tests the "given role" is the same, namely the * active current user. So we can optimize it by keeping cached lists of all * the roles the "given role" is a member of, directly or indirectly. * * Possibly this mechanism should be generalized to allow caching membership * info for multiple roles? * * Each element of cached_roles is an OID list of constituent roles for the * corresponding element of cached_role (always including the cached_role * itself). One cache has ROLERECURSE_PRIVS semantics, and the other has * ROLERECURSE_MEMBERS semantics. */ enum RoleRecurseType { ROLERECURSE_PRIVS = 0, /* recurse if rolinherit */ ROLERECURSE_MEMBERS = 1 /* recurse unconditionally */ }; static Oid cached_role[] = {InvalidOid, InvalidOid}; static List *cached_roles[] = {NIL, NIL}; static uint32 cached_db_hash; static const char *getid(const char *s, char *n); static void putid(char *p, const char *s); static Acl *allocacl(int n); static void check_acl(const Acl *acl); static const char *aclparse(const char *s, AclItem *aip); static bool aclitem_match(const AclItem *a1, const AclItem *a2); static int aclitemComparator(const void *arg1, const void *arg2); static void check_circularity(const Acl *old_acl, const AclItem *mod_aip, Oid ownerId); static Acl *recursive_revoke(Acl *acl, Oid grantee, AclMode revoke_privs, Oid ownerId, DropBehavior behavior); static AclMode convert_priv_string(text *priv_type_text); static AclMode convert_any_priv_string(text *priv_type_text, const priv_map *privileges); static Oid convert_table_name(text *tablename); static AclMode convert_table_priv_string(text *priv_type_text); static AclMode convert_sequence_priv_string(text *priv_type_text); static AttrNumber convert_column_name(Oid tableoid, text *column); static AclMode convert_column_priv_string(text *priv_type_text); static Oid convert_database_name(text *databasename); static AclMode convert_database_priv_string(text *priv_type_text); static Oid convert_foreign_data_wrapper_name(text *fdwname); static AclMode convert_foreign_data_wrapper_priv_string(text *priv_type_text); static Oid convert_function_name(text *functionname); static AclMode convert_function_priv_string(text *priv_type_text); static Oid convert_language_name(text *languagename); static AclMode convert_language_priv_string(text *priv_type_text); static Oid convert_schema_name(text *schemaname); static AclMode convert_schema_priv_string(text *priv_type_text); static Oid convert_server_name(text *servername); static AclMode convert_server_priv_string(text *priv_type_text); static Oid convert_tablespace_name(text *tablespacename); static AclMode convert_tablespace_priv_string(text *priv_type_text); static Oid convert_type_name(text *typename); static AclMode convert_type_priv_string(text *priv_type_text); static AclMode convert_role_priv_string(text *priv_type_text); static AclResult pg_role_aclcheck(Oid role_oid, Oid roleid, AclMode mode); static void RoleMembershipCacheCallback(Datum arg, int cacheid, uint32 hashvalue); /* * getid * Consumes the first alphanumeric string (identifier) found in string * 's', ignoring any leading white space. If it finds a double quote * it returns the word inside the quotes. * * RETURNS: * the string position in 's' that points to the next non-space character * in 's', after any quotes. Also: * - loads the identifier into 'n'. (If no identifier is found, 'n' * contains an empty string.) 'n' must be NAMEDATALEN bytes. */ static const char * getid(const char *s, char *n) { int len = 0; bool in_quotes = false; Assert(s && n); while (isspace((unsigned char) *s)) s++; /* This code had better match what putid() does, below */ for (; *s != '\0' && (isalnum((unsigned char) *s) || *s == '_' || *s == '"' || in_quotes); s++) { if (*s == '"') { /* safe to look at next char (could be '\0' though) */ if (*(s + 1) != '"') { in_quotes = !in_quotes; continue; } /* it's an escaped double quote; skip the escaping char */ s++; } /* Add the character to the string */ if (len >= NAMEDATALEN - 1) ereport(ERROR, (errcode(ERRCODE_NAME_TOO_LONG), errmsg("identifier too long"), errdetail("Identifier must be less than %d characters.", NAMEDATALEN))); n[len++] = *s; } n[len] = '\0'; while (isspace((unsigned char) *s)) s++; return s; } /* * Write a role name at *p, adding double quotes if needed. * There must be at least (2*NAMEDATALEN)+2 bytes available at *p. * This needs to be kept in sync with copyAclUserName in pg_dump/dumputils.c */ static void putid(char *p, const char *s) { const char *src; bool safe = true; for (src = s; *src; src++) { /* This test had better match what getid() does, above */ if (!isalnum((unsigned char) *src) && *src != '_') { safe = false; break; } } if (!safe) *p++ = '"'; for (src = s; *src; src++) { /* A double quote character in a username is encoded as "" */ if (*src == '"') *p++ = '"'; *p++ = *src; } if (!safe) *p++ = '"'; *p = '\0'; } /* * aclparse * Consumes and parses an ACL specification of the form: * [group|user] [A-Za-z0-9]*=[rwaR]* * from string 's', ignoring any leading white space or white space * between the optional id type keyword (group|user) and the actual * ACL specification. * * The group|user decoration is unnecessary in the roles world, * but we still accept it for backward compatibility. * * This routine is called by the parser as well as aclitemin(), hence * the added generality. * * RETURNS: * the string position in 's' immediately following the ACL * specification. Also: * - loads the structure pointed to by 'aip' with the appropriate * UID/GID, id type identifier and mode type values. */ static const char * aclparse(const char *s, AclItem *aip) { AclMode privs, goption, read; char name[NAMEDATALEN]; char name2[NAMEDATALEN]; Assert(s && aip); s = getid(s, name); if (*s != '=') { /* we just read a keyword, not a name */ if (strcmp(name, "group") != 0 && strcmp(name, "user") != 0) ereport(ERROR, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("unrecognized key word: \"%s\"", name), errhint("ACL key word must be \"group\" or \"user\"."))); s = getid(s, name); /* move s to the name beyond the keyword */ if (name[0] == '\0') ereport(ERROR, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("missing name"), errhint("A name must follow the \"group\" or \"user\" key word."))); } if (*s != '=') ereport(ERROR, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("missing \"=\" sign"))); privs = goption = ACL_NO_RIGHTS; for (++s, read = 0; isalpha((unsigned char) *s) || *s == '*'; s++) { switch (*s) { case '*': goption |= read; break; case ACL_INSERT_CHR: read = ACL_INSERT; break; case ACL_SELECT_CHR: read = ACL_SELECT; break; case ACL_UPDATE_CHR: read = ACL_UPDATE; break; case ACL_DELETE_CHR: read = ACL_DELETE; break; case ACL_TRUNCATE_CHR: read = ACL_TRUNCATE; break; case ACL_REFERENCES_CHR: read = ACL_REFERENCES; break; case ACL_TRIGGER_CHR: read = ACL_TRIGGER; break; case ACL_EXECUTE_CHR: read = ACL_EXECUTE; break; case ACL_USAGE_CHR: read = ACL_USAGE; break; case ACL_CREATE_CHR: read = ACL_CREATE; break; case ACL_CREATE_TEMP_CHR: read = ACL_CREATE_TEMP; break; case ACL_CONNECT_CHR: read = ACL_CONNECT; break; case 'R': /* ignore old RULE privileges */ read = 0; break; default: ereport(ERROR, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("invalid mode character: must be one of \"%s\"", ACL_ALL_RIGHTS_STR))); } privs |= read; } if (name[0] == '\0') aip->ai_grantee = ACL_ID_PUBLIC; else aip->ai_grantee = get_role_oid(name, false); /* * XXX Allow a degree of backward compatibility by defaulting the grantor * to the superuser. */ if (*s == '/') { s = getid(s + 1, name2); if (name2[0] == '\0') ereport(ERROR, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("a name must follow the \"/\" sign"))); aip->ai_grantor = get_role_oid(name2, false); } else { aip->ai_grantor = BOOTSTRAP_SUPERUSERID; ereport(WARNING, (errcode(ERRCODE_INVALID_GRANTOR), errmsg("defaulting grantor to user ID %u", BOOTSTRAP_SUPERUSERID))); } ACLITEM_SET_PRIVS_GOPTIONS(*aip, privs, goption); return s; } /* * allocacl * Allocates storage for a new Acl with 'n' entries. * * RETURNS: * the new Acl */ static Acl * allocacl(int n) { Acl *new_acl; Size size; if (n < 0) elog(ERROR, "invalid size: %d", n); size = ACL_N_SIZE(n); new_acl = (Acl *) palloc0(size); SET_VARSIZE(new_acl, size); new_acl->ndim = 1; new_acl->dataoffset = 0; /* we never put in any nulls */ new_acl->elemtype = ACLITEMOID; ARR_LBOUND(new_acl)[0] = 1; ARR_DIMS(new_acl)[0] = n; return new_acl; } /* * Create a zero-entry ACL */ Acl * make_empty_acl(void) { return allocacl(0); } /* * Copy an ACL */ Acl * aclcopy(const Acl *orig_acl) { Acl *result_acl; result_acl = allocacl(ACL_NUM(orig_acl)); memcpy(ACL_DAT(result_acl), ACL_DAT(orig_acl), ACL_NUM(orig_acl) * sizeof(AclItem)); return result_acl; } /* * Concatenate two ACLs * * This is a bit cheesy, since we may produce an ACL with redundant entries. * Be careful what the result is used for! */ Acl * aclconcat(const Acl *left_acl, const Acl *right_acl) { Acl *result_acl; result_acl = allocacl(ACL_NUM(left_acl) + ACL_NUM(right_acl)); memcpy(ACL_DAT(result_acl), ACL_DAT(left_acl), ACL_NUM(left_acl) * sizeof(AclItem)); memcpy(ACL_DAT(result_acl) + ACL_NUM(left_acl), ACL_DAT(right_acl), ACL_NUM(right_acl) * sizeof(AclItem)); return result_acl; } /* * Merge two ACLs * * This produces a properly merged ACL with no redundant entries. * Returns NULL on NULL input. */ Acl * aclmerge(const Acl *left_acl, const Acl *right_acl, Oid ownerId) { Acl *result_acl; AclItem *aip; int i, num; /* Check for cases where one or both are empty/null */ if (left_acl == NULL || ACL_NUM(left_acl) == 0) { if (right_acl == NULL || ACL_NUM(right_acl) == 0) return NULL; else return aclcopy(right_acl); } else { if (right_acl == NULL || ACL_NUM(right_acl) == 0) return aclcopy(left_acl); } /* Merge them the hard way, one item at a time */ result_acl = aclcopy(left_acl); aip = ACL_DAT(right_acl); num = ACL_NUM(right_acl); for (i = 0; i < num; i++, aip++) { Acl *tmp_acl; tmp_acl = aclupdate(result_acl, aip, ACL_MODECHG_ADD, ownerId, DROP_RESTRICT); pfree(result_acl); result_acl = tmp_acl; } return result_acl; } /* * Sort the items in an ACL (into an arbitrary but consistent order) */ void aclitemsort(Acl *acl) { if (acl != NULL && ACL_NUM(acl) > 1) qsort(ACL_DAT(acl), ACL_NUM(acl), sizeof(AclItem), aclitemComparator); } /* * Check if two ACLs are exactly equal * * This will not detect equality if the two arrays contain the same items * in different orders. To handle that case, sort both inputs first, * using aclitemsort(). */ bool aclequal(const Acl *left_acl, const Acl *right_acl) { /* Check for cases where one or both are empty/null */ if (left_acl == NULL || ACL_NUM(left_acl) == 0) { if (right_acl == NULL || ACL_NUM(right_acl) == 0) return true; else return false; } else { if (right_acl == NULL || ACL_NUM(right_acl) == 0) return false; } if (ACL_NUM(left_acl) != ACL_NUM(right_acl)) return false; if (memcmp(ACL_DAT(left_acl), ACL_DAT(right_acl), ACL_NUM(left_acl) * sizeof(AclItem)) == 0) return true; return false; } /* * Verify that an ACL array is acceptable (one-dimensional and has no nulls) */ static void check_acl(const Acl *acl) { if (ARR_ELEMTYPE(acl) != ACLITEMOID) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("ACL array contains wrong data type"))); if (ARR_NDIM(acl) != 1) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("ACL arrays must be one-dimensional"))); if (ARR_HASNULL(acl)) ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), errmsg("ACL arrays must not contain null values"))); } /* * aclitemin * Allocates storage for, and fills in, a new AclItem given a string * 's' that contains an ACL specification. See aclparse for details. * * RETURNS: * the new AclItem */ Datum aclitemin(PG_FUNCTION_ARGS) { const char *s = PG_GETARG_CSTRING(0); AclItem *aip; aip = (AclItem *) palloc(sizeof(AclItem)); s = aclparse(s, aip); while (isspace((unsigned char) *s)) ++s; if (*s) ereport(ERROR, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("extra garbage at the end of the ACL specification"))); PG_RETURN_ACLITEM_P(aip); } /* * aclitemout * Allocates storage for, and fills in, a new null-delimited string * containing a formatted ACL specification. See aclparse for details. * * RETURNS: * the new string */ Datum aclitemout(PG_FUNCTION_ARGS) { AclItem *aip = PG_GETARG_ACLITEM_P(0); char *p; char *out; HeapTuple htup; unsigned i; out = palloc(strlen("=/") + 2 * N_ACL_RIGHTS + 2 * (2 * NAMEDATALEN + 2) + 1); p = out; *p = '\0'; if (aip->ai_grantee != ACL_ID_PUBLIC) { htup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(aip->ai_grantee)); if (HeapTupleIsValid(htup)) { putid(p, NameStr(((Form_pg_authid) GETSTRUCT(htup))->rolname)); ReleaseSysCache(htup); } else { /* Generate numeric OID if we don't find an entry */ sprintf(p, "%u", aip->ai_grantee); } } while (*p) ++p; *p++ = '='; for (i = 0; i < N_ACL_RIGHTS; ++i) { if (ACLITEM_GET_PRIVS(*aip) & (1 << i)) *p++ = ACL_ALL_RIGHTS_STR[i]; if (ACLITEM_GET_GOPTIONS(*aip) & (1 << i)) *p++ = '*'; } *p++ = '/'; *p = '\0'; htup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(aip->ai_grantor)); if (HeapTupleIsValid(htup)) { putid(p, NameStr(((Form_pg_authid) GETSTRUCT(htup))->rolname)); ReleaseSysCache(htup); } else { /* Generate numeric OID if we don't find an entry */ sprintf(p, "%u", aip->ai_grantor); } PG_RETURN_CSTRING(out); } /* * aclitem_match * Two AclItems are considered to match iff they have the same * grantee and grantor; the privileges are ignored. */ static bool aclitem_match(const AclItem *a1, const AclItem *a2) { return a1->ai_grantee == a2->ai_grantee && a1->ai_grantor == a2->ai_grantor; } /* * aclitemComparator * qsort comparison function for AclItems */ static int aclitemComparator(const void *arg1, const void *arg2) { const AclItem *a1 = (const AclItem *) arg1; const AclItem *a2 = (const AclItem *) arg2; if (a1->ai_grantee > a2->ai_grantee) return 1; if (a1->ai_grantee < a2->ai_grantee) return -1; if (a1->ai_grantor > a2->ai_grantor) return 1; if (a1->ai_grantor < a2->ai_grantor) return -1; if (a1->ai_privs > a2->ai_privs) return 1; if (a1->ai_privs < a2->ai_privs) return -1; return 0; } /* * aclitem equality operator */ Datum aclitem_eq(PG_FUNCTION_ARGS) { AclItem *a1 = PG_GETARG_ACLITEM_P(0); AclItem *a2 = PG_GETARG_ACLITEM_P(1); bool result; result = a1->ai_privs == a2->ai_privs && a1->ai_grantee == a2->ai_grantee && a1->ai_grantor == a2->ai_grantor; PG_RETURN_BOOL(result); } /* * aclitem hash function * * We make aclitems hashable not so much because anyone is likely to hash * them, as because we want array equality to work on aclitem arrays, and * with the typcache mechanism we must have a hash or btree opclass. */ Datum hash_aclitem(PG_FUNCTION_ARGS) { AclItem *a = PG_GETARG_ACLITEM_P(0); /* not very bright, but avoids any issue of padding in struct */ PG_RETURN_UINT32((uint32) (a->ai_privs + a->ai_grantee + a->ai_grantor)); } /* * 64-bit hash function for aclitem. * * Similar to hash_aclitem, but accepts a seed and returns a uint64 value. */ Datum hash_aclitem_extended(PG_FUNCTION_ARGS) { AclItem *a = PG_GETARG_ACLITEM_P(0); uint64 seed = PG_GETARG_INT64(1); uint32 sum = (uint32) (a->ai_privs + a->ai_grantee + a->ai_grantor); return (seed == 0) ? UInt64GetDatum(sum) : hash_uint32_extended(sum, seed); } /* * acldefault() --- create an ACL describing default access permissions * * Change this routine if you want to alter the default access policy for * newly-created objects (or any object with a NULL acl entry). When * you make a change here, don't forget to update the GRANT man page, * which explains all the default permissions. * * Note that these are the hard-wired "defaults" that are used in the * absence of any pg_default_acl entry. */ Acl * acldefault(ObjectType objtype, Oid ownerId) { AclMode world_default; AclMode owner_default; int nacl; Acl *acl; AclItem *aip; switch (objtype) { case OBJECT_COLUMN: /* by default, columns have no extra privileges */ world_default = ACL_NO_RIGHTS; owner_default = ACL_NO_RIGHTS; break; case OBJECT_TABLE: world_default = ACL_NO_RIGHTS; owner_default = ACL_ALL_RIGHTS_RELATION; break; case OBJECT_SEQUENCE: world_default = ACL_NO_RIGHTS; owner_default = ACL_ALL_RIGHTS_SEQUENCE; break; case OBJECT_DATABASE: /* for backwards compatibility, grant some rights by default */ world_default = ACL_CREATE_TEMP | ACL_CONNECT; owner_default = ACL_ALL_RIGHTS_DATABASE; break; case OBJECT_FUNCTION: /* Grant EXECUTE by default, for now */ world_default = ACL_EXECUTE; owner_default = ACL_ALL_RIGHTS_FUNCTION; break; case OBJECT_LANGUAGE: /* Grant USAGE by default, for now */ world_default = ACL_USAGE; owner_default = ACL_ALL_RIGHTS_LANGUAGE; break; case OBJECT_LARGEOBJECT: world_default = ACL_NO_RIGHTS; owner_default = ACL_ALL_RIGHTS_LARGEOBJECT; break; case OBJECT_SCHEMA: world_default = ACL_NO_RIGHTS; owner_default = ACL_ALL_RIGHTS_SCHEMA; break; case OBJECT_TABLESPACE: world_default = ACL_NO_RIGHTS; owner_default = ACL_ALL_RIGHTS_TABLESPACE; break; case OBJECT_FDW: world_default = ACL_NO_RIGHTS; owner_default = ACL_ALL_RIGHTS_FDW; break; case OBJECT_FOREIGN_SERVER: world_default = ACL_NO_RIGHTS; owner_default = ACL_ALL_RIGHTS_FOREIGN_SERVER; break; case OBJECT_DOMAIN: case OBJECT_TYPE: world_default = ACL_USAGE; owner_default = ACL_ALL_RIGHTS_TYPE; break; default: elog(ERROR, "unrecognized objtype: %d", (int) objtype); world_default = ACL_NO_RIGHTS; /* keep compiler quiet */ owner_default = ACL_NO_RIGHTS; break; } nacl = 0; if (world_default != ACL_NO_RIGHTS) nacl++; if (owner_default != ACL_NO_RIGHTS) nacl++; acl = allocacl(nacl); aip = ACL_DAT(acl); if (world_default != ACL_NO_RIGHTS) { aip->ai_grantee = ACL_ID_PUBLIC; aip->ai_grantor = ownerId; ACLITEM_SET_PRIVS_GOPTIONS(*aip, world_default, ACL_NO_RIGHTS); aip++; } /* * Note that the owner's entry shows all ordinary privileges but no grant * options. This is because his grant options come "from the system" and * not from his own efforts. (The SQL spec says that the owner's rights * come from a "_SYSTEM" authid.) However, we do consider that the * owner's ordinary privileges are self-granted; this lets him revoke * them. We implement the owner's grant options without any explicit * "_SYSTEM"-like ACL entry, by internally special-casing the owner * wherever we are testing grant options. */ if (owner_default != ACL_NO_RIGHTS) { aip->ai_grantee = ownerId; aip->ai_grantor = ownerId; ACLITEM_SET_PRIVS_GOPTIONS(*aip, owner_default, ACL_NO_RIGHTS); } return acl; } /* * SQL-accessible version of acldefault(). Hackish mapping from "char" type to * OBJECT_* values. */ Datum acldefault_sql(PG_FUNCTION_ARGS) { char objtypec = PG_GETARG_CHAR(0); Oid owner = PG_GETARG_OID(1); ObjectType objtype = 0; switch (objtypec) { case 'c': objtype = OBJECT_COLUMN; break; case 'r': objtype = OBJECT_TABLE; break; case 's': objtype = OBJECT_SEQUENCE; break; case 'd': objtype = OBJECT_DATABASE; break; case 'f': objtype = OBJECT_FUNCTION; break; case 'l': objtype = OBJECT_LANGUAGE; break; case 'L': objtype = OBJECT_LARGEOBJECT; break; case 'n': objtype = OBJECT_SCHEMA; break; case 't': objtype = OBJECT_TABLESPACE; break; case 'F': objtype = OBJECT_FDW; break; case 'S': objtype = OBJECT_FOREIGN_SERVER; break; case 'T': objtype = OBJECT_TYPE; break; default: elog(ERROR, "unrecognized objtype abbreviation: %c", objtypec); } PG_RETURN_ACL_P(acldefault(objtype, owner)); } /* * Update an ACL array to add or remove specified privileges. * * old_acl: the input ACL array * mod_aip: defines the privileges to be added, removed, or substituted * modechg: ACL_MODECHG_ADD, ACL_MODECHG_DEL, or ACL_MODECHG_EQL * ownerId: Oid of object owner * behavior: RESTRICT or CASCADE behavior for recursive removal * * ownerid and behavior are only relevant when the update operation specifies * deletion of grant options. * * The result is a modified copy; the input object is not changed. * * NB: caller is responsible for having detoasted the input ACL, if needed. */ Acl * aclupdate(const Acl *old_acl, const AclItem *mod_aip, int modechg, Oid ownerId, DropBehavior behavior) { Acl *new_acl = NULL; AclItem *old_aip, *new_aip = NULL; AclMode old_rights, old_goptions, new_rights, new_goptions; int dst, num; /* Caller probably already checked old_acl, but be safe */ check_acl(old_acl); /* If granting grant options, check for circularity */ if (modechg != ACL_MODECHG_DEL && ACLITEM_GET_GOPTIONS(*mod_aip) != ACL_NO_RIGHTS) check_circularity(old_acl, mod_aip, ownerId); num = ACL_NUM(old_acl); old_aip = ACL_DAT(old_acl); /* * Search the ACL for an existing entry for this grantee and grantor. If * one exists, just modify the entry in-place (well, in the same position, * since we actually return a copy); otherwise, insert the new entry at * the end. */ for (dst = 0; dst < num; ++dst) { if (aclitem_match(mod_aip, old_aip + dst)) { /* found a match, so modify existing item */ new_acl = allocacl(num); new_aip = ACL_DAT(new_acl); memcpy(new_acl, old_acl, ACL_SIZE(old_acl)); break; } } if (dst == num) { /* need to append a new item */ new_acl = allocacl(num + 1); new_aip = ACL_DAT(new_acl); memcpy(new_aip, old_aip, num * sizeof(AclItem)); /* initialize the new entry with no permissions */ new_aip[dst].ai_grantee = mod_aip->ai_grantee; new_aip[dst].ai_grantor = mod_aip->ai_grantor; ACLITEM_SET_PRIVS_GOPTIONS(new_aip[dst], ACL_NO_RIGHTS, ACL_NO_RIGHTS); num++; /* set num to the size of new_acl */ } old_rights = ACLITEM_GET_RIGHTS(new_aip[dst]); old_goptions = ACLITEM_GET_GOPTIONS(new_aip[dst]); /* apply the specified permissions change */ switch (modechg) { case ACL_MODECHG_ADD: ACLITEM_SET_RIGHTS(new_aip[dst], old_rights | ACLITEM_GET_RIGHTS(*mod_aip)); break; case ACL_MODECHG_DEL: ACLITEM_SET_RIGHTS(new_aip[dst], old_rights & ~ACLITEM_GET_RIGHTS(*mod_aip)); break; case ACL_MODECHG_EQL: ACLITEM_SET_RIGHTS(new_aip[dst], ACLITEM_GET_RIGHTS(*mod_aip)); break; } new_rights = ACLITEM_GET_RIGHTS(new_aip[dst]); new_goptions = ACLITEM_GET_GOPTIONS(new_aip[dst]); /* * If the adjusted entry has no permissions, delete it from the list. */ if (new_rights == ACL_NO_RIGHTS) { memmove(new_aip + dst, new_aip + dst + 1, (num - dst - 1) * sizeof(AclItem)); /* Adjust array size to be 'num - 1' items */ ARR_DIMS(new_acl)[0] = num - 1; SET_VARSIZE(new_acl, ACL_N_SIZE(num - 1)); } /* * Remove abandoned privileges (cascading revoke). Currently we can only * handle this when the grantee is not PUBLIC. */ if ((old_goptions & ~new_goptions) != 0) { Assert(mod_aip->ai_grantee != ACL_ID_PUBLIC); new_acl = recursive_revoke(new_acl, mod_aip->ai_grantee, (old_goptions & ~new_goptions), ownerId, behavior); } return new_acl; } /* * Update an ACL array to reflect a change of owner to the parent object * * old_acl: the input ACL array (must not be NULL) * oldOwnerId: Oid of the old object owner * newOwnerId: Oid of the new object owner * * The result is a modified copy; the input object is not changed. * * NB: caller is responsible for having detoasted the input ACL, if needed. */ Acl * aclnewowner(const Acl *old_acl, Oid oldOwnerId, Oid newOwnerId) { Acl *new_acl; AclItem *new_aip; AclItem *old_aip; AclItem *dst_aip; AclItem *src_aip; AclItem *targ_aip; bool newpresent = false; int dst, src, targ, num; check_acl(old_acl); /* * Make a copy of the given ACL, substituting new owner ID for old * wherever it appears as either grantor or grantee. Also note if the new * owner ID is already present. */ num = ACL_NUM(old_acl); old_aip = ACL_DAT(old_acl); new_acl = allocacl(num); new_aip = ACL_DAT(new_acl); memcpy(new_aip, old_aip, num * sizeof(AclItem)); for (dst = 0, dst_aip = new_aip; dst < num; dst++, dst_aip++) { if (dst_aip->ai_grantor == oldOwnerId) dst_aip->ai_grantor = newOwnerId; else if (dst_aip->ai_grantor == newOwnerId) newpresent = true; if (dst_aip->ai_grantee == oldOwnerId) dst_aip->ai_grantee = newOwnerId; else if (dst_aip->ai_grantee == newOwnerId) newpresent = true; } /* * If the old ACL contained any references to the new owner, then we may * now have generated an ACL containing duplicate entries. Find them and * merge them so that there are not duplicates. (This is relatively * expensive since we use a stupid O(N^2) algorithm, but it's unlikely to * be the normal case.) * * To simplify deletion of duplicate entries, we temporarily leave them in * the array but set their privilege masks to zero; when we reach such an * entry it's just skipped. (Thus, a side effect of this code will be to * remove privilege-free entries, should there be any in the input.) dst * is the next output slot, targ is the currently considered input slot * (always >= dst), and src scans entries to the right of targ looking for * duplicates. Once an entry has been emitted to dst it is known * duplicate-free and need not be considered anymore. */ if (newpresent) { dst = 0; for (targ = 0, targ_aip = new_aip; targ < num; targ++, targ_aip++) { /* ignore if deleted in an earlier pass */ if (ACLITEM_GET_RIGHTS(*targ_aip) == ACL_NO_RIGHTS) continue; /* find and merge any duplicates */ for (src = targ + 1, src_aip = targ_aip + 1; src < num; src++, src_aip++) { if (ACLITEM_GET_RIGHTS(*src_aip) == ACL_NO_RIGHTS) continue; if (aclitem_match(targ_aip, src_aip)) { ACLITEM_SET_RIGHTS(*targ_aip, ACLITEM_GET_RIGHTS(*targ_aip) | ACLITEM_GET_RIGHTS(*src_aip)); /* mark the duplicate deleted */ ACLITEM_SET_RIGHTS(*src_aip, ACL_NO_RIGHTS); } } /* and emit to output */ new_aip[dst] = *targ_aip; dst++; } /* Adjust array size to be 'dst' items */ ARR_DIMS(new_acl)[0] = dst; SET_VARSIZE(new_acl, ACL_N_SIZE(dst)); } return new_acl; } /* * When granting grant options, we must disallow attempts to set up circular * chains of grant options. Suppose A (the object owner) grants B some * privileges with grant option, and B re-grants them to C. If C could * grant the privileges to B as well, then A would be unable to effectively * revoke the privileges from B, since recursive_revoke would consider that * B still has 'em from C. * * We check for this by recursively deleting all grant options belonging to * the target grantee, and then seeing if the would-be grantor still has the * grant option or not. */ static void check_circularity(const Acl *old_acl, const AclItem *mod_aip, Oid ownerId) { Acl *acl; AclItem *aip; int i, num; AclMode own_privs; check_acl(old_acl); /* * For now, grant options can only be granted to roles, not PUBLIC. * Otherwise we'd have to work a bit harder here. */ Assert(mod_aip->ai_grantee != ACL_ID_PUBLIC); /* The owner always has grant options, no need to check */ if (mod_aip->ai_grantor == ownerId) return; /* Make a working copy */ acl = allocacl(ACL_NUM(old_acl)); memcpy(acl, old_acl, ACL_SIZE(old_acl)); /* Zap all grant options of target grantee, plus what depends on 'em */ cc_restart: num = ACL_NUM(acl); aip = ACL_DAT(acl); for (i = 0; i < num; i++) { if (aip[i].ai_grantee == mod_aip->ai_grantee && ACLITEM_GET_GOPTIONS(aip[i]) != ACL_NO_RIGHTS) { Acl *new_acl; /* We'll actually zap ordinary privs too, but no matter */ new_acl = aclupdate(acl, &aip[i], ACL_MODECHG_DEL, ownerId, DROP_CASCADE); pfree(acl); acl = new_acl; goto cc_restart; } } /* Now we can compute grantor's independently-derived privileges */ own_privs = aclmask(acl, mod_aip->ai_grantor, ownerId, ACL_GRANT_OPTION_FOR(ACLITEM_GET_GOPTIONS(*mod_aip)), ACLMASK_ALL); own_privs = ACL_OPTION_TO_PRIVS(own_privs); if ((ACLITEM_GET_GOPTIONS(*mod_aip) & ~own_privs) != 0) ereport(ERROR, (errcode(ERRCODE_INVALID_GRANT_OPERATION), errmsg("grant options cannot be granted back to your own grantor"))); pfree(acl); } /* * Ensure that no privilege is "abandoned". A privilege is abandoned * if the user that granted the privilege loses the grant option. (So * the chain through which it was granted is broken.) Either the * abandoned privileges are revoked as well, or an error message is * printed, depending on the drop behavior option. * * acl: the input ACL list * grantee: the user from whom some grant options have been revoked * revoke_privs: the grant options being revoked * ownerId: Oid of object owner * behavior: RESTRICT or CASCADE behavior for recursive removal * * The input Acl object is pfree'd if replaced. */ static Acl * recursive_revoke(Acl *acl, Oid grantee, AclMode revoke_privs, Oid ownerId, DropBehavior behavior) { AclMode still_has; AclItem *aip; int i, num; check_acl(acl); /* The owner can never truly lose grant options, so short-circuit */ if (grantee == ownerId) return acl; /* The grantee might still have some grant options via another grantor */ still_has = aclmask(acl, grantee, ownerId, ACL_GRANT_OPTION_FOR(revoke_privs), ACLMASK_ALL); revoke_privs &= ~ACL_OPTION_TO_PRIVS(still_has); if (revoke_privs == ACL_NO_RIGHTS) return acl; restart: num = ACL_NUM(acl); aip = ACL_DAT(acl); for (i = 0; i < num; i++) { if (aip[i].ai_grantor == grantee && (ACLITEM_GET_PRIVS(aip[i]) & revoke_privs) != 0) { AclItem mod_acl; Acl *new_acl; if (behavior == DROP_RESTRICT) ereport(ERROR, (errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST), errmsg("dependent privileges exist"), errhint("Use CASCADE to revoke them too."))); mod_acl.ai_grantor = grantee; mod_acl.ai_grantee = aip[i].ai_grantee; ACLITEM_SET_PRIVS_GOPTIONS(mod_acl, revoke_privs, revoke_privs); new_acl = aclupdate(acl, &mod_acl, ACL_MODECHG_DEL, ownerId, behavior); pfree(acl); acl = new_acl; goto restart; } } return acl; } /* * aclmask --- compute bitmask of all privileges held by roleid. * * When 'how' = ACLMASK_ALL, this simply returns the privilege bits * held by the given roleid according to the given ACL list, ANDed * with 'mask'. (The point of passing 'mask' is to let the routine * exit early if all privileges of interest have been found.) * * When 'how' = ACLMASK_ANY, returns as soon as any bit in the mask * is known true. (This lets us exit soonest in cases where the * caller is only going to test for zero or nonzero result.) * * Usage patterns: * * To see if any of a set of privileges are held: * if (aclmask(acl, roleid, ownerId, privs, ACLMASK_ANY) != 0) * * To see if all of a set of privileges are held: * if (aclmask(acl, roleid, ownerId, privs, ACLMASK_ALL) == privs) * * To determine exactly which of a set of privileges are held: * heldprivs = aclmask(acl, roleid, ownerId, privs, ACLMASK_ALL); */ AclMode aclmask(const Acl *acl, Oid roleid, Oid ownerId, AclMode mask, AclMaskHow how) { AclMode result; AclMode remaining; AclItem *aidat; int i, num; /* * Null ACL should not happen, since caller should have inserted * appropriate default */ if (acl == NULL) elog(ERROR, "null ACL"); check_acl(acl); /* Quick exit for mask == 0 */ if (mask == 0) return 0; result = 0; /* Owner always implicitly has all grant options */ if ((mask & ACLITEM_ALL_GOPTION_BITS) && has_privs_of_role(roleid, ownerId)) { result = mask & ACLITEM_ALL_GOPTION_BITS; if ((how == ACLMASK_ALL) ? (result == mask) : (result != 0)) return result; } num = ACL_NUM(acl); aidat = ACL_DAT(acl); /* * Check privileges granted directly to roleid or to public */ for (i = 0; i < num; i++) { AclItem *aidata = &aidat[i]; if (aidata->ai_grantee == ACL_ID_PUBLIC || aidata->ai_grantee == roleid) { result |= aidata->ai_privs & mask; if ((how == ACLMASK_ALL) ? (result == mask) : (result != 0)) return result; } } /* * Check privileges granted indirectly via role memberships. We do this in * a separate pass to minimize expensive indirect membership tests. In * particular, it's worth testing whether a given ACL entry grants any * privileges still of interest before we perform the has_privs_of_role * test. */ remaining = mask & ~result; for (i = 0; i < num; i++) { AclItem *aidata = &aidat[i]; if (aidata->ai_grantee == ACL_ID_PUBLIC || aidata->ai_grantee == roleid) continue; /* already checked it */ if ((aidata->ai_privs & remaining) && has_privs_of_role(roleid, aidata->ai_grantee)) { result |= aidata->ai_privs & mask; if ((how == ACLMASK_ALL) ? (result == mask) : (result != 0)) return result; remaining = mask & ~result; } } return result; } /* * aclmask_direct --- compute bitmask of all privileges held by roleid. * * This is exactly like aclmask() except that we consider only privileges * held *directly* by roleid, not those inherited via role membership. */ static AclMode aclmask_direct(const Acl *acl, Oid roleid, Oid ownerId, AclMode mask, AclMaskHow how) { AclMode result; AclItem *aidat; int i, num; /* * Null ACL should not happen, since caller should have inserted * appropriate default */ if (acl == NULL) elog(ERROR, "null ACL"); check_acl(acl); /* Quick exit for mask == 0 */ if (mask == 0) return 0; result = 0; /* Owner always implicitly has all grant options */ if ((mask & ACLITEM_ALL_GOPTION_BITS) && roleid == ownerId) { result = mask & ACLITEM_ALL_GOPTION_BITS; if ((how == ACLMASK_ALL) ? (result == mask) : (result != 0)) return result; } num = ACL_NUM(acl); aidat = ACL_DAT(acl); /* * Check privileges granted directly to roleid (and not to public) */ for (i = 0; i < num; i++) { AclItem *aidata = &aidat[i]; if (aidata->ai_grantee == roleid) { result |= aidata->ai_privs & mask; if ((how == ACLMASK_ALL) ? (result == mask) : (result != 0)) return result; } } return result; } /* * aclmembers * Find out all the roleids mentioned in an Acl. * Note that we do not distinguish grantors from grantees. * * *roleids is set to point to a palloc'd array containing distinct OIDs * in sorted order. The length of the array is the function result. */ int aclmembers(const Acl *acl, Oid **roleids) { Oid *list; const AclItem *acldat; int i, j; if (acl == NULL || ACL_NUM(acl) == 0) { *roleids = NULL; return 0; } check_acl(acl); /* Allocate the worst-case space requirement */ list = palloc(ACL_NUM(acl) * 2 * sizeof(Oid)); acldat = ACL_DAT(acl); /* * Walk the ACL collecting mentioned RoleIds. */ j = 0; for (i = 0; i < ACL_NUM(acl); i++) { const AclItem *ai = &acldat[i]; if (ai->ai_grantee != ACL_ID_PUBLIC) list[j++] = ai->ai_grantee; /* grantor is currently never PUBLIC, but let's check anyway */ if (ai->ai_grantor != ACL_ID_PUBLIC) list[j++] = ai->ai_grantor; } /* Sort the array */ qsort(list, j, sizeof(Oid), oid_cmp); /* * We could repalloc the array down to minimum size, but it's hardly worth * it since it's only transient memory. */ *roleids = list; /* Remove duplicates from the array */ return qunique(list, j, sizeof(Oid), oid_cmp); } /* * aclinsert (exported function) */ Datum aclinsert(PG_FUNCTION_ARGS) { ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("aclinsert is no longer supported"))); PG_RETURN_NULL(); /* keep compiler quiet */ } Datum aclremove(PG_FUNCTION_ARGS) { ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("aclremove is no longer supported"))); PG_RETURN_NULL(); /* keep compiler quiet */ } Datum aclcontains(PG_FUNCTION_ARGS) { Acl *acl = PG_GETARG_ACL_P(0); AclItem *aip = PG_GETARG_ACLITEM_P(1); AclItem *aidat; int i, num; check_acl(acl); num = ACL_NUM(acl); aidat = ACL_DAT(acl); for (i = 0; i < num; ++i) { if (aip->ai_grantee == aidat[i].ai_grantee && aip->ai_grantor == aidat[i].ai_grantor && (ACLITEM_GET_RIGHTS(*aip) & ACLITEM_GET_RIGHTS(aidat[i])) == ACLITEM_GET_RIGHTS(*aip)) PG_RETURN_BOOL(true); } PG_RETURN_BOOL(false); } Datum makeaclitem(PG_FUNCTION_ARGS) { Oid grantee = PG_GETARG_OID(0); Oid grantor = PG_GETARG_OID(1); text *privtext = PG_GETARG_TEXT_PP(2); bool goption = PG_GETARG_BOOL(3); AclItem *result; AclMode priv; priv = convert_priv_string(privtext); result = (AclItem *) palloc(sizeof(AclItem)); result->ai_grantee = grantee; result->ai_grantor = grantor; ACLITEM_SET_PRIVS_GOPTIONS(*result, priv, (goption ? priv : ACL_NO_RIGHTS)); PG_RETURN_ACLITEM_P(result); } static AclMode convert_priv_string(text *priv_type_text) { char *priv_type = text_to_cstring(priv_type_text); if (pg_strcasecmp(priv_type, "SELECT") == 0) return ACL_SELECT; if (pg_strcasecmp(priv_type, "INSERT") == 0) return ACL_INSERT; if (pg_strcasecmp(priv_type, "UPDATE") == 0) return ACL_UPDATE; if (pg_strcasecmp(priv_type, "DELETE") == 0) return ACL_DELETE; if (pg_strcasecmp(priv_type, "TRUNCATE") == 0) return ACL_TRUNCATE; if (pg_strcasecmp(priv_type, "REFERENCES") == 0) return ACL_REFERENCES; if (pg_strcasecmp(priv_type, "TRIGGER") == 0) return ACL_TRIGGER; if (pg_strcasecmp(priv_type, "EXECUTE") == 0) return ACL_EXECUTE; if (pg_strcasecmp(priv_type, "USAGE") == 0) return ACL_USAGE; if (pg_strcasecmp(priv_type, "CREATE") == 0) return ACL_CREATE; if (pg_strcasecmp(priv_type, "TEMP") == 0) return ACL_CREATE_TEMP; if (pg_strcasecmp(priv_type, "TEMPORARY") == 0) return ACL_CREATE_TEMP; if (pg_strcasecmp(priv_type, "CONNECT") == 0) return ACL_CONNECT; if (pg_strcasecmp(priv_type, "RULE") == 0) return 0; /* ignore old RULE privileges */ ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("unrecognized privilege type: \"%s\"", priv_type))); return ACL_NO_RIGHTS; /* keep compiler quiet */ } /* * convert_any_priv_string: recognize privilege strings for has_foo_privilege * * We accept a comma-separated list of case-insensitive privilege names, * producing a bitmask of the OR'd privilege bits. We are liberal about * whitespace between items, not so much about whitespace within items. * The allowed privilege names are given as an array of priv_map structs, * terminated by one with a NULL name pointer. */ static AclMode convert_any_priv_string(text *priv_type_text, const priv_map *privileges) { AclMode result = 0; char *priv_type = text_to_cstring(priv_type_text); char *chunk; char *next_chunk; /* We rely on priv_type being a private, modifiable string */ for (chunk = priv_type; chunk; chunk = next_chunk) { int chunk_len; const priv_map *this_priv; /* Split string at commas */ next_chunk = strchr(chunk, ','); if (next_chunk) *next_chunk++ = '\0'; /* Drop leading/trailing whitespace in this chunk */ while (*chunk && isspace((unsigned char) *chunk)) chunk++; chunk_len = strlen(chunk); while (chunk_len > 0 && isspace((unsigned char) chunk[chunk_len - 1])) chunk_len--; chunk[chunk_len] = '\0'; /* Match to the privileges list */ for (this_priv = privileges; this_priv->name; this_priv++) { if (pg_strcasecmp(this_priv->name, chunk) == 0) { result |= this_priv->value; break; } } if (!this_priv->name) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("unrecognized privilege type: \"%s\"", chunk))); } pfree(priv_type); return result; } static const char * convert_aclright_to_string(int aclright) { switch (aclright) { case ACL_INSERT: return "INSERT"; case ACL_SELECT: return "SELECT"; case ACL_UPDATE: return "UPDATE"; case ACL_DELETE: return "DELETE"; case ACL_TRUNCATE: return "TRUNCATE"; case ACL_REFERENCES: return "REFERENCES"; case ACL_TRIGGER: return "TRIGGER"; case ACL_EXECUTE: return "EXECUTE"; case ACL_USAGE: return "USAGE"; case ACL_CREATE: return "CREATE"; case ACL_CREATE_TEMP: return "TEMPORARY"; case ACL_CONNECT: return "CONNECT"; default: elog(ERROR, "unrecognized aclright: %d", aclright); return NULL; } } /*---------- * Convert an aclitem[] to a table. * * Example: * * aclexplode('{=r/joe,foo=a*w/joe}'::aclitem[]) * * returns the table * * {{ OID(joe), 0::OID, 'SELECT', false }, * { OID(joe), OID(foo), 'INSERT', true }, * { OID(joe), OID(foo), 'UPDATE', false }} *---------- */ Datum aclexplode(PG_FUNCTION_ARGS) { Acl *acl = PG_GETARG_ACL_P(0); FuncCallContext *funcctx; int *idx; AclItem *aidat; if (SRF_IS_FIRSTCALL()) { TupleDesc tupdesc; MemoryContext oldcontext; check_acl(acl); funcctx = SRF_FIRSTCALL_INIT(); oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); /* * build tupdesc for result tuples (matches out parameters in pg_proc * entry) */ tupdesc = CreateTemplateTupleDesc(4); TupleDescInitEntry(tupdesc, (AttrNumber) 1, "grantor", OIDOID, -1, 0); TupleDescInitEntry(tupdesc, (AttrNumber) 2, "grantee", OIDOID, -1, 0); TupleDescInitEntry(tupdesc, (AttrNumber) 3, "privilege_type", TEXTOID, -1, 0); TupleDescInitEntry(tupdesc, (AttrNumber) 4, "is_grantable", BOOLOID, -1, 0); funcctx->tuple_desc = BlessTupleDesc(tupdesc); /* allocate memory for user context */ idx = (int *) palloc(sizeof(int[2])); idx[0] = 0; /* ACL array item index */ idx[1] = -1; /* privilege type counter */ funcctx->user_fctx = (void *) idx; MemoryContextSwitchTo(oldcontext); } funcctx = SRF_PERCALL_SETUP(); idx = (int *) funcctx->user_fctx; aidat = ACL_DAT(acl); /* need test here in case acl has no items */ while (idx[0] < ACL_NUM(acl)) { AclItem *aidata; AclMode priv_bit; idx[1]++; if (idx[1] == N_ACL_RIGHTS) { idx[1] = 0; idx[0]++; if (idx[0] >= ACL_NUM(acl)) /* done */ break; } aidata = &aidat[idx[0]]; priv_bit = 1 << idx[1]; if (ACLITEM_GET_PRIVS(*aidata) & priv_bit) { Datum result; Datum values[4]; bool nulls[4]; HeapTuple tuple; values[0] = ObjectIdGetDatum(aidata->ai_grantor); values[1] = ObjectIdGetDatum(aidata->ai_grantee); values[2] = CStringGetTextDatum(convert_aclright_to_string(priv_bit)); values[3] = BoolGetDatum((ACLITEM_GET_GOPTIONS(*aidata) & priv_bit) != 0); MemSet(nulls, 0, sizeof(nulls)); tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls); result = HeapTupleGetDatum(tuple); SRF_RETURN_NEXT(funcctx, result); } } SRF_RETURN_DONE(funcctx); } /* * has_table_privilege variants * These are all named "has_table_privilege" at the SQL level. * They take various combinations of relation name, relation OID, * user name, user OID, or implicit user = current_user. * * The result is a boolean value: true if user has the indicated * privilege, false if not. The variants that take a relation OID * return NULL if the OID doesn't exist (rather than failing, as * they did before Postgres 8.4). */ /* * has_table_privilege_name_name * Check user privileges on a table given * name username, text tablename, and text priv name. */ Datum has_table_privilege_name_name(PG_FUNCTION_ARGS) { Name rolename = PG_GETARG_NAME(0); text *tablename = PG_GETARG_TEXT_PP(1); text *priv_type_text = PG_GETARG_TEXT_PP(2); Oid roleid; Oid tableoid; AclMode mode; AclResult aclresult; roleid = get_role_oid_or_public(NameStr(*rolename)); tableoid = convert_table_name(tablename); mode = convert_table_priv_string(priv_type_text); aclresult = pg_class_aclcheck(tableoid, roleid, mode); PG_RETURN_BOOL(aclresult == ACLCHECK_OK); } /* * has_table_privilege_name * Check user privileges on a table given * text tablename and text priv name. * current_user is assumed */ Datum has_table_privilege_name(PG_FUNCTION_ARGS) { text *tablename = PG_GETARG_TEXT_PP(0); text *priv_type_text = PG_GETARG_TEXT_PP(1); Oid roleid; Oid tableoid; AclMode mode; AclResult aclresult; roleid = GetUserId(); tableoid = convert_table_name(tablename); mode = convert_table_priv_string(priv_type_text); aclresult = pg_class_aclcheck(tableoid, roleid, mode); PG_RETURN_BOOL(aclresult == ACLCHECK_OK); } /* * has_table_privilege_name_id * Check user privileges on a table given * name usename, table oid, and text priv name. */ Datum has_table_privilege_name_id(PG_FUNCTION_ARGS) { Name username = PG_GETARG_NAME(0); Oid tableoid = PG_GETARG_OID(1); text *priv_type_text = PG_GETARG_TEXT_PP(2); Oid roleid; AclMode mode; AclResult aclresult; roleid = get_role_oid_or_public(NameStr(*username)); mode = convert_table_priv_string(priv_type_text); if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(tableoid))) PG_RETURN_NULL(); aclresult = pg_class_aclcheck(tableoid, roleid, mode); PG_RETURN_BOOL(aclresult == ACLCHECK_OK); } /* * has_table_privilege_id * Check user privileges on a table given * table oid, and text priv name. * current_user is assumed */ Datum has_table_privilege_id(PG_FUNCTION_ARGS) { Oid tableoid = PG_GETARG_OID(0); text *priv_type_text = PG_GETARG_TEXT_PP(1); Oid roleid; AclMode mode; AclResult aclresult; roleid = GetUserId(); mode = convert_table_priv_string(priv_type_text); if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(tableoid))) PG_RETURN_NULL(); aclresult = pg_class_aclcheck(tableoid, roleid, mode); PG_RETURN_BOOL(aclresult == ACLCHECK_OK); } /* * has_table_privilege_id_name * Check user privileges on a table given * roleid, text tablename, and text priv name. */ Datum has_table_privilege_id_name(PG_FUNCTION_ARGS) { Oid roleid = PG_GETARG_OID(0); text *tablename = PG_GETARG_TEXT_PP(1); text *priv_type_text = PG_GETARG_TEXT_PP(2); Oid tableoid; AclMode mode; AclResult aclresult; tableoid = convert_table_name(tablename); mode = convert_table_priv_string(priv_type_text); aclresult = pg_class_aclcheck(tableoid, roleid, mode); PG_RETURN_BOOL(aclresult == ACLCHECK_OK); } /* * has_table_privilege_id_id * Check user privileges on a table given * roleid, table oid, and text priv name. */ Datum has_table_privilege_id_id(PG_FUNCTION_ARGS) { Oid roleid = PG_GETARG_OID(0); Oid tableoid = PG_GETARG_OID(1); text *priv_type_text = PG_GETARG_TEXT_PP(2); AclMode mode; AclResult aclresult; mode = convert_table_priv_string(priv_type_text); if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(tableoid))) PG_RETURN_NULL(); aclresult = pg_class_aclcheck(tableoid, roleid, mode); PG_RETURN_BOOL(aclresult == ACLCHECK_OK); } /* * Support routines for has_table_privilege family. */ /* * Given a table name expressed as a string, look it up and return Oid */ static Oid convert_table_name(text *tablename) { RangeVar *relrv; relrv = makeRangeVarFromNameList(textToQualifiedNameList(tablename)); /* We might not even have permissions on this relation; don't lock it. */ return RangeVarGetRelid(relrv, NoLock, false); } /* * convert_table_priv_string * Convert text string to AclMode value. */ static AclMode convert_table_priv_string(text *priv_type_text) { static const priv_map table_priv_map[] = { {"SELECT", ACL_SELECT}, {"SELECT WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_SELECT)}, {"INSERT", ACL_INSERT}, {"INSERT WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_INSERT)}, {"UPDATE", ACL_UPDATE}, {"UPDATE WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_UPDATE)}, {"DELETE", ACL_DELETE}, {"DELETE WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_DELETE)}, {"TRUNCATE", ACL_TRUNCATE}, {"TRUNCATE WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_TRUNCATE)}, {"REFERENCES", ACL_REFERENCES}, {"REFERENCES WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_REFERENCES)}, {"TRIGGER", ACL_TRIGGER}, {"TRIGGER WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_TRIGGER)}, {"RULE", 0}, /* ignore old RULE privileges */ {"RULE WITH GRANT OPTION", 0}, {NULL, 0} }; return convert_any_priv_string(priv_type_text, table_priv_map); } /* * has_sequence_privilege variants * These are all named "has_sequence_privilege" at the SQL level. * They take various combinations of relation name, relation OID, * user name, user OID, or implicit user = current_user. * * The result is a boolean value: true if user has the indicated * privilege, false if not. The variants that take a relation OID * return NULL if the OID doesn't exist. */ /* * has_sequence_privilege_name_name * Check user privileges on a sequence given * name username, text sequencename, and text priv name. */ Datum has_sequence_privilege_name_name(PG_FUNCTION_ARGS) { Name rolename = PG_GETARG_NAME(0); text *sequencename = PG_GETARG_TEXT_PP(1); text *priv_type_text = PG_GETARG_TEXT_PP(2); Oid roleid; Oid sequenceoid; AclMode mode; AclResult aclresult; roleid = get_role_oid_or_public(NameStr(*rolename)); mode = convert_sequence_priv_string(priv_type_text); sequenceoid = convert_table_name(sequencename); if (get_rel_relkind(sequenceoid) != RELKIND_SEQUENCE) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("\"%s\" is not a sequence", text_to_cstring(sequencename)))); aclresult = pg_class_aclcheck(sequenceoid, roleid, mode); PG_RETURN_BOOL(aclresult == ACLCHECK_OK); } /* * has_sequence_privilege_name * Check user privileges on a sequence given * text sequencename and text priv name. * current_user is assumed */ Datum has_sequence_privilege_name(PG_FUNCTION_ARGS) { text *sequencename = PG_GETARG_TEXT_PP(0); text *priv_type_text = PG_GETARG_TEXT_PP(1); Oid roleid; Oid sequenceoid; AclMode mode; AclResult aclresult; roleid = GetUserId(); mode = convert_sequence_priv_string(priv_type_text); sequenceoid = convert_table_name(sequencename); if (get_rel_relkind(sequenceoid) != RELKIND_SEQUENCE) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("\"%s\" is not a sequence", text_to_cstring(sequencename)))); aclresult = pg_class_aclcheck(sequenceoid, roleid, mode); PG_RETURN_BOOL(aclresult == ACLCHECK_OK); } /* * has_sequence_privilege_name_id * Check user privileges on a sequence given * name usename, sequence oid, and text priv name. */ Datum has_sequence_privilege_name_id(PG_FUNCTION_ARGS) { Name username = PG_GETARG_NAME(0); Oid sequenceoid = PG_GETARG_OID(1); text *priv_type_text = PG_GETARG_TEXT_PP(2); Oid roleid; AclMode mode; AclResult aclresult; char relkind; roleid = get_role_oid_or_public(NameStr(*username)); mode = convert_sequence_priv_string(priv_type_text); relkind = get_rel_relkind(sequenceoid); if (relkind == '\0') PG_RETURN_NULL(); else if (relkind != RELKIND_SEQUENCE) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("\"%s\" is not a sequence", get_rel_name(sequenceoid)))); aclresult = pg_class_aclcheck(sequenceoid, roleid, mode); PG_RETURN_BOOL(aclresult == ACLCHECK_OK); } /* * has_sequence_privilege_id * Check user privileges on a sequence given * sequence oid, and text priv name. * current_user is assumed */ Datum has_sequence_privilege_id(PG_FUNCTION_ARGS) { Oid sequenceoid = PG_GETARG_OID(0); text *priv_type_text = PG_GETARG_TEXT_PP(1); Oid roleid; AclMode mode; AclResult aclresult; char relkind; roleid = GetUserId(); mode = convert_sequence_priv_string(priv_type_text); relkind = get_rel_relkind(sequenceoid); if (relkind == '\0') PG_RETURN_NULL(); else if (relkind != RELKIND_SEQUENCE) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("\"%s\" is not a sequence", get_rel_name(sequenceoid)))); aclresult = pg_class_aclcheck(sequenceoid, roleid, mode); PG_RETURN_BOOL(aclresult == ACLCHECK_OK); } /* * has_sequence_privilege_id_name * Check user privileges on a sequence given * roleid, text sequencename, and text priv name. */ Datum has_sequence_privilege_id_name(PG_FUNCTION_ARGS) { Oid roleid = PG_GETARG_OID(0); text *sequencename = PG_GETARG_TEXT_PP(1); text *priv_type_text = PG_GETARG_TEXT_PP(2); Oid sequenceoid; AclMode mode; AclResult aclresult; mode = convert_sequence_priv_string(priv_type_text); sequenceoid = convert_table_name(sequencename); if (get_rel_relkind(sequenceoid) != RELKIND_SEQUENCE) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("\"%s\" is not a sequence", text_to_cstring(sequencename)))); aclresult = pg_class_aclcheck(sequenceoid, roleid, mode); PG_RETURN_BOOL(aclresult == ACLCHECK_OK); } /* * has_sequence_privilege_id_id * Check user privileges on a sequence given * roleid, sequence oid, and text priv name. */ Datum has_sequence_privilege_id_id(PG_FUNCTION_ARGS) { Oid roleid = PG_GETARG_OID(0); Oid sequenceoid = PG_GETARG_OID(1); text *priv_type_text = PG_GETARG_TEXT_PP(2); AclMode mode; AclResult aclresult; char relkind; mode = convert_sequence_priv_string(priv_type_text); relkind = get_rel_relkind(sequenceoid); if (relkind == '\0') PG_RETURN_NULL(); else if (relkind != RELKIND_SEQUENCE) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("\"%s\" is not a sequence", get_rel_name(sequenceoid)))); aclresult = pg_class_aclcheck(sequenceoid, roleid, mode); PG_RETURN_BOOL(aclresult == ACLCHECK_OK); } /* * convert_sequence_priv_string * Convert text string to AclMode value. */ static AclMode convert_sequence_priv_string(text *priv_type_text) { static const priv_map sequence_priv_map[] = { {"USAGE", ACL_USAGE}, {"USAGE WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_USAGE)}, {"SELECT", ACL_SELECT}, {"SELECT WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_SELECT)}, {"UPDATE", ACL_UPDATE}, {"UPDATE WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_UPDATE)}, {NULL, 0} }; return convert_any_priv_string(priv_type_text, sequence_priv_map); } /* * has_any_column_privilege variants * These are all named "has_any_column_privilege" at the SQL level. * They take various combinations of relation name, relation OID, * user name, user OID, or implicit user = current_user. * * The result is a boolean value: true if user has the indicated * privilege for any column of the table, false if not. The variants * that take a relation OID return NULL if the OID doesn't exist. */ /* * has_any_column_privilege_name_name * Check user privileges on any column of a table given * name username, text tablename, and text priv name. */ Datum has_any_column_privilege_name_name(PG_FUNCTION_ARGS) { Name rolename = PG_GETARG_NAME(0); text *tablename = PG_GETARG_TEXT_PP(1); text *priv_type_text = PG_GETARG_TEXT_PP(2); Oid roleid; Oid tableoid; AclMode mode; AclResult aclresult; roleid = get_role_oid_or_public(NameStr(*rolename)); tableoid = convert_table_name(tablename); mode = convert_column_priv_string(priv_type_text); /* First check at table level, then examine each column if needed */ aclresult = pg_class_aclcheck(tableoid, roleid, mode); if (aclresult != ACLCHECK_OK) aclresult = pg_attribute_aclcheck_all(tableoid, roleid, mode, ACLMASK_ANY); PG_RETURN_BOOL(aclresult == ACLCHECK_OK); } /* * has_any_column_privilege_name * Check user privileges on any column of a table given * text tablename and text priv name. * current_user is assumed */ Datum has_any_column_privilege_name(PG_FUNCTION_ARGS) { text *tablename = PG_GETARG_TEXT_PP(0); text *priv_type_text = PG_GETARG_TEXT_PP(1); Oid roleid; Oid tableoid; AclMode mode; AclResult aclresult; roleid = GetUserId(); tableoid = convert_table_name(tablename); mode = convert_column_priv_string(priv_type_text); /* First check at table level, then examine each column if needed */ aclresult = pg_class_aclcheck(tableoid, roleid, mode); if (aclresult != ACLCHECK_OK) aclresult = pg_attribute_aclcheck_all(tableoid, roleid, mode, ACLMASK_ANY); PG_RETURN_BOOL(aclresult == ACLCHECK_OK); } /* * has_any_column_privilege_name_id * Check user privileges on any column of a table given * name usename, table oid, and text priv name. */ Datum has_any_column_privilege_name_id(PG_FUNCTION_ARGS) { Name username = PG_GETARG_NAME(0); Oid tableoid = PG_GETARG_OID(1); text *priv_type_text = PG_GETARG_TEXT_PP(2); Oid roleid; AclMode mode; AclResult aclresult; roleid = get_role_oid_or_public(NameStr(*username)); mode = convert_column_priv_string(priv_type_text); if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(tableoid))) PG_RETURN_NULL(); /* First check at table level, then examine each column if needed */ aclresult = pg_class_aclcheck(tableoid, roleid, mode); if (aclresult != ACLCHECK_OK) aclresult = pg_attribute_aclcheck_all(tableoid, roleid, mode, ACLMASK_ANY); PG_RETURN_BOOL(aclresult == ACLCHECK_OK); } /* * has_any_column_privilege_id * Check user privileges on any column of a table given * table oid, and text priv name. * current_user is assumed */ Datum has_any_column_privilege_id(PG_FUNCTION_ARGS) { Oid tableoid = PG_GETARG_OID(0); text *priv_type_text = PG_GETARG_TEXT_PP(1); Oid roleid; AclMode mode; AclResult aclresult; roleid = GetUserId(); mode = convert_column_priv_string(priv_type_text); if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(tableoid))) PG_RETURN_NULL(); /* First check at table level, then examine each column if needed */ aclresult = pg_class_aclcheck(tableoid, roleid, mode); if (aclresult != ACLCHECK_OK) aclresult = pg_attribute_aclcheck_all(tableoid, roleid, mode, ACLMASK_ANY); PG_RETURN_BOOL(aclresult == ACLCHECK_OK); } /* * has_any_column_privilege_id_name * Check user privileges on any column of a table given * roleid, text tablename, and text priv name. */ Datum has_any_column_privilege_id_name(PG_FUNCTION_ARGS) { Oid roleid = PG_GETARG_OID(0); text *tablename = PG_GETARG_TEXT_PP(1); text *priv_type_text = PG_GETARG_TEXT_PP(2); Oid tableoid; AclMode mode; AclResult aclresult; tableoid = convert_table_name(tablename); mode = convert_column_priv_string(priv_type_text); /* First check at table level, then examine each column if needed */ aclresult = pg_class_aclcheck(tableoid, roleid, mode); if (aclresult != ACLCHECK_OK) aclresult = pg_attribute_aclcheck_all(tableoid, roleid, mode, ACLMASK_ANY); PG_RETURN_BOOL(aclresult == ACLCHECK_OK); } /* * has_any_column_privilege_id_id * Check user privileges on any column of a table given * roleid, table oid, and text priv name. */ Datum has_any_column_privilege_id_id(PG_FUNCTION_ARGS) { Oid roleid = PG_GETARG_OID(0); Oid tableoid = PG_GETARG_OID(1); text *priv_type_text = PG_GETARG_TEXT_PP(2); AclMode mode; AclResult aclresult; mode = convert_column_priv_string(priv_type_text); if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(tableoid))) PG_RETURN_NULL(); /* First check at table level, then examine each column if needed */ aclresult = pg_class_aclcheck(tableoid, roleid, mode); if (aclresult != ACLCHECK_OK) aclresult = pg_attribute_aclcheck_all(tableoid, roleid, mode, ACLMASK_ANY); PG_RETURN_BOOL(aclresult == ACLCHECK_OK); } /* * has_column_privilege variants * These are all named "has_column_privilege" at the SQL level. * They take various combinations of relation name, relation OID, * column name, column attnum, user name, user OID, or * implicit user = current_user. * * The result is a boolean value: true if user has the indicated * privilege, false if not. The variants that take a relation OID * return NULL (rather than throwing an error) if that relation OID * doesn't exist. Likewise, the variants that take an integer attnum * return NULL (rather than throwing an error) if there is no such * pg_attribute entry. All variants return NULL if an attisdropped * column is selected. These rules are meant to avoid unnecessary * failures in queries that scan pg_attribute. */ /* * column_privilege_check: check column privileges, but don't throw an error * for dropped column or table * * Returns 1 if have the privilege, 0 if not, -1 if dropped column/table. */ static int column_privilege_check(Oid tableoid, AttrNumber attnum, Oid roleid, AclMode mode) { AclResult aclresult; bool is_missing = false; /* * If convert_column_name failed, we can just return -1 immediately. */ if (attnum == InvalidAttrNumber) return -1; /* * Check for column-level privileges first. This serves in part as a check * on whether the column even exists, so we need to do it before checking * table-level privilege. */ aclresult = pg_attribute_aclcheck_ext(tableoid, attnum, roleid, mode, &is_missing); if (aclresult == ACLCHECK_OK) return 1; else if (is_missing) return -1; /* Next check if we have the privilege at the table level */ aclresult = pg_class_aclcheck_ext(tableoid, roleid, mode, &is_missing); if (aclresult == ACLCHECK_OK) return 1; else if (is_missing) return -1; else return 0; } /* * has_column_privilege_name_name_name * Check user privileges on a column given * name username, text tablename, text colname, and text priv name. */ Datum has_column_privilege_name_name_name(PG_FUNCTION_ARGS) { Name rolename = PG_GETARG_NAME(0); text *tablename = PG_GETARG_TEXT_PP(1); text *column = PG_GETARG_TEXT_PP(2); text *priv_type_text = PG_GETARG_TEXT_PP(3); Oid roleid; Oid tableoid; AttrNumber colattnum; AclMode mode; int privresult; roleid = get_role_oid_or_public(NameStr(*rolename)); tableoid = convert_table_name(tablename); colattnum = convert_column_name(tableoid, column); mode = convert_column_priv_string(priv_type_text); privresult = column_privilege_check(tableoid, colattnum, roleid, mode); if (privresult < 0) PG_RETURN_NULL(); PG_RETURN_BOOL(privresult); } /* * has_column_privilege_name_name_attnum * Check user privileges on a column given * name username, text tablename, int attnum, and text priv name. */ Datum has_column_privilege_name_name_attnum(PG_FUNCTION_ARGS) { Name rolename = PG_GETARG_NAME(0); text *tablename = PG_GETARG_TEXT_PP(1); AttrNumber colattnum = PG_GETARG_INT16(2); text *priv_type_text = PG_GETARG_TEXT_PP(3); Oid roleid; Oid tableoid; AclMode mode; int privresult; roleid = get_role_oid_or_public(NameStr(*rolename)); tableoid = convert_table_name(tablename); mode = convert_column_priv_string(priv_type_text); privresult = column_privilege_check(tableoid, colattnum, roleid, mode); if (privresult < 0) PG_RETURN_NULL(); PG_RETURN_BOOL(privresult); } /* * has_column_privilege_name_id_name * Check user privileges on a column given * name username, table oid, text colname, and text priv name. */ Datum has_column_privilege_name_id_name(PG_FUNCTION_ARGS) { Name username = PG_GETARG_NAME(0); Oid tableoid = PG_GETARG_OID(1); text *column = PG_GETARG_TEXT_PP(2); text *priv_type_text = PG_GETARG_TEXT_PP(3); Oid roleid; AttrNumber colattnum; AclMode mode; int privresult; roleid = get_role_oid_or_public(NameStr(*username)); colattnum = convert_column_name(tableoid, column); mode = convert_column_priv_string(priv_type_text); privresult = column_privilege_check(tableoid, colattnum, roleid, mode); if (privresult < 0) PG_RETURN_NULL(); PG_RETURN_BOOL(privresult); } /* * has_column_privilege_name_id_attnum * Check user privileges on a column given * name username, table oid, int attnum, and text priv name. */ Datum has_column_privilege_name_id_attnum(PG_FUNCTION_ARGS) { Name username = PG_GETARG_NAME(0); Oid tableoid = PG_GETARG_OID(1); AttrNumber colattnum = PG_GETARG_INT16(2); text *priv_type_text = PG_GETARG_TEXT_PP(3); Oid roleid; AclMode mode; int privresult; roleid = get_role_oid_or_public(NameStr(*username)); mode = convert_column_priv_string(priv_type_text); privresult = column_privilege_check(tableoid, colattnum, roleid, mode); if (privresult < 0) PG_RETURN_NULL(); PG_RETURN_BOOL(privresult); } /* * has_column_privilege_id_name_name * Check user privileges on a column given * oid roleid, text tablename, text colname, and text priv name. */ Datum has_column_privilege_id_name_name(PG_FUNCTION_ARGS) { Oid roleid = PG_GETARG_OID(0); text *tablename = PG_GETARG_TEXT_PP(1); text *column = PG_GETARG_TEXT_PP(2); text *priv_type_text = PG_GETARG_TEXT_PP(3); Oid tableoid; AttrNumber colattnum; AclMode mode; int privresult; tableoid = convert_table_name(tablename); colattnum = convert_column_name(tableoid, column); mode = convert_column_priv_string(priv_type_text); privresult = column_privilege_check(tableoid, colattnum, roleid, mode); if (privresult < 0) PG_RETURN_NULL(); PG_RETURN_BOOL(privresult); } /* * has_column_privilege_id_name_attnum * Check user privileges on a column given * oid roleid, text tablename, int attnum, and text priv name. */ Datum has_column_privilege_id_name_attnum(PG_FUNCTION_ARGS) { Oid roleid = PG_GETARG_OID(0); text *tablename = PG_GETARG_TEXT_PP(1); AttrNumber colattnum = PG_GETARG_INT16(2); text *priv_type_text = PG_GETARG_TEXT_PP(3); Oid tableoid; AclMode mode; int privresult; tableoid = convert_table_name(tablename); mode = convert_column_priv_string(priv_type_text); privresult = column_privilege_check(tableoid, colattnum, roleid, mode); if (privresult < 0) PG_RETURN_NULL(); PG_RETURN_BOOL(privresult); } /* * has_column_privilege_id_id_name * Check user privileges on a column given * oid roleid, table oid, text colname, and text priv name. */ Datum has_column_privilege_id_id_name(PG_FUNCTION_ARGS) { Oid roleid = PG_GETARG_OID(0); Oid tableoid = PG_GETARG_OID(1); text *column = PG_GETARG_TEXT_PP(2); text *priv_type_text = PG_GETARG_TEXT_PP(3); AttrNumber colattnum; AclMode mode; int privresult; colattnum = convert_column_name(tableoid, column); mode = convert_column_priv_string(priv_type_text); privresult = column_privilege_check(tableoid, colattnum, roleid, mode); if (privresult < 0) PG_RETURN_NULL(); PG_RETURN_BOOL(privresult); } /* * has_column_privilege_id_id_attnum * Check user privileges on a column given * oid roleid, table oid, int attnum, and text priv name. */ Datum has_column_privilege_id_id_attnum(PG_FUNCTION_ARGS) { Oid roleid = PG_GETARG_OID(0); Oid tableoid = PG_GETARG_OID(1); AttrNumber colattnum = PG_GETARG_INT16(2); text *priv_type_text = PG_GETARG_TEXT_PP(3); AclMode mode; int privresult; mode = convert_column_priv_string(priv_type_text); privresult = column_privilege_check(tableoid, colattnum, roleid, mode); if (privresult < 0) PG_RETURN_NULL(); PG_RETURN_BOOL(privresult); } /* * has_column_privilege_name_name * Check user privileges on a column given * text tablename, text colname, and text priv name. * current_user is assumed */ Datum has_column_privilege_name_name(PG_FUNCTION_ARGS) { text *tablename = PG_GETARG_TEXT_PP(0); text *column = PG_GETARG_TEXT_PP(1); text *priv_type_text = PG_GETARG_TEXT_PP(2); Oid roleid; Oid tableoid; AttrNumber colattnum; AclMode mode; int privresult; roleid = GetUserId(); tableoid = convert_table_name(tablename); colattnum = convert_column_name(tableoid, column); mode = convert_column_priv_string(priv_type_text); privresult = column_privilege_check(tableoid, colattnum, roleid, mode); if (privresult < 0) PG_RETURN_NULL(); PG_RETURN_BOOL(privresult); } /* * has_column_privilege_name_attnum * Check user privileges on a column given * text tablename, int attnum, and text priv name. * current_user is assumed */ Datum has_column_privilege_name_attnum(PG_FUNCTION_ARGS) { text *tablename = PG_GETARG_TEXT_PP(0); AttrNumber colattnum = PG_GETARG_INT16(1); text *priv_type_text = PG_GETARG_TEXT_PP(2); Oid roleid; Oid tableoid; AclMode mode; int privresult; roleid = GetUserId(); tableoid = convert_table_name(tablename); mode = convert_column_priv_string(priv_type_text); privresult = column_privilege_check(tableoid, colattnum, roleid, mode); if (privresult < 0) PG_RETURN_NULL(); PG_RETURN_BOOL(privresult); } /* * has_column_privilege_id_name * Check user privileges on a column given * table oid, text colname, and text priv name. * current_user is assumed */ Datum has_column_privilege_id_name(PG_FUNCTION_ARGS) { Oid tableoid = PG_GETARG_OID(0); text *column = PG_GETARG_TEXT_PP(1); text *priv_type_text = PG_GETARG_TEXT_PP(2); Oid roleid; AttrNumber colattnum; AclMode mode; int privresult; roleid = GetUserId(); colattnum = convert_column_name(tableoid, column); mode = convert_column_priv_string(priv_type_text); privresult = column_privilege_check(tableoid, colattnum, roleid, mode); if (privresult < 0) PG_RETURN_NULL(); PG_RETURN_BOOL(privresult); } /* * has_column_privilege_id_attnum * Check user privileges on a column given * table oid, int attnum, and text priv name. * current_user is assumed */ Datum has_column_privilege_id_attnum(PG_FUNCTION_ARGS) { Oid tableoid = PG_GETARG_OID(0); AttrNumber colattnum = PG_GETARG_INT16(1); text *priv_type_text = PG_GETARG_TEXT_PP(2); Oid roleid; AclMode mode; int privresult; roleid = GetUserId(); mode = convert_column_priv_string(priv_type_text); privresult = column_privilege_check(tableoid, colattnum, roleid, mode); if (privresult < 0) PG_RETURN_NULL(); PG_RETURN_BOOL(privresult); } /* * Support routines for has_column_privilege family. */ /* * Given a table OID and a column name expressed as a string, look it up * and return the column number. Returns InvalidAttrNumber in cases * where caller should return NULL instead of failing. */ static AttrNumber convert_column_name(Oid tableoid, text *column) { char *colname; HeapTuple attTuple; AttrNumber attnum; colname = text_to_cstring(column); /* * We don't use get_attnum() here because it will report that dropped * columns don't exist. We need to treat dropped columns differently from * nonexistent columns. */ attTuple = SearchSysCache2(ATTNAME, ObjectIdGetDatum(tableoid), CStringGetDatum(colname)); if (HeapTupleIsValid(attTuple)) { Form_pg_attribute attributeForm; attributeForm = (Form_pg_attribute) GETSTRUCT(attTuple); /* We want to return NULL for dropped columns */ if (attributeForm->attisdropped) attnum = InvalidAttrNumber; else attnum = attributeForm->attnum; ReleaseSysCache(attTuple); } else { char *tablename = get_rel_name(tableoid); /* * If the table OID is bogus, or it's just been dropped, we'll get * NULL back. In such cases we want has_column_privilege to return * NULL too, so just return InvalidAttrNumber. */ if (tablename != NULL) { /* tableoid exists, colname does not, so throw error */ ereport(ERROR, (errcode(ERRCODE_UNDEFINED_COLUMN), errmsg("column \"%s\" of relation \"%s\" does not exist", colname, tablename))); } /* tableoid doesn't exist, so act like attisdropped case */ attnum = InvalidAttrNumber; } pfree(colname); return attnum; } /* * convert_column_priv_string * Convert text string to AclMode value. */ static AclMode convert_column_priv_string(text *priv_type_text) { static const priv_map column_priv_map[] = { {"SELECT", ACL_SELECT}, {"SELECT WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_SELECT)}, {"INSERT", ACL_INSERT}, {"INSERT WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_INSERT)}, {"UPDATE", ACL_UPDATE}, {"UPDATE WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_UPDATE)}, {"REFERENCES", ACL_REFERENCES}, {"REFERENCES WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_REFERENCES)}, {NULL, 0} }; return convert_any_priv_string(priv_type_text, column_priv_map); } /* * has_database_privilege variants * These are all named "has_database_privilege" at the SQL level. * They take various combinations of database name, database OID, * user name, user OID, or implicit user = current_user. * * The result is a boolean value: true if user has the indicated * privilege, false if not, or NULL if object doesn't exist. */ /* * has_database_privilege_name_name * Check user privileges on a database given * name username, text databasename, and text priv name. */ Datum has_database_privilege_name_name(PG_FUNCTION_ARGS) { Name username = PG_GETARG_NAME(0); text *databasename = PG_GETARG_TEXT_PP(1); text *priv_type_text = PG_GETARG_TEXT_PP(2); Oid roleid; Oid databaseoid; AclMode mode; AclResult aclresult; roleid = get_role_oid_or_public(NameStr(*username)); databaseoid = convert_database_name(databasename); mode = convert_database_priv_string(priv_type_text); aclresult = pg_database_aclcheck(databaseoid, roleid, mode); PG_RETURN_BOOL(aclresult == ACLCHECK_OK); } /* * has_database_privilege_name * Check user privileges on a database given * text databasename and text priv name. * current_user is assumed */ Datum has_database_privilege_name(PG_FUNCTION_ARGS) { text *databasename = PG_GETARG_TEXT_PP(0); text *priv_type_text = PG_GETARG_TEXT_PP(1); Oid roleid; Oid databaseoid; AclMode mode; AclResult aclresult; roleid = GetUserId(); databaseoid = convert_database_name(databasename); mode = convert_database_priv_string(priv_type_text); aclresult = pg_database_aclcheck(databaseoid, roleid, mode); PG_RETURN_BOOL(aclresult == ACLCHECK_OK); } /* * has_database_privilege_name_id * Check user privileges on a database given * name usename, database oid, and text priv name. */ Datum has_database_privilege_name_id(PG_FUNCTION_ARGS) { Name username = PG_GETARG_NAME(0); Oid databaseoid = PG_GETARG_OID(1); text *priv_type_text = PG_GETARG_TEXT_PP(2); Oid roleid; AclMode mode; AclResult aclresult; roleid = get_role_oid_or_public(NameStr(*username)); mode = convert_database_priv_string(priv_type_text); if (!SearchSysCacheExists1(DATABASEOID, ObjectIdGetDatum(databaseoid))) PG_RETURN_NULL(); aclresult = pg_database_aclcheck(databaseoid, roleid, mode); PG_RETURN_BOOL(aclresult == ACLCHECK_OK); } /* * has_database_privilege_id * Check user privileges on a database given * database oid, and text priv name. * current_user is assumed */ Datum has_database_privilege_id(PG_FUNCTION_ARGS) { Oid databaseoid = PG_GETARG_OID(0); text *priv_type_text = PG_GETARG_TEXT_PP(1); Oid roleid; AclMode mode; AclResult aclresult; roleid = GetUserId(); mode = convert_database_priv_string(priv_type_text); if (!SearchSysCacheExists1(DATABASEOID, ObjectIdGetDatum(databaseoid))) PG_RETURN_NULL(); aclresult = pg_database_aclcheck(databaseoid, roleid, mode); PG_RETURN_BOOL(aclresult == ACLCHECK_OK); } /* * has_database_privilege_id_name * Check user privileges on a database given * roleid, text databasename, and text priv name. */ Datum has_database_privilege_id_name(PG_FUNCTION_ARGS) { Oid roleid = PG_GETARG_OID(0); text *databasename = PG_GETARG_TEXT_PP(1); text *priv_type_text = PG_GETARG_TEXT_PP(2); Oid databaseoid; AclMode mode; AclResult aclresult; databaseoid = convert_database_name(databasename); mode = convert_database_priv_string(priv_type_text); aclresult = pg_database_aclcheck(databaseoid, roleid, mode); PG_RETURN_BOOL(aclresult == ACLCHECK_OK); } /* * has_database_privilege_id_id * Check user privileges on a database given * roleid, database oid, and text priv name. */ Datum has_database_privilege_id_id(PG_FUNCTION_ARGS) { Oid roleid = PG_GETARG_OID(0); Oid databaseoid = PG_GETARG_OID(1); text *priv_type_text = PG_GETARG_TEXT_PP(2); AclMode mode; AclResult aclresult; mode = convert_database_priv_string(priv_type_text); if (!SearchSysCacheExists1(DATABASEOID, ObjectIdGetDatum(databaseoid))) PG_RETURN_NULL(); aclresult = pg_database_aclcheck(databaseoid, roleid, mode); PG_RETURN_BOOL(aclresult == ACLCHECK_OK); } /* * Support routines for has_database_privilege family. */ /* * Given a database name expressed as a string, look it up and return Oid */ static Oid convert_database_name(text *databasename) { char *dbname = text_to_cstring(databasename); return get_database_oid(dbname, false); } /* * convert_database_priv_string * Convert text string to AclMode value. */ static AclMode convert_database_priv_string(text *priv_type_text) { static const priv_map database_priv_map[] = { {"CREATE", ACL_CREATE}, {"CREATE WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_CREATE)}, {"TEMPORARY", ACL_CREATE_TEMP}, {"TEMPORARY WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_CREATE_TEMP)}, {"TEMP", ACL_CREATE_TEMP}, {"TEMP WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_CREATE_TEMP)}, {"CONNECT", ACL_CONNECT}, {"CONNECT WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_CONNECT)}, {NULL, 0} }; return convert_any_priv_string(priv_type_text, database_priv_map); } /* * has_foreign_data_wrapper_privilege variants * These are all named "has_foreign_data_wrapper_privilege" at the SQL level. * They take various combinations of foreign-data wrapper name, * fdw OID, user name, user OID, or implicit user = current_user. * * The result is a boolean value: true if user has the indicated * privilege, false if not. */ /* * has_foreign_data_wrapper_privilege_name_name * Check user privileges on a foreign-data wrapper given * name username, text fdwname, and text priv name. */ Datum has_foreign_data_wrapper_privilege_name_name(PG_FUNCTION_ARGS) { Name username = PG_GETARG_NAME(0); text *fdwname = PG_GETARG_TEXT_PP(1); text *priv_type_text = PG_GETARG_TEXT_PP(2); Oid roleid; Oid fdwid; AclMode mode; AclResult aclresult; roleid = get_role_oid_or_public(NameStr(*username)); fdwid = convert_foreign_data_wrapper_name(fdwname); mode = convert_foreign_data_wrapper_priv_string(priv_type_text); aclresult = pg_foreign_data_wrapper_aclcheck(fdwid, roleid, mode); PG_RETURN_BOOL(aclresult == ACLCHECK_OK); } /* * has_foreign_data_wrapper_privilege_name * Check user privileges on a foreign-data wrapper given * text fdwname and text priv name. * current_user is assumed */ Datum has_foreign_data_wrapper_privilege_name(PG_FUNCTION_ARGS) { text *fdwname = PG_GETARG_TEXT_PP(0); text *priv_type_text = PG_GETARG_TEXT_PP(1); Oid roleid; Oid fdwid; AclMode mode; AclResult aclresult; roleid = GetUserId(); fdwid = convert_foreign_data_wrapper_name(fdwname); mode = convert_foreign_data_wrapper_priv_string(priv_type_text); aclresult = pg_foreign_data_wrapper_aclcheck(fdwid, roleid, mode); PG_RETURN_BOOL(aclresult == ACLCHECK_OK); } /* * has_foreign_data_wrapper_privilege_name_id * Check user privileges on a foreign-data wrapper given * name usename, foreign-data wrapper oid, and text priv name. */ Datum has_foreign_data_wrapper_privilege_name_id(PG_FUNCTION_ARGS) { Name username = PG_GETARG_NAME(0); Oid fdwid = PG_GETARG_OID(1); text *priv_type_text = PG_GETARG_TEXT_PP(2); Oid roleid; AclMode mode; AclResult aclresult; roleid = get_role_oid_or_public(NameStr(*username)); mode = convert_foreign_data_wrapper_priv_string(priv_type_text); if (!SearchSysCacheExists1(FOREIGNDATAWRAPPEROID, ObjectIdGetDatum(fdwid))) PG_RETURN_NULL(); aclresult = pg_foreign_data_wrapper_aclcheck(fdwid, roleid, mode); PG_RETURN_BOOL(aclresult == ACLCHECK_OK); } /* * has_foreign_data_wrapper_privilege_id * Check user privileges on a foreign-data wrapper given * foreign-data wrapper oid, and text priv name. * current_user is assumed */ Datum has_foreign_data_wrapper_privilege_id(PG_FUNCTION_ARGS) { Oid fdwid = PG_GETARG_OID(0); text *priv_type_text = PG_GETARG_TEXT_PP(1); Oid roleid; AclMode mode; AclResult aclresult; roleid = GetUserId(); mode = convert_foreign_data_wrapper_priv_string(priv_type_text); if (!SearchSysCacheExists1(FOREIGNDATAWRAPPEROID, ObjectIdGetDatum(fdwid))) PG_RETURN_NULL(); aclresult = pg_foreign_data_wrapper_aclcheck(fdwid, roleid, mode); PG_RETURN_BOOL(aclresult == ACLCHECK_OK); } /* * has_foreign_data_wrapper_privilege_id_name * Check user privileges on a foreign-data wrapper given * roleid, text fdwname, and text priv name. */ Datum has_foreign_data_wrapper_privilege_id_name(PG_FUNCTION_ARGS) { Oid roleid = PG_GETARG_OID(0); text *fdwname = PG_GETARG_TEXT_PP(1); text *priv_type_text = PG_GETARG_TEXT_PP(2); Oid fdwid; AclMode mode; AclResult aclresult; fdwid = convert_foreign_data_wrapper_name(fdwname); mode = convert_foreign_data_wrapper_priv_string(priv_type_text); aclresult = pg_foreign_data_wrapper_aclcheck(fdwid, roleid, mode); PG_RETURN_BOOL(aclresult == ACLCHECK_OK); } /* * has_foreign_data_wrapper_privilege_id_id * Check user privileges on a foreign-data wrapper given * roleid, fdw oid, and text priv name. */ Datum has_foreign_data_wrapper_privilege_id_id(PG_FUNCTION_ARGS) { Oid roleid = PG_GETARG_OID(0); Oid fdwid = PG_GETARG_OID(1); text *priv_type_text = PG_GETARG_TEXT_PP(2); AclMode mode; AclResult aclresult; mode = convert_foreign_data_wrapper_priv_string(priv_type_text); if (!SearchSysCacheExists1(FOREIGNDATAWRAPPEROID, ObjectIdGetDatum(fdwid))) PG_RETURN_NULL(); aclresult = pg_foreign_data_wrapper_aclcheck(fdwid, roleid, mode); PG_RETURN_BOOL(aclresult == ACLCHECK_OK); } /* * Support routines for has_foreign_data_wrapper_privilege family. */ /* * Given a FDW name expressed as a string, look it up and return Oid */ static Oid convert_foreign_data_wrapper_name(text *fdwname) { char *fdwstr = text_to_cstring(fdwname); return get_foreign_data_wrapper_oid(fdwstr, false); } /* * convert_foreign_data_wrapper_priv_string * Convert text string to AclMode value. */ static AclMode convert_foreign_data_wrapper_priv_string(text *priv_type_text) { static const priv_map foreign_data_wrapper_priv_map[] = { {"USAGE", ACL_USAGE}, {"USAGE WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_USAGE)}, {NULL, 0} }; return convert_any_priv_string(priv_type_text, foreign_data_wrapper_priv_map); } /* * has_function_privilege variants * These are all named "has_function_privilege" at the SQL level. * They take various combinations of function name, function OID, * user name, user OID, or implicit user = current_user. * * The result is a boolean value: true if user has the indicated * privilege, false if not, or NULL if object doesn't exist. */ /* * has_function_privilege_name_name * Check user privileges on a function given * name username, text functionname, and text priv name. */ Datum has_function_privilege_name_name(PG_FUNCTION_ARGS) { Name username = PG_GETARG_NAME(0); text *functionname = PG_GETARG_TEXT_PP(1); text *priv_type_text = PG_GETARG_TEXT_PP(2); Oid roleid; Oid functionoid; AclMode mode; AclResult aclresult; roleid = get_role_oid_or_public(NameStr(*username)); functionoid = convert_function_name(functionname); mode = convert_function_priv_string(priv_type_text); aclresult = pg_proc_aclcheck(functionoid, roleid, mode); PG_RETURN_BOOL(aclresult == ACLCHECK_OK); } /* * has_function_privilege_name * Check user privileges on a function given * text functionname and text priv name. * current_user is assumed */ Datum has_function_privilege_name(PG_FUNCTION_ARGS) { text *functionname = PG_GETARG_TEXT_PP(0); text *priv_type_text = PG_GETARG_TEXT_PP(1); Oid roleid; Oid functionoid; AclMode mode; AclResult aclresult; roleid = GetUserId(); functionoid = convert_function_name(functionname); mode = convert_function_priv_string(priv_type_text); aclresult = pg_proc_aclcheck(functionoid, roleid, mode); PG_RETURN_BOOL(aclresult == ACLCHECK_OK); } /* * has_function_privilege_name_id * Check user privileges on a function given * name usename, function oid, and text priv name. */ Datum has_function_privilege_name_id(PG_FUNCTION_ARGS) { Name username = PG_GETARG_NAME(0); Oid functionoid = PG_GETARG_OID(1); text *priv_type_text = PG_GETARG_TEXT_PP(2); Oid roleid; AclMode mode; AclResult aclresult; roleid = get_role_oid_or_public(NameStr(*username)); mode = convert_function_priv_string(priv_type_text); if (!SearchSysCacheExists1(PROCOID, ObjectIdGetDatum(functionoid))) PG_RETURN_NULL(); aclresult = pg_proc_aclcheck(functionoid, roleid, mode); PG_RETURN_BOOL(aclresult == ACLCHECK_OK); } /* * has_function_privilege_id * Check user privileges on a function given * function oid, and text priv name. * current_user is assumed */ Datum has_function_privilege_id(PG_FUNCTION_ARGS) { Oid functionoid = PG_GETARG_OID(0); text *priv_type_text = PG_GETARG_TEXT_PP(1); Oid roleid; AclMode mode; AclResult aclresult; roleid = GetUserId(); mode = convert_function_priv_string(priv_type_text); if (!SearchSysCacheExists1(PROCOID, ObjectIdGetDatum(functionoid))) PG_RETURN_NULL(); aclresult = pg_proc_aclcheck(functionoid, roleid, mode); PG_RETURN_BOOL(aclresult == ACLCHECK_OK); } /* * has_function_privilege_id_name * Check user privileges on a function given * roleid, text functionname, and text priv name. */ Datum has_function_privilege_id_name(PG_FUNCTION_ARGS) { Oid roleid = PG_GETARG_OID(0); text *functionname = PG_GETARG_TEXT_PP(1); text *priv_type_text = PG_GETARG_TEXT_PP(2); Oid functionoid; AclMode mode; AclResult aclresult; functionoid = convert_function_name(functionname); mode = convert_function_priv_string(priv_type_text); aclresult = pg_proc_aclcheck(functionoid, roleid, mode); PG_RETURN_BOOL(aclresult == ACLCHECK_OK); } /* * has_function_privilege_id_id * Check user privileges on a function given * roleid, function oid, and text priv name. */ Datum has_function_privilege_id_id(PG_FUNCTION_ARGS) { Oid roleid = PG_GETARG_OID(0); Oid functionoid = PG_GETARG_OID(1); text *priv_type_text = PG_GETARG_TEXT_PP(2); AclMode mode; AclResult aclresult; mode = convert_function_priv_string(priv_type_text); if (!SearchSysCacheExists1(PROCOID, ObjectIdGetDatum(functionoid))) PG_RETURN_NULL(); aclresult = pg_proc_aclcheck(functionoid, roleid, mode); PG_RETURN_BOOL(aclresult == ACLCHECK_OK); } /* * Support routines for has_function_privilege family. */ /* * Given a function name expressed as a string, look it up and return Oid */ static Oid convert_function_name(text *functionname) { char *funcname = text_to_cstring(functionname); Oid oid; oid = DatumGetObjectId(DirectFunctionCall1(regprocedurein, CStringGetDatum(funcname))); if (!OidIsValid(oid)) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_FUNCTION), errmsg("function \"%s\" does not exist", funcname))); return oid; } /* * convert_function_priv_string * Convert text string to AclMode value. */ static AclMode convert_function_priv_string(text *priv_type_text) { static const priv_map function_priv_map[] = { {"EXECUTE", ACL_EXECUTE}, {"EXECUTE WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_EXECUTE)}, {NULL, 0} }; return convert_any_priv_string(priv_type_text, function_priv_map); } /* * has_language_privilege variants * These are all named "has_language_privilege" at the SQL level. * They take various combinations of language name, language OID, * user name, user OID, or implicit user = current_user. * * The result is a boolean value: true if user has the indicated * privilege, false if not, or NULL if object doesn't exist. */ /* * has_language_privilege_name_name * Check user privileges on a language given * name username, text languagename, and text priv name. */ Datum has_language_privilege_name_name(PG_FUNCTION_ARGS) { Name username = PG_GETARG_NAME(0); text *languagename = PG_GETARG_TEXT_PP(1); text *priv_type_text = PG_GETARG_TEXT_PP(2); Oid roleid; Oid languageoid; AclMode mode; AclResult aclresult; roleid = get_role_oid_or_public(NameStr(*username)); languageoid = convert_language_name(languagename); mode = convert_language_priv_string(priv_type_text); aclresult = pg_language_aclcheck(languageoid, roleid, mode); PG_RETURN_BOOL(aclresult == ACLCHECK_OK); } /* * has_language_privilege_name * Check user privileges on a language given * text languagename and text priv name. * current_user is assumed */ Datum has_language_privilege_name(PG_FUNCTION_ARGS) { text *languagename = PG_GETARG_TEXT_PP(0); text *priv_type_text = PG_GETARG_TEXT_PP(1); Oid roleid; Oid languageoid; AclMode mode; AclResult aclresult; roleid = GetUserId(); languageoid = convert_language_name(languagename); mode = convert_language_priv_string(priv_type_text); aclresult = pg_language_aclcheck(languageoid, roleid, mode); PG_RETURN_BOOL(aclresult == ACLCHECK_OK); } /* * has_language_privilege_name_id * Check user privileges on a language given * name usename, language oid, and text priv name. */ Datum has_language_privilege_name_id(PG_FUNCTION_ARGS) { Name username = PG_GETARG_NAME(0); Oid languageoid = PG_GETARG_OID(1); text *priv_type_text = PG_GETARG_TEXT_PP(2); Oid roleid; AclMode mode; AclResult aclresult; roleid = get_role_oid_or_public(NameStr(*username)); mode = convert_language_priv_string(priv_type_text); if (!SearchSysCacheExists1(LANGOID, ObjectIdGetDatum(languageoid))) PG_RETURN_NULL(); aclresult = pg_language_aclcheck(languageoid, roleid, mode); PG_RETURN_BOOL(aclresult == ACLCHECK_OK); } /* * has_language_privilege_id * Check user privileges on a language given * language oid, and text priv name. * current_user is assumed */ Datum has_language_privilege_id(PG_FUNCTION_ARGS) { Oid languageoid = PG_GETARG_OID(0); text *priv_type_text = PG_GETARG_TEXT_PP(1); Oid roleid; AclMode mode; AclResult aclresult; roleid = GetUserId(); mode = convert_language_priv_string(priv_type_text); if (!SearchSysCacheExists1(LANGOID, ObjectIdGetDatum(languageoid))) PG_RETURN_NULL(); aclresult = pg_language_aclcheck(languageoid, roleid, mode); PG_RETURN_BOOL(aclresult == ACLCHECK_OK); } /* * has_language_privilege_id_name * Check user privileges on a language given * roleid, text languagename, and text priv name. */ Datum has_language_privilege_id_name(PG_FUNCTION_ARGS) { Oid roleid = PG_GETARG_OID(0); text *languagename = PG_GETARG_TEXT_PP(1); text *priv_type_text = PG_GETARG_TEXT_PP(2); Oid languageoid; AclMode mode; AclResult aclresult; languageoid = convert_language_name(languagename); mode = convert_language_priv_string(priv_type_text); aclresult = pg_language_aclcheck(languageoid, roleid, mode); PG_RETURN_BOOL(aclresult == ACLCHECK_OK); } /* * has_language_privilege_id_id * Check user privileges on a language given * roleid, language oid, and text priv name. */ Datum has_language_privilege_id_id(PG_FUNCTION_ARGS) { Oid roleid = PG_GETARG_OID(0); Oid languageoid = PG_GETARG_OID(1); text *priv_type_text = PG_GETARG_TEXT_PP(2); AclMode mode; AclResult aclresult; mode = convert_language_priv_string(priv_type_text); if (!SearchSysCacheExists1(LANGOID, ObjectIdGetDatum(languageoid))) PG_RETURN_NULL(); aclresult = pg_language_aclcheck(languageoid, roleid, mode); PG_RETURN_BOOL(aclresult == ACLCHECK_OK); } /* * Support routines for has_language_privilege family. */ /* * Given a language name expressed as a string, look it up and return Oid */ static Oid convert_language_name(text *languagename) { char *langname = text_to_cstring(languagename); return get_language_oid(langname, false); } /* * convert_language_priv_string * Convert text string to AclMode value. */ static AclMode convert_language_priv_string(text *priv_type_text) { static const priv_map language_priv_map[] = { {"USAGE", ACL_USAGE}, {"USAGE WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_USAGE)}, {NULL, 0} }; return convert_any_priv_string(priv_type_text, language_priv_map); } /* * has_schema_privilege variants * These are all named "has_schema_privilege" at the SQL level. * They take various combinations of schema name, schema OID, * user name, user OID, or implicit user = current_user. * * The result is a boolean value: true if user has the indicated * privilege, false if not, or NULL if object doesn't exist. */ /* * has_schema_privilege_name_name * Check user privileges on a schema given * name username, text schemaname, and text priv name. */ Datum has_schema_privilege_name_name(PG_FUNCTION_ARGS) { Name username = PG_GETARG_NAME(0); text *schemaname = PG_GETARG_TEXT_PP(1); text *priv_type_text = PG_GETARG_TEXT_PP(2); Oid roleid; Oid schemaoid; AclMode mode; AclResult aclresult; roleid = get_role_oid_or_public(NameStr(*username)); schemaoid = convert_schema_name(schemaname); mode = convert_schema_priv_string(priv_type_text); aclresult = pg_namespace_aclcheck(schemaoid, roleid, mode); PG_RETURN_BOOL(aclresult == ACLCHECK_OK); } /* * has_schema_privilege_name * Check user privileges on a schema given * text schemaname and text priv name. * current_user is assumed */ Datum has_schema_privilege_name(PG_FUNCTION_ARGS) { text *schemaname = PG_GETARG_TEXT_PP(0); text *priv_type_text = PG_GETARG_TEXT_PP(1); Oid roleid; Oid schemaoid; AclMode mode; AclResult aclresult; roleid = GetUserId(); schemaoid = convert_schema_name(schemaname); mode = convert_schema_priv_string(priv_type_text); aclresult = pg_namespace_aclcheck(schemaoid, roleid, mode); PG_RETURN_BOOL(aclresult == ACLCHECK_OK); } /* * has_schema_privilege_name_id * Check user privileges on a schema given * name usename, schema oid, and text priv name. */ Datum has_schema_privilege_name_id(PG_FUNCTION_ARGS) { Name username = PG_GETARG_NAME(0); Oid schemaoid = PG_GETARG_OID(1); text *priv_type_text = PG_GETARG_TEXT_PP(2); Oid roleid; AclMode mode; AclResult aclresult; roleid = get_role_oid_or_public(NameStr(*username)); mode = convert_schema_priv_string(priv_type_text); if (!SearchSysCacheExists1(NAMESPACEOID, ObjectIdGetDatum(schemaoid))) PG_RETURN_NULL(); aclresult = pg_namespace_aclcheck(schemaoid, roleid, mode); PG_RETURN_BOOL(aclresult == ACLCHECK_OK); } /* * has_schema_privilege_id * Check user privileges on a schema given * schema oid, and text priv name. * current_user is assumed */ Datum has_schema_privilege_id(PG_FUNCTION_ARGS) { Oid schemaoid = PG_GETARG_OID(0); text *priv_type_text = PG_GETARG_TEXT_PP(1); Oid roleid; AclMode mode; AclResult aclresult; roleid = GetUserId(); mode = convert_schema_priv_string(priv_type_text); if (!SearchSysCacheExists1(NAMESPACEOID, ObjectIdGetDatum(schemaoid))) PG_RETURN_NULL(); aclresult = pg_namespace_aclcheck(schemaoid, roleid, mode); PG_RETURN_BOOL(aclresult == ACLCHECK_OK); } /* * has_schema_privilege_id_name * Check user privileges on a schema given * roleid, text schemaname, and text priv name. */ Datum has_schema_privilege_id_name(PG_FUNCTION_ARGS) { Oid roleid = PG_GETARG_OID(0); text *schemaname = PG_GETARG_TEXT_PP(1); text *priv_type_text = PG_GETARG_TEXT_PP(2); Oid schemaoid; AclMode mode; AclResult aclresult; schemaoid = convert_schema_name(schemaname); mode = convert_schema_priv_string(priv_type_text); aclresult = pg_namespace_aclcheck(schemaoid, roleid, mode); PG_RETURN_BOOL(aclresult == ACLCHECK_OK); } /* * has_schema_privilege_id_id * Check user privileges on a schema given * roleid, schema oid, and text priv name. */ Datum has_schema_privilege_id_id(PG_FUNCTION_ARGS) { Oid roleid = PG_GETARG_OID(0); Oid schemaoid = PG_GETARG_OID(1); text *priv_type_text = PG_GETARG_TEXT_PP(2); AclMode mode; AclResult aclresult; mode = convert_schema_priv_string(priv_type_text); if (!SearchSysCacheExists1(NAMESPACEOID, ObjectIdGetDatum(schemaoid))) PG_RETURN_NULL(); aclresult = pg_namespace_aclcheck(schemaoid, roleid, mode); PG_RETURN_BOOL(aclresult == ACLCHECK_OK); } /* * Support routines for has_schema_privilege family. */ /* * Given a schema name expressed as a string, look it up and return Oid */ static Oid convert_schema_name(text *schemaname) { char *nspname = text_to_cstring(schemaname); return get_namespace_oid(nspname, false); } /* * convert_schema_priv_string * Convert text string to AclMode value. */ static AclMode convert_schema_priv_string(text *priv_type_text) { static const priv_map schema_priv_map[] = { {"CREATE", ACL_CREATE}, {"CREATE WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_CREATE)}, {"USAGE", ACL_USAGE}, {"USAGE WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_USAGE)}, {NULL, 0} }; return convert_any_priv_string(priv_type_text, schema_priv_map); } /* * has_server_privilege variants * These are all named "has_server_privilege" at the SQL level. * They take various combinations of foreign server name, * server OID, user name, user OID, or implicit user = current_user. * * The result is a boolean value: true if user has the indicated * privilege, false if not. */ /* * has_server_privilege_name_name * Check user privileges on a foreign server given * name username, text servername, and text priv name. */ Datum has_server_privilege_name_name(PG_FUNCTION_ARGS) { Name username = PG_GETARG_NAME(0); text *servername = PG_GETARG_TEXT_PP(1); text *priv_type_text = PG_GETARG_TEXT_PP(2); Oid roleid; Oid serverid; AclMode mode; AclResult aclresult; roleid = get_role_oid_or_public(NameStr(*username)); serverid = convert_server_name(servername); mode = convert_server_priv_string(priv_type_text); aclresult = pg_foreign_server_aclcheck(serverid, roleid, mode); PG_RETURN_BOOL(aclresult == ACLCHECK_OK); } /* * has_server_privilege_name * Check user privileges on a foreign server given * text servername and text priv name. * current_user is assumed */ Datum has_server_privilege_name(PG_FUNCTION_ARGS) { text *servername = PG_GETARG_TEXT_PP(0); text *priv_type_text = PG_GETARG_TEXT_PP(1); Oid roleid; Oid serverid; AclMode mode; AclResult aclresult; roleid = GetUserId(); serverid = convert_server_name(servername); mode = convert_server_priv_string(priv_type_text); aclresult = pg_foreign_server_aclcheck(serverid, roleid, mode); PG_RETURN_BOOL(aclresult == ACLCHECK_OK); } /* * has_server_privilege_name_id * Check user privileges on a foreign server given * name usename, foreign server oid, and text priv name. */ Datum has_server_privilege_name_id(PG_FUNCTION_ARGS) { Name username = PG_GETARG_NAME(0); Oid serverid = PG_GETARG_OID(1); text *priv_type_text = PG_GETARG_TEXT_PP(2); Oid roleid; AclMode mode; AclResult aclresult; roleid = get_role_oid_or_public(NameStr(*username)); mode = convert_server_priv_string(priv_type_text); if (!SearchSysCacheExists1(FOREIGNSERVEROID, ObjectIdGetDatum(serverid))) PG_RETURN_NULL(); aclresult = pg_foreign_server_aclcheck(serverid, roleid, mode); PG_RETURN_BOOL(aclresult == ACLCHECK_OK); } /* * has_server_privilege_id * Check user privileges on a foreign server given * server oid, and text priv name. * current_user is assumed */ Datum has_server_privilege_id(PG_FUNCTION_ARGS) { Oid serverid = PG_GETARG_OID(0); text *priv_type_text = PG_GETARG_TEXT_PP(1); Oid roleid; AclMode mode; AclResult aclresult; roleid = GetUserId(); mode = convert_server_priv_string(priv_type_text); if (!SearchSysCacheExists1(FOREIGNSERVEROID, ObjectIdGetDatum(serverid))) PG_RETURN_NULL(); aclresult = pg_foreign_server_aclcheck(serverid, roleid, mode); PG_RETURN_BOOL(aclresult == ACLCHECK_OK); } /* * has_server_privilege_id_name * Check user privileges on a foreign server given * roleid, text servername, and text priv name. */ Datum has_server_privilege_id_name(PG_FUNCTION_ARGS) { Oid roleid = PG_GETARG_OID(0); text *servername = PG_GETARG_TEXT_PP(1); text *priv_type_text = PG_GETARG_TEXT_PP(2); Oid serverid; AclMode mode; AclResult aclresult; serverid = convert_server_name(servername); mode = convert_server_priv_string(priv_type_text); aclresult = pg_foreign_server_aclcheck(serverid, roleid, mode); PG_RETURN_BOOL(aclresult == ACLCHECK_OK); } /* * has_server_privilege_id_id * Check user privileges on a foreign server given * roleid, server oid, and text priv name. */ Datum has_server_privilege_id_id(PG_FUNCTION_ARGS) { Oid roleid = PG_GETARG_OID(0); Oid serverid = PG_GETARG_OID(1); text *priv_type_text = PG_GETARG_TEXT_PP(2); AclMode mode; AclResult aclresult; mode = convert_server_priv_string(priv_type_text); if (!SearchSysCacheExists1(FOREIGNSERVEROID, ObjectIdGetDatum(serverid))) PG_RETURN_NULL(); aclresult = pg_foreign_server_aclcheck(serverid, roleid, mode); PG_RETURN_BOOL(aclresult == ACLCHECK_OK); } /* * Support routines for has_server_privilege family. */ /* * Given a server name expressed as a string, look it up and return Oid */ static Oid convert_server_name(text *servername) { char *serverstr = text_to_cstring(servername); return get_foreign_server_oid(serverstr, false); } /* * convert_server_priv_string * Convert text string to AclMode value. */ static AclMode convert_server_priv_string(text *priv_type_text) { static const priv_map server_priv_map[] = { {"USAGE", ACL_USAGE}, {"USAGE WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_USAGE)}, {NULL, 0} }; return convert_any_priv_string(priv_type_text, server_priv_map); } /* * has_tablespace_privilege variants * These are all named "has_tablespace_privilege" at the SQL level. * They take various combinations of tablespace name, tablespace OID, * user name, user OID, or implicit user = current_user. * * The result is a boolean value: true if user has the indicated * privilege, false if not. */ /* * has_tablespace_privilege_name_name * Check user privileges on a tablespace given * name username, text tablespacename, and text priv name. */ Datum has_tablespace_privilege_name_name(PG_FUNCTION_ARGS) { Name username = PG_GETARG_NAME(0); text *tablespacename = PG_GETARG_TEXT_PP(1); text *priv_type_text = PG_GETARG_TEXT_PP(2); Oid roleid; Oid tablespaceoid; AclMode mode; AclResult aclresult; roleid = get_role_oid_or_public(NameStr(*username)); tablespaceoid = convert_tablespace_name(tablespacename); mode = convert_tablespace_priv_string(priv_type_text); aclresult = pg_tablespace_aclcheck(tablespaceoid, roleid, mode); PG_RETURN_BOOL(aclresult == ACLCHECK_OK); } /* * has_tablespace_privilege_name * Check user privileges on a tablespace given * text tablespacename and text priv name. * current_user is assumed */ Datum has_tablespace_privilege_name(PG_FUNCTION_ARGS) { text *tablespacename = PG_GETARG_TEXT_PP(0); text *priv_type_text = PG_GETARG_TEXT_PP(1); Oid roleid; Oid tablespaceoid; AclMode mode; AclResult aclresult; roleid = GetUserId(); tablespaceoid = convert_tablespace_name(tablespacename); mode = convert_tablespace_priv_string(priv_type_text); aclresult = pg_tablespace_aclcheck(tablespaceoid, roleid, mode); PG_RETURN_BOOL(aclresult == ACLCHECK_OK); } /* * has_tablespace_privilege_name_id * Check user privileges on a tablespace given * name usename, tablespace oid, and text priv name. */ Datum has_tablespace_privilege_name_id(PG_FUNCTION_ARGS) { Name username = PG_GETARG_NAME(0); Oid tablespaceoid = PG_GETARG_OID(1); text *priv_type_text = PG_GETARG_TEXT_PP(2); Oid roleid; AclMode mode; AclResult aclresult; roleid = get_role_oid_or_public(NameStr(*username)); mode = convert_tablespace_priv_string(priv_type_text); if (!SearchSysCacheExists1(TABLESPACEOID, ObjectIdGetDatum(tablespaceoid))) PG_RETURN_NULL(); aclresult = pg_tablespace_aclcheck(tablespaceoid, roleid, mode); PG_RETURN_BOOL(aclresult == ACLCHECK_OK); } /* * has_tablespace_privilege_id * Check user privileges on a tablespace given * tablespace oid, and text priv name. * current_user is assumed */ Datum has_tablespace_privilege_id(PG_FUNCTION_ARGS) { Oid tablespaceoid = PG_GETARG_OID(0); text *priv_type_text = PG_GETARG_TEXT_PP(1); Oid roleid; AclMode mode; AclResult aclresult; roleid = GetUserId(); mode = convert_tablespace_priv_string(priv_type_text); if (!SearchSysCacheExists1(TABLESPACEOID, ObjectIdGetDatum(tablespaceoid))) PG_RETURN_NULL(); aclresult = pg_tablespace_aclcheck(tablespaceoid, roleid, mode); PG_RETURN_BOOL(aclresult == ACLCHECK_OK); } /* * has_tablespace_privilege_id_name * Check user privileges on a tablespace given * roleid, text tablespacename, and text priv name. */ Datum has_tablespace_privilege_id_name(PG_FUNCTION_ARGS) { Oid roleid = PG_GETARG_OID(0); text *tablespacename = PG_GETARG_TEXT_PP(1); text *priv_type_text = PG_GETARG_TEXT_PP(2); Oid tablespaceoid; AclMode mode; AclResult aclresult; tablespaceoid = convert_tablespace_name(tablespacename); mode = convert_tablespace_priv_string(priv_type_text); aclresult = pg_tablespace_aclcheck(tablespaceoid, roleid, mode); PG_RETURN_BOOL(aclresult == ACLCHECK_OK); } /* * has_tablespace_privilege_id_id * Check user privileges on a tablespace given * roleid, tablespace oid, and text priv name. */ Datum has_tablespace_privilege_id_id(PG_FUNCTION_ARGS) { Oid roleid = PG_GETARG_OID(0); Oid tablespaceoid = PG_GETARG_OID(1); text *priv_type_text = PG_GETARG_TEXT_PP(2); AclMode mode; AclResult aclresult; mode = convert_tablespace_priv_string(priv_type_text); if (!SearchSysCacheExists1(TABLESPACEOID, ObjectIdGetDatum(tablespaceoid))) PG_RETURN_NULL(); aclresult = pg_tablespace_aclcheck(tablespaceoid, roleid, mode); PG_RETURN_BOOL(aclresult == ACLCHECK_OK); } /* * Support routines for has_tablespace_privilege family. */ /* * Given a tablespace name expressed as a string, look it up and return Oid */ static Oid convert_tablespace_name(text *tablespacename) { char *spcname = text_to_cstring(tablespacename); return get_tablespace_oid(spcname, false); } /* * convert_tablespace_priv_string * Convert text string to AclMode value. */ static AclMode convert_tablespace_priv_string(text *priv_type_text) { static const priv_map tablespace_priv_map[] = { {"CREATE", ACL_CREATE}, {"CREATE WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_CREATE)}, {NULL, 0} }; return convert_any_priv_string(priv_type_text, tablespace_priv_map); } /* * has_type_privilege variants * These are all named "has_type_privilege" at the SQL level. * They take various combinations of type name, type OID, * user name, user OID, or implicit user = current_user. * * The result is a boolean value: true if user has the indicated * privilege, false if not, or NULL if object doesn't exist. */ /* * has_type_privilege_name_name * Check user privileges on a type given * name username, text typename, and text priv name. */ Datum has_type_privilege_name_name(PG_FUNCTION_ARGS) { Name username = PG_GETARG_NAME(0); text *typename = PG_GETARG_TEXT_PP(1); text *priv_type_text = PG_GETARG_TEXT_PP(2); Oid roleid; Oid typeoid; AclMode mode; AclResult aclresult; roleid = get_role_oid_or_public(NameStr(*username)); typeoid = convert_type_name(typename); mode = convert_type_priv_string(priv_type_text); aclresult = pg_type_aclcheck(typeoid, roleid, mode); PG_RETURN_BOOL(aclresult == ACLCHECK_OK); } /* * has_type_privilege_name * Check user privileges on a type given * text typename and text priv name. * current_user is assumed */ Datum has_type_privilege_name(PG_FUNCTION_ARGS) { text *typename = PG_GETARG_TEXT_PP(0); text *priv_type_text = PG_GETARG_TEXT_PP(1); Oid roleid; Oid typeoid; AclMode mode; AclResult aclresult; roleid = GetUserId(); typeoid = convert_type_name(typename); mode = convert_type_priv_string(priv_type_text); aclresult = pg_type_aclcheck(typeoid, roleid, mode); PG_RETURN_BOOL(aclresult == ACLCHECK_OK); } /* * has_type_privilege_name_id * Check user privileges on a type given * name usename, type oid, and text priv name. */ Datum has_type_privilege_name_id(PG_FUNCTION_ARGS) { Name username = PG_GETARG_NAME(0); Oid typeoid = PG_GETARG_OID(1); text *priv_type_text = PG_GETARG_TEXT_PP(2); Oid roleid; AclMode mode; AclResult aclresult; roleid = get_role_oid_or_public(NameStr(*username)); mode = convert_type_priv_string(priv_type_text); if (!SearchSysCacheExists1(TYPEOID, ObjectIdGetDatum(typeoid))) PG_RETURN_NULL(); aclresult = pg_type_aclcheck(typeoid, roleid, mode); PG_RETURN_BOOL(aclresult == ACLCHECK_OK); } /* * has_type_privilege_id * Check user privileges on a type given * type oid, and text priv name. * current_user is assumed */ Datum has_type_privilege_id(PG_FUNCTION_ARGS) { Oid typeoid = PG_GETARG_OID(0); text *priv_type_text = PG_GETARG_TEXT_PP(1); Oid roleid; AclMode mode; AclResult aclresult; roleid = GetUserId(); mode = convert_type_priv_string(priv_type_text); if (!SearchSysCacheExists1(TYPEOID, ObjectIdGetDatum(typeoid))) PG_RETURN_NULL(); aclresult = pg_type_aclcheck(typeoid, roleid, mode); PG_RETURN_BOOL(aclresult == ACLCHECK_OK); } /* * has_type_privilege_id_name * Check user privileges on a type given * roleid, text typename, and text priv name. */ Datum has_type_privilege_id_name(PG_FUNCTION_ARGS) { Oid roleid = PG_GETARG_OID(0); text *typename = PG_GETARG_TEXT_PP(1); text *priv_type_text = PG_GETARG_TEXT_PP(2); Oid typeoid; AclMode mode; AclResult aclresult; typeoid = convert_type_name(typename); mode = convert_type_priv_string(priv_type_text); aclresult = pg_type_aclcheck(typeoid, roleid, mode); PG_RETURN_BOOL(aclresult == ACLCHECK_OK); } /* * has_type_privilege_id_id * Check user privileges on a type given * roleid, type oid, and text priv name. */ Datum has_type_privilege_id_id(PG_FUNCTION_ARGS) { Oid roleid = PG_GETARG_OID(0); Oid typeoid = PG_GETARG_OID(1); text *priv_type_text = PG_GETARG_TEXT_PP(2); AclMode mode; AclResult aclresult; mode = convert_type_priv_string(priv_type_text); if (!SearchSysCacheExists1(TYPEOID, ObjectIdGetDatum(typeoid))) PG_RETURN_NULL(); aclresult = pg_type_aclcheck(typeoid, roleid, mode); PG_RETURN_BOOL(aclresult == ACLCHECK_OK); } /* * Support routines for has_type_privilege family. */ /* * Given a type name expressed as a string, look it up and return Oid */ static Oid convert_type_name(text *typename) { char *typname = text_to_cstring(typename); Oid oid; oid = DatumGetObjectId(DirectFunctionCall1(regtypein, CStringGetDatum(typname))); if (!OidIsValid(oid)) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("type \"%s\" does not exist", typname))); return oid; } /* * convert_type_priv_string * Convert text string to AclMode value. */ static AclMode convert_type_priv_string(text *priv_type_text) { static const priv_map type_priv_map[] = { {"USAGE", ACL_USAGE}, {"USAGE WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_USAGE)}, {NULL, 0} }; return convert_any_priv_string(priv_type_text, type_priv_map); } /* * pg_has_role variants * These are all named "pg_has_role" at the SQL level. * They take various combinations of role name, role OID, * user name, user OID, or implicit user = current_user. * * The result is a boolean value: true if user has the indicated * privilege, false if not. */ /* * pg_has_role_name_name * Check user privileges on a role given * name username, name rolename, and text priv name. */ Datum pg_has_role_name_name(PG_FUNCTION_ARGS) { Name username = PG_GETARG_NAME(0); Name rolename = PG_GETARG_NAME(1); text *priv_type_text = PG_GETARG_TEXT_PP(2); Oid roleid; Oid roleoid; AclMode mode; AclResult aclresult; roleid = get_role_oid(NameStr(*username), false); roleoid = get_role_oid(NameStr(*rolename), false); mode = convert_role_priv_string(priv_type_text); aclresult = pg_role_aclcheck(roleoid, roleid, mode); PG_RETURN_BOOL(aclresult == ACLCHECK_OK); } /* * pg_has_role_name * Check user privileges on a role given * name rolename and text priv name. * current_user is assumed */ Datum pg_has_role_name(PG_FUNCTION_ARGS) { Name rolename = PG_GETARG_NAME(0); text *priv_type_text = PG_GETARG_TEXT_PP(1); Oid roleid; Oid roleoid; AclMode mode; AclResult aclresult; roleid = GetUserId(); roleoid = get_role_oid(NameStr(*rolename), false); mode = convert_role_priv_string(priv_type_text); aclresult = pg_role_aclcheck(roleoid, roleid, mode); PG_RETURN_BOOL(aclresult == ACLCHECK_OK); } /* * pg_has_role_name_id * Check user privileges on a role given * name usename, role oid, and text priv name. */ Datum pg_has_role_name_id(PG_FUNCTION_ARGS) { Name username = PG_GETARG_NAME(0); Oid roleoid = PG_GETARG_OID(1); text *priv_type_text = PG_GETARG_TEXT_PP(2); Oid roleid; AclMode mode; AclResult aclresult; roleid = get_role_oid(NameStr(*username), false); mode = convert_role_priv_string(priv_type_text); aclresult = pg_role_aclcheck(roleoid, roleid, mode); PG_RETURN_BOOL(aclresult == ACLCHECK_OK); } /* * pg_has_role_id * Check user privileges on a role given * role oid, and text priv name. * current_user is assumed */ Datum pg_has_role_id(PG_FUNCTION_ARGS) { Oid roleoid = PG_GETARG_OID(0); text *priv_type_text = PG_GETARG_TEXT_PP(1); Oid roleid; AclMode mode; AclResult aclresult; roleid = GetUserId(); mode = convert_role_priv_string(priv_type_text); aclresult = pg_role_aclcheck(roleoid, roleid, mode); PG_RETURN_BOOL(aclresult == ACLCHECK_OK); } /* * pg_has_role_id_name * Check user privileges on a role given * roleid, name rolename, and text priv name. */ Datum pg_has_role_id_name(PG_FUNCTION_ARGS) { Oid roleid = PG_GETARG_OID(0); Name rolename = PG_GETARG_NAME(1); text *priv_type_text = PG_GETARG_TEXT_PP(2); Oid roleoid; AclMode mode; AclResult aclresult; roleoid = get_role_oid(NameStr(*rolename), false); mode = convert_role_priv_string(priv_type_text); aclresult = pg_role_aclcheck(roleoid, roleid, mode); PG_RETURN_BOOL(aclresult == ACLCHECK_OK); } /* * pg_has_role_id_id * Check user privileges on a role given * roleid, role oid, and text priv name. */ Datum pg_has_role_id_id(PG_FUNCTION_ARGS) { Oid roleid = PG_GETARG_OID(0); Oid roleoid = PG_GETARG_OID(1); text *priv_type_text = PG_GETARG_TEXT_PP(2); AclMode mode; AclResult aclresult; mode = convert_role_priv_string(priv_type_text); aclresult = pg_role_aclcheck(roleoid, roleid, mode); PG_RETURN_BOOL(aclresult == ACLCHECK_OK); } /* * Support routines for pg_has_role family. */ /* * convert_role_priv_string * Convert text string to AclMode value. * * We use USAGE to denote whether the privileges of the role are accessible * (has_privs), MEMBER to denote is_member, and MEMBER WITH GRANT OPTION * (or ADMIN OPTION) to denote is_admin. There is no ACL bit corresponding * to MEMBER so we cheat and use ACL_CREATE for that. This convention * is shared only with pg_role_aclcheck, below. */ static AclMode convert_role_priv_string(text *priv_type_text) { static const priv_map role_priv_map[] = { {"USAGE", ACL_USAGE}, {"MEMBER", ACL_CREATE}, {"USAGE WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_CREATE)}, {"USAGE WITH ADMIN OPTION", ACL_GRANT_OPTION_FOR(ACL_CREATE)}, {"MEMBER WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_CREATE)}, {"MEMBER WITH ADMIN OPTION", ACL_GRANT_OPTION_FOR(ACL_CREATE)}, {NULL, 0} }; return convert_any_priv_string(priv_type_text, role_priv_map); } /* * pg_role_aclcheck * Quick-and-dirty support for pg_has_role */ static AclResult pg_role_aclcheck(Oid role_oid, Oid roleid, AclMode mode) { if (mode & ACL_GRANT_OPTION_FOR(ACL_CREATE)) { /* * XXX For roleid == role_oid, is_admin_of_role() also examines the * session and call stack. That suits two-argument pg_has_role(), but * it gives the three-argument version a lamentable whimsy. */ if (is_admin_of_role(roleid, role_oid)) return ACLCHECK_OK; } if (mode & ACL_CREATE) { if (is_member_of_role(roleid, role_oid)) return ACLCHECK_OK; } if (mode & ACL_USAGE) { if (has_privs_of_role(roleid, role_oid)) return ACLCHECK_OK; } return ACLCHECK_NO_PRIV; } /* * initialization function (called by InitPostgres) */ void initialize_acl(void) { if (!IsBootstrapProcessingMode()) { cached_db_hash = GetSysCacheHashValue1(DATABASEOID, ObjectIdGetDatum(MyDatabaseId)); /* * In normal mode, set a callback on any syscache invalidation of rows * of pg_auth_members (for roles_is_member_of()), pg_authid (for * has_rolinherit()), or pg_database (for roles_is_member_of()) */ CacheRegisterSyscacheCallback(AUTHMEMROLEMEM, RoleMembershipCacheCallback, (Datum) 0); CacheRegisterSyscacheCallback(AUTHOID, RoleMembershipCacheCallback, (Datum) 0); CacheRegisterSyscacheCallback(DATABASEOID, RoleMembershipCacheCallback, (Datum) 0); } } /* * RoleMembershipCacheCallback * Syscache inval callback function */ static void RoleMembershipCacheCallback(Datum arg, int cacheid, uint32 hashvalue) { if (cacheid == DATABASEOID && hashvalue != cached_db_hash && hashvalue != 0) { return; /* ignore pg_database changes for other DBs */ } /* Force membership caches to be recomputed on next use */ cached_role[ROLERECURSE_PRIVS] = InvalidOid; cached_role[ROLERECURSE_MEMBERS] = InvalidOid; } /* Check if specified role has rolinherit set */ static bool has_rolinherit(Oid roleid) { bool result = false; HeapTuple utup; utup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid)); if (HeapTupleIsValid(utup)) { result = ((Form_pg_authid) GETSTRUCT(utup))->rolinherit; ReleaseSysCache(utup); } return result; } /* * Get a list of roles that the specified roleid is a member of * * Type ROLERECURSE_PRIVS recurses only through roles that have rolinherit * set, while ROLERECURSE_MEMBERS recurses through all roles. This sets * *is_admin==true if and only if role "roleid" has an ADMIN OPTION membership * in role "admin_of". * * Since indirect membership testing is relatively expensive, we cache * a list of memberships. Hence, the result is only guaranteed good until * the next call of roles_is_member_of()! * * For the benefit of select_best_grantor, the result is defined to be * in breadth-first order, ie, closer relationships earlier. */ static List * roles_is_member_of(Oid roleid, enum RoleRecurseType type, Oid admin_of, bool *is_admin) { Oid dba; List *roles_list; ListCell *l; List *new_cached_roles; MemoryContext oldctx; Assert(OidIsValid(admin_of) == PointerIsValid(is_admin)); /* If cache is valid and ADMIN OPTION not sought, just return the list */ if (cached_role[type] == roleid && !OidIsValid(admin_of) && OidIsValid(cached_role[type])) return cached_roles[type]; /* * Role expansion happens in a non-database backend when guc.c checks * ROLE_PG_READ_ALL_SETTINGS for a physical walsender SHOW command. In * that case, no role gets pg_database_owner. */ if (!OidIsValid(MyDatabaseId)) dba = InvalidOid; else { HeapTuple dbtup; dbtup = SearchSysCache1(DATABASEOID, ObjectIdGetDatum(MyDatabaseId)); if (!HeapTupleIsValid(dbtup)) elog(ERROR, "cache lookup failed for database %u", MyDatabaseId); dba = ((Form_pg_database) GETSTRUCT(dbtup))->datdba; ReleaseSysCache(dbtup); } /* * Find all the roles that roleid is a member of, including multi-level * recursion. The role itself will always be the first element of the * resulting list. * * Each element of the list is scanned to see if it adds any indirect * memberships. We can use a single list as both the record of * already-found memberships and the agenda of roles yet to be scanned. * This is a bit tricky but works because the foreach() macro doesn't * fetch the next list element until the bottom of the loop. */ roles_list = list_make1_oid(roleid); foreach(l, roles_list) { Oid memberid = lfirst_oid(l); CatCList *memlist; int i; if (type == ROLERECURSE_PRIVS && !has_rolinherit(memberid)) continue; /* ignore non-inheriting roles */ /* Find roles that memberid is directly a member of */ memlist = SearchSysCacheList1(AUTHMEMMEMROLE, ObjectIdGetDatum(memberid)); for (i = 0; i < memlist->n_members; i++) { HeapTuple tup = &memlist->members[i]->tuple; Oid otherid = ((Form_pg_auth_members) GETSTRUCT(tup))->roleid; /* * While otherid==InvalidOid shouldn't appear in the catalog, the * OidIsValid() avoids crashing if that arises. */ if (otherid == admin_of && ((Form_pg_auth_members) GETSTRUCT(tup))->admin_option && OidIsValid(admin_of)) *is_admin = true; /* * Even though there shouldn't be any loops in the membership * graph, we must test for having already seen this role. It is * legal for instance to have both A->B and A->C->B. */ roles_list = list_append_unique_oid(roles_list, otherid); } ReleaseSysCacheList(memlist); /* implement pg_database_owner implicit membership */ if (memberid == dba && OidIsValid(dba)) roles_list = list_append_unique_oid(roles_list, ROLE_PG_DATABASE_OWNER); } /* * Copy the completed list into TopMemoryContext so it will persist. */ oldctx = MemoryContextSwitchTo(TopMemoryContext); new_cached_roles = list_copy(roles_list); MemoryContextSwitchTo(oldctx); list_free(roles_list); /* * Now safe to assign to state variable */ cached_role[type] = InvalidOid; /* just paranoia */ list_free(cached_roles[type]); cached_roles[type] = new_cached_roles; cached_role[type] = roleid; /* And now we can return the answer */ return cached_roles[type]; } /* * Does member have the privileges of role (directly or indirectly)? * * This is defined not to recurse through roles that don't have rolinherit * set; for such roles, membership implies the ability to do SET ROLE, but * the privileges are not available until you've done so. */ bool has_privs_of_role(Oid member, Oid role) { /* Fast path for simple case */ if (member == role) return true; /* Superusers have every privilege, so are part of every role */ if (superuser_arg(member)) return true; /* * Find all the roles that member has the privileges of, including * multi-level recursion, then see if target role is any one of them. */ return list_member_oid(roles_is_member_of(member, ROLERECURSE_PRIVS, InvalidOid, NULL), role); } /* * Is member a member of role (directly or indirectly)? * * This is defined to recurse through roles regardless of rolinherit. */ bool is_member_of_role(Oid member, Oid role) { /* Fast path for simple case */ if (member == role) return true; /* Superusers have every privilege, so are part of every role */ if (superuser_arg(member)) return true; /* * Find all the roles that member is a member of, including multi-level * recursion, then see if target role is any one of them. */ return list_member_oid(roles_is_member_of(member, ROLERECURSE_MEMBERS, InvalidOid, NULL), role); } /* * check_is_member_of_role * is_member_of_role with a standard permission-violation error if not */ void check_is_member_of_role(Oid member, Oid role) { if (!is_member_of_role(member, role)) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("must be member of role \"%s\"", GetUserNameFromId(role, false)))); } /* * Is member a member of role, not considering superuserness? * * This is identical to is_member_of_role except we ignore superuser * status. */ bool is_member_of_role_nosuper(Oid member, Oid role) { /* Fast path for simple case */ if (member == role) return true; /* * Find all the roles that member is a member of, including multi-level * recursion, then see if target role is any one of them. */ return list_member_oid(roles_is_member_of(member, ROLERECURSE_MEMBERS, InvalidOid, NULL), role); } /* * Is member an admin of role? That is, is member the role itself (subject to * restrictions below), a member (directly or indirectly) WITH ADMIN OPTION, * or a superuser? */ bool is_admin_of_role(Oid member, Oid role) { bool result = false; if (superuser_arg(member)) return true; if (member == role) /* * A role can admin itself when it matches the session user and we're * outside any security-restricted operation, SECURITY DEFINER or * similar context. SQL-standard roles cannot self-admin. However, * SQL-standard users are distinct from roles, and they are not * grantable like roles: PostgreSQL's role-user duality extends the * standard. Checking for a session user match has the effect of * letting a role self-admin only when it's conspicuously behaving * like a user. Note that allowing self-admin under a mere SET ROLE * would make WITH ADMIN OPTION largely irrelevant; any member could * SET ROLE to issue the otherwise-forbidden command. * * Withholding self-admin in a security-restricted operation prevents * object owners from harnessing the session user identity during * administrative maintenance. Suppose Alice owns a database, has * issued "GRANT alice TO bob", and runs a daily ANALYZE. Bob creates * an alice-owned SECURITY DEFINER function that issues "REVOKE alice * FROM carol". If he creates an expression index calling that * function, Alice will attempt the REVOKE during each ANALYZE. * Checking InSecurityRestrictedOperation() thwarts that attack. * * Withholding self-admin in SECURITY DEFINER functions makes their * behavior independent of the calling user. There's no security or * SQL-standard-conformance need for that restriction, though. * * A role cannot have actual WITH ADMIN OPTION on itself, because that * would imply a membership loop. Therefore, we're done either way. */ return member == GetSessionUserId() && !InLocalUserIdChange() && !InSecurityRestrictedOperation(); (void) roles_is_member_of(member, ROLERECURSE_MEMBERS, role, &result); return result; } /* does what it says ... */ static int count_one_bits(AclMode mask) { int nbits = 0; /* this code relies on AclMode being an unsigned type */ while (mask) { if (mask & 1) nbits++; mask >>= 1; } return nbits; } /* * Select the effective grantor ID for a GRANT or REVOKE operation. * * The grantor must always be either the object owner or some role that has * been explicitly granted grant options. This ensures that all granted * privileges appear to flow from the object owner, and there are never * multiple "original sources" of a privilege. Therefore, if the would-be * grantor is a member of a role that has the needed grant options, we have * to do the grant as that role instead. * * It is possible that the would-be grantor is a member of several roles * that have different subsets of the desired grant options, but no one * role has 'em all. In this case we pick a role with the largest number * of desired options. Ties are broken in favor of closer ancestors. * * roleId: the role attempting to do the GRANT/REVOKE * privileges: the privileges to be granted/revoked * acl: the ACL of the object in question * ownerId: the role owning the object in question * *grantorId: receives the OID of the role to do the grant as * *grantOptions: receives the grant options actually held by grantorId * * If no grant options exist, we set grantorId to roleId, grantOptions to 0. */ void select_best_grantor(Oid roleId, AclMode privileges, const Acl *acl, Oid ownerId, Oid *grantorId, AclMode *grantOptions) { AclMode needed_goptions = ACL_GRANT_OPTION_FOR(privileges); List *roles_list; int nrights; ListCell *l; /* * The object owner is always treated as having all grant options, so if * roleId is the owner it's easy. Also, if roleId is a superuser it's * easy: superusers are implicitly members of every role, so they act as * the object owner. */ if (roleId == ownerId || superuser_arg(roleId)) { *grantorId = ownerId; *grantOptions = needed_goptions; return; } /* * Otherwise we have to do a careful search to see if roleId has the * privileges of any suitable role. Note: we can hang onto the result of * roles_is_member_of() throughout this loop, because aclmask_direct() * doesn't query any role memberships. */ roles_list = roles_is_member_of(roleId, ROLERECURSE_PRIVS, InvalidOid, NULL); /* initialize candidate result as default */ *grantorId = roleId; *grantOptions = ACL_NO_RIGHTS; nrights = 0; foreach(l, roles_list) { Oid otherrole = lfirst_oid(l); AclMode otherprivs; otherprivs = aclmask_direct(acl, otherrole, ownerId, needed_goptions, ACLMASK_ALL); if (otherprivs == needed_goptions) { /* Found a suitable grantor */ *grantorId = otherrole; *grantOptions = otherprivs; return; } /* * If it has just some of the needed privileges, remember best * candidate. */ if (otherprivs != ACL_NO_RIGHTS) { int nnewrights = count_one_bits(otherprivs); if (nnewrights > nrights) { *grantorId = otherrole; *grantOptions = otherprivs; nrights = nnewrights; } } } } /* * get_role_oid - Given a role name, look up the role's OID. * * If missing_ok is false, throw an error if role name not found. If * true, just return InvalidOid. */ Oid get_role_oid(const char *rolname, bool missing_ok) { Oid oid; oid = GetSysCacheOid1(AUTHNAME, Anum_pg_authid_oid, CStringGetDatum(rolname)); if (!OidIsValid(oid) && !missing_ok) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("role \"%s\" does not exist", rolname))); return oid; } /* * get_role_oid_or_public - As above, but return ACL_ID_PUBLIC if the * role name is "public". */ Oid get_role_oid_or_public(const char *rolname) { if (strcmp(rolname, "public") == 0) return ACL_ID_PUBLIC; return get_role_oid(rolname, false); } /* * Given a RoleSpec node, return the OID it corresponds to. If missing_ok is * true, return InvalidOid if the role does not exist. * * PUBLIC is always disallowed here. Routines wanting to handle the PUBLIC * case must check the case separately. */ Oid get_rolespec_oid(const RoleSpec *role, bool missing_ok) { Oid oid; switch (role->roletype) { case ROLESPEC_CSTRING: Assert(role->rolename); oid = get_role_oid(role->rolename, missing_ok); break; case ROLESPEC_CURRENT_ROLE: case ROLESPEC_CURRENT_USER: oid = GetUserId(); break; case ROLESPEC_SESSION_USER: oid = GetSessionUserId(); break; case ROLESPEC_PUBLIC: ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("role \"%s\" does not exist", "public"))); oid = InvalidOid; /* make compiler happy */ break; default: elog(ERROR, "unexpected role type %d", role->roletype); } return oid; } /* * Given a RoleSpec node, return the pg_authid HeapTuple it corresponds to. * Caller must ReleaseSysCache when done with the result tuple. */ HeapTuple get_rolespec_tuple(const RoleSpec *role) { HeapTuple tuple; switch (role->roletype) { case ROLESPEC_CSTRING: Assert(role->rolename); tuple = SearchSysCache1(AUTHNAME, CStringGetDatum(role->rolename)); if (!HeapTupleIsValid(tuple)) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("role \"%s\" does not exist", role->rolename))); break; case ROLESPEC_CURRENT_ROLE: case ROLESPEC_CURRENT_USER: tuple = SearchSysCache1(AUTHOID, GetUserId()); if (!HeapTupleIsValid(tuple)) elog(ERROR, "cache lookup failed for role %u", GetUserId()); break; case ROLESPEC_SESSION_USER: tuple = SearchSysCache1(AUTHOID, GetSessionUserId()); if (!HeapTupleIsValid(tuple)) elog(ERROR, "cache lookup failed for role %u", GetSessionUserId()); break; case ROLESPEC_PUBLIC: ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("role \"%s\" does not exist", "public"))); tuple = NULL; /* make compiler happy */ break; default: elog(ERROR, "unexpected role type %d", role->roletype); } return tuple; } /* * Given a RoleSpec, returns a palloc'ed copy of the corresponding role's name. */ char * get_rolespec_name(const RoleSpec *role) { HeapTuple tp; Form_pg_authid authForm; char *rolename; tp = get_rolespec_tuple(role); authForm = (Form_pg_authid) GETSTRUCT(tp); rolename = pstrdup(NameStr(authForm->rolname)); ReleaseSysCache(tp); return rolename; } /* * Given a RoleSpec, throw an error if the name is reserved, using detail_msg, * if provided (which must be already translated). * * If node is NULL, no error is thrown. If detail_msg is NULL then no detail * message is provided. */ void check_rolespec_name(const RoleSpec *role, const char *detail_msg) { if (!role) return; if (role->roletype != ROLESPEC_CSTRING) return; if (IsReservedName(role->rolename)) { if (detail_msg) ereport(ERROR, (errcode(ERRCODE_RESERVED_NAME), errmsg("role name \"%s\" is reserved", role->rolename), errdetail_internal("%s", detail_msg))); else ereport(ERROR, (errcode(ERRCODE_RESERVED_NAME), errmsg("role name \"%s\" is reserved", role->rolename))); } }