diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml index 34679d8..66a3306 100644 *** a/doc/src/sgml/catalogs.sgml --- b/doc/src/sgml/catalogs.sgml *************** *** 3130,3135 **** --- 3130,3199 ---- + + <structname>pg_namespace_default_acl</structname> + + + pg_namespace_default_acl + + + + The catalog pg_namespace_default_acl stores default + privileges for newly created objects inside the schema. + + + + <structname>pg_namespace</> Columns + + + + + Name + Type + References + Description + + + + + + defaclnamespace + oid + pg_namespace.oid + Oid of the namespace + + + + defaclgrantobjtype + char + + + r = table, v = view, + f = function, S = sequence + + + + + defacllist + aclitem[] + + + Array of acls that the object should have on creation + this is NOT a mask, it's exactly what the object will get. + See + , + and + + for details. + + + + +
+ +
+ + <structname>pg_opclass</structname> diff --git a/doc/src/sgml/ref/alter_schema.sgml b/doc/src/sgml/ref/alter_schema.sgml index 2458d19..dd3aad2 100644 *** a/doc/src/sgml/ref/alter_schema.sgml --- b/doc/src/sgml/ref/alter_schema.sgml *************** PostgreSQL documentation *** 23,40 **** ALTER SCHEMA name RENAME TO newname ALTER SCHEMA name OWNER TO newowner ! Description ! ALTER SCHEMA changes the definition of a schema. - You must own the schema to use ALTER SCHEMA. To rename a schema you must also have the CREATE privilege for the database. To alter the owner, you must also be a direct or --- 23,68 ---- ALTER SCHEMA name RENAME TO newname ALTER SCHEMA name OWNER TO newowner + + ALTER SCHEMA name { SET | ADD } DEFAULT PRIVILEGES { { ON default_privileges + TO { [ GROUP ] rolename | PUBLIC } [, ...] [ WITH GRANT OPTION ] } [AND ...] } [...] + + where default_privileges is: + + { { TABLE | VIEW } { { SELECT | INSERT | UPDATE | DELETE | TRUNCATE | REFERENCES | TRIGGER } + [,...] | ALL [ PRIVILEGES ] } | + SEQUENCE { { USAGE | SELECT | UPDATE } + [,...] | ALL [ PRIVILEGES ] } | + FUNCTION { EXECUTE | ALL [ PRIVILEGES ] } } + + ALTER SCHEMA name DROP DEFAULT PRIVILEGES { { ON drop_default_privileges + FROM { [ GROUP ] rolename | PUBLIC } [, ...] } [AND ...] } [...] + + where drop_default_privileges is: + + { { TABLE | VIEW } [ GRANT OPTION FOR ] + { { SELECT | INSERT | UPDATE | DELETE | TRUNCATE | REFERENCES | TRIGGER } + [,...] | ALL [ PRIVILEGES ] } | + SEQUENCE [ GRANT OPTION FOR ] + { { USAGE | SELECT | UPDATE } [,...] | ALL [ PRIVILEGES ] } | + FUNCTION [ GRANT OPTION FOR ] + { EXECUTE | ALL [ PRIVILEGES ] } } ! Description ! You must own the schema to use ALTER SCHEMA. + + + + Change the definition of a schema + To rename a schema you must also have the CREATE privilege for the database. To alter the owner, you must also be a direct or *************** ALTER SCHEMA nameCREATE privilege for the database. (Note that superusers have all these privileges automatically.) - ! Parameters --- 70,77 ---- CREATE privilege for the database. (Note that superusers have all these privileges automatically.) ! Parameters *************** ALTER SCHEMA name + + + + + Change the default privileges for new objects + + + The ALTER SCHEMA { SET | ADD | DROP } DEFAULT PRIVILEGES command + allows you to define privileges for newly created objects in the schema. + + + + See the description of the and + commands for more detailed + explanation of privilege system and the meaning of the privilege types. + + + + The possible commands are: + + + + SET + + + Replaces the current default privileges with the newly specified ones. + Behaves like GRANT on object that had no privileges set before. + + + + + + ADD + + + Adds new default privileges. Behaves like standard GRANT. + + + + + + DROP + + + Removes specified privileges from defaults. Behaves like REVOKE. + + + + + + + + + + Examples + + + Grant select privilege for every new table in schema + public to everybody: + + + ALTER SCHEMA public SET DEFAULT PRIVILEGES ON TABLE SELECT TO public; + + + + + User webuser will have select privilege on all new + tables created in schema users, while user + admin will also be able to insert, update and delete + rows in those tables: + + + ALTER SCHEMA users SET DEFAULT PRIVILEGES ON TABLE SELECT TO webuser, admin AND UPDATE, INSERT, DELETE TO admin; + + + + + Give user webuser select privilege on all new + tables and views, let him execute all new functions and read new sequences. + The admin user will be able not only to select but also + change contents of new tables and also grant those permissions he has on + tables to other users. He will also be able to select from new views, + execute new functions and read new sequences like webuser + does but will also be able to update new sequences. + All for schema public: + + + ALTER SCHEMA public SET DEFAULT PRIVILEGES + ON TABLE SELECT TO webuser, admin AND UPDATE, INSERT, DELETE TO admin WITH GRANT OPTION + ON VIEW SELECT TO webuser, admin + ON FUNCTION EXECUTE TO webuser, admin + ON SEQUENCE USAGE, SELECT TO webuser AND ALL PRIVILEGES TO admin; + + diff --git a/doc/src/sgml/ref/grant.sgml b/doc/src/sgml/ref/grant.sgml index bf963b8..a8d76b6 100644 *** a/doc/src/sgml/ref/grant.sgml --- b/doc/src/sgml/ref/grant.sgml *************** PostgreSQL documentation *** 22,29 **** GRANT { { SELECT | INSERT | UPDATE | DELETE | TRUNCATE | REFERENCES | TRIGGER } ! [,...] | ALL [ PRIVILEGES ] } ! ON [ TABLE ] tablename [, ...] TO { [ GROUP ] rolename | PUBLIC } [, ...] [ WITH GRANT OPTION ] GRANT { { SELECT | INSERT | UPDATE | REFERENCES } ( column [, ...] ) --- 22,29 ---- GRANT { { SELECT | INSERT | UPDATE | DELETE | TRUNCATE | REFERENCES | TRIGGER } ! [,...] | ALL [ PRIVILEGES ] | DEFAULT PRIVILEGES } ! ON [ TABLE | VIEW ] tablename [, ...] TO { [ GROUP ] rolename | PUBLIC } [, ...] [ WITH GRANT OPTION ] GRANT { { SELECT | INSERT | UPDATE | REFERENCES } ( column [, ...] ) *************** GRANT { { SELECT | INSERT | UPDATE | REF *** 32,38 **** TO { [ GROUP ] rolename | PUBLIC } [, ...] [ WITH GRANT OPTION ] GRANT { { USAGE | SELECT | UPDATE } ! [,...] | ALL [ PRIVILEGES ] } ON SEQUENCE sequencename [, ...] TO { [ GROUP ] rolename | PUBLIC } [, ...] [ WITH GRANT OPTION ] --- 32,38 ---- TO { [ GROUP ] rolename | PUBLIC } [, ...] [ WITH GRANT OPTION ] GRANT { { USAGE | SELECT | UPDATE } ! [,...] | ALL [ PRIVILEGES ] | DEFAULT PRIVILEGES } ON SEQUENCE sequencename [, ...] TO { [ GROUP ] rolename | PUBLIC } [, ...] [ WITH GRANT OPTION ] *************** GRANT { USAGE | ALL [ PRIVILEGES ] } *** 48,54 **** ON FOREIGN SERVER servername [, ...] TO { [ GROUP ] rolename | PUBLIC } [, ...] [ WITH GRANT OPTION ] ! GRANT { EXECUTE | ALL [ PRIVILEGES ] } ON FUNCTION funcname ( [ [ argmode ] [ argname ] argtype [, ...] ] ) [, ...] TO { [ GROUP ] rolename | PUBLIC } [, ...] [ WITH GRANT OPTION ] --- 48,54 ---- ON FOREIGN SERVER servername [, ...] TO { [ GROUP ] rolename | PUBLIC } [, ...] [ WITH GRANT OPTION ] ! GRANT { EXECUTE | ALL [ PRIVILEGES ] | DEFAULT PRIVILEGES } ON FUNCTION funcname ( [ [ argmode ] [ argname ] argtype [, ...] ] ) [, ...] TO { [ GROUP ] rolename | PUBLIC } [, ...] [ WITH GRANT OPTION ] *************** GRANT rol *** 132,137 **** --- 132,139 ---- include granting some privileges to PUBLIC. The default is no public access for tables, columns, schemas, and tablespaces; + For tables, views, functions and sequences the initial default privileges + can also be changed using ALTER SCHEMA command; CONNECT privilege and TEMP table creation privilege for databases; EXECUTE privilege for functions; and *************** GRANT rol *** 339,344 **** --- 341,361 ---- + + + DEFAULT PRIVILEGES + + + Replace current privileges with the ones specified using + . + The WITH GRANT OPTION parameter is not applicable + because it is copied from default privileges too. + Note: this can actually revoke some + privileges because it clears all existing privileges object has and + replaces them with default ones. + + + The privileges required by other commands are listed on the diff --git a/doc/src/sgml/ref/revoke.sgml b/doc/src/sgml/ref/revoke.sgml index 8d62580..109b58c 100644 *** a/doc/src/sgml/ref/revoke.sgml --- b/doc/src/sgml/ref/revoke.sgml *************** PostgreSQL documentation *** 24,30 **** REVOKE [ GRANT OPTION FOR ] { { SELECT | INSERT | UPDATE | DELETE | TRUNCATE | REFERENCES | TRIGGER } [,...] | ALL [ PRIVILEGES ] } ! ON [ TABLE ] tablename [, ...] FROM { [ GROUP ] rolename | PUBLIC } [, ...] [ CASCADE | RESTRICT ] --- 24,30 ---- REVOKE [ GRANT OPTION FOR ] { { SELECT | INSERT | UPDATE | DELETE | TRUNCATE | REFERENCES | TRIGGER } [,...] | ALL [ PRIVILEGES ] } ! ON [ TABLE | VIEW ] tablename [, ...] FROM { [ GROUP ] rolename | PUBLIC } [, ...] [ CASCADE | RESTRICT ] diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile index 400ae80..aa4b35e 100644 *** a/src/backend/catalog/Makefile --- b/src/backend/catalog/Makefile *************** POSTGRES_BKI_SRCS = $(addprefix $(top_sr *** 37,42 **** --- 37,43 ---- pg_ts_config.h pg_ts_config_map.h pg_ts_dict.h \ pg_ts_parser.h pg_ts_template.h \ pg_foreign_data_wrapper.h pg_foreign_server.h pg_user_mapping.h \ + pg_namespace_default_acl.h \ toasting.h indexing.h \ ) diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c index ec4aaf0..e991400 100644 *** a/src/backend/catalog/aclchk.c --- b/src/backend/catalog/aclchk.c *************** *** 31,36 **** --- 31,37 ---- #include "catalog/pg_foreign_server.h" #include "catalog/pg_language.h" #include "catalog/pg_namespace.h" + #include "catalog/pg_namespace_default_acl.h" #include "catalog/pg_opclass.h" #include "catalog/pg_operator.h" #include "catalog/pg_opfamily.h" *************** *** 50,57 **** #include "utils/syscache.h" #include "utils/tqual.h" ! static void ExecGrant_Relation(InternalGrant *grantStmt); static void ExecGrant_Database(InternalGrant *grantStmt); static void ExecGrant_Fdw(InternalGrant *grantStmt); static void ExecGrant_ForeignServer(InternalGrant *grantStmt); --- 51,60 ---- #include "utils/syscache.h" #include "utils/tqual.h" ! static void ExecGrantStmt_Defaults(InternalGrant *istmt); static void ExecGrant_Relation(InternalGrant *grantStmt); + static void ExecGrantDefaults_Relation(InternalGrant *grantStmt); + static void ExecGrantDefaults_Function(InternalGrant *grantStmt); static void ExecGrant_Database(InternalGrant *grantStmt); static void ExecGrant_Fdw(InternalGrant *grantStmt); static void ExecGrant_ForeignServer(InternalGrant *grantStmt); *************** static void expand_all_col_privileges(Oi *** 69,76 **** AclMode this_privileges, AclMode *col_privileges, int num_col_privileges); - static AclMode string_to_privilege(const char *privname); - static const char *privilege_to_string(AclMode privilege); static AclMode restrict_and_check_grant(bool is_grant, AclMode avail_goptions, bool all_privs, AclMode privileges, Oid objectId, Oid grantorId, --- 72,77 ---- *************** dumpacl(Acl *acl) *** 105,111 **** * * NB: the original old_acl is pfree'd. */ ! static Acl * merge_acl_with_grant(Acl *old_acl, bool is_grant, bool grant_option, DropBehavior behavior, List *grantees, AclMode privileges, --- 106,112 ---- * * NB: the original old_acl is pfree'd. */ ! Acl * merge_acl_with_grant(Acl *old_acl, bool is_grant, bool grant_option, DropBehavior behavior, List *grantees, AclMode privileges, *************** restrict_and_check_grant(bool is_grant, *** 270,275 **** --- 271,277 ---- return this_privileges; } + /* * Called to execute the utility commands GRANT and REVOKE */ *************** ExecuteGrantStmt(GrantStmt *stmt) *** 294,299 **** --- 296,310 ---- istmt.grant_option = stmt->grant_option; istmt.behavior = stmt->behavior; + /* + * GRANT DEFAULT PRIVILEGES + */ + if (!stmt->grantees) + { + ExecGrantStmt_Defaults(&istmt); + return; + } + /* * Convert the PrivGrantee list into an Oid list. Note that at this point * we insert an ACL_ID_PUBLIC into the list if an empty role name is *************** ExecGrant_Tablespace(InternalGrant *istm *** 1988,1994 **** } ! static AclMode string_to_privilege(const char *privname) { if (strcmp(privname, "insert") == 0) --- 1999,2218 ---- } ! /* ! * ExecGrantStmt_Defaults ! * ! * "Internal" entrypoint for reseting privileges to schema DEFAULT ! */ ! static void ! ExecGrantStmt_Defaults(InternalGrant *istmt) ! { ! switch (istmt->objtype) ! { ! case ACL_OBJECT_RELATION: ! case ACL_OBJECT_SEQUENCE: ! ExecGrantDefaults_Relation(istmt); ! break; ! case ACL_OBJECT_FUNCTION: ! ExecGrantDefaults_Function(istmt); ! break; ! default: ! elog(ERROR, "unrecognized GrantStmt.objtype: %d", ! (int) istmt->objtype); ! } ! } ! ! static void ! ExecGrantDefaults_Relation(InternalGrant *grantStmt) ! { ! Relation relation; ! ListCell *cell; ! ! relation = heap_open(RelationRelationId, RowExclusiveLock); ! ! foreach(cell, grantStmt->objects) ! { ! Oid relOid = lfirst_oid(cell); ! Datum aclDatum; ! Form_pg_class pg_class_tuple; ! bool isNull; ! Acl *old_acl = NULL; ! Acl *new_acl; ! HeapTuple tuple; ! HeapTuple newtuple; ! Datum values[Natts_pg_class]; ! bool nulls[Natts_pg_class]; ! bool replaces[Natts_pg_class]; ! int noldmembers; ! int nnewmembers; ! Oid *oldmembers; ! Oid *newmembers; ! char objtype; ! ! tuple = SearchSysCache(RELOID, ! ObjectIdGetDatum(relOid), ! 0, 0, 0); ! ! if (!HeapTupleIsValid(tuple)) ! elog(ERROR, "cache lookup failed for relation %u", relOid); ! pg_class_tuple = (Form_pg_class) GETSTRUCT(tuple); ! ! /* Only owner can replace acls */ ! pg_class_ownercheck(relOid, GetUserId()); ! ! aclDatum = SysCacheGetAttr(RELOID, tuple, Anum_pg_class_relacl, ! &isNull); ! if (!isNull) ! old_acl = DatumGetAclPCopy(aclDatum); ! ! noldmembers = aclmembers(old_acl, &oldmembers); ! ! /* Check relkind and convert it to DefaultACLs object */ ! switch (pg_class_tuple->relkind) ! { ! case RELKIND_SEQUENCE: ! objtype = NSPDEFACLOBJ_SEQUENCE; ! break; ! case RELKIND_VIEW: ! objtype = NSPDEFACLOBJ_VIEW; ! break; ! case RELKIND_RELATION: ! objtype = NSPDEFACLOBJ_TABLE; ! break; ! default: /* parser error ? */ ! elog(ERROR, "relation can't have DEFAULT PRIVILEGES"); ! } ! ! new_acl = pg_namespace_object_default_acl(pg_class_tuple->relnamespace, ! objtype, &isNull); ! ! if (isNull) ! elog(ERROR, "no DEFAULT PRIVILEGES for relation"); ! ! nnewmembers = aclmembers(new_acl, &newmembers); ! ! /* Replace ACLs */ ! MemSet(values, 0, sizeof(values)); ! MemSet(nulls, false, sizeof(nulls)); ! MemSet(replaces, false, sizeof(replaces)); ! ! replaces[Anum_pg_class_relacl - 1] = true; ! values[Anum_pg_class_relacl - 1] = PointerGetDatum(new_acl); ! ! newtuple = heap_modify_tuple(tuple, RelationGetDescr(relation), ! values, nulls, replaces); ! ! simple_heap_update(relation, &newtuple->t_self, newtuple); ! ! /* keep the catalog indexes up to date */ ! CatalogUpdateIndexes(relation, newtuple); ! ! /* Update the shared dependency ACL info */ ! updateAclDependencies(RelationRelationId, relOid, 0, ! pg_class_tuple->relowner, grantStmt->is_grant, ! noldmembers, oldmembers, ! nnewmembers, newmembers); ! ! pfree(new_acl); ! ! ReleaseSysCache(tuple); ! ! /* prevent error when processing duplicate objects */ ! CommandCounterIncrement(); ! } ! ! heap_close(relation, RowExclusiveLock); ! } ! ! ! static void ! ExecGrantDefaults_Function(InternalGrant *grantStmt) ! { ! Relation relation; ! ListCell *cell; ! ! relation = heap_open(ProcedureRelationId, RowExclusiveLock); ! ! foreach(cell, grantStmt->objects) ! { ! Oid funcId = lfirst_oid(cell); ! Form_pg_proc pg_proc_tuple; ! Datum aclDatum; ! bool isNull; ! Acl *old_acl = NULL; ! Acl *new_acl; ! HeapTuple tuple; ! HeapTuple newtuple; ! Datum values[Natts_pg_class]; ! bool nulls[Natts_pg_class]; ! bool replaces[Natts_pg_class]; ! int noldmembers; ! int nnewmembers; ! Oid *oldmembers; ! Oid *newmembers; ! ! tuple = SearchSysCache(PROCOID, ! ObjectIdGetDatum(funcId), ! 0, 0, 0); ! if (!HeapTupleIsValid(tuple)) ! elog(ERROR, "cache lookup failed for function %u", funcId); ! ! pg_proc_tuple = (Form_pg_proc) GETSTRUCT(tuple); ! ! /* Only owner can replace acls */ ! pg_proc_ownercheck(funcId, GetUserId()); ! ! aclDatum = SysCacheGetAttr(PROCOID, tuple, Anum_pg_proc_proacl, ! &isNull); ! ! if (!isNull) ! old_acl = DatumGetAclPCopy(aclDatum); ! ! noldmembers = aclmembers(old_acl, &oldmembers); ! ! new_acl = pg_namespace_object_default_acl(pg_proc_tuple->pronamespace, ! NSPDEFACLOBJ_FUNCTION, &isNull); ! ! if (isNull) ! elog(ERROR, "no DEFAULT PRIVILEGES for function"); ! ! nnewmembers = aclmembers(new_acl, &newmembers); ! ! /* Replace ACLs */ ! MemSet(values, 0, sizeof(values)); ! MemSet(nulls, false, sizeof(nulls)); ! MemSet(replaces, false, sizeof(replaces)); ! ! replaces[Anum_pg_proc_proacl - 1] = true; ! values[Anum_pg_proc_proacl - 1] = PointerGetDatum(new_acl); ! ! newtuple = heap_modify_tuple(tuple, RelationGetDescr(relation), values, ! nulls, replaces); ! ! simple_heap_update(relation, &newtuple->t_self, newtuple); ! ! /* keep the catalog indexes up to date */ ! CatalogUpdateIndexes(relation, newtuple); ! ! /* Update the shared dependency ACL info */ ! updateAclDependencies(ProcedureRelationId, funcId, 0, ! pg_proc_tuple->proowner, grantStmt->is_grant, ! noldmembers, oldmembers, ! nnewmembers, newmembers); ! ! ReleaseSysCache(tuple); ! ! pfree(new_acl); ! ! /* prevent error when processing duplicate objects */ ! CommandCounterIncrement(); ! } ! ! heap_close(relation, RowExclusiveLock); ! } ! ! ! AclMode string_to_privilege(const char *privname) { if (strcmp(privname, "insert") == 0) *************** string_to_privilege(const char *privname *** 2025,2031 **** return 0; /* appease compiler */ } ! static const char * privilege_to_string(AclMode privilege) { switch (privilege) --- 2249,2255 ---- return 0; /* appease compiler */ } ! const char * privilege_to_string(AclMode privilege) { switch (privilege) *************** pg_conversion_ownercheck(Oid conv_oid, O *** 3532,3534 **** --- 3756,3830 ---- return has_privs_of_role(roleid, ownerId); } + + /* + * Get default permissions for newly created object inside given schema + */ + Acl * + pg_namespace_object_default_acl(Oid nsp_oid, char objtype, bool *isNull) + { + ScanKeyData key[2]; + HeapScanDesc scan; + HeapTuple tuple; + Relation rel; + Datum aclDatum; + Acl *result; + + *isNull = true; + + /* + * Use NULL value for object acls if it is created in system schema + * or if it does not have default permissions support (which is usually + * because it does not have namespace support either). + */ + if (nsp_oid == PG_CATALOG_NAMESPACE || nsp_oid == PG_TOAST_NAMESPACE) + return NULL; + + switch (objtype) + { + case NSPDEFACLOBJ_TABLE: + case NSPDEFACLOBJ_VIEW: + case NSPDEFACLOBJ_FUNCTION: + case NSPDEFACLOBJ_SEQUENCE: + break; + + default: + return NULL; + } + + /* + * Search for default permissions for given namespace and objecttype + */ + ScanKeyInit(&key[0], + Anum_pg_namespace_default_acl_defaclnamespace, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(nsp_oid)); + + ScanKeyInit(&key[1], + Anum_pg_namespace_default_acl_defaclgrantobjtype, + BTEqualStrategyNumber, F_CHAREQ, + CharGetDatum(objtype)); + + rel = heap_open(NamespaceDefaultAclRelationId, AccessShareLock); + + scan = heap_beginscan(rel, SnapshotNow, 2, key); + + tuple = heap_getnext(scan, ForwardScanDirection); + + if (HeapTupleIsValid(tuple)) + { + aclDatum = heap_getattr(tuple, Anum_pg_namespace_default_acl_defacldefacllist, + RelationGetDescr(rel), isNull); + + result = DatumGetAclPCopy(aclDatum); + } + else + { + result = NULL; + } + + heap_endscan(scan); + heap_close(rel, AccessShareLock); + + return result; + } diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c index f338382..b6467e6 100644 *** a/src/backend/catalog/dependency.c --- b/src/backend/catalog/dependency.c *************** *** 37,42 **** --- 37,43 ---- #include "catalog/pg_foreign_server.h" #include "catalog/pg_language.h" #include "catalog/pg_namespace.h" + #include "catalog/pg_namespace_default_acl.h" #include "catalog/pg_opclass.h" #include "catalog/pg_operator.h" #include "catalog/pg_opfamily.h" *************** static bool object_address_present_add_f *** 175,180 **** --- 176,182 ---- ObjectAddresses *addrs); static void getRelationDescription(StringInfo buffer, Oid relid); static void getOpFamilyDescription(StringInfo buffer, Oid opfid); + static void getDefaultACLDescription(StringInfo buffer, char objtype); /* *************** doDeletion(const ObjectAddress *object) *** 1127,1132 **** --- 1129,1138 ---- RemoveForeignDataWrapperById(object->objectId); break; + case OCLASS_NSPDEFACLS: + RemoveDefaultACLsById(object->objectId); + break; + /* OCLASS_ROLE, OCLASS_DATABASE, OCLASS_TBLSPACE not handled */ default: *************** getObjectClass(const ObjectAddress *obje *** 2048,2053 **** --- 2054,2063 ---- case UserMappingRelationId: Assert(object->objectSubId == 0); return OCLASS_USER_MAPPING; + + case NamespaceDefaultAclRelationId: + Assert(object->objectSubId == 0); + return OCLASS_NSPDEFACLS; } /* shouldn't get here */ *************** getObjectDescription(const ObjectAddress *** 2590,2595 **** --- 2600,2643 ---- break; } + case OCLASS_NSPDEFACLS: + { + Relation rel; + ScanKeyData key[1]; + HeapScanDesc scan; + HeapTuple tup; + char *nspname; + Form_pg_namespace_default_acl nspda; + + rel = heap_open(NamespaceDefaultAclRelationId, AccessShareLock); + + ScanKeyInit(&key[0], + ObjectIdAttributeNumber, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(object->objectId)); + + scan = heap_beginscan(rel, SnapshotNow, 1, key); + + tup = heap_getnext(scan, ForwardScanDirection); + + if (!HeapTupleIsValid(tup)) + elog(ERROR, "could not find tuple for default acls %u", + object->objectId); + + nspda = (Form_pg_namespace_default_acl) GETSTRUCT(tup); + + nspname = get_namespace_name(nspda->defaclnamespace); + + appendStringInfo(&buffer, + _("default acls in namespace %s for new "), + nspname); + getDefaultACLDescription(&buffer, nspda->defaclobjtype); + + heap_endscan(scan); + heap_close(rel, AccessShareLock); + break; + } + default: appendStringInfo(&buffer, "unrecognized object %u %u %d", object->classId, *************** getOpFamilyDescription(StringInfo buffer *** 2708,2710 **** --- 2756,2784 ---- ReleaseSysCache(amTup); ReleaseSysCache(opfTup); } + + + /* + * subroutine for getObjectDescription: describe DefaultACLs object type + */ + static void + getDefaultACLDescription(StringInfo buffer, char objtype) + { + switch (objtype) + { + case NSPDEFACLOBJ_TABLE: + appendStringInfo(buffer, _("TABLE")); + break; + case NSPDEFACLOBJ_VIEW: + appendStringInfo(buffer, _("VIEW")); + break; + case NSPDEFACLOBJ_FUNCTION: + appendStringInfo(buffer, _("FUNCTION")); + break; + case NSPDEFACLOBJ_SEQUENCE: + appendStringInfo(buffer, _("SEQUENCE")); + break; + default: + appendStringInfo(buffer, _("unknown")); + } + } diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c index 7557400..368f2ce 100644 *** a/src/backend/catalog/heap.c --- b/src/backend/catalog/heap.c *************** *** 67,72 **** --- 67,73 ---- #include "utils/snapmgr.h" #include "utils/syscache.h" #include "utils/tqual.h" + #include "utils/acl.h" static void AddNewRelationTuple(Relation pg_class_desc, *************** AddNewAttributeTuples(Oid new_rel_oid, *** 633,640 **** * Caller has already opened and locked pg_class. * Tuple data is taken from new_rel_desc->rd_rel, except for the * variable-width fields which are not present in a cached reldesc. ! * We always initialize relacl to NULL (i.e., default permissions), ! * and reloptions is set to the passed-in text array (if any). * -------------------------------- */ void --- 634,641 ---- * Caller has already opened and locked pg_class. * Tuple data is taken from new_rel_desc->rd_rel, except for the * variable-width fields which are not present in a cached reldesc. ! * Initial permissions are obtained from pg_namespace_default_acl ! * (if applicable) and reloptions is set to the passed-in text array (if any). * -------------------------------- */ void *************** InsertPgClassTuple(Relation pg_class_des *** 646,651 **** --- 647,654 ---- Form_pg_class rd_rel = new_rel_desc->rd_rel; Datum values[Natts_pg_class]; bool nulls[Natts_pg_class]; + bool isNull; + Acl *relacl; HeapTuple tup; /* This is a tad tedious, but way cleaner than what we used to do... */ *************** InsertPgClassTuple(Relation pg_class_des *** 675,682 **** values[Anum_pg_class_relhastriggers - 1] = BoolGetDatum(rd_rel->relhastriggers); values[Anum_pg_class_relhassubclass - 1] = BoolGetDatum(rd_rel->relhassubclass); values[Anum_pg_class_relfrozenxid - 1] = TransactionIdGetDatum(rd_rel->relfrozenxid); ! /* start out with empty permissions */ ! nulls[Anum_pg_class_relacl - 1] = true; if (reloptions != (Datum) 0) values[Anum_pg_class_reloptions - 1] = reloptions; else --- 678,691 ---- values[Anum_pg_class_relhastriggers - 1] = BoolGetDatum(rd_rel->relhastriggers); values[Anum_pg_class_relhassubclass - 1] = BoolGetDatum(rd_rel->relhassubclass); values[Anum_pg_class_relfrozenxid - 1] = TransactionIdGetDatum(rd_rel->relfrozenxid); ! ! /* get default permissions */ ! relacl = pg_namespace_object_default_acl(rd_rel->relnamespace, rd_rel->relkind, &isNull); ! if (isNull) ! nulls[Anum_pg_class_relacl - 1] = true; ! else ! values[Anum_pg_class_relacl - 1] = PointerGetDatum(relacl); ! if (reloptions != (Datum) 0) values[Anum_pg_class_reloptions - 1] = reloptions; else *************** InsertPgClassTuple(Relation pg_class_des *** 695,700 **** --- 704,724 ---- CatalogUpdateIndexes(pg_class_desc, tup); + if (!isNull) + { + int nnewmembers; + Oid *newmembers; + + /* Update the shared dependency ACL info */ + nnewmembers = aclmembers(relacl, &newmembers); + updateAclDependencies(RelationRelationId, new_rel_oid, 0, + rd_rel->relowner, true, + 0, NULL, + nnewmembers, newmembers); + + pfree(relacl); + } + heap_freetuple(tup); } diff --git a/src/backend/catalog/pg_proc.c b/src/backend/catalog/pg_proc.c index 1a06530..8ffb552 100644 *** a/src/backend/catalog/pg_proc.c --- b/src/backend/catalog/pg_proc.c *************** *** 20,25 **** --- 20,26 ---- #include "catalog/indexing.h" #include "catalog/pg_language.h" #include "catalog/pg_namespace.h" + #include "catalog/pg_namespace_default_acl.h" #include "catalog/pg_proc.h" #include "catalog/pg_proc_fn.h" #include "catalog/pg_type.h" *************** ProcedureCreate(const char *procedureNam *** 95,100 **** --- 96,103 ---- bool nulls[Natts_pg_proc]; Datum values[Natts_pg_proc]; bool replaces[Natts_pg_proc]; + bool isNull; + Acl *proacl; Oid relid; NameData procname; TupleDesc tupDesc; *************** ProcedureCreate(const char *procedureNam *** 330,337 **** values[Anum_pg_proc_proconfig - 1] = proconfig; else nulls[Anum_pg_proc_proconfig - 1] = true; ! /* start out with empty permissions */ ! nulls[Anum_pg_proc_proacl - 1] = true; rel = heap_open(ProcedureRelationId, RowExclusiveLock); tupDesc = RelationGetDescr(rel); --- 333,345 ---- values[Anum_pg_proc_proconfig - 1] = proconfig; else nulls[Anum_pg_proc_proconfig - 1] = true; ! ! /* get default permissions */ ! proacl = pg_namespace_object_default_acl(procNamespace, NSPDEFACLOBJ_FUNCTION, &isNull); ! if (isNull) ! nulls[Anum_pg_proc_proacl - 1] = true; ! else ! values[Anum_pg_proc_proacl - 1] = PointerGetDatum(proacl); rel = heap_open(ProcedureRelationId, RowExclusiveLock); tupDesc = RelationGetDescr(rel); *************** ProcedureCreate(const char *procedureNam *** 539,544 **** --- 547,567 ---- /* dependency on owner */ recordDependencyOnOwner(ProcedureRelationId, retval, GetUserId()); + if (!isNull) + { + int nnewmembers; + Oid *newmembers; + + /* Update the shared dependency ACL info */ + nnewmembers = aclmembers(proacl, &newmembers); + updateAclDependencies(ProcedureRelationId, retval, 0, + GetUserId(), true, + 0, NULL, + nnewmembers, newmembers); + + pfree(proacl); + } + heap_freetuple(tup); heap_close(rel, RowExclusiveLock); diff --git a/src/backend/commands/schemacmds.c b/src/backend/commands/schemacmds.c index c70d4a8..3599166 100644 *** a/src/backend/commands/schemacmds.c --- b/src/backend/commands/schemacmds.c *************** *** 16,26 **** --- 16,28 ---- #include "access/heapam.h" #include "access/xact.h" + #include "access/sysattr.h" #include "catalog/catalog.h" #include "catalog/dependency.h" #include "catalog/indexing.h" #include "catalog/namespace.h" #include "catalog/pg_namespace.h" + #include "catalog/pg_namespace_default_acl.h" #include "commands/dbcommands.h" #include "commands/schemacmds.h" #include "miscadmin.h" *************** *** 28,38 **** --- 30,57 ---- #include "tcop/utility.h" #include "utils/acl.h" #include "utils/builtins.h" + #include "utils/fmgroids.h" #include "utils/lsyscache.h" #include "utils/syscache.h" + #include "utils/tqual.h" + + + + typedef struct + { + int action; + Oid nspId; + Oid nspOwnerId; + GrantObjectType objtype; + AclMode privileges; + List *grantees; + bool grant_option; + } InternalDefaultACLs; + static void AlterSchemaOwner_internal(HeapTuple tup, Relation rel, Oid newOwnerId); + static void SetDefaultACLs(InternalDefaultACLs *iacls); /* * CREATE SCHEMA *************** CreateSchemaCommand(CreateSchemaStmt *st *** 147,152 **** --- 166,558 ---- } + + /* + * Default ACLs + * Implements ALTER SCHEMA DEFAULT PRIVILEGES + */ + void + DefaultACLsCommand(DefaultACLsStmt *stmt) + { + InternalDefaultACLs iacls; + List *objlist; + ListCell *objcell; + ListCell *privcell; + ListCell *cell; + Relation relation; + HeapTuple tuple; + const char *errormsg; + AclMode all_privileges; + Form_pg_namespace pg_namespace_tuple; + + relation = heap_open(NamespaceRelationId, RowExclusiveLock); + + tuple = SearchSysCache(NAMESPACENAME, + CStringGetDatum(stmt->schemaname), + 0, 0, 0); + + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for namespace \"%s\"", stmt->schemaname); + + iacls.nspId = HeapTupleGetOid(tuple); + pg_namespace_tuple = (Form_pg_namespace) GETSTRUCT(tuple); + iacls.nspOwnerId = pg_namespace_tuple->nspowner; + + if (iacls.nspId == PG_CATALOG_NAMESPACE || iacls.nspId == PG_TOAST_NAMESPACE) + elog(ERROR, "can't set default permissions for system catalog"); + + /* Permission check */ + if (!pg_namespace_ownercheck(iacls.nspId, GetUserId())) + aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_NAMESPACE, + stmt->schemaname); + + objlist = transformDefaultACLsStmt(stmt); + + foreach(objcell, stmt->objlist) + { + DefaultACLsObject *object = (DefaultACLsObject *) lfirst(objcell); + + iacls.action = stmt->action; + iacls.objtype = object->objtype; + + /* + * Set allowed privileges and apropriate error message for object type + * used in privilege conversion few lines down + */ + switch (object->objtype) + { + case NSP_ACL_OBJ_RELATION: + all_privileges = ACL_ALL_RIGHTS_RELATION; + errormsg = gettext_noop("invalid privilege type %s for relation"); + break; + case NSP_ACL_OBJ_VIEW: + all_privileges = ACL_ALL_RIGHTS_RELATION; + errormsg = gettext_noop("invalid privilege type %s for view"); + break; + case NSP_ACL_OBJ_FUNCTION: + all_privileges = ACL_ALL_RIGHTS_FUNCTION; + errormsg = gettext_noop("invalid privilege type %s for function"); + break; + case NSP_ACL_OBJ_SEQUENCE: + all_privileges = ACL_ALL_RIGHTS_SEQUENCE; + errormsg = gettext_noop("invalid privilege type %s for sequence"); + break; + default: + /* keep compiler quiet */ + all_privileges = ACL_NO_RIGHTS; + errormsg = NULL; + elog(ERROR, "unrecognized DefaultACLsObject.objtype: %d", + (int) object->objtype); + } + + foreach(privcell, object->privileges) + { + DefaultACLsPriv *privs = (DefaultACLsPriv *) lfirst(privcell); + + iacls.grantees = NIL; + iacls.grant_option = privs->grant_option; + + foreach(cell, privs->grantees) + { + PrivGrantee *grantee = (PrivGrantee *) lfirst(cell); + + if (grantee->rolname == NULL) + iacls.grantees = lappend_oid(iacls.grantees, ACL_ID_PUBLIC); + else + iacls.grantees = + lappend_oid(iacls.grantees, + get_roleid_checked(grantee->rolname)); + } + + /* + * Convert DefaultACLsPriv->privileges, a list of privilege strings, + * into an AclMode bitmask. + */ + if (privs->privileges == NIL) + { + iacls.privileges = all_privileges; + } + else + { + iacls.privileges = ACL_NO_RIGHTS; + + foreach(cell, privs->privileges) + { + char *priv_name = (char *) lfirst(cell); + AclMode priv; + + priv = string_to_privilege(priv_name); + + if (priv & ~((AclMode) all_privileges)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_GRANT_OPERATION), + errmsg(errormsg, privilege_to_string(priv)))); + + iacls.privileges |= priv; + } + } + + /* Apply the privileges */ + SetDefaultACLs(&iacls); + + /* If this is SET command chnage it to ADD for subsequent privileges */ + if (iacls.action == 0) + iacls.action = 1; + } + } + + ReleaseSysCache(tuple); + + heap_close(relation, RowExclusiveLock); + } + + + /* + * Create or update the DefaultACL entry + */ + void + SetDefaultACLs(InternalDefaultACLs *iacls) + { + Form_pg_namespace_default_acl pg_namespace_default_acl_tuple; + char objtype; + GrantObjectType aclobjtype; + Datum aclDatum; + bool isNull; + bool isNew; + ScanKeyData key[2]; + HeapScanDesc scan; + HeapTuple tuple; + Relation rel; + Acl *old_acl; + Acl *base_acl; + Acl *new_acl; + HeapTuple newtuple; + Datum values[Natts_pg_namespace_default_acl]; + bool nulls[Natts_pg_namespace_default_acl]; + bool replaces[Natts_pg_namespace_default_acl]; + int nbasemembers; + int nnewmembers; + Oid *basemembers; + Oid *newmembers; + + /* + * Convert ACL object type to DefaultACLs object type + */ + switch (iacls->objtype) + { + case NSP_ACL_OBJ_RELATION: + objtype = NSPDEFACLOBJ_TABLE; + aclobjtype = ACL_OBJECT_RELATION; + break; + + case NSP_ACL_OBJ_VIEW: + objtype = NSPDEFACLOBJ_VIEW; + aclobjtype = ACL_OBJECT_RELATION; + break; + + case NSP_ACL_OBJ_FUNCTION: + objtype = NSPDEFACLOBJ_FUNCTION; + aclobjtype = ACL_OBJECT_FUNCTION; + break; + + case NSP_ACL_OBJ_SEQUENCE: + objtype = NSPDEFACLOBJ_SEQUENCE; + aclobjtype = ACL_OBJECT_SEQUENCE; + break; + + default: + elog(ERROR, "unrecognized objtype: %d", + (int) iacls->objtype); + objtype = 0; /* keep compiler quiet */ + break; + } + + /* Search for existing row for our object type in catalog */ + ScanKeyInit(&key[0], + Anum_pg_namespace_default_acl_defaclnamespace, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(iacls->nspId)); + + ScanKeyInit(&key[1], + Anum_pg_namespace_default_acl_defaclgrantobjtype, + BTEqualStrategyNumber, F_CHAREQ, + CharGetDatum(objtype)); + + rel = heap_open(NamespaceDefaultAclRelationId, RowExclusiveLock); + + scan = heap_beginscan(rel, SnapshotNow, 2, key); + + tuple = heap_getnext(scan, ForwardScanDirection); + if (HeapTupleIsValid(tuple)) + { + pg_namespace_default_acl_tuple = (Form_pg_namespace_default_acl) GETSTRUCT(tuple); + aclDatum = heap_getattr(tuple, Anum_pg_namespace_default_acl_defacldefacllist, + RelationGetDescr(rel), &isNull); + isNew = false; + } + else + { + isNull = true; + isNew = true; + } + + /* + * If there are no default privileges for current schema and object type + * or if we are SETting the privileges, use the default privileges + * based on object type. + */ + if (isNull) + old_acl = acldefault(aclobjtype, iacls->nspOwnerId); + else + old_acl = DatumGetAclPCopy(aclDatum); + + /* we need to keep old_acl for later use */ + if (!isNull && iacls->action == 0) + base_acl = acldefault(aclobjtype, iacls->nspOwnerId); + else + base_acl = old_acl; + + nbasemembers = aclmembers(base_acl, &basemembers); + + /* + * Generate new ACL. + * + * We need the members of both old and new ACLs so we can correct + * the shared dependency information. + */ + new_acl = merge_acl_with_grant(base_acl, + iacls->action != -1, + iacls->grant_option, + DROP_RESTRICT, // this shouldn't affect us at all + iacls->grantees, + iacls->privileges, + GetUserId(), + iacls->nspOwnerId); + + /* finished building new ACL value, now insert it */ + MemSet(values, 0, sizeof(values)); + MemSet(nulls, false, sizeof(nulls)); + MemSet(replaces, false, sizeof(replaces)); + + if (isNew) + { + ObjectAddress myself, + referenced; + + values[Anum_pg_namespace_default_acl_defaclnamespace - 1] = ObjectIdGetDatum(iacls->nspId); + values[Anum_pg_namespace_default_acl_defaclgrantobjtype - 1] = CharGetDatum(objtype); + values[Anum_pg_namespace_default_acl_defacldefacllist - 1] = PointerGetDatum(new_acl); + + newtuple = heap_form_tuple(rel->rd_att, values, nulls); + simple_heap_insert(rel, newtuple); + + myself.classId = NamespaceDefaultAclRelationId; + myself.objectId = HeapTupleGetOid(newtuple); + myself.objectSubId = 0; + + /* dependency on namespace */ + referenced.classId = NamespaceRelationId; + referenced.objectId = iacls->nspId; + referenced.objectSubId = 0; + recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + } + else + { + replaces[Anum_pg_namespace_default_acl_defacldefacllist - 1] = true; + values[Anum_pg_namespace_default_acl_defacldefacllist - 1] = PointerGetDatum(new_acl); + + newtuple = heap_modify_tuple(tuple, RelationGetDescr(rel), + values, nulls, replaces); + simple_heap_update(rel, &newtuple->t_self, newtuple); + } + + /* keep the catalog indexes up to date */ + CatalogUpdateIndexes(rel, newtuple); + + /* + * Update the shared dependency ACL info + */ + nnewmembers = aclmembers(new_acl, &newmembers); + + /* remove old ACL depencencies first when using SET */ + if (!isNull && iacls->action == 0) + { + Acl *tmp_acl; + int noldmembers; + int ntmpmembers; + Oid *oldmembers; + Oid *tmpmembers; + + noldmembers = aclmembers(old_acl, &oldmembers); + + tmp_acl = merge_acl_with_grant(old_acl, + false, + iacls->grant_option, + DROP_RESTRICT, // this shouldn't affect us at all + iacls->grantees, + iacls->privileges, + GetUserId(), + iacls->nspOwnerId); + + ntmpmembers = aclmembers(tmp_acl, &tmpmembers); + + updateAclDependencies(NamespaceDefaultAclRelationId, HeapTupleGetOid(newtuple), 0, + iacls->nspOwnerId, false, + noldmembers, oldmembers, + ntmpmembers, tmpmembers); + + pfree(tmp_acl); + } + + updateAclDependencies(NamespaceDefaultAclRelationId, HeapTupleGetOid(newtuple), 0, + iacls->nspOwnerId, iacls->action != -1, + nbasemembers, basemembers, + nnewmembers, newmembers); + + pfree(new_acl); + + /* prevent error when processing duplicate objects */ + CommandCounterIncrement(); + + heap_endscan(scan); + heap_close(rel, RowExclusiveLock); + } + + + /* + * Remove DefaultACLs tuple. + */ + void + RemoveDefaultACLsById(Oid nsdaOid) + { + Relation rel; + ScanKeyData key[1]; + HeapScanDesc scan; + HeapTuple tup; + + rel = heap_open(NamespaceDefaultAclRelationId, RowExclusiveLock); + + ScanKeyInit(&key[0], + ObjectIdAttributeNumber, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(nsdaOid)); + + scan = heap_beginscan(rel, SnapshotNow, 1, key); + + tup = heap_getnext(scan, ForwardScanDirection); + + if (!HeapTupleIsValid(tup)) + elog(ERROR, "could not find tuple for default acls %u", + nsdaOid); + + simple_heap_delete(rel, &tup->t_self); + + heap_endscan(scan); + heap_close(rel, RowExclusiveLock); + } + + + /* * RemoveSchemas * Implements DROP SCHEMA. diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 1976648..1d69e93 100644 *** a/src/backend/nodes/copyfuncs.c --- b/src/backend/nodes/copyfuncs.c *************** _copyCreateSchemaStmt(CreateSchemaStmt * *** 3224,3229 **** --- 3224,3241 ---- return newnode; } + static DefaultACLsStmt * + _copyDefaultACLsStmt(DefaultACLsStmt *from) + { + DefaultACLsStmt *newnode = makeNode(DefaultACLsStmt); + + COPY_STRING_FIELD(schemaname); + COPY_SCALAR_FIELD(action); + COPY_NODE_FIELD(objlist); + + return newnode; + } + static CreateConversionStmt * _copyCreateConversionStmt(CreateConversionStmt *from) { *************** copyObject(void *from) *** 3966,3971 **** --- 3978,3986 ---- case T_CreateSchemaStmt: retval = _copyCreateSchemaStmt(from); break; + case T_DefaultACLsStmt: + retval = _copyDefaultACLsStmt(from); + break; case T_CreateConversionStmt: retval = _copyCreateConversionStmt(from); break; diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index 8b466f4..4aac71d 100644 *** a/src/backend/nodes/equalfuncs.c --- b/src/backend/nodes/equalfuncs.c *************** _equalCreateSchemaStmt(CreateSchemaStmt *** 1761,1766 **** --- 1761,1776 ---- } static bool + _equalDefaultACLsStmt(DefaultACLsStmt *a, DefaultACLsStmt *b) + { + COMPARE_STRING_FIELD(schemaname); + COMPARE_SCALAR_FIELD(action); + COMPARE_NODE_FIELD(objlist); + + return true; + } + + static bool _equalCreateConversionStmt(CreateConversionStmt *a, CreateConversionStmt *b) { COMPARE_NODE_FIELD(conversion_name); *************** equal(void *a, void *b) *** 2743,2748 **** --- 2753,2761 ---- case T_CreateSchemaStmt: retval = _equalCreateSchemaStmt(a, b); break; + case T_DefaultACLsStmt: + retval = _equalDefaultACLsStmt(a, b); + break; case T_CreateConversionStmt: retval = _equalCreateConversionStmt(a, b); break; diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 858e16c..8cdbb11 100644 *** a/src/backend/parser/gram.y --- b/src/backend/parser/gram.y *************** static TypeName *TableFuncTypeName(List *** 179,184 **** --- 179,187 ---- ResTarget *target; struct PrivTarget *privtarget; AccessPriv *accesspriv; + ACLSchemaObjectType aclschemaobjecttype; + DefaultACLsObject *defaultaclsobject; + DefaultACLsPriv *defaultaclspriv; InsertStmt *istmt; VariableSetStmt *vsetstmt; *************** static TypeName *TableFuncTypeName(List *** 196,202 **** CreateSchemaStmt CreateSeqStmt CreateStmt CreateTableSpaceStmt CreateFdwStmt CreateForeignServerStmt CreateAssertStmt CreateTrigStmt CreateUserStmt CreateUserMappingStmt CreateRoleStmt ! CreatedbStmt DeclareCursorStmt DefineStmt DeleteStmt DiscardStmt DropGroupStmt DropOpClassStmt DropOpFamilyStmt DropPLangStmt DropStmt DropAssertStmt DropTrigStmt DropRuleStmt DropCastStmt DropRoleStmt DropUserStmt DropdbStmt DropTableSpaceStmt DropFdwStmt --- 199,205 ---- CreateSchemaStmt CreateSeqStmt CreateStmt CreateTableSpaceStmt CreateFdwStmt CreateForeignServerStmt CreateAssertStmt CreateTrigStmt CreateUserStmt CreateUserMappingStmt CreateRoleStmt ! CreatedbStmt DeclareCursorStmt DefaultACLsStmt DefaultACLsDropStmt DefineStmt DeleteStmt DiscardStmt DropGroupStmt DropOpClassStmt DropOpFamilyStmt DropPLangStmt DropStmt DropAssertStmt DropTrigStmt DropRuleStmt DropCastStmt DropRoleStmt DropUserStmt DropdbStmt DropTableSpaceStmt DropFdwStmt *************** static TypeName *TableFuncTypeName(List *** 218,224 **** simple_select values_clause %type alter_column_default opclass_item opclass_drop alter_using ! %type add_drop opt_asc_desc opt_nulls_order %type alter_table_cmd %type alter_table_cmds --- 221,227 ---- simple_select values_clause %type alter_column_default opclass_item opclass_drop alter_using ! %type add_drop set_add opt_asc_desc opt_nulls_order %type alter_table_cmd %type alter_table_cmds *************** static TypeName *TableFuncTypeName(List *** 233,239 **** %type opt_lock lock_type cast_context %type opt_force opt_or_replace opt_grant_grant_option opt_grant_admin_option ! opt_nowait opt_if_exists opt_with_data %type OptRoleList %type OptRoleElem --- 236,242 ---- %type opt_lock lock_type cast_context %type opt_force opt_or_replace opt_grant_grant_option opt_grant_admin_option ! opt_nowait opt_if_exists opt_revoke_grant_option opt_with_data %type OptRoleList %type OptRoleElem *************** static TypeName *TableFuncTypeName(List *** 266,273 **** %type grantee %type grantee_list %type privilege ! %type privileges privilege_list %type privilege_target %type function_with_argtypes %type function_with_argtypes_list --- 269,282 ---- %type grantee %type grantee_list %type privilege ! %type default_acls_object_list default_acls_object_list_drop ! default_acls_priv_list default_acls_priv_list_drop default_priv_list ! default_priv_type privileges privilege_list %type privilege_target + %type default_priv_target + %type default_acls_object default_acls_object_drop + %type default_acls_priv default_acls_priv_drop + %type default_priv %type function_with_argtypes %type function_with_argtypes_list *************** stmt : *** 664,669 **** --- 673,680 ---- | CreatedbStmt | DeallocateStmt | DeclareCursorStmt + | DefaultACLsStmt + | DefaultACLsDropStmt | DefineStmt | DeleteStmt | DiscardStmt *************** schema_stmt: *** 1105,1110 **** --- 1116,1251 ---- ; + + /***************************************************************************** + * + * DEFAULT PRIVILEGES for newly created objects in schema + * + *****************************************************************************/ + + DefaultACLsStmt: + ALTER SCHEMA name set_add DEFAULT PRIVILEGES + default_acls_object_list + { + DefaultACLsStmt *n = makeNode(DefaultACLsStmt); + n->schemaname = $3; + n->action = $4; + n->objlist = $7; + $$ = (Node*)n; + } + ; + + DefaultACLsDropStmt: + ALTER SCHEMA name DROP DEFAULT PRIVILEGES + default_acls_object_list_drop + { + DefaultACLsStmt *n = makeNode(DefaultACLsStmt); + n->schemaname = $3; + n->action = -1; + n->objlist = $7; + $$ = (Node*)n; + } + ; + + set_add: SET { $$ = 0; } + | ADD_P { $$ = +1; } + ; + + default_acls_object: + ON default_priv_target default_acls_priv_list + { + DefaultACLsObject *n = makeNode(DefaultACLsObject); + n->objtype = $2; + n->privileges = $3; + $$ = n; + } + ; + + default_acls_object_list: + default_acls_object { $$ = list_make1($1); } + | default_acls_object_list default_acls_object { $$ = lappend($1, $2); } + ; + + default_acls_priv: + default_priv_type TO grantee_list opt_grant_grant_option + { + DefaultACLsPriv *n = makeNode(DefaultACLsPriv); + n->privileges = $1; + n->grantees = $3; + n->grant_option = $4; + $$ = n; + } + ; + + default_acls_priv_list: + default_acls_priv { $$ = list_make1($1); } + | default_acls_priv_list AND default_acls_priv { $$ = lappend($1, $3); } + ; + + default_acls_object_drop: + ON default_priv_target default_acls_priv_list_drop + { + DefaultACLsObject *n = makeNode(DefaultACLsObject); + n->objtype = $2; + n->privileges = $3; + $$ = n; + } + ; + + default_acls_object_list_drop: + default_acls_object_drop { $$ = list_make1($1); } + | default_acls_object_list_drop default_acls_object_drop { $$ = lappend($1, $2); } + ; + + default_acls_priv_drop: + opt_revoke_grant_option default_priv_type FROM grantee_list + { + DefaultACLsPriv *n = makeNode(DefaultACLsPriv); + n->privileges = $2; + n->grantees = $4; + n->grant_option = $1; + $$ = n; + } + ; + + default_acls_priv_list_drop: + default_acls_priv_drop { $$ = list_make1($1); } + | default_acls_priv_list_drop AND default_acls_priv_drop { $$ = lappend($1, $3); } + ; + + default_priv_type: + default_priv_list + { $$ = $1; } + | ALL + { $$ = NIL; } + | ALL PRIVILEGES + { $$ = NIL; } + ; + + default_priv_list: + default_priv { $$ = list_make1($1); } + | default_priv_list ',' default_priv { $$ = lappend($1, $3); } + ; + + default_priv: + SELECT { $$ = pstrdup($1); } + | REFERENCES { $$ = pstrdup($1); } + | CREATE { $$ = pstrdup($1); } + | ColId { $$ = $1; } + ; + + default_priv_target: + TABLE { $$ = NSP_ACL_OBJ_RELATION; } + | VIEW { $$ = NSP_ACL_OBJ_VIEW; } + | FUNCTION { $$ = NSP_ACL_OBJ_FUNCTION; } + | SEQUENCE { $$ = NSP_ACL_OBJ_SEQUENCE; } + ; + + opt_revoke_grant_option: + GRANT OPTION FOR { $$ = TRUE; } + | /*EMPTY*/ { $$ = FALSE; } + ; + /***************************************************************************** * * Set PG internal variable *************** GrantStmt: GRANT privileges ON privilege *** 4238,4243 **** --- 4379,4395 ---- n->grant_option = $7; $$ = (Node*)n; } + | GRANT DEFAULT PRIVILEGES ON privilege_target + { + GrantStmt *n = makeNode(GrantStmt); + n->is_grant = true; + n->privileges = NIL; + n->objtype = ($5)->objtype; + n->objects = ($5)->objs; + n->grantees = NIL; + n->grant_option = FALSE; + $$ = (Node*)n; + } ; RevokeStmt: *************** privilege_target: *** 4354,4359 **** --- 4506,4518 ---- n->objs = $2; $$ = n; } + | VIEW qualified_name_list + { + PrivTarget *n = (PrivTarget *) palloc(sizeof(PrivTarget)); + n->objtype = ACL_OBJECT_RELATION; + n->objs = $2; + $$ = n; + } | SEQUENCE qualified_name_list { PrivTarget *n = (PrivTarget *) palloc(sizeof(PrivTarget)); diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index 54cc9e9..57f0230 100644 *** a/src/backend/parser/parse_utilcmd.c --- b/src/backend/parser/parse_utilcmd.c *************** typedef struct *** 91,96 **** --- 91,104 ---- List *grants; /* GRANT items */ } CreateSchemaStmtContext; + /* State for transformDefaultACLsStmt */ + typedef struct + { + DefaultACLsObject *table; /* TABLE permissions */ + DefaultACLsObject *view; /* VIEW permissions */ + DefaultACLsObject *function; /* FUNCTION permissions */ + DefaultACLsObject *sequence; /* SEQUENCE permissions */ + } DefaultACLsStmtContext; static void transformColumnDefinition(ParseState *pstate, CreateStmtContext *cxt, *************** static void transformFKConstraints(Parse *** 114,119 **** --- 122,128 ---- static void transformConstraintAttrs(List *constraintList); static void transformColumnType(ParseState *pstate, ColumnDef *column); static void setSchemaName(char *context_schema, char **stmt_schema_name); + static DefaultACLsObject *mergeDefaultACLsObjects(DefaultACLsObject *a, DefaultACLsObject *b); /* *************** setSchemaName(char *context_schema, char *** 2115,2117 **** --- 2124,2203 ---- "different from the one being created (%s)", *stmt_schema_name, context_schema))); } + + + /* + * transformDefaultACLsStmt + * merge duplicate DefaultACLsObjects + * + * We also check if we are setting default ACLs only for supported objects, + * so that we can use functions from aclchk.c which allow more object types. + */ + List * + transformDefaultACLsStmt(DefaultACLsStmt *stmt) + { + DefaultACLsStmtContext cxt; + ListCell *object_cell; + List *result; + + cxt.table = NULL; + cxt.view = NULL; + cxt.function = NULL; + cxt.sequence = NULL; + + foreach(object_cell, stmt->objlist) + { + DefaultACLsObject *object = (DefaultACLsObject *) lfirst(object_cell); + + switch (object->objtype) + { + case NSP_ACL_OBJ_RELATION: + cxt.table = mergeDefaultACLsObjects(cxt.table, object); + break; + + case NSP_ACL_OBJ_VIEW: + cxt.view = mergeDefaultACLsObjects(cxt.view, object); + break; + + case NSP_ACL_OBJ_FUNCTION: + cxt.function = mergeDefaultACLsObjects(cxt.function, object); + break; + + case NSP_ACL_OBJ_SEQUENCE: + cxt.sequence = mergeDefaultACLsObjects(cxt.sequence, object); + break; + + default: + elog(ERROR, "unrecognized objtype: %d", + (int) object->objtype); + } + } + + result = NIL; + result = lappend(result, cxt.table); + result = lappend(result, cxt.view); + result = lappend(result, cxt.function); + result = lappend(result, cxt.sequence); + + return result; + } + + + static DefaultACLsObject* + mergeDefaultACLsObjects(DefaultACLsObject *a, DefaultACLsObject *b) + { + if (a == NULL) + return b; + if (b == NULL) + return a; + + /* sanity checks */ + if (a == b) + elog(ERROR, "cannot merge DefaultACLsObject to itself"); + + Assert(a->objtype == b->objtype); + + a->privileges = list_concat(a->privileges, b->privileges); + + return a; + } diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index c9f2b87..5f00fc7 100644 *** a/src/backend/tcop/utility.c --- b/src/backend/tcop/utility.c *************** check_xact_readonly(Node *parsetree) *** 180,185 **** --- 180,186 ---- case T_AlterOpFamilyStmt: case T_RuleStmt: case T_CreateSchemaStmt: + case T_DefaultACLsStmt: case T_CreateSeqStmt: case T_CreateStmt: case T_CreateTableSpaceStmt: *************** ProcessUtility(Node *parsetree, *** 406,411 **** --- 407,416 ---- queryString); break; + case T_DefaultACLsStmt: + DefaultACLsCommand((DefaultACLsStmt *) parsetree); + break; + case T_CreateStmt: { List *stmts; *************** CreateCommandTag(Node *parsetree) *** 1362,1367 **** --- 1367,1376 ---- tag = "CREATE SCHEMA"; break; + case T_DefaultACLsStmt: + tag = "ALTER SCHEMA"; + break; + case T_CreateStmt: tag = "CREATE TABLE"; break; *************** GetCommandLogLevel(Node *parsetree) *** 2131,2136 **** --- 2140,2149 ---- lev = LOGSTMT_DDL; break; + case T_DefaultACLsStmt: + lev = LOGSTMT_DDL; + break; + case T_CreateStmt: lev = LOGSTMT_DDL; break; diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c index dbd3619..7c56b6f 100644 *** a/src/backend/utils/cache/syscache.c --- b/src/backend/utils/cache/syscache.c *************** *** 36,41 **** --- 36,42 ---- #include "catalog/pg_foreign_server.h" #include "catalog/pg_language.h" #include "catalog/pg_namespace.h" + #include "catalog/pg_namespace_default_acl.h" #include "catalog/pg_opclass.h" #include "catalog/pg_operator.h" #include "catalog/pg_opfamily.h" diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h index a2f6761..246a113 100644 *** a/src/include/catalog/dependency.h --- b/src/include/catalog/dependency.h *************** typedef enum ObjectClass *** 146,151 **** --- 146,152 ---- OCLASS_FDW, /* pg_foreign_data_wrapper */ OCLASS_FOREIGN_SERVER, /* pg_foreign_server */ OCLASS_USER_MAPPING, /* pg_user_mapping */ + OCLASS_NSPDEFACLS, /* pg_namespace_default_acl */ MAX_OCLASS /* MUST BE LAST */ } ObjectClass; diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h index 81e18a1..a4b9d48 100644 *** a/src/include/catalog/indexing.h --- b/src/include/catalog/indexing.h *************** DECLARE_UNIQUE_INDEX(pg_user_mapping_oid *** 267,272 **** --- 267,278 ---- DECLARE_UNIQUE_INDEX(pg_user_mapping_user_server_index, 175, on pg_user_mapping using btree(umuser oid_ops, umserver oid_ops)); #define UserMappingUserServerIndexId 175 + DECLARE_UNIQUE_INDEX(pg_namespace_default_acl_nspobj_index, 626, on pg_namespace_default_acl using btree(defaclnamespace oid_ops, defaclobjtype char_ops)); + #define NamespaceDefaultAclNspObjIndexId 626 + DECLARE_UNIQUE_INDEX(pg_namespace_default_acl_oid_index, 627, on pg_namespace_default_acl using btree(oid oid_ops)); + #define NamespaceDefaultAclOidIndexId 627 + + /* last step of initialization script: build the indexes declared above */ BUILD_INDICES diff --git a/src/include/catalog/pg_namespace_default_acl.h b/src/include/catalog/pg_namespace_default_acl.h index ...f969a8d . *** a/src/include/catalog/pg_namespace_default_acl.h --- b/src/include/catalog/pg_namespace_default_acl.h *************** *** 0 **** --- 1,79 ---- + /*------------------------------------------------------------------------- + * + * pg_namespace_default_acl.h + * definition of default ACLs for new objects created in schemas + * Specified by schema and grantable object type + * + * + * Copyright (c) 2009, PostgreSQL Global Development Group + * + * $Id$ + * + * NOTES + * the genbki.sh script reads this file and generates .bki + * information from the DATA() statements. + * + *------------------------------------------------------------------------- + */ + #ifndef PG_NAMESPACE_DEFAULT_ACL_H + #define PG_NAMESPACE_DEFAULT_ACL_H + + #include "catalog/genbki.h" + + /* ---------------- + * pg_namespace_default_acl definition. cpp turns this into + * typedef struct FormData_pg_namespace_default_acl + * ---------------- + */ + #define NamespaceDefaultAclRelationId 1248 + + CATALOG(pg_namespace_default_acl,1248) /*BKI_BOOTSTRAP*/ + { + Oid defaclnamespace; /* OID of namespace these ACLs apply to */ + char defaclobjtype; /* see DEFACL_OBJECT_xxx constants below */ + + /* + * VARIABLE LENGTH FIELDS start here. These fields may be NULL, too. + */ + + aclitem defacllist[1]; /* Permissions to be applied at CREATE time */ + } FormData_pg_namespace_default_acl; + + /* Size of fixed part of pg_namespace_default_acl tuples, not counting var-length fields */ + #define NAMESPACE_DEFAULT_ACL_TUPLE_SIZE \ + (offsetof(FormData_pg_namespace_default_acl,relfrozenxid) + sizeof(TransactionId)) + + /* ---------------- + * Form_pg_namespace_default_acl corresponds to a pointer to a tuple with + * the format of pg_namespace_default_acl relation. + * ---------------- + */ + typedef FormData_pg_namespace_default_acl *Form_pg_namespace_default_acl; + + /* ---------------- + * compiler constants for pg_namespace_default_acl + * ---------------- + */ + + #define Natts_pg_namespace_default_acl 3 + #define Anum_pg_namespace_default_acl_defaclnamespace 1 + #define Anum_pg_namespace_default_acl_defaclgrantobjtype 2 + #define Anum_pg_namespace_default_acl_defacldefacllist 3 + + /* ---------------- + * pg_namespace_default_acl has no initial contents + * ---------------- + */ + + /* + * Types of objects which the user is allowed to specify default + * permissions on through pg_namespace_default_acl. + * Must have same value as relkind for objects stored in pg_class ! + */ + + #define NSPDEFACLOBJ_TABLE 'r' /* table */ + #define NSPDEFACLOBJ_VIEW 'v' /* view */ + #define NSPDEFACLOBJ_FUNCTION 'f' /* function */ + #define NSPDEFACLOBJ_SEQUENCE 'S' /* sequence */ + + #endif /* PG_NAMESPACE_DEFAULT_ACL_H */ diff --git a/src/include/commands/schemacmds.h b/src/include/commands/schemacmds.h index 5f384a1..1067bee 100644 *** a/src/include/commands/schemacmds.h --- b/src/include/commands/schemacmds.h *************** *** 20,25 **** --- 20,28 ---- extern void CreateSchemaCommand(CreateSchemaStmt *parsetree, const char *queryString); + extern void DefaultACLsCommand(DefaultACLsStmt *parsetree); + extern void RemoveDefaultACLsById(Oid nsdaOid); + extern void RemoveSchemas(DropStmt *drop); extern void RemoveSchemaById(Oid schemaOid); diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h index 925375b..5ac8538 100644 *** a/src/include/nodes/nodes.h --- b/src/include/nodes/nodes.h *************** typedef enum NodeTag *** 337,342 **** --- 337,343 ---- T_CreateUserMappingStmt, T_AlterUserMappingStmt, T_DropUserMappingStmt, + T_DefaultACLsStmt, /* * TAGS FOR PARSE TREE NODES (parsenodes.h) *************** typedef enum NodeTag *** 376,381 **** --- 377,385 ---- T_XmlSerialize, T_WithClause, T_CommonTableExpr, + T_DefaultACLsObject, + T_DefaultACLsPriv, + /* * TAGS FOR RANDOM OTHER STUFF diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 9d53ab9..1452d9f 100644 *** a/src/include/nodes/parsenodes.h --- b/src/include/nodes/parsenodes.h *************** typedef enum DropBehavior *** 1079,1084 **** --- 1079,1085 ---- DROP_CASCADE /* remove dependent objects too */ } DropBehavior; + /* ---------------------- * Alter Table * ---------------------- *************** typedef struct GrantRoleStmt *** 1257,1262 **** --- 1258,1300 ---- DropBehavior behavior; /* drop behavior (for REVOKE) */ } GrantRoleStmt; + + /* ---------------------- + * Alter Schema DEFAULT PRIVILEGES + * ---------------------- + */ + typedef enum ACLSchemaObjectType + { + NSP_ACL_OBJ_RELATION, /* table */ + NSP_ACL_OBJ_VIEW, /* view */ + NSP_ACL_OBJ_FUNCTION, /* function */ + NSP_ACL_OBJ_SEQUENCE /* sequence */ + } ACLSchemaObjectType; + + typedef struct DefaultACLsStmt + { + NodeTag type; + char *schemaname; + int action; + List *objlist; + } DefaultACLsStmt; + + typedef struct DefaultACLsObject + { + NodeTag type; + ACLSchemaObjectType objtype; + List *privileges; + } DefaultACLsObject; + + typedef struct DefaultACLsPriv + { + NodeTag type; + List *privileges; + List *grantees; + bool grant_option; + } DefaultACLsPriv; + + /* ---------------------- * Copy Statement * diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 1fc3efd..98c13c4 100644 *** a/src/include/parser/parse_utilcmd.h --- b/src/include/parser/parse_utilcmd.h *************** extern IndexStmt *transformIndexStmt(Ind *** 24,28 **** --- 24,29 ---- extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); extern List *transformCreateSchemaStmt(CreateSchemaStmt *stmt); + extern List *transformDefaultACLsStmt(DefaultACLsStmt *stmt); #endif /* PARSE_UTILCMD_H */ diff --git a/src/include/utils/acl.h b/src/include/utils/acl.h index bde8727..b12e590 100644 *** a/src/include/utils/acl.h --- b/src/include/utils/acl.h *************** extern Datum hash_aclitem(PG_FUNCTION_AR *** 262,267 **** --- 262,274 ---- */ extern void ExecuteGrantStmt(GrantStmt *stmt); extern void ExecGrantStmt_oids(InternalGrant *istmt); + extern AclMode string_to_privilege(const char *privname); + extern const char *privilege_to_string(AclMode privilege); + extern Acl *merge_acl_with_grant(Acl *old_acl, bool is_grant, + bool grant_option, DropBehavior behavior, + List *grantees, AclMode privileges, + Oid grantorId, Oid ownerId); + extern AclMode pg_attribute_aclmask(Oid table_oid, AttrNumber attnum, Oid roleid, AclMode mask, AclMaskHow how); *************** extern bool pg_ts_dict_ownercheck(Oid di *** 317,320 **** --- 324,329 ---- extern bool pg_ts_config_ownercheck(Oid cfg_oid, Oid roleid); extern bool pg_foreign_server_ownercheck(Oid srv_oid, Oid roleid); + extern Acl *pg_namespace_object_default_acl(Oid nsp_oid, char objtype, bool *isNull); + #endif /* ACL_H */ diff --git a/src/test/regress/expected/privileges.out b/src/test/regress/expected/privileges.out index a17ff59..2fa466f 100644 *** a/src/test/regress/expected/privileges.out --- b/src/test/regress/expected/privileges.out *************** SELECT has_table_privilege('regressuser1 *** 815,822 **** --- 815,945 ---- t (1 row) + -- Default ACLs + \c - + CREATE SCHEMA regressns; + GRANT USAGE ON SCHEMA regressns TO regressgroup1, regressuser2; + -- bad input + ALTER SCHEMA regressns SET DEFAULT PRIVILEGES ON FUNCTION USAGE TO regressgroup1; + ERROR: invalid privilege type USAGE for function + -- try different possible syntaxes from simple ones to more complex ones + ALTER SCHEMA regressns SET DEFAULT PRIVILEGES ON TABLE SELECT TO public; + CREATE TABLE regressns.acltest1 (one int, two int, three int); + SELECT has_table_privilege('regressuser1', 'regressns.acltest1', 'INSERT'); -- false + has_table_privilege + --------------------- + f + (1 row) + + SELECT has_table_privilege('regressuser1', 'regressns.acltest1', 'SELECT'); -- true + has_table_privilege + --------------------- + t + (1 row) + + ALTER SCHEMA regressns SET DEFAULT PRIVILEGES ON TABLE SELECT TO regressgroup1, regressuser2 AND UPDATE TO regressuser2 AND INSERT, DELETE TO regressuser2; + CREATE TABLE regressns.acltest2 (one int, two int, three int); + SELECT has_table_privilege('regressuser2', 'regressns.acltest2', 'DELETE'); -- true + has_table_privilege + --------------------- + t + (1 row) + + SELECT has_table_privilege('regressuser4', 'regressns.acltest2', 'INSERT'); -- false + has_table_privilege + --------------------- + f + (1 row) + + SELECT has_table_privilege('regressuser4', 'regressns.acltest2', 'SELECT'); -- true (as member of regressgroup1) + has_table_privilege + --------------------- + t + (1 row) + + ALTER SCHEMA regressns ADD DEFAULT PRIVILEGES ON TABLE INSERT TO regressgroup1; + CREATE TABLE regressns.acltest3 (foo text); + SELECT has_table_privilege('regressuser4', 'regressns.acltest3', 'INSERT'); -- true + has_table_privilege + --------------------- + t + (1 row) + + ALTER SCHEMA regressns DROP DEFAULT PRIVILEGES ON TABLE DELETE FROM regressuser2; + -- supress implicit sequence creation NOTICE + SET client_min_messages TO 'error'; + CREATE TABLE regressns.acltest4 (bar serial); + RESET client_min_messages; + SELECT has_table_privilege('regressuser2', 'regressns.acltest4', 'DELETE'); -- false + has_table_privilege + --------------------- + f + (1 row) + + SET SESSION AUTHORIZATION regressuser2; + SELECT nextval(pg_get_serial_sequence('regressns.acltest4', 'bar')::regclass); -- error + ERROR: permission denied for sequence acltest4_bar_seq + RESET SESSION AUTHORIZATION; + ALTER SCHEMA regressns SET DEFAULT PRIVILEGES + ON TABLE SELECT TO regressgroup1, regressuser2 AND UPDATE,INSERT,DELETE TO regressuser2 + ON VIEW SELECT TO regressgroup1 AND ALL PRIVILEGES TO regressuser2 WITH GRANT OPTION + ON SEQUENCE ALL TO regressgroup1, regressuser2 + ON FUNCTION EXECUTE TO regressgroup1; + -- supress implicit sequence creation NOTICE + SET client_min_messages TO 'error'; + CREATE TABLE regressns.acltest5 (a serial, b text); + RESET client_min_messages; + SELECT has_table_privilege('regressgroup1', 'regressns.acltest5', 'INSERT'); -- false + has_table_privilege + --------------------- + f + (1 row) + + SELECT has_table_privilege('regressuser2', 'regressns.acltest5', 'INSERT'); -- true + has_table_privilege + --------------------- + t + (1 row) + + SET SESSION AUTHORIZATION regressuser2; + SELECT nextval(pg_get_serial_sequence('regressns.acltest5', 'a')::regclass); -- 1 + nextval + --------- + 1 + (1 row) + + RESET SESSION AUTHORIZATION; + CREATE FUNCTION regressns.testfunc1() RETURNS int AS 'select 1;' LANGUAGE sql; + SELECT has_function_privilege('regressgroup1', 'regressns.testfunc1()', 'EXECUTE'); -- true + has_function_privilege + ------------------------ + t + (1 row) + + CREATE VIEW regressns.acltest6 AS SELECT * FROM regressns.acltest4; + SET SESSION AUTHORIZATION regressuser2; + GRANT SELECT ON regressns.acltest6 TO regressuser1; + SELECT has_table_privilege('regressuser1', 'regressns.acltest6', 'SELECT'); -- true + has_table_privilege + --------------------- + t + (1 row) + -- clean up \c + DROP SCHEMA regressns CASCADE; + NOTICE: drop cascades to 11 other objects + DETAIL: drop cascades to default acls in namespace regressns for new TABLE + drop cascades to table regressns.acltest1 + drop cascades to table regressns.acltest2 + drop cascades to table regressns.acltest3 + drop cascades to table regressns.acltest4 + drop cascades to default acls in namespace regressns for new VIEW + drop cascades to default acls in namespace regressns for new SEQUENCE + drop cascades to default acls in namespace regressns for new FUNCTION + drop cascades to table regressns.acltest5 + drop cascades to function regressns.testfunc1() + drop cascades to view regressns.acltest6 DROP FUNCTION testfunc2(int); DROP FUNCTION testfunc4(boolean); DROP VIEW atestv1; diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out index c6f1f15..37411f6 100644 *** a/src/test/regress/expected/sanity_check.out --- b/src/test/regress/expected/sanity_check.out *************** SELECT relname, relhasindex *** 9,157 **** FROM pg_class c LEFT JOIN pg_namespace n ON n.oid = relnamespace WHERE relkind = 'r' AND (nspname ~ '^pg_temp_') IS NOT TRUE ORDER BY relname; ! relname | relhasindex ! -------------------------+------------- ! a | f ! a_star | f ! abstime_tbl | f ! aggtest | f ! array_index_op_test | t ! array_op_test | f ! b | f ! b_star | f ! box_tbl | f ! bprime | f ! bt_f8_heap | t ! bt_i4_heap | t ! bt_name_heap | t ! bt_txt_heap | t ! c | f ! c_star | f ! char_tbl | f ! check2_tbl | f ! check_tbl | f ! circle_tbl | t ! city | f ! copy_tbl | f ! d | f ! d_star | f ! date_tbl | f ! default_tbl | f ! defaultexpr_tbl | f ! dept | f ! e_star | f ! emp | f ! equipment_r | f ! f_star | f ! fast_emp4000 | t ! float4_tbl | f ! float8_tbl | f ! func_index_heap | t ! hash_f8_heap | t ! hash_i4_heap | t ! hash_name_heap | t ! hash_txt_heap | t ! hobbies_r | f ! ihighway | t ! inet_tbl | f ! inhe | f ! inhf | f ! inhx | t ! insert_tbl | f ! int2_tbl | f ! int4_tbl | f ! int8_tbl | f ! interval_tbl | f ! iportaltest | f ! log_table | f ! lseg_tbl | f ! main_table | f ! money_data | f ! num_data | f ! num_exp_add | t ! num_exp_div | t ! num_exp_ln | t ! num_exp_log10 | t ! num_exp_mul | t ! num_exp_power_10_ln | t ! num_exp_sqrt | t ! num_exp_sub | t ! num_input_test | f ! num_result | f ! onek | t ! onek2 | t ! path_tbl | f ! person | f ! pg_aggregate | t ! pg_am | t ! pg_amop | t ! pg_amproc | t ! pg_attrdef | t ! pg_attribute | t ! pg_auth_members | t ! pg_authid | t ! pg_cast | t ! pg_class | t ! pg_constraint | t ! pg_conversion | t ! pg_database | t ! pg_depend | t ! pg_description | t ! pg_enum | t ! pg_foreign_data_wrapper | t ! pg_foreign_server | t ! pg_index | t ! pg_inherits | t ! pg_language | t ! pg_largeobject | t ! pg_listener | f ! pg_namespace | t ! pg_opclass | t ! pg_operator | t ! pg_opfamily | t ! pg_pltemplate | t ! pg_proc | t ! pg_rewrite | t ! pg_shdepend | t ! pg_shdescription | t ! pg_statistic | t ! pg_tablespace | t ! pg_trigger | t ! pg_ts_config | t ! pg_ts_config_map | t ! pg_ts_dict | t ! pg_ts_parser | t ! pg_ts_template | t ! pg_type | t ! pg_user_mapping | t ! point_tbl | f ! polygon_tbl | t ! ramp | f ! real_city | f ! reltime_tbl | f ! road | t ! shighway | t ! slow_emp4000 | f ! sql_features | f ! sql_implementation_info | f ! sql_languages | f ! sql_packages | f ! sql_parts | f ! sql_sizing | f ! sql_sizing_profiles | f ! stud_emp | f ! student | f ! tenk1 | t ! tenk2 | t ! test_tsvector | f ! text_tbl | f ! time_tbl | f ! timestamp_tbl | f ! timestamptz_tbl | f ! timetz_tbl | f ! tinterval_tbl | f ! varchar_tbl | f ! (140 rows) -- -- another sanity check: every system catalog that has OIDs should have --- 9,158 ---- FROM pg_class c LEFT JOIN pg_namespace n ON n.oid = relnamespace WHERE relkind = 'r' AND (nspname ~ '^pg_temp_') IS NOT TRUE ORDER BY relname; ! relname | relhasindex ! --------------------------+------------- ! a | f ! a_star | f ! abstime_tbl | f ! aggtest | f ! array_index_op_test | t ! array_op_test | f ! b | f ! b_star | f ! box_tbl | f ! bprime | f ! bt_f8_heap | t ! bt_i4_heap | t ! bt_name_heap | t ! bt_txt_heap | t ! c | f ! c_star | f ! char_tbl | f ! check2_tbl | f ! check_tbl | f ! circle_tbl | t ! city | f ! copy_tbl | f ! d | f ! d_star | f ! date_tbl | f ! default_tbl | f ! defaultexpr_tbl | f ! dept | f ! e_star | f ! emp | f ! equipment_r | f ! f_star | f ! fast_emp4000 | t ! float4_tbl | f ! float8_tbl | f ! func_index_heap | t ! hash_f8_heap | t ! hash_i4_heap | t ! hash_name_heap | t ! hash_txt_heap | t ! hobbies_r | f ! ihighway | t ! inet_tbl | f ! inhe | f ! inhf | f ! inhx | t ! insert_tbl | f ! int2_tbl | f ! int4_tbl | f ! int8_tbl | f ! interval_tbl | f ! iportaltest | f ! log_table | f ! lseg_tbl | f ! main_table | f ! money_data | f ! num_data | f ! num_exp_add | t ! num_exp_div | t ! num_exp_ln | t ! num_exp_log10 | t ! num_exp_mul | t ! num_exp_power_10_ln | t ! num_exp_sqrt | t ! num_exp_sub | t ! num_input_test | f ! num_result | f ! onek | t ! onek2 | t ! path_tbl | f ! person | f ! pg_aggregate | t ! pg_am | t ! pg_amop | t ! pg_amproc | t ! pg_attrdef | t ! pg_attribute | t ! pg_auth_members | t ! pg_authid | t ! pg_cast | t ! pg_class | t ! pg_constraint | t ! pg_conversion | t ! pg_database | t ! pg_depend | t ! pg_description | t ! pg_enum | t ! pg_foreign_data_wrapper | t ! pg_foreign_server | t ! pg_index | t ! pg_inherits | t ! pg_language | t ! pg_largeobject | t ! pg_listener | f ! pg_namespace | t ! pg_namespace_default_acl | t ! pg_opclass | t ! pg_operator | t ! pg_opfamily | t ! pg_pltemplate | t ! pg_proc | t ! pg_rewrite | t ! pg_shdepend | t ! pg_shdescription | t ! pg_statistic | t ! pg_tablespace | t ! pg_trigger | t ! pg_ts_config | t ! pg_ts_config_map | t ! pg_ts_dict | t ! pg_ts_parser | t ! pg_ts_template | t ! pg_type | t ! pg_user_mapping | t ! point_tbl | f ! polygon_tbl | t ! ramp | f ! real_city | f ! reltime_tbl | f ! road | t ! shighway | t ! slow_emp4000 | f ! sql_features | f ! sql_implementation_info | f ! sql_languages | f ! sql_packages | f ! sql_parts | f ! sql_sizing | f ! sql_sizing_profiles | f ! stud_emp | f ! student | f ! tenk1 | t ! tenk2 | t ! test_tsvector | f ! text_tbl | f ! time_tbl | f ! timestamp_tbl | f ! timestamptz_tbl | f ! timetz_tbl | f ! tinterval_tbl | f ! varchar_tbl | f ! (141 rows) -- -- another sanity check: every system catalog that has OIDs should have diff --git a/src/test/regress/sql/privileges.sql b/src/test/regress/sql/privileges.sql index 5aa1012..eab3a3b 100644 *** a/src/test/regress/sql/privileges.sql --- b/src/test/regress/sql/privileges.sql *************** SELECT has_table_privilege('regressuser3 *** 469,478 **** --- 469,542 ---- SELECT has_table_privilege('regressuser1', 'atest4', 'SELECT WITH GRANT OPTION'); -- true + -- Default ACLs + + \c - + + CREATE SCHEMA regressns; + GRANT USAGE ON SCHEMA regressns TO regressgroup1, regressuser2; + + -- bad input + + ALTER SCHEMA regressns SET DEFAULT PRIVILEGES ON FUNCTION USAGE TO regressgroup1; + + -- try different possible syntaxes from simple ones to more complex ones + + ALTER SCHEMA regressns SET DEFAULT PRIVILEGES ON TABLE SELECT TO public; + CREATE TABLE regressns.acltest1 (one int, two int, three int); + SELECT has_table_privilege('regressuser1', 'regressns.acltest1', 'INSERT'); -- false + SELECT has_table_privilege('regressuser1', 'regressns.acltest1', 'SELECT'); -- true + + ALTER SCHEMA regressns SET DEFAULT PRIVILEGES ON TABLE SELECT TO regressgroup1, regressuser2 AND UPDATE TO regressuser2 AND INSERT, DELETE TO regressuser2; + CREATE TABLE regressns.acltest2 (one int, two int, three int); + SELECT has_table_privilege('regressuser2', 'regressns.acltest2', 'DELETE'); -- true + SELECT has_table_privilege('regressuser4', 'regressns.acltest2', 'INSERT'); -- false + SELECT has_table_privilege('regressuser4', 'regressns.acltest2', 'SELECT'); -- true (as member of regressgroup1) + + ALTER SCHEMA regressns ADD DEFAULT PRIVILEGES ON TABLE INSERT TO regressgroup1; + CREATE TABLE regressns.acltest3 (foo text); + SELECT has_table_privilege('regressuser4', 'regressns.acltest3', 'INSERT'); -- true + + ALTER SCHEMA regressns DROP DEFAULT PRIVILEGES ON TABLE DELETE FROM regressuser2; + -- supress implicit sequence creation NOTICE + SET client_min_messages TO 'error'; + CREATE TABLE regressns.acltest4 (bar serial); + RESET client_min_messages; + SELECT has_table_privilege('regressuser2', 'regressns.acltest4', 'DELETE'); -- false + SET SESSION AUTHORIZATION regressuser2; + SELECT nextval(pg_get_serial_sequence('regressns.acltest4', 'bar')::regclass); -- error + RESET SESSION AUTHORIZATION; + + ALTER SCHEMA regressns SET DEFAULT PRIVILEGES + ON TABLE SELECT TO regressgroup1, regressuser2 AND UPDATE,INSERT,DELETE TO regressuser2 + ON VIEW SELECT TO regressgroup1 AND ALL PRIVILEGES TO regressuser2 WITH GRANT OPTION + ON SEQUENCE ALL TO regressgroup1, regressuser2 + ON FUNCTION EXECUTE TO regressgroup1; + + -- supress implicit sequence creation NOTICE + SET client_min_messages TO 'error'; + CREATE TABLE regressns.acltest5 (a serial, b text); + RESET client_min_messages; + SELECT has_table_privilege('regressgroup1', 'regressns.acltest5', 'INSERT'); -- false + SELECT has_table_privilege('regressuser2', 'regressns.acltest5', 'INSERT'); -- true + SET SESSION AUTHORIZATION regressuser2; + SELECT nextval(pg_get_serial_sequence('regressns.acltest5', 'a')::regclass); -- 1 + RESET SESSION AUTHORIZATION; + + CREATE FUNCTION regressns.testfunc1() RETURNS int AS 'select 1;' LANGUAGE sql; + SELECT has_function_privilege('regressgroup1', 'regressns.testfunc1()', 'EXECUTE'); -- true + + CREATE VIEW regressns.acltest6 AS SELECT * FROM regressns.acltest4; + SET SESSION AUTHORIZATION regressuser2; + GRANT SELECT ON regressns.acltest6 TO regressuser1; + SELECT has_table_privilege('regressuser1', 'regressns.acltest6', 'SELECT'); -- true + -- clean up \c + DROP SCHEMA regressns CASCADE; + DROP FUNCTION testfunc2(int); DROP FUNCTION testfunc4(boolean);