diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml index cc40c14..52164d2 100644 *** a/doc/src/sgml/catalogs.sgml --- b/doc/src/sgml/catalogs.sgml *************** *** 3171,3176 **** --- 3171,3247 ---- + + <structname>pg_default_acls</structname> + + + pg_default_acls + + + + The catalog pg_default_acls stores default + privileges for newly created objects. + + + + <structname>pg_default_acls</> Columns + + + + + Name + Type + References + Description + + + + + + defaclrole + oid + pg_authid.oid + The OID of the role associated with this entry + + + + defaclnamespace + oid + pg_namespace.oid + The OID of the namespace associated with this entry + + + + defaclgrantobjtype + char + + + r = relation (table, view) + f = function, S = sequence + + + + + defacllist + aclitem[] + + + Access privileges 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/allfiles.sgml b/doc/src/sgml/ref/allfiles.sgml index dbf3a3a..87f33c8 100644 *** a/doc/src/sgml/ref/allfiles.sgml --- b/doc/src/sgml/ref/allfiles.sgml *************** Complete list of usable sgml source file *** 9,14 **** --- 9,15 ---- + diff --git a/doc/src/sgml/ref/alter_default_privileges.sgml b/doc/src/sgml/ref/alter_default_privileges.sgml index ...72bde1b . *** a/doc/src/sgml/ref/alter_default_privileges.sgml --- b/doc/src/sgml/ref/alter_default_privileges.sgml *************** *** 0 **** --- 1,197 ---- + + + + + ALTER DEFAULT PRIVILEGES + 7 + SQL - Language Statements + + + + ALTER DEFAULT PRIVILEGES + define default access privileges + + + + ALTER DEFAULT PRIVILEGES + + + + + ALTER DEFAULT PRIVILEGES + [ FOR { ROLE | USER } role_name [, ...] ] + [ IN SCHEMA schema_name [, ...] ] + grant_statement + + where grant_statement is: + GRANT { { { { SELECT | INSERT | UPDATE | DELETE | TRUNCATE | REFERENCES | TRIGGER } + [,...] | ALL [ PRIVILEGES ] } + ON TABLE + TO { [ GROUP ] rolename | PUBLIC } [, ...] [ WITH GRANT OPTION ] } | + { { { USAGE | SELECT | UPDATE } + [,...] | ALL [ PRIVILEGES ] } + ON SEQUENCE + TO { [ GROUP ] rolename | PUBLIC } [, ...] [ WITH GRANT OPTION ] } | + { { EXECUTE | ALL [ PRIVILEGES ] } + ON FUNCTION + TO { [ GROUP ] rolename | PUBLIC } [, ...] [ WITH GRANT OPTION ] } } + + ALTER DEFAULT PRIVILEGES + [ FOR { ROLE | USER } role_name [, ...] ] + [ IN SCHEMA schema_name [, ...] ] + revoke_statement + + where revoke_statement is: + REVOKE [ GRANT OPTION FOR ] + { { { { SELECT | INSERT | UPDATE | DELETE | TRUNCATE | REFERENCES | TRIGGER } + [,...] | ALL [ PRIVILEGES ] } + ON TABLE + FROM { [ GROUP ] rolename | PUBLIC } [, ...] + [ CASCADE | RESTRICT ] } | + { { { USAGE | SELECT | UPDATE } + [,...] | ALL [ PRIVILEGES ] } + ON SEQUENCE + FROM { [ GROUP ] rolename | PUBLIC } [, ...] + [ CASCADE | RESTRICT ] } | + { { EXECUTE | ALL [ PRIVILEGES ] } + ON FUNCTION + FROM { [ GROUP ] rolename | PUBLIC } [, ...] + [ CASCADE | RESTRICT ] } } + + + + + Description + + + ALTER DEFAULT PRIVILEGES command allows you to define privileges for newly + created objects. + + + + You can change default privileges only for yourself or roles for which + you have ADMIN privilege. + + + + If you ommit the schema_name parameter, + the specified default privileges will be used globaly for whole database. + + + + Default privileges are not inherited in any way. So if you for example + define database wide default privileges and also the default privileges + for new objects in schema public, then upon creating + new objects in schema public only those default + privileges defined for the schema will apply. + + + + See the description of the and + commands for more detailed + explanation of privilege system and the meaning of the privilege types. + + + + Parameters + + + + role_name + + + The name of an existing role for which you have ADMIN privilege. + + + + + + schema_name + + + The name of an existing schema. + + + + + + + + + Examples + + + Grant select privilege for every tables and views you'll create in schema + public to everybody: + + + ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLE TO public; + + + + + Grant admin SELECT, UPDATE, INSERT and DELETE + privileges on all tables and views you'll create in the users schema. + + + ALTER DEFAULT PRIVILEGES IN SCHEMA users GRANT SELECT, UPDATE, INSERT, DELETE TO admin; + + + + + Give user webuser SELECT privilege on all new + tables, views, and sequences, and EXECUTE on all new functions in + the public schema. + Allow the admin to not only select but also + change the contents of new tables and views in the public schema, and grant those permissions + to other users. The admin user will also be allowed to execute new functions, + and read new sequences like webuser. + Finally, allow admin to update new sequences. + All above only for objects your role will create. + + + ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLE TO webuser, admin; + ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT UPDATE, INSERT, DELETE ON TABLE TO admin WITH GRANT OPTION; + ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT EXECUTE ON FUNCTION TO webuser, admin; + ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT USAGE, SELECT ON SEQUENCE TO webuser; + ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL PRIVILEGES ON SEQUENCE TO admin; + + + + + For new tables and views created by role admin in any + schema in current database give role webuser + SELECT privilege (per schema default privileges can override it). + And for new functions created in schema public by role + admin give role webuser + EXECUTE privilege. + + + ALTER DEFAULT PRIVILEGES FOR ROLE admin GRANT SELECT ON TABLE TO webuser; + ALTER DEFAULT PRIVILEGES FOR ROLE admin IN SCHEMA public GRANT EXECUTE ON FUNCTION TO webuser; + + + + + + Compatibility + + + There is no ALTER DEFAULT PRIVILEGES statement in the SQL + standard. + + + + + See Also + + + + + + + + diff --git a/doc/src/sgml/ref/grant.sgml b/doc/src/sgml/ref/grant.sgml index 8aaedb3..8facea9 100644 *** a/doc/src/sgml/ref/grant.sgml --- b/doc/src/sgml/ref/grant.sgml *************** PostgreSQL documentation *** 22,28 **** GRANT { { SELECT | INSERT | UPDATE | DELETE | TRUNCATE | REFERENCES | TRIGGER } ! [,...] | ALL [ PRIVILEGES ] } ON [ TABLE ] table_name [, ...] TO { [ GROUP ] role_name | PUBLIC } [, ...] [ WITH GRANT OPTION ] --- 22,28 ---- GRANT { { SELECT | INSERT | UPDATE | DELETE | TRUNCATE | REFERENCES | TRIGGER } ! [,...] | ALL [ PRIVILEGES ] | DEFAULT PRIVILEGES } ON [ TABLE ] table_name [, ...] TO { [ GROUP ] role_name | PUBLIC } [, ...] [ WITH GRANT OPTION ] *************** GRANT { { SELECT | INSERT | UPDATE | REF *** 32,38 **** TO { [ GROUP ] role_name | PUBLIC } [, ...] [ WITH GRANT OPTION ] GRANT { { USAGE | SELECT | UPDATE } ! [,...] | ALL [ PRIVILEGES ] } ON SEQUENCE sequence_name [, ...] TO { [ GROUP ] role_name | PUBLIC } [, ...] [ WITH GRANT OPTION ] --- 32,38 ---- TO { [ GROUP ] role_name | PUBLIC } [, ...] [ WITH GRANT OPTION ] GRANT { { USAGE | SELECT | UPDATE } ! [,...] | ALL [ PRIVILEGES ] | DEFAULT PRIVILEGES } ON SEQUENCE sequence_name [, ...] TO { [ GROUP ] role_name | PUBLIC } [, ...] [ WITH GRANT OPTION ] *************** GRANT { USAGE | ALL [ PRIVILEGES ] } *** 48,54 **** ON FOREIGN SERVER server_name [, ...] TO { [ GROUP ] role_name | PUBLIC } [, ...] [ WITH GRANT OPTION ] ! GRANT { EXECUTE | ALL [ PRIVILEGES ] } ON FUNCTION function_name ( [ [ argmode ] [ arg_name ] arg_type [, ...] ] ) [, ...] TO { [ GROUP ] role_name | PUBLIC } [, ...] [ WITH GRANT OPTION ] --- 48,54 ---- ON FOREIGN SERVER server_name [, ...] TO { [ GROUP ] role_name | PUBLIC } [, ...] [ WITH GRANT OPTION ] ! GRANT { EXECUTE | ALL [ PRIVILEGES ] | DEFAULT PRIVILEGES } ON FUNCTION function_name ( [ [ argmode ] [ arg_name ] arg_type [, ...] ] ) [, ...] TO { [ GROUP ] role_name | PUBLIC } [, ...] [ WITH GRANT OPTION ] *************** GRANT rol *** 137,142 **** --- 137,144 ---- 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 DEFAULT PRIVILEGES command; CONNECT privilege and TEMP table creation privilege for databases; EXECUTE privilege for functions; and *************** GRANT rol *** 344,349 **** --- 346,368 ---- + + + 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. You also can't specify + TO role_name for above reason. + Note: this can actually revoke some + privileges because it clears all existing privileges object has and + replaces them with the default ones for the schema in which this object + resides. + + + The privileges required by other commands are listed on the diff --git a/doc/src/sgml/reference.sgml b/doc/src/sgml/reference.sgml index 723bb9c..ac95ec8 100644 *** a/doc/src/sgml/reference.sgml --- b/doc/src/sgml/reference.sgml *************** *** 37,42 **** --- 37,43 ---- &alterAggregate; &alterConversion; &alterDatabase; + &alterDefaultPrivileges; &alterDomain; &alterForeignDataWrapper; &alterFunction; diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile index 861cb1d..d80d72b 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_default_acls.h \ toasting.h indexing.h \ ) diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c index ec4aaf0..c859e2d 100644 *** a/src/backend/catalog/aclchk.c --- b/src/backend/catalog/aclchk.c *************** *** 27,32 **** --- 27,33 ---- #include "catalog/pg_authid.h" #include "catalog/pg_conversion.h" #include "catalog/pg_database.h" + #include "catalog/pg_default_acls.h" #include "catalog/pg_foreign_data_wrapper.h" #include "catalog/pg_foreign_server.h" #include "catalog/pg_language.h" *************** *** 51,57 **** --- 52,61 ---- #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 AclMode restrict_and_check_grant( *** 79,84 **** --- 83,90 ---- static AclMode pg_aclmask(AclObjectKind objkind, Oid table_oid, AttrNumber attnum, Oid roleid, AclMode mask, AclMaskHow how); + static Acl *get_default_acl(Oid roleId, Oid nsp_oid, char objtype); + #ifdef ACLDEBUG static void *************** ExecuteGrantStmt(GrantStmt *stmt) *** 294,299 **** --- 300,314 ---- istmt.grant_option = stmt->grant_option; istmt.behavior = stmt->behavior; + /* + * GRANT DEFAULT PRIVILEGES + */ + if (stmt->grantees == NIL) + { + 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 *************** objectNamesToOids(GrantObjectType objtyp *** 610,615 **** --- 625,1086 ---- } /* + * Default ACLs + * Implements ALTER DEFAULT PRIVILEGES + */ + void + ExecAlterDefaultPrivilegesStmt(AlterDefaultPrivilegesStmt *stmt) + { + InternalDefaultACLs iacls; + GrantStmt *action; + ListCell *cell; + ListCell *rolecell; + ListCell *nspcell; + List *rolenames = NIL; + List *roleids = NIL; + List *nspnames = NIL; + List *nspids = NIL; + DefElem *drolenames = NULL; + DefElem *dnspnames = NULL; + Relation rolerel; + Relation nsprel; + const char *errormsg; + AclMode all_privileges; + + Assert(IsA(stmt->action, GrantStmt)); + + foreach(cell, stmt->options) + { + DefElem *defel = (DefElem *) lfirst(cell); + + if (strcmp(defel->defname, "schemas") == 0) + { + if (dnspnames) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("conflicting or redundant options"))); + dnspnames = defel; + } + else if (strcmp(defel->defname, "roles") == 0) + { + if (drolenames) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("conflicting or redundant options"))); + drolenames = defel; + } + } + + if (dnspnames) + nspnames = (List *) dnspnames->arg; + if (drolenames) + rolenames = (List *) drolenames->arg; + + action = (GrantStmt *) stmt->action; + iacls.is_grant = action->is_grant; + iacls.objtype = action->objtype; + /* privileges to be filled below */ + iacls.grantees = NIL; /* filled below */ + iacls.grant_option = action->grant_option; + iacls.behavior = action->behavior; + + /* + * 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 + * detected (which is what the grammar uses if PUBLIC is found), so + * downstream there shouldn't be any additional work needed to support + * this case. + */ + foreach(cell, action->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 stmt->privileges, a list of AccessPriv nodes, into an AclMode + * bitmask. Note: objtype can't be ACL_OBJECT_COLUMN. + */ + switch (action->objtype) + { + case ACL_OBJECT_RELATION: + all_privileges = ACL_ALL_RIGHTS_RELATION; + errormsg = gettext_noop("invalid privilege type %s for relation"); + break; + case ACL_OBJECT_SEQUENCE: + all_privileges = ACL_ALL_RIGHTS_SEQUENCE; + errormsg = gettext_noop("invalid privilege type %s for sequence"); + break; + case ACL_OBJECT_FUNCTION: + all_privileges = ACL_ALL_RIGHTS_FUNCTION; + errormsg = gettext_noop("invalid privilege type %s for function"); + break; + default: + /* keep compiler quiet */ + all_privileges = ACL_NO_RIGHTS; + errormsg = NULL; + elog(ERROR, "unrecognized GrantStmt.objtype: %d", + (int) action->objtype); + } + + /* + * Convert stmt->action->privileges, a list of privilege strings, + * into an AclMode bitmask. + */ + if (action->privileges == NIL) + { + iacls.all_privs = true; + iacls.privileges = ACL_NO_RIGHTS; + } + else + { + iacls.all_privs = false; + iacls.privileges = ACL_NO_RIGHTS; + + foreach(cell, action->privileges) + { + AccessPriv *privnode = (AccessPriv *) lfirst(cell); + AclMode priv; + + /* + * If it's a column-level specification, we just set it aside in + * col_privs for the moment; but insist it's for a relation. + */ + if (privnode->cols) + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_GRANT_OPERATION), + errmsg("column privileges are not valid for default privileges"))); + } + + if (privnode->priv_name == NULL) /* parser mistake? */ + elog(ERROR, "AccessPriv node must specify privilege"); + priv = string_to_privilege(privnode->priv_name); + + if (priv & ~((AclMode) all_privileges)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_GRANT_OPERATION), + errmsg(errormsg, privilege_to_string(priv)))); + + iacls.privileges |= priv; + } + } + + /* + * Ensure roles and namespaces don't get deleted while + * we are adding default privileges for them. + */ + rolerel = heap_open(AuthIdRelationId, ShareLock); + nsprel = heap_open(NamespaceRelationId, ShareLock); + + /* + * Convert role and namespace names to oids + * and check permissions while we are at it. + */ + foreach(rolecell, rolenames) + { + char *rolename = strVal(lfirst(rolecell)); + Oid roleid = get_roleid_checked(rolename); + + + if (!have_createrole_privilege() && + !is_admin_of_role(GetUserId(), roleid)) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must have admin option on role \"%s\"", + rolename))); + + roleids = lappend_oid(roleids, roleid); + } + + foreach(nspcell, nspnames) + { + char *nspname = strVal(lfirst(nspcell)); + Oid nspid = LookupExplicitNamespace(nspname); + + if (nspid == PG_CATALOG_NAMESPACE || nspid == PG_TOAST_NAMESPACE) + elog(ERROR, "can't set default permissions for system catalog"); + + nspids = lappend_oid(nspids, nspid); + } + + /* + * We need to set default acls for current role if no roles were specified + * and database wide if no schema was specified. + */ + if (roleids == NIL) + roleids = lappend_oid(roleids, GetUserId()); + if (nspids == NIL) + nspids = lappend_oid(nspids, InvalidOid); + + /* + * Go through roles and namespaces and update the catalog for them. + */ + foreach(rolecell, roleids) + { + iacls.roleid = lfirst_oid(rolecell); + foreach(nspcell, nspids) + { + iacls.nspid = lfirst_oid(nspcell); + SetDefaultACLs(&iacls); + } + } + + heap_close(nsprel, ShareLock); + heap_close(rolerel, ShareLock); + } + + + /* + * Create or update the DefaultACL entry + * + * Caller is responsible for checking privileges + */ + void + SetDefaultACLs(InternalDefaultACLs *iacls) + { + Form_pg_default_acls pg_default_acls_tuple; + char objtype; + Datum aclDatum; + bool isNew; + bool isNull; + AclMode this_privileges = iacls->privileges; + ScanKeyData key[3]; + HeapScanDesc scan; + HeapTuple tuple; + Relation rel; + Acl *old_acl; + Acl *new_acl; + HeapTuple newtuple; + Datum values[Natts_pg_default_acls]; + bool nulls[Natts_pg_default_acls]; + bool replaces[Natts_pg_default_acls]; + int noldmembers; + int nnewmembers; + Oid *oldmembers; + Oid *newmembers; + + /* + * Convert ACL object type to DefaultACLs object type + * and handle all_privs option + */ + switch (iacls->objtype) + { + case ACL_OBJECT_RELATION: + objtype = DEFACLOBJ_RELATION; + if (iacls->all_privs && this_privileges == ACL_NO_RIGHTS) + this_privileges = ACL_ALL_RIGHTS_RELATION; + break; + + case ACL_OBJECT_FUNCTION: + objtype = DEFACLOBJ_FUNCTION; + if (iacls->all_privs && this_privileges == ACL_NO_RIGHTS) + this_privileges = ACL_ALL_RIGHTS_FUNCTION; + break; + + case ACL_OBJECT_SEQUENCE: + objtype = DEFACLOBJ_SEQUENCE; + if (iacls->all_privs && this_privileges == ACL_NO_RIGHTS) + this_privileges = ACL_ALL_RIGHTS_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_default_acls_defaclrole, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(iacls->roleid)); + + ScanKeyInit(&key[1], + Anum_pg_default_acls_defaclnamespace, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(iacls->nspid)); + + ScanKeyInit(&key[2], + Anum_pg_default_acls_defaclgrantobjtype, + BTEqualStrategyNumber, F_CHAREQ, + CharGetDatum(objtype)); + + rel = heap_open(DefaultAclsRelationId, RowExclusiveLock); + + scan = heap_beginscan(rel, SnapshotNow, 3, key); + + tuple = heap_getnext(scan, ForwardScanDirection); + if (HeapTupleIsValid(tuple)) + { + pg_default_acls_tuple = (Form_pg_default_acls) GETSTRUCT(tuple); + aclDatum = heap_getattr(tuple, Anum_pg_default_acls_defacldefacllist, + RelationGetDescr(rel), &isNull); + isNew = false; + } + else + { + isNull = true; + isNew = true; + } + + if (isNull) + old_acl = acldefault(iacls->objtype, iacls->roleid); + else + old_acl = DatumGetAclPCopy(aclDatum); + + noldmembers = aclmembers(old_acl, &oldmembers); + + /* + * 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(old_acl, + iacls->is_grant, + iacls->grant_option, + iacls->behavior, + iacls->grantees, + this_privileges, + GetUserId(), + iacls->roleid); + + /* 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) + { + values[Anum_pg_default_acls_defaclrole - 1] = ObjectIdGetDatum(iacls->roleid); + values[Anum_pg_default_acls_defaclnamespace - 1] = ObjectIdGetDatum(iacls->nspid); + values[Anum_pg_default_acls_defaclgrantobjtype - 1] = CharGetDatum(objtype); + values[Anum_pg_default_acls_defacldefacllist - 1] = PointerGetDatum(new_acl); + + newtuple = heap_form_tuple(rel->rd_att, values, nulls); + simple_heap_insert(rel, newtuple); + + /* dependency on namespace */ + if (OidIsValid(iacls->nspid)) + { + ObjectAddress myself, + referenced; + + myself.classId = DefaultAclsRelationId; + myself.objectId = HeapTupleGetOid(newtuple); + myself.objectSubId = 0; + + referenced.classId = NamespaceRelationId; + referenced.objectId = iacls->nspid; + referenced.objectSubId = 0; + + recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + } + } + else + { + values[Anum_pg_default_acls_defacldefacllist - 1] = PointerGetDatum(new_acl); + replaces[Anum_pg_default_acls_defacldefacllist - 1] = true; + + 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); + + updateAclDependencies(DefaultAclsRelationId, HeapTupleGetOid(newtuple), 0, + GetUserId(), iacls->is_grant, + noldmembers, oldmembers, + nnewmembers, newmembers); + + pfree(new_acl); + + heap_endscan(scan); + heap_close(rel, RowExclusiveLock); + } + + + /* + * Remove DefaultACLs tuple. + * + * Used for dependency removal. + */ + void + RemoveDefaultACLsById(Oid nsdaOid) + { + Relation rel; + ScanKeyData key[1]; + HeapScanDesc scan; + HeapTuple tuple; + + rel = heap_open(DefaultAclsRelationId, RowExclusiveLock); + + ScanKeyInit(&key[0], + ObjectIdAttributeNumber, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(nsdaOid)); + + scan = heap_beginscan(rel, SnapshotNow, 1, key); + + tuple = heap_getnext(scan, ForwardScanDirection); + + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "could not find tuple for default acls %u", + nsdaOid); + + simple_heap_delete(rel, &tuple->t_self); + + heap_endscan(scan); + heap_close(rel, RowExclusiveLock); + } + + + /* + * Remove DefaultACLs tuples for given role. + * + * Used by DropRole. + */ + void + RemoveDefaultACLsByRoleId(Oid roleid) + { + Relation rel; + ScanKeyData key[1]; + HeapScanDesc scan; + HeapTuple tuple; + + rel = heap_open(DefaultAclsRelationId, RowExclusiveLock); + + ScanKeyInit(&key[0], + Anum_pg_default_acls_defaclrole, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(roleid)); + + scan = heap_beginscan(rel, SnapshotNow, 1, key); + + while (HeapTupleIsValid(tuple = heap_getnext(scan, ForwardScanDirection))) + simple_heap_delete(rel, &tuple->t_self); + + heap_endscan(scan); + heap_close(rel, RowExclusiveLock); + } + + + /* * expand_col_privileges * * OR the specified privilege(s) into per-column array entries for each *************** ExecGrant_Tablespace(InternalGrant *istm *** 1988,1993 **** --- 2459,2696 ---- } + /* + * 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); + Oid ownerId; + Datum aclDatum; + bool isNull; + Form_pg_class pg_class_tuple; + 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()); + + ownerId = ((Form_pg_class) GETSTRUCT(tuple))->relowner; + + 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 = DEFACLOBJ_SEQUENCE; + break; + case RELKIND_RELATION: + objtype = DEFACLOBJ_RELATION; + break; + default: /* parser error ? */ + elog(ERROR, "relation can't have DEFAULT PRIVILEGES"); + } + + new_acl = pg_namespace_object_default_acl(pg_class_tuple->relnamespace, + objtype, ownerId); + + if (new_acl == NULL) + 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); + + /* Remove old ACL dependencies */ + updateAclDependencies(RelationRelationId, relOid, 0, + pg_class_tuple->relowner, false, + noldmembers, oldmembers, + 0, NULL); + + /* Add new ACL dependencies */ + updateAclDependencies(RelationRelationId, relOid, 0, + pg_class_tuple->relowner, true, + 0, NULL, + 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); + Oid ownerId; + 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()); + + ownerId = ((Form_pg_proc) GETSTRUCT(tuple))->proowner; + + /* Get the default acls */ + new_acl = pg_namespace_object_default_acl(pg_proc_tuple->pronamespace, + DEFACLOBJ_FUNCTION, + ownerId); + + if (new_acl == NULL) + elog(ERROR, "no DEFAULT PRIVILEGES for function"); + + nnewmembers = aclmembers(new_acl, &newmembers); + + /* Get original acls */ + aclDatum = SysCacheGetAttr(PROCOID, tuple, Anum_pg_proc_proacl, + &isNull); + + if (!isNull) + old_acl = DatumGetAclPCopy(aclDatum); + + noldmembers = aclmembers(old_acl, &oldmembers); + + + /* 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); + + /* Remove old ACL dependencies */ + updateAclDependencies(ProcedureRelationId, funcId, 0, + pg_proc_tuple->proowner, false, + noldmembers, oldmembers, + 0, NULL); + + /* Add new ACL dependencies */ + updateAclDependencies(ProcedureRelationId, funcId, 0, + pg_proc_tuple->proowner, true, + 0, NULL, + nnewmembers, newmembers); + + ReleaseSysCache(tuple); + + pfree(new_acl); + + /* prevent error when processing duplicate objects */ + CommandCounterIncrement(); + } + + heap_close(relation, RowExclusiveLock); + } + + static AclMode string_to_privilege(const char *privname) { *************** pg_conversion_ownercheck(Oid conv_oid, O *** 3532,3534 **** --- 4235,4330 ---- return has_privs_of_role(roleid, ownerId); } + + /* + * Search for default permissions for given role, namespace and objecttype + */ + Acl * + get_default_acl(Oid roleId, Oid nsp_oid, char objtype) + { + ScanKeyData key[3]; + HeapScanDesc scan; + HeapTuple tuple; + Relation rel; + Datum aclDatum; + bool isNull; + Acl *result = NULL; + + ScanKeyInit(&key[0], + Anum_pg_default_acls_defaclrole, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(roleId)); + + ScanKeyInit(&key[1], + Anum_pg_default_acls_defaclnamespace, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(nsp_oid)); + + ScanKeyInit(&key[2], + Anum_pg_default_acls_defaclgrantobjtype, + BTEqualStrategyNumber, F_CHAREQ, + CharGetDatum(objtype)); + + rel = heap_open(DefaultAclsRelationId, AccessShareLock); + + scan = heap_beginscan(rel, SnapshotNow, 3, key); + + tuple = heap_getnext(scan, ForwardScanDirection); + + if (HeapTupleIsValid(tuple)) + { + aclDatum = heap_getattr(tuple, Anum_pg_default_acls_defacldefacllist, + RelationGetDescr(rel), &isNull); + + if (!isNull) + result = DatumGetAclPCopy(aclDatum); + } + + heap_endscan(scan); + heap_close(rel, AccessShareLock); + + return result; + } + + /* + * Get default permissions for newly created object inside given schema + */ + Acl * + pg_namespace_object_default_acl(Oid nsp_oid, char objtype, Oid ownerId) + { + Acl *result; + GrantObjectType aclobjtype; + + /* + * Use NULL value for object acls if it is created in system schema + * or if it does not have default permissions support. + */ + if (nsp_oid == PG_CATALOG_NAMESPACE || nsp_oid == PG_TOAST_NAMESPACE) + return NULL; + + switch (objtype) + { + case DEFACLOBJ_RELATION: + aclobjtype = ACL_OBJECT_RELATION; + break; + + case DEFACLOBJ_FUNCTION: + aclobjtype = ACL_OBJECT_FUNCTION; + break; + + case DEFACLOBJ_SEQUENCE: + aclobjtype = ACL_OBJECT_SEQUENCE; + break; + + default: + return NULL; + } + + result = get_default_acl(ownerId, nsp_oid, objtype); + + /* If nothing found for given namespace, try global default permissions */ + if (result == NULL) + result = get_default_acl(ownerId, InvalidOid, objtype); + + return result; + } diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c index cb9a9c2..c4b0f12 100644 *** a/src/backend/catalog/dependency.c --- b/src/backend/catalog/dependency.c *************** *** 32,37 **** --- 32,38 ---- #include "catalog/pg_conversion.h" #include "catalog/pg_conversion_fn.h" #include "catalog/pg_database.h" + #include "catalog/pg_default_acls.h" #include "catalog/pg_depend.h" #include "catalog/pg_foreign_data_wrapper.h" #include "catalog/pg_foreign_server.h" *************** *** 70,75 **** --- 71,77 ---- #include "utils/lsyscache.h" #include "utils/syscache.h" #include "utils/tqual.h" + #include "utils/acl.h" /* *************** static const Oid object_classes[MAX_OCLA *** 146,152 **** TableSpaceRelationId, /* OCLASS_TBLSPACE */ ForeignDataWrapperRelationId, /* OCLASS_FDW */ ForeignServerRelationId, /* OCLASS_FOREIGN_SERVER */ ! UserMappingRelationId /* OCLASS_USER_MAPPING */ }; --- 148,155 ---- TableSpaceRelationId, /* OCLASS_TBLSPACE */ ForeignDataWrapperRelationId, /* OCLASS_FDW */ ForeignServerRelationId, /* OCLASS_FOREIGN_SERVER */ ! UserMappingRelationId, /* OCLASS_USER_MAPPING */ ! DefaultAclsRelationId /* OCLASS_DEFACLS */ }; *************** static bool object_address_present_add_f *** 178,183 **** --- 181,187 ---- 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) *** 1136,1141 **** --- 1140,1149 ---- RemoveUserMappingById(object->objectId); break; + case OCLASS_DEFACLS: + RemoveDefaultACLsById(object->objectId); + break; + default: elog(ERROR, "unrecognized object class: %u", object->classId); *************** getObjectClass(const ObjectAddress *obje *** 2055,2060 **** --- 2063,2072 ---- case UserMappingRelationId: Assert(object->objectSubId == 0); return OCLASS_USER_MAPPING; + + case DefaultAclsRelationId: + Assert(object->objectSubId == 0); + return OCLASS_DEFACLS; } /* shouldn't get here */ *************** getObjectDescription(const ObjectAddress *** 2597,2602 **** --- 2609,2657 ---- break; } + case OCLASS_DEFACLS: + { + Relation rel; + ScanKeyData key[1]; + HeapScanDesc scan; + HeapTuple tup; + Form_pg_default_acls defacls; + + rel = heap_open(DefaultAclsRelationId, 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); + + defacls = (Form_pg_default_acls) GETSTRUCT(tup); + + appendStringInfo(&buffer, + _("default acls for role %s on new "), + GetUserNameFromId(defacls->defaclrole)); + + getDefaultACLDescription(&buffer, defacls->defaclobjtype); + + if (OidIsValid(defacls->defaclnamespace)) + { + appendStringInfo(&buffer, + _(" in namespace %s"), + get_namespace_name(defacls->defaclnamespace)); + } + + heap_endscan(scan); + heap_close(rel, AccessShareLock); + break; + } + default: appendStringInfo(&buffer, "unrecognized object %u %u %d", object->classId, *************** getOpFamilyDescription(StringInfo buffer *** 2715,2717 **** --- 2770,2797 ---- ReleaseSysCache(amTup); ReleaseSysCache(opfTup); } + + + /* + * subroutine for getObjectDescription: describe DefaultACLs object type + */ + static void + getDefaultACLDescription(StringInfo buffer, char objtype) + { + switch (objtype) + { + case DEFACLOBJ_RELATION: + appendStringInfo(buffer, _("relation")); + break; + case DEFACLOBJ_FUNCTION: + appendStringInfo(buffer, _("function")); + break; + case DEFACLOBJ_SEQUENCE: + appendStringInfo(buffer, _("sequence")); + break; + default: + /* shouldn't get here */ + appendStringInfo(buffer, _("unknown")); + break; + } + } diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c index ae406ae..812e287 100644 *** a/src/backend/catalog/heap.c --- b/src/backend/catalog/heap.c *************** *** 41,46 **** --- 41,47 ---- #include "catalog/indexing.h" #include "catalog/pg_attrdef.h" #include "catalog/pg_constraint.h" + #include "catalog/pg_default_acls.h" #include "catalog/pg_inherits.h" #include "catalog/pg_namespace.h" #include "catalog/pg_statistic.h" *************** *** 67,72 **** --- 68,74 ---- #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, *** 635,642 **** * 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 --- 637,644 ---- * 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_default_acls ! * (if applicable) and reloptions is set to the passed-in text array (if any). * -------------------------------- */ void *************** InsertPgClassTuple(Relation pg_class_des *** 648,653 **** --- 650,658 ---- Form_pg_class rd_rel = new_rel_desc->rd_rel; Datum values[Natts_pg_class]; bool nulls[Natts_pg_class]; + bool isNull; + Acl *relacl; + char aclobjtype; HeapTuple tup; /* This is a tad tedious, but way cleaner than what we used to do... */ *************** InsertPgClassTuple(Relation pg_class_des *** 677,684 **** 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 --- 682,715 ---- 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 */ ! switch (rd_rel->relkind) ! { ! case RELKIND_RELATION: ! case RELKIND_VIEW: ! isNull = false; ! aclobjtype = DEFACLOBJ_RELATION; ! break; ! case RELKIND_SEQUENCE: ! isNull = false; ! aclobjtype = DEFACLOBJ_SEQUENCE; ! break; ! default: ! isNull = true; ! break; ! } ! ! if (!isNull) ! relacl = pg_namespace_object_default_acl(rd_rel->relnamespace, aclobjtype, rd_rel->relowner); ! else ! relacl = NULL; ! ! if (relacl != NULL) ! values[Anum_pg_class_relacl - 1] = PointerGetDatum(relacl); ! else ! nulls[Anum_pg_class_relacl - 1] = true; ! if (reloptions != (Datum) 0) values[Anum_pg_class_reloptions - 1] = reloptions; else *************** InsertPgClassTuple(Relation pg_class_des *** 697,702 **** --- 728,748 ---- CatalogUpdateIndexes(pg_class_desc, tup); + if (relacl != NULL) + { + 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 342381e..71ebbea 100644 *** a/src/backend/catalog/pg_proc.c --- b/src/backend/catalog/pg_proc.c *************** *** 18,23 **** --- 18,24 ---- #include "access/xact.h" #include "catalog/dependency.h" #include "catalog/indexing.h" + #include "catalog/pg_default_acls.h" #include "catalog/pg_language.h" #include "catalog/pg_namespace.h" #include "catalog/pg_proc.h" *************** ProcedureCreate(const char *procedureNam *** 95,100 **** --- 96,102 ---- bool nulls[Natts_pg_proc]; Datum values[Natts_pg_proc]; bool replaces[Natts_pg_proc]; + 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); --- 332,344 ---- 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, DEFACLOBJ_FUNCTION, GetUserId()); ! if (proacl != NULL) ! values[Anum_pg_proc_proacl - 1] = PointerGetDatum(proacl); ! else ! nulls[Anum_pg_proc_proacl - 1] = true; rel = heap_open(ProcedureRelationId, RowExclusiveLock); tupDesc = RelationGetDescr(rel); *************** ProcedureCreate(const char *procedureNam *** 539,544 **** --- 546,566 ---- /* dependency on owner */ recordDependencyOnOwner(ProcedureRelationId, retval, GetUserId()); + if (proacl != NULL) + { + 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/catalog/pg_shdepend.c b/src/backend/catalog/pg_shdepend.c index cd04053..a9c586e 100644 *** a/src/backend/catalog/pg_shdepend.c --- b/src/backend/catalog/pg_shdepend.c *************** *** 17,28 **** --- 17,30 ---- #include "access/genam.h" #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/pg_authid.h" #include "catalog/pg_conversion.h" #include "catalog/pg_database.h" + #include "catalog/pg_default_acls.h" #include "catalog/pg_language.h" #include "catalog/pg_namespace.h" #include "catalog/pg_operator.h" *************** *** 40,45 **** --- 42,48 ---- #include "miscadmin.h" #include "utils/acl.h" #include "utils/fmgroids.h" + #include "utils/lsyscache.h" #include "utils/syscache.h" #include "utils/tqual.h" *************** static void storeObjectDescription(Strin *** 74,79 **** --- 77,84 ---- int count); static bool isSharedObjectPinned(Oid classId, Oid objectId, Relation sdepRel); + static void shdepDropObjACLs(Oid classid, Oid objid, Oid roleid); + static void shdepDropDefaultACLs(Oid objid, Oid roleid); /* * recordSharedDependencyOn *************** shdepDropOwned(List *roleids, DropBehavi *** 1180,1186 **** while ((tuple = systable_getnext(scan)) != NULL) { Form_pg_shdepend sdepForm = (Form_pg_shdepend) GETSTRUCT(tuple); - InternalGrant istmt; ObjectAddress obj; /* We only operate on objects in the current database */ --- 1185,1190 ---- *************** shdepDropOwned(List *roleids, DropBehavi *** 1195,1236 **** elog(ERROR, "unexpected dependency type"); break; case SHARED_DEPENDENCY_ACL: ! switch (sdepForm->classid) ! { ! case RelationRelationId: ! /* it's OK to use RELATION for a sequence */ ! istmt.objtype = ACL_OBJECT_RELATION; ! break; ! case DatabaseRelationId: ! istmt.objtype = ACL_OBJECT_DATABASE; ! break; ! case ProcedureRelationId: ! istmt.objtype = ACL_OBJECT_FUNCTION; ! break; ! case LanguageRelationId: ! istmt.objtype = ACL_OBJECT_LANGUAGE; ! break; ! case NamespaceRelationId: ! istmt.objtype = ACL_OBJECT_NAMESPACE; ! break; ! case TableSpaceRelationId: ! istmt.objtype = ACL_OBJECT_TABLESPACE; ! break; ! default: ! elog(ERROR, "unexpected object type %d", ! sdepForm->classid); ! break; ! } ! istmt.is_grant = false; ! istmt.objects = list_make1_oid(sdepForm->objid); ! istmt.all_privs = true; ! istmt.privileges = ACL_NO_RIGHTS; ! istmt.col_privs = NIL; ! istmt.grantees = list_make1_oid(roleid); ! istmt.grant_option = false; ! istmt.behavior = DROP_CASCADE; ! ! ExecGrantStmt_oids(&istmt); break; case SHARED_DEPENDENCY_OWNER: /* Save it for deletion below */ --- 1199,1208 ---- elog(ERROR, "unexpected dependency type"); break; case SHARED_DEPENDENCY_ACL: ! if (sdepForm->classid == DefaultAclsRelationId) ! shdepDropDefaultACLs(sdepForm->objid, roleid); ! else ! shdepDropObjACLs(sdepForm->classid, sdepForm->objid, roleid); break; case SHARED_DEPENDENCY_OWNER: /* Save it for deletion below */ *************** shdepDropOwned(List *roleids, DropBehavi *** 1253,1258 **** --- 1225,1353 ---- free_object_addresses(deleteobjs); } + /* + * shdepDropObjACLs + * + * Does the actual ACLs (Grant) removal for shdepDropOwned + */ + void + shdepDropObjACLs(Oid classid, Oid objid, Oid roleid) + { + InternalGrant istmt; + + switch (classid) + { + case RelationRelationId: + /* it's OK to use RELATION for a sequence */ + istmt.objtype = ACL_OBJECT_RELATION; + break; + case DatabaseRelationId: + istmt.objtype = ACL_OBJECT_DATABASE; + break; + case ProcedureRelationId: + istmt.objtype = ACL_OBJECT_FUNCTION; + break; + case LanguageRelationId: + istmt.objtype = ACL_OBJECT_LANGUAGE; + break; + case NamespaceRelationId: + istmt.objtype = ACL_OBJECT_NAMESPACE; + break; + case TableSpaceRelationId: + istmt.objtype = ACL_OBJECT_TABLESPACE; + break; + default: + elog(ERROR, "unexpected object type %d", classid); + break; + } + istmt.is_grant = false; + istmt.objects = list_make1_oid(objid); + istmt.all_privs = true; + istmt.privileges = ACL_NO_RIGHTS; + istmt.col_privs = NIL; + istmt.grantees = list_make1_oid(roleid); + istmt.grant_option = false; + istmt.behavior = DROP_CASCADE; + + ExecGrantStmt_oids(&istmt); + } + + + /* + * shdepDropDefaultACLs + * + * Drops roles ACLs from DefaultAcls tuple, used by shdepDropOwned + */ + void + shdepDropDefaultACLs(Oid objid, Oid roleid) + { + InternalDefaultACLs iacls; + Form_pg_default_acls pg_default_acls_tuple; + Relation rel; + ScanKeyData key[1]; + HeapScanDesc scan; + HeapTuple tuple; + AclResult aclresult; + + /* first fetch info needed by SetDefaultACLs */ + rel = heap_open(DefaultAclsRelationId, RowExclusiveLock); + + ScanKeyInit(&key[0], + ObjectIdAttributeNumber, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(objid)); + + scan = heap_beginscan(rel, SnapshotNow, 1, key); + + tuple = heap_getnext(scan, ForwardScanDirection); + + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "could not find tuple for default acls %u", + objid); + + pg_default_acls_tuple = (Form_pg_default_acls) GETSTRUCT(tuple); + + iacls.roleid = pg_default_acls_tuple->defaclrole; + iacls.nspid = pg_default_acls_tuple->defaclnamespace; + + switch (pg_default_acls_tuple->defaclobjtype) + { + case DEFACLOBJ_RELATION: + iacls.objtype = ACL_OBJECT_RELATION; + break; + case DEFACLOBJ_FUNCTION: + iacls.objtype = ACL_OBJECT_FUNCTION; + break; + case ACL_OBJECT_SEQUENCE: + iacls.objtype = ACL_OBJECT_SEQUENCE; + break; + default: + /* Shouldn't get here */ + elog(ERROR, "unexpected object type %d", pg_default_acls_tuple->defaclobjtype); + break; + } + + heap_endscan(scan); + heap_close(rel, RowExclusiveLock); + + /* Check privileges */ + aclresult = pg_namespace_aclcheck(iacls.nspid, GetUserId(), ACL_USAGE); + if (aclresult != ACLCHECK_OK) + aclcheck_error(aclresult, ACL_KIND_NAMESPACE, + get_namespace_name(iacls.nspid)); + + iacls.is_grant = false; + iacls.all_privs = true; + iacls.privileges = ACL_NO_RIGHTS; + iacls.grantees = list_make1_oid(roleid); + iacls.grant_option = false; + iacls.behavior = DROP_CASCADE; + + /* Do it */ + SetDefaultACLs(&iacls); + } + + /* * shdepReassignOwned * diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c index fa260c4..7df7e07 100644 *** a/src/backend/commands/user.c --- b/src/backend/commands/user.c *************** static void DelRoleMems(const char *role *** 44,72 **** bool admin_opt); - /* Check if current user has createrole privileges */ - static bool - have_createrole_privilege(void) - { - bool result = false; - HeapTuple utup; - - /* Superusers can always do everything */ - if (superuser()) - return true; - - utup = SearchSysCache(AUTHOID, - ObjectIdGetDatum(GetUserId()), - 0, 0, 0); - if (HeapTupleIsValid(utup)) - { - result = ((Form_pg_authid) GETSTRUCT(utup))->rolcreaterole; - ReleaseSysCache(utup); - } - return result; - } - - /* * CREATE ROLE */ --- 44,49 ---- *************** DropRole(DropRoleStmt *stmt) *** 939,944 **** --- 916,926 ---- systable_endscan(sscan); /* + * Remove AlterDefaultPrivileges for this role. + */ + RemoveDefaultACLsByRoleId(roleid); + + /* * Remove any comments on this role. */ DeleteSharedComments(roleid, AuthIdRelationId); diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 6420e4a..f8f2aa7 100644 *** a/src/backend/nodes/copyfuncs.c --- b/src/backend/nodes/copyfuncs.c *************** _copyCreateSchemaStmt(CreateSchemaStmt * *** 3228,3233 **** --- 3228,3244 ---- return newnode; } + static AlterDefaultPrivilegesStmt * + _copyAlterDefaultPrivilegesStmt(AlterDefaultPrivilegesStmt *from) + { + AlterDefaultPrivilegesStmt *newnode = makeNode(AlterDefaultPrivilegesStmt); + + COPY_NODE_FIELD(options); + COPY_NODE_FIELD(action); + + return newnode; + } + static CreateConversionStmt * _copyCreateConversionStmt(CreateConversionStmt *from) { *************** copyObject(void *from) *** 3973,3978 **** --- 3984,3992 ---- case T_CreateSchemaStmt: retval = _copyCreateSchemaStmt(from); break; + case T_AlterDefaultPrivilegesStmt: + retval = _copyAlterDefaultPrivilegesStmt(from); + break; case T_CreateConversionStmt: retval = _copyCreateConversionStmt(from); break; diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index f125719..2b5c7eb 100644 *** a/src/backend/nodes/equalfuncs.c --- b/src/backend/nodes/equalfuncs.c *************** _equalCreateSchemaStmt(CreateSchemaStmt *** 1772,1777 **** --- 1772,1786 ---- } static bool + _equalAlterDefaultPrivilegesStmt(AlterDefaultPrivilegesStmt *a, AlterDefaultPrivilegesStmt *b) + { + COMPARE_NODE_FIELD(options); + COMPARE_NODE_FIELD(action); + + return true; + } + + static bool _equalCreateConversionStmt(CreateConversionStmt *a, CreateConversionStmt *b) { COMPARE_NODE_FIELD(conversion_name); *************** equal(void *a, void *b) *** 2750,2755 **** --- 2759,2767 ---- case T_CreateSchemaStmt: retval = _equalCreateSchemaStmt(a, b); break; + case T_AlterDefaultPrivilegesStmt: + retval = _equalAlterDefaultPrivilegesStmt(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 732b1d7..7d639c4 100644 *** a/src/backend/parser/gram.y --- b/src/backend/parser/gram.y *************** static TypeName *TableFuncTypeName(List *** 179,184 **** --- 179,185 ---- ResTarget *target; struct PrivTarget *privtarget; AccessPriv *accesspriv; + GrantObjectType grantobjecttype; 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 DoStmt DropGroupStmt DropOpClassStmt DropOpFamilyStmt DropPLangStmt DropStmt DropAssertStmt DropTrigStmt DropRuleStmt DropCastStmt DropRoleStmt DropUserStmt DropdbStmt DropTableSpaceStmt DropFdwStmt --- 197,203 ---- CreateSchemaStmt CreateSeqStmt CreateStmt CreateTableSpaceStmt CreateFdwStmt CreateForeignServerStmt CreateAssertStmt CreateTrigStmt CreateUserStmt CreateUserMappingStmt CreateRoleStmt ! CreatedbStmt DeclareCursorStmt AlterDefaultPrivilegesStmt AlterDefaultPrivilegesAction DefineStmt DeleteStmt DiscardStmt DoStmt DropGroupStmt DropOpClassStmt DropOpFamilyStmt DropPLangStmt DropStmt DropAssertStmt DropTrigStmt DropRuleStmt DropCastStmt DropRoleStmt DropUserStmt DropdbStmt DropTableSpaceStmt DropFdwStmt *************** static TypeName *TableFuncTypeName(List *** 267,272 **** --- 268,274 ---- %type privilege %type privileges privilege_list %type privilege_target + %type defacl_privilege_target %type function_with_argtypes %type function_with_argtypes_list *************** static TypeName *TableFuncTypeName(List *** 427,432 **** --- 429,436 ---- %type opt_existing_window_name %type opt_frame_clause frame_extent frame_bound + %type OptDefACLOption + %type OptDefACLOptionList /* * Non-keyword token types. These are hard-wired into the "flex" lexer. *************** stmt : *** 672,677 **** --- 676,682 ---- | CreatedbStmt | DeallocateStmt | DeclareCursorStmt + | AlterDefaultPrivilegesStmt | DefineStmt | DeleteStmt | DiscardStmt *************** schema_stmt: *** 1114,1119 **** --- 1119,1208 ---- ; + + /***************************************************************************** + * + * DEFAULT PRIVILEGES for newly created objects in schema + * + *****************************************************************************/ + + AlterDefaultPrivilegesStmt: + ALTER DEFAULT PRIVILEGES OptDefACLOptionList AlterDefaultPrivilegesAction + { + AlterDefaultPrivilegesStmt *n = makeNode(AlterDefaultPrivilegesStmt); + n->options = $4; + n->action = $5; + $$ = (Node*)n; + } + ; + + OptDefACLOptionList: + OptDefACLOptionList OptDefACLOption { $$ = lappend($1, $2); } + | /* EMPTY */ { $$ = NIL; } + ; + + OptDefACLOption: + IN_P SCHEMA name_list + { + $$ = makeDefElem("schemas", (Node *)$3); + } + | FOR ROLE name_list + { + $$ = makeDefElem("roles", (Node *)$3); + } + | FOR USER name_list + { + $$ = makeDefElem("roles", (Node *)$3); + } + ; + + AlterDefaultPrivilegesAction: + GRANT privileges ON defacl_privilege_target TO grantee_list + opt_grant_grant_option + { + GrantStmt *n = makeNode(GrantStmt); + n->is_grant = true; + n->privileges = $2; + n->objtype = $4; + n->objects = NIL; + n->grantees = $6; + n->grant_option = $7; + $$ = (Node*)n; + } + | REVOKE privileges ON defacl_privilege_target + FROM grantee_list opt_drop_behavior + { + GrantStmt *n = makeNode(GrantStmt); + n->is_grant = false; + n->grant_option = false; + n->privileges = $2; + n->objtype = $4; + n->objects = NIL; + n->grantees = $6; + n->behavior = $7; + $$ = (Node *)n; + } + | REVOKE GRANT OPTION FOR privileges ON defacl_privilege_target + FROM grantee_list opt_drop_behavior + { + GrantStmt *n = makeNode(GrantStmt); + n->is_grant = false; + n->grant_option = true; + n->privileges = $5; + n->objtype = $7; + n->objects = NIL; + n->grantees = $9; + n->behavior = $10; + $$ = (Node *)n; + } + ; + + defacl_privilege_target: + TABLE { $$ = ACL_OBJECT_RELATION; } + | FUNCTION { $$ = ACL_OBJECT_FUNCTION; } + | SEQUENCE { $$ = ACL_OBJECT_SEQUENCE; } + ; + /***************************************************************************** * * Set PG internal variable *************** GrantStmt: GRANT privileges ON privilege *** 4302,4307 **** --- 4391,4407 ---- 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: diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index ddf87b4..e4ab20a 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_AlterDefaultPrivilegesStmt: case T_CreateSeqStmt: case T_CreateStmt: case T_CreateTableSpaceStmt: *************** ProcessUtility(Node *parsetree, *** 406,411 **** --- 407,416 ---- queryString); break; + case T_AlterDefaultPrivilegesStmt: + ExecAlterDefaultPrivilegesStmt((AlterDefaultPrivilegesStmt *) parsetree); + break; + case T_CreateStmt: { List *stmts; *************** CreateCommandTag(Node *parsetree) *** 1369,1374 **** --- 1374,1383 ---- tag = "CREATE SCHEMA"; break; + case T_AlterDefaultPrivilegesStmt: + tag = "ALTER DEFAULT PRIVILEGES"; + break; + case T_CreateStmt: tag = "CREATE TABLE"; break; *************** GetCommandLogLevel(Node *parsetree) *** 2142,2147 **** --- 2151,2160 ---- lev = LOGSTMT_DDL; break; + case T_AlterDefaultPrivilegesStmt: + lev = LOGSTMT_DDL; + break; + case T_CreateStmt: lev = LOGSTMT_DDL; break; diff --git a/src/backend/utils/adt/acl.c b/src/backend/utils/adt/acl.c index e19e96d..e7538db 100644 *** a/src/backend/utils/adt/acl.c --- b/src/backend/utils/adt/acl.c *************** is_admin_of_role(Oid member, Oid role) *** 4499,4504 **** --- 4499,4527 ---- } + /* Check if user has createrole privileges */ + bool + have_createrole_privilege(void) + { + bool result = false; + HeapTuple utup; + + /* Superusers can always do everything */ + if (superuser()) + return true; + + utup = SearchSysCache(AUTHOID, + ObjectIdGetDatum(GetUserId()), + 0, 0, 0); + if (HeapTupleIsValid(utup)) + { + result = ((Form_pg_authid) GETSTRUCT(utup))->rolcreaterole; + ReleaseSysCache(utup); + } + return result; + } + + /* does what it says ... */ static int count_one_bits(AclMode mask) diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c index dbd3619..e00e2e2 100644 *** a/src/backend/utils/cache/syscache.c --- b/src/backend/utils/cache/syscache.c *************** *** 31,36 **** --- 31,37 ---- #include "catalog/pg_constraint.h" #include "catalog/pg_conversion.h" #include "catalog/pg_database.h" + #include "catalog/pg_default_acls.h" #include "catalog/pg_enum.h" #include "catalog/pg_foreign_data_wrapper.h" #include "catalog/pg_foreign_server.h" diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h index a2f6761..ab37b16 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_DEFACLS, /* pg_default_acls */ MAX_OCLASS /* MUST BE LAST */ } ObjectClass; diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h index 81e18a1..fcbe2f9 100644 *** a/src/include/catalog/indexing.h --- b/src/include/catalog/indexing.h *************** DECLARE_UNIQUE_INDEX(pg_user_mapping_oid *** 267,272 **** --- 267,277 ---- 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_default_acls_role_nsp_obj_index, 626, on pg_default_acls using btree(defaclrole oid_ops, defaclnamespace oid_ops, defaclobjtype char_ops)); + #define DefaultAclsNspObjIndexId 626 + DECLARE_UNIQUE_INDEX(pg_default_acls_oid_index, 627, on pg_default_acls using btree(oid oid_ops)); + #define DefaultAclsOidIndexId 627 + /* last step of initialization script: build the indexes declared above */ BUILD_INDICES diff --git a/src/include/catalog/pg_default_acls.h b/src/include/catalog/pg_default_acls.h index ...9b4019f . *** a/src/include/catalog/pg_default_acls.h --- b/src/include/catalog/pg_default_acls.h *************** *** 0 **** --- 1,79 ---- + /*------------------------------------------------------------------------- + * + * pg_default_acls.h + * definition of default ACLs for new objects + * 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_DEFAULT_ACLS_H + #define PG_DEFAULT_ACLS_H + + #include "catalog/genbki.h" + + /* ---------------- + * pg_default_acls definition. cpp turns this into + * typedef struct FormData_pg_default_acls + * ---------------- + */ + #define DefaultAclsRelationId 1248 + + CATALOG(pg_default_acls,1248) /*BKI_BOOTSTRAP*/ + { + Oid defaclrole; /* OID of the role these ACLs apply to */ + 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_default_acls; + + /* Size of fixed part of pg_default_acls tuples, not counting var-length fields */ + #define DEFAULT_ACLS_TUPLE_SIZE \ + (offsetof(FormData_pg_default_acls,relfrozenxid) + sizeof(TransactionId)) + + /* ---------------- + * Form_pg_default_acls corresponds to a pointer to a tuple with + * the format of pg_default_acls relation. + * ---------------- + */ + typedef FormData_pg_default_acls *Form_pg_default_acls; + + /* ---------------- + * compiler constants for pg_default_acls + * ---------------- + */ + + #define Natts_pg_default_acls 4 + #define Anum_pg_default_acls_defaclrole 1 + #define Anum_pg_default_acls_defaclnamespace 2 + #define Anum_pg_default_acls_defaclgrantobjtype 3 + #define Anum_pg_default_acls_defacldefacllist 4 + + /* ---------------- + * pg_default_acls has no initial contents + * ---------------- + */ + + /* + * Types of objects which the user is allowed to specify default + * permissions on through pg_default_acls. + */ + + #define DEFACLOBJ_RELATION 'r' /* table, view */ + #define DEFACLOBJ_FUNCTION 'f' /* function */ + #define DEFACLOBJ_SEQUENCE 'S' /* sequence */ + + #endif /* PG_DEFAULT_ACLS_H */ diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h index 3977081..9d8729f 100644 *** a/src/include/nodes/nodes.h --- b/src/include/nodes/nodes.h *************** typedef enum NodeTag *** 339,344 **** --- 339,345 ---- T_CreateUserMappingStmt, T_AlterUserMappingStmt, T_DropUserMappingStmt, + T_AlterDefaultPrivilegesStmt, /* * TAGS FOR PARSE TREE NODES (parsenodes.h) diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 076ed8b..27f9de3 100644 *** a/src/include/nodes/parsenodes.h --- b/src/include/nodes/parsenodes.h *************** typedef struct GrantRoleStmt *** 1260,1265 **** --- 1260,1277 ---- } GrantRoleStmt; /* ---------------------- + * Alter DEFAULT PRIVILEGES + * ---------------------- + */ + + typedef struct AlterDefaultPrivilegesStmt + { + NodeTag type; + List *options; + Node *action; + } AlterDefaultPrivilegesStmt; + + /* ---------------------- * Copy Statement * * We support "COPY relation FROM file", "COPY relation TO file", and diff --git a/src/include/utils/acl.h b/src/include/utils/acl.h index bde8727..da8521f 100644 *** a/src/include/utils/acl.h --- b/src/include/utils/acl.h *************** typedef struct *** 219,224 **** --- 219,243 ---- DropBehavior behavior; } InternalGrant; + + /* + * Internal format used by DefaultACLs. + * Needs to be exported because it's needed for dropping owned objects. + */ + typedef struct + { + Oid roleid; /* Role for which we are changing DefaultACLs */ + Oid nspid; /* Namespace this DefaultACLs aplies to - can be invalidOid for database wide */ + bool is_grant; /* True means we are granting, false means we are revoking */ + GrantObjectType objtype; /* Object type affected by these DefaultACLs */ + bool all_privs; /* See comment for InternalGrant */ + AclMode privileges; /* See comment for InternalGrant */ + List *grantees; /* Users to/from who we are Granting/Revoking privileges in these DefaultACLs */ + bool grant_option; /* Indicates that WITH GRAN OPTION was specified */ + DropBehavior behavior; /* CASCADE or RESTRICT */ + } InternalDefaultACLs; + + /* * routines used internally */ *************** extern bool is_member_of_role(Oid member *** 238,243 **** --- 257,263 ---- extern bool is_member_of_role_nosuper(Oid member, Oid role); extern bool is_admin_of_role(Oid member, Oid role); extern void check_is_member_of_role(Oid member, Oid role); + extern bool have_createrole_privilege(void); extern void select_best_grantor(Oid roleId, AclMode privileges, const Acl *acl, Oid ownerId, *************** extern Datum hash_aclitem(PG_FUNCTION_AR *** 263,268 **** --- 283,293 ---- extern void ExecuteGrantStmt(GrantStmt *stmt); extern void ExecGrantStmt_oids(InternalGrant *istmt); + extern void ExecAlterDefaultPrivilegesStmt(AlterDefaultPrivilegesStmt *stmt); + extern void SetDefaultACLs(InternalDefaultACLs *iacls); + extern void RemoveDefaultACLsById(Oid nsdaOid); + extern void RemoveDefaultACLsByRoleId(Oid roleid); + extern AclMode pg_attribute_aclmask(Oid table_oid, AttrNumber attnum, Oid roleid, AclMode mask, AclMaskHow how); extern AclMode pg_class_aclmask(Oid table_oid, Oid roleid, *************** extern bool pg_ts_dict_ownercheck(Oid di *** 317,320 **** --- 342,347 ---- 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, Oid ownerId); + #endif /* ACL_H */ diff --git a/src/test/regress/expected/privileges.out b/src/test/regress/expected/privileges.out index 809b656..a068517 100644 *** a/src/test/regress/expected/privileges.out --- b/src/test/regress/expected/privileges.out *************** SELECT has_sequence_privilege('x_seq', ' *** 836,843 **** --- 836,957 ---- t (1 row) + -- Default ACLs + \c - + CREATE SCHEMA regressns; + GRANT USAGE ON SCHEMA regressns TO regressgroup1, regressuser2; + GRANT ALL ON SCHEMA regressns TO regressuser1; + -- bad input + ALTER DEFAULT PRIVILEGES IN SCHEMA regressns GRANT USAGE ON FUNCTION TO regressgroup1; + ERROR: invalid privilege type USAGE for function + -- try different possible syntaxes from simple ones to more complex ones + ALTER DEFAULT PRIVILEGES IN SCHEMA regressns GRANT SELECT ON TABLE 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 DEFAULT PRIVILEGES IN SCHEMA regressns GRANT SELECT, UPDATE, INSERT, DELETE ON TABLE 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 DEFAULT PRIVILEGES IN SCHEMA regressns GRANT INSERT ON TABLE TO regressgroup1; + CREATE TABLE regressns.acltest3 (foo text); + SELECT has_table_privilege('regressuser4', 'regressns.acltest3', 'INSERT'); -- true + has_table_privilege + --------------------- + t + (1 row) + + ALTER DEFAULT PRIVILEGES IN SCHEMA regressns REVOKE DELETE ON TABLE 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; + -- set default privileges for other user + ALTER DEFAULT PRIVILEGES FOR USER regressuser1 IN SCHEMA regressns GRANT ALL ON SEQUENCE TO regressgroup1, regressuser2; + ALTER DEFAULT PRIVILEGES FOR ROLE regressuser1 IN SCHEMA regressns GRANT ALL PRIVILEGES ON TABLE TO regressuser2 WITH GRANT OPTION; + ALTER DEFAULT PRIVILEGES FOR ROLE regressuser1 IN SCHEMA regressns GRANT EXECUTE ON FUNCTION TO regressgroup1; + SET SESSION AUTHORIZATION regressuser1; + ALTER DEFAULT PRIVILEGES FOR USER regressuser2 IN SCHEMA regressns GRANT SELECT ON TABLE TO regressgroup1; -- error + ERROR: must have admin option on role "regressuser2" + -- 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) + + 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; + SELECT nextval(pg_get_serial_sequence('regressns.acltest5', 'a')::regclass); -- 1 + nextval + --------- + 1 + (1 row) + + GRANT SELECT ON regressns.acltest6 TO regressuser3; + SELECT has_table_privilege('regressuser3', 'regressns.acltest6', 'SELECT'); -- true + has_table_privilege + --------------------- + t + (1 row) + -- clean up \c + SET client_min_messages TO 'error'; + DROP SCHEMA regressns CASCADE; + RESET client_min_messages; drop sequence x_seq; DROP FUNCTION testfunc2(int); DROP FUNCTION testfunc4(boolean); diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out index 7213192..ec1e704 100644 *** a/src/test/regress/expected/sanity_check.out --- b/src/test/regress/expected/sanity_check.out *************** SELECT relname, relhasindex *** 95,100 **** --- 95,101 ---- pg_constraint | t pg_conversion | t pg_database | t + pg_default_acls | t pg_depend | t pg_description | t pg_enum | t *************** SELECT relname, relhasindex *** 151,157 **** timetz_tbl | f tinterval_tbl | f varchar_tbl | f ! (140 rows) -- -- another sanity check: every system catalog that has OIDs should have --- 152,158 ---- 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 917e8e5..d96dc9c 100644 *** a/src/test/regress/sql/privileges.sql --- b/src/test/regress/sql/privileges.sql *************** SET SESSION AUTHORIZATION regressuser2; *** 484,493 **** --- 484,562 ---- SELECT has_sequence_privilege('x_seq', 'USAGE'); + + -- Default ACLs + + \c - + + CREATE SCHEMA regressns; + GRANT USAGE ON SCHEMA regressns TO regressgroup1, regressuser2; + GRANT ALL ON SCHEMA regressns TO regressuser1; + + -- bad input + + ALTER DEFAULT PRIVILEGES IN SCHEMA regressns GRANT USAGE ON FUNCTION TO regressgroup1; + + -- try different possible syntaxes from simple ones to more complex ones + + ALTER DEFAULT PRIVILEGES IN SCHEMA regressns GRANT SELECT ON TABLE 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 DEFAULT PRIVILEGES IN SCHEMA regressns GRANT SELECT, UPDATE, INSERT, DELETE ON TABLE 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 DEFAULT PRIVILEGES IN SCHEMA regressns GRANT INSERT ON TABLE TO regressgroup1; + CREATE TABLE regressns.acltest3 (foo text); + SELECT has_table_privilege('regressuser4', 'regressns.acltest3', 'INSERT'); -- true + + ALTER DEFAULT PRIVILEGES IN SCHEMA regressns REVOKE DELETE ON TABLE 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; + + -- set default privileges for other user + ALTER DEFAULT PRIVILEGES FOR USER regressuser1 IN SCHEMA regressns GRANT ALL ON SEQUENCE TO regressgroup1, regressuser2; + ALTER DEFAULT PRIVILEGES FOR ROLE regressuser1 IN SCHEMA regressns GRANT ALL PRIVILEGES ON TABLE TO regressuser2 WITH GRANT OPTION; + ALTER DEFAULT PRIVILEGES FOR ROLE regressuser1 IN SCHEMA regressns GRANT EXECUTE ON FUNCTION TO regressgroup1; + + SET SESSION AUTHORIZATION regressuser1; + ALTER DEFAULT PRIVILEGES FOR USER regressuser2 IN SCHEMA regressns GRANT SELECT ON TABLE TO regressgroup1; -- error + + -- 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 + + 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; + SELECT nextval(pg_get_serial_sequence('regressns.acltest5', 'a')::regclass); -- 1 + GRANT SELECT ON regressns.acltest6 TO regressuser3; + SELECT has_table_privilege('regressuser3', 'regressns.acltest6', 'SELECT'); -- true + + -- clean up \c + SET client_min_messages TO 'error'; + DROP SCHEMA regressns CASCADE; + RESET client_min_messages; + drop sequence x_seq; DROP FUNCTION testfunc2(int);