diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml index dd103573a5..15dab71cc0 100644 --- a/doc/src/sgml/catalogs.sgml +++ b/doc/src/sgml/catalogs.sgml @@ -1,4 +1,4 @@ - + @@ -113,6 +113,11 @@ databases within this database cluster + + pg_default_acl + default privileges for object types + + pg_depend dependencies between database objects @@ -2155,6 +2160,93 @@ + + <structname>pg_default_acl</structname> + + + pg_default_acl + + + + The catalog pg_default_acl stores initial + privileges to be assigned to newly created objects. + + + + <structname>pg_default_acl</> 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, + or 0 if none + + + + defaclobjtype + char + + + Type of object this entry is for: + r = relation (table, view), + S = sequence, + f = function + + + + + defaclacl + aclitem[] + + + Access privileges that this type of object should have on creation + + + + +
+ + + A pg_default_acl entry shows the initial privileges to + be assigned to an object belonging to the indicated user. There are + currently two types of entry: global entries with + defaclnamespace = 0, and per-schema entries + that reference a particular schema. If a global entry is present then + it overrides the normal hard-wired default privileges + for the object type. A per-schema entry, if present, represents privileges + to be added to the global or hard-wired default privileges. + + + + Note that when an ACL entry in another catalog is NULL, it is taken + to represent the hard-wired default privileges for its object, + not whatever might be in pg_default_acl + at the moment. pg_default_acl is only consulted during + object creation. + + +
+ + <structname>pg_depend</structname> diff --git a/doc/src/sgml/ref/allfiles.sgml b/doc/src/sgml/ref/allfiles.sgml index 845033b6b6..c15579c516 100644 --- a/doc/src/sgml/ref/allfiles.sgml +++ b/doc/src/sgml/ref/allfiles.sgml @@ -1,5 +1,5 @@ @@ -9,6 +9,7 @@ Complete list of usable sgml source files in this directory. + diff --git a/doc/src/sgml/ref/alter_default_privileges.sgml b/doc/src/sgml/ref/alter_default_privileges.sgml new file mode 100644 index 0000000000..b2054b1780 --- /dev/null +++ b/doc/src/sgml/ref/alter_default_privileges.sgml @@ -0,0 +1,211 @@ + + + + + ALTER DEFAULT PRIVILEGES + 7 + SQL - Language Statements + + + + ALTER DEFAULT PRIVILEGES + define default access privileges + + + + ALTER DEFAULT PRIVILEGES + + + + +ALTER DEFAULT PRIVILEGES + [ FOR { ROLE | USER } target_role [, ...] ] + [ IN SCHEMA schema_name [, ...] ] + abbreviated_grant_or_revoke + +where abbreviated_grant_or_revoke is one of: + +GRANT { { SELECT | INSERT | UPDATE | DELETE | TRUNCATE | REFERENCES | TRIGGER } + [,...] | ALL [ PRIVILEGES ] } + ON TABLE + TO { [ GROUP ] role_name | PUBLIC } [, ...] [ WITH GRANT OPTION ] + +GRANT { { USAGE | SELECT | UPDATE } + [,...] | ALL [ PRIVILEGES ] } + ON SEQUENCE + TO { [ GROUP ] role_name | PUBLIC } [, ...] [ WITH GRANT OPTION ] + +GRANT { EXECUTE | ALL [ PRIVILEGES ] } + ON FUNCTION + TO { [ GROUP ] role_name | PUBLIC } [, ...] [ WITH GRANT OPTION ] + +REVOKE [ GRANT OPTION FOR ] + { { SELECT | INSERT | UPDATE | DELETE | TRUNCATE | REFERENCES | TRIGGER } + [,...] | ALL [ PRIVILEGES ] } + ON TABLE + FROM { [ GROUP ] role_name | PUBLIC } [, ...] + [ CASCADE | RESTRICT ] + +REVOKE [ GRANT OPTION FOR ] + { { USAGE | SELECT | UPDATE } + [,...] | ALL [ PRIVILEGES ] } + ON SEQUENCE + FROM { [ GROUP ] role_name | PUBLIC } [, ...] + [ CASCADE | RESTRICT ] + +REVOKE [ GRANT OPTION FOR ] + { EXECUTE | ALL [ PRIVILEGES ] } + ON FUNCTION + FROM { [ GROUP ] role_name | PUBLIC } [, ...] + [ CASCADE | RESTRICT ] + + + + + Description + + + ALTER DEFAULT PRIVILEGES allows you to set the privileges + that will be applied to objects created in the future. (It does not + affect privileges assigned to already-existing objects.) Currently, + only the privileges for tables (including views), sequences, and + functions can be altered. + + + + You can change default privileges only for objects that will be created by + yourself or by roles that you are a member of. The privileges can be set + globally (i.e., for all objects created in the current database), + or just for objects created in specified schemas. Default privileges + that are specified per-schema are added to whatever the global default + privileges are for the particular object type. + + + + As explained under , + the default privileges for any object type normally grant all grantable + permissions to the object owner, and may grant some privileges to + PUBLIC as well. However, this behavior can be changed by + altering the global default privileges with + ALTER DEFAULT PRIVILEGES. + + + + Parameters + + + + target_role + + + The name of an existing role of which the current role is a member. + If FOR ROLE is omitted, the current role is assumed. + + + + + + schema_name + + + The name of an existing schema. Each target_role + must have CREATE privileges for each specified schema. + If IN SCHEMA is omitted, the global default privileges + are altered. + + + + + + role_name + + + The name of an existing role to grant or revoke privileges for. + This parameter, and all the other parameters in + abbreviated_grant_or_revoke, + act as described under + or + , + except that one is setting permissions for a whole class of objects + rather than specific named objects. + + + + + + + + + Notes + + + Use 's \ddp command + to obtain information about existing assignments of default privileges. + The meaning of the privilege values is the same as explained for + \dp under + . + + + + If you wish to drop a role that has had its global default privileges + altered, it is necessary to use DROP OWNED BY first, + to get rid of the default privileges entry for the role. + + + + + Examples + + + Grant SELECT privilege to everyone for all tables (and views) you + subsequently create in schema myschema, and allow + role webuser to INSERT into them too: + + +ALTER DEFAULT PRIVILEGES IN SCHEMA myschema GRANT SELECT ON TABLE TO PUBLIC; +ALTER DEFAULT PRIVILEGES IN SCHEMA myschema GRANT INSERT ON TABLE TO webuser; + + + + + Undo the above, so that subsequently-created tables won't have any + more permissions than normal: + + +ALTER DEFAULT PRIVILEGES IN SCHEMA myschema REVOKE SELECT ON TABLE FROM PUBLIC; +ALTER DEFAULT PRIVILEGES IN SCHEMA myschema REVOKE INSERT ON TABLE FROM webuser; + + + + + Remove the public EXECUTE permission that is normally granted on functions, + for all functions subsequently created by role admin: + + +ALTER DEFAULT PRIVILEGES FOR ROLE admin REVOKE EXECUTE ON FUNCTION FROM PUBLIC; + + + + + + 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 4dddde27b9..2dcf4aa0f0 100644 --- a/doc/src/sgml/ref/grant.sgml +++ b/doc/src/sgml/ref/grant.sgml @@ -1,5 +1,5 @@ @@ -80,14 +80,6 @@ GRANT role_name [, ...] TO - - As of PostgreSQL 8.1, the concepts of users and - groups have been unified into a single kind of entity called a role. - It is therefore no longer necessary to use the keyword GROUP - to identify whether a grantee is a user or a group. GROUP - is still allowed in the command, but it is a noise word. - - GRANT on Database Objects @@ -145,6 +137,9 @@ GRANT role_name [, ...] TO REVOKE in the same transaction that creates the object; then there is no window in which another user can use the object.) + Also, these initial default privilege settings can be changed using the + + command. @@ -388,6 +383,14 @@ GRANT role_name [, ...] TO + + Since PostgreSQL 8.1, the concepts of users and + groups have been unified into a single kind of entity called a role. + It is therefore no longer necessary to use the keyword GROUP + to identify whether a grantee is a user or a group. GROUP + is still allowed in the command, but it is a noise word. + + A user may perform SELECT, INSERT, etc. on a column if he holds that privilege for either the specific column or @@ -518,8 +521,13 @@ GRANT SELECT (col1), UPDATE (col1) ON mytable TO miriam_rw; REVOKE on an object will instantiate the default privileges (producing, for example, {miriam=arwdDxt/miriam}) and then modify them per the - specified request. Entries are shown in Column access + specified request. Similarly, entries are shown in Column access privileges only for columns with nondefault privileges. + (Note: for this purpose, default privileges always means the + built-in default privileges for the object's type. An object whose + privileges have been affected by an ALTER DEFAULT PRIVILEGES + command will always be shown with an explicit privilege entry that + includes the effects of the ALTER.) @@ -602,9 +610,10 @@ GRANT admins TO joe; See Also - - - + + + + diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index f60c3150e9..e689d275cb 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -1,5 +1,5 @@ @@ -978,6 +978,29 @@ testdb=> + + \ddp [ pattern ] + + + Lists default access privilege settings. An entry is shown for + each role (and schema, if applicable) for which the default + privilege settings have been changed from the built-in defaults. + If pattern is + specified, only entries whose role name or schema name matches + the pattern are listed. + + + + The command is used to set + default access privileges. The meaning of the + privilege display is explained under + . + + + + + \dD[S] [ pattern ] @@ -1142,8 +1165,8 @@ testdb=> class="parameter">pattern is specified, only those roles whose names match the pattern are listed. (This command is now effectively the same as \du). - If the form \dg+ is used, additional information - is shown about each role, including the comment for each role. + If the form \dg+ is used, additional information + is shown about each role, including the comment for each role. @@ -1235,7 +1258,9 @@ testdb=> The and - commands are used to set access privileges. + commands are used to set access privileges. The meaning of the + privilege display is explained under + . @@ -2045,12 +2070,6 @@ lo_import 152801 specified, only tables,views and sequences whose names match the pattern are listed. - - The and - - commands are used to set access privileges. - - This is an alias for \dp (display privileges). diff --git a/doc/src/sgml/reference.sgml b/doc/src/sgml/reference.sgml index 48f8040541..0e72fc5475 100644 --- a/doc/src/sgml/reference.sgml +++ b/doc/src/sgml/reference.sgml @@ -1,4 +1,4 @@ - + Reference @@ -37,6 +37,7 @@ &alterAggregate; &alterConversion; &alterDatabase; + &alterDefaultPrivileges; &alterDomain; &alterForeignDataWrapper; &alterFunction; diff --git a/src/backend/bootstrap/bootparse.y b/src/backend/bootstrap/bootparse.y index b7624547aa..9d6f854d12 100644 --- a/src/backend/bootstrap/bootparse.y +++ b/src/backend/bootstrap/bootparse.y @@ -9,7 +9,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/bootstrap/bootparse.y,v 1.99 2009/09/27 01:32:11 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/bootstrap/bootparse.y,v 1.100 2009/10/05 19:24:34 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -226,6 +226,7 @@ Boot_CreateStmt: 0, ONCOMMIT_NOOP, (Datum) 0, + false, true); elog(DEBUG4, "relation created with oid %u", id); } diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile index e4414ed2bf..53784e9c54 100644 --- a/src/backend/catalog/Makefile +++ b/src/backend/catalog/Makefile @@ -2,7 +2,7 @@ # # Makefile for backend/catalog # -# $PostgreSQL: pgsql/src/backend/catalog/Makefile,v 1.71 2009/08/26 22:24:43 petere Exp $ +# $PostgreSQL: pgsql/src/backend/catalog/Makefile,v 1.72 2009/10/05 19:24:34 tgl Exp $ # #------------------------------------------------------------------------- @@ -37,6 +37,7 @@ POSTGRES_BKI_SRCS = $(addprefix $(top_srcdir)/src/include/catalog/,\ 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_acl.h \ toasting.h indexing.h \ ) diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c index bb15e78d1c..b06e587a1b 100644 --- a/src/backend/catalog/aclchk.c +++ b/src/backend/catalog/aclchk.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/catalog/aclchk.c,v 1.154 2009/06/11 14:48:54 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/catalog/aclchk.c,v 1.155 2009/10/05 19:24:35 tgl Exp $ * * NOTES * See acl.h. @@ -27,6 +27,7 @@ #include "catalog/pg_authid.h" #include "catalog/pg_conversion.h" #include "catalog/pg_database.h" +#include "catalog/pg_default_acl.h" #include "catalog/pg_foreign_data_wrapper.h" #include "catalog/pg_foreign_server.h" #include "catalog/pg_language.h" @@ -51,6 +52,51 @@ #include "utils/tqual.h" +/* + * The information about one Grant/Revoke statement, in internal format: object + * and grantees names have been turned into Oids, the privilege list is an + * AclMode bitmask. If 'privileges' is ACL_NO_RIGHTS (the 0 value) and + * all_privs is true, 'privileges' will be internally set to the right kind of + * ACL_ALL_RIGHTS_*, depending on the object type (NB - this will modify the + * InternalGrant struct!) + * + * Note: 'all_privs' and 'privileges' represent object-level privileges only. + * There might also be column-level privilege specifications, which are + * represented in col_privs (this is a list of untransformed AccessPriv nodes). + * Column privileges are only valid for objtype ACL_OBJECT_RELATION. + */ +typedef struct +{ + bool is_grant; + GrantObjectType objtype; + List *objects; + bool all_privs; + AclMode privileges; + List *col_privs; + List *grantees; + bool grant_option; + DropBehavior behavior; +} InternalGrant; + +/* + * Internal format used by ALTER DEFAULT PRIVILEGES. + */ +typedef struct +{ + Oid roleid; /* owning role */ + Oid nspid; /* namespace, or InvalidOid if none */ + /* remaining fields are same as in InternalGrant: */ + bool is_grant; + GrantObjectType objtype; + bool all_privs; + AclMode privileges; + List *grantees; + bool grant_option; + DropBehavior behavior; +} InternalDefaultACL; + + +static void ExecGrantStmt_oids(InternalGrant *istmt); static void ExecGrant_Relation(InternalGrant *grantStmt); static void ExecGrant_Database(InternalGrant *grantStmt); static void ExecGrant_Fdw(InternalGrant *grantStmt); @@ -60,6 +106,9 @@ static void ExecGrant_Language(InternalGrant *grantStmt); static void ExecGrant_Namespace(InternalGrant *grantStmt); static void ExecGrant_Tablespace(InternalGrant *grantStmt); +static void SetDefaultACLsInSchemas(InternalDefaultACL *iacls, List *nspnames); +static void SetDefaultACL(InternalDefaultACL *iacls); + static List *objectNamesToOids(GrantObjectType objtype, List *objnames); static void expand_col_privileges(List *colnames, Oid table_oid, AclMode this_privileges, @@ -361,11 +410,11 @@ ExecuteGrantStmt(GrantStmt *stmt) errormsg = gettext_noop("invalid privilege type %s for foreign server"); break; default: + elog(ERROR, "unrecognized GrantStmt.objtype: %d", + (int) stmt->objtype); /* keep compiler quiet */ all_privileges = ACL_NO_RIGHTS; errormsg = NULL; - elog(ERROR, "unrecognized GrantStmt.objtype: %d", - (int) stmt->objtype); } if (stmt->privileges == NIL) @@ -421,11 +470,9 @@ ExecuteGrantStmt(GrantStmt *stmt) /* * ExecGrantStmt_oids * - * "Internal" entrypoint for granting and revoking privileges. This is - * exported for pg_shdepend.c to use in revoking privileges when dropping - * a role. + * Internal entry point for granting and revoking privileges. */ -void +static void ExecGrantStmt_oids(InternalGrant *istmt) { switch (istmt->objtype) @@ -609,6 +656,563 @@ objectNamesToOids(GrantObjectType objtype, List *objnames) return objects; } +/* + * ALTER DEFAULT PRIVILEGES statement + */ +void +ExecAlterDefaultPrivilegesStmt(AlterDefaultPrivilegesStmt *stmt) +{ + GrantStmt *action = stmt->action; + InternalDefaultACL iacls; + ListCell *cell; + List *rolenames = NIL; + List *nspnames = NIL; + DefElem *drolenames = NULL; + DefElem *dnspnames = NULL; + AclMode all_privileges; + const char *errormsg; + + /* Deconstruct the "options" part of the statement */ + 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; + } + else + elog(ERROR, "option \"%s\" not recognized", defel->defname); + } + + if (dnspnames) + nspnames = (List *) dnspnames->arg; + if (drolenames) + rolenames = (List *) drolenames->arg; + + /* Prepare the InternalDefaultACL representation of the statement */ + /* roleid to be filled below */ + /* nspid to be filled in SetDefaultACLsInSchemas */ + iacls.is_grant = action->is_grant; + iacls.objtype = action->objtype; + /* all_privs to be filled below */ + /* 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 action->privileges, a list of privilege strings, + * into an AclMode bitmask. + */ + 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: + elog(ERROR, "unrecognized GrantStmt.objtype: %d", + (int) action->objtype); + /* keep compiler quiet */ + all_privileges = ACL_NO_RIGHTS; + errormsg = NULL; + } + + if (action->privileges == NIL) + { + iacls.all_privs = true; + + /* + * will be turned into ACL_ALL_RIGHTS_* by the internal routines + * depending on the object type + */ + 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 (privnode->cols) + ereport(ERROR, + (errcode(ERRCODE_INVALID_GRANT_OPERATION), + errmsg("default privileges cannot be set for columns"))); + + 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; + } + } + + if (rolenames == NIL) + { + /* Set permissions for myself */ + iacls.roleid = GetUserId(); + + SetDefaultACLsInSchemas(&iacls, nspnames); + } + else + { + /* Look up the role OIDs and do permissions checks */ + ListCell *rolecell; + + foreach(rolecell, rolenames) + { + char *rolename = strVal(lfirst(rolecell)); + + iacls.roleid = get_roleid_checked(rolename); + + /* + * We insist that calling user be a member of each target role. + * If he has that, he could become that role anyway via SET ROLE, + * so FOR ROLE is just a syntactic convenience and doesn't give + * any special privileges. + */ + check_is_member_of_role(GetUserId(), iacls.roleid); + + SetDefaultACLsInSchemas(&iacls, nspnames); + } + } +} + +/* + * Process ALTER DEFAULT PRIVILEGES for a list of target schemas + * + * All fields of *iacls except nspid were filled already + */ +static void +SetDefaultACLsInSchemas(InternalDefaultACL *iacls, List *nspnames) +{ + if (nspnames == NIL) + { + /* Set database-wide permissions if no schema was specified */ + iacls->nspid = InvalidOid; + + SetDefaultACL(iacls); + } + else + { + /* Look up the schema OIDs and do permissions checks */ + ListCell *nspcell; + + foreach(nspcell, nspnames) + { + char *nspname = strVal(lfirst(nspcell)); + AclResult aclresult; + + /* + * Normally we'd use LookupCreationNamespace here, but it's + * important to do the permissions check against the target role + * not the calling user, so write it out in full. We require + * CREATE privileges, since without CREATE you won't be able to do + * anything using the default privs anyway. + */ + iacls->nspid = GetSysCacheOid(NAMESPACENAME, + CStringGetDatum(nspname), + 0, 0, 0); + if (!OidIsValid(iacls->nspid)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_SCHEMA), + errmsg("schema \"%s\" does not exist", nspname))); + + aclresult = pg_namespace_aclcheck(iacls->nspid, iacls->roleid, + ACL_CREATE); + if (aclresult != ACLCHECK_OK) + aclcheck_error(aclresult, ACL_KIND_NAMESPACE, + nspname); + + SetDefaultACL(iacls); + } + } +} + + +/* + * Create or update a pg_default_acl entry + */ +static void +SetDefaultACL(InternalDefaultACL *iacls) +{ + AclMode this_privileges = iacls->privileges; + char objtype; + Relation rel; + HeapTuple tuple; + bool isNew; + Acl *old_acl; + Acl *new_acl; + HeapTuple newtuple; + Datum values[Natts_pg_default_acl]; + bool nulls[Natts_pg_default_acl]; + bool replaces[Natts_pg_default_acl]; + int noldmembers; + int nnewmembers; + Oid *oldmembers; + Oid *newmembers; + + rel = heap_open(DefaultAclRelationId, RowExclusiveLock); + + /* + * Convert ACL object type to pg_default_acl 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_SEQUENCE: + objtype = DEFACLOBJ_SEQUENCE; + if (iacls->all_privs && this_privileges == ACL_NO_RIGHTS) + this_privileges = ACL_ALL_RIGHTS_SEQUENCE; + break; + + case ACL_OBJECT_FUNCTION: + objtype = DEFACLOBJ_FUNCTION; + if (iacls->all_privs && this_privileges == ACL_NO_RIGHTS) + this_privileges = ACL_ALL_RIGHTS_FUNCTION; + break; + + default: + elog(ERROR, "unrecognized objtype: %d", + (int) iacls->objtype); + objtype = 0; /* keep compiler quiet */ + break; + } + + /* Search for existing row for this object type in catalog */ + tuple = SearchSysCache(DEFACLROLENSPOBJ, + ObjectIdGetDatum(iacls->roleid), + ObjectIdGetDatum(iacls->nspid), + CharGetDatum(objtype), + 0); + + if (HeapTupleIsValid(tuple)) + { + Datum aclDatum; + bool isNull; + + aclDatum = SysCacheGetAttr(DEFACLROLENSPOBJ, tuple, + Anum_pg_default_acl_defaclacl, + &isNull); + if (!isNull) + old_acl = DatumGetAclPCopy(aclDatum); + else + old_acl = NULL; + isNew = false; + } + else + { + old_acl = NULL; + isNew = true; + } + + if (old_acl == NULL) + { + /* + * If we are creating a global entry, start with the hard-wired + * defaults and modify as per command. Otherwise, start with an empty + * ACL and modify that. This is needed because global entries + * replace the hard-wired defaults, while others do not. + */ + if (!OidIsValid(iacls->nspid)) + old_acl = acldefault(iacls->objtype, iacls->roleid); + else + old_acl = make_empty_acl(); + } + + /* + * We need the members of both old and new ACLs so we can correct the + * shared dependency information. Collect data before + * merge_acl_with_grant throws away old_acl. + */ + noldmembers = aclmembers(old_acl, &oldmembers); + + /* + * Generate new ACL. Grantor of rights is always the same as the + * target role. + */ + new_acl = merge_acl_with_grant(old_acl, + iacls->is_grant, + iacls->grant_option, + iacls->behavior, + iacls->grantees, + this_privileges, + iacls->roleid, + 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_acl_defaclrole - 1] = ObjectIdGetDatum(iacls->roleid); + values[Anum_pg_default_acl_defaclnamespace - 1] = ObjectIdGetDatum(iacls->nspid); + values[Anum_pg_default_acl_defaclobjtype - 1] = CharGetDatum(objtype); + values[Anum_pg_default_acl_defaclacl - 1] = PointerGetDatum(new_acl); + + newtuple = heap_form_tuple(RelationGetDescr(rel), values, nulls); + simple_heap_insert(rel, newtuple); + } + else + { + values[Anum_pg_default_acl_defaclacl - 1] = PointerGetDatum(new_acl); + replaces[Anum_pg_default_acl_defaclacl - 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); + + /* these dependencies don't change in an update */ + if (isNew) + { + /* dependency on role */ + recordDependencyOnOwner(DefaultAclRelationId, + HeapTupleGetOid(newtuple), + iacls->roleid); + + /* dependency on namespace */ + if (OidIsValid(iacls->nspid)) + { + ObjectAddress myself, + referenced; + + myself.classId = DefaultAclRelationId; + myself.objectId = HeapTupleGetOid(newtuple); + myself.objectSubId = 0; + + referenced.classId = NamespaceRelationId; + referenced.objectId = iacls->nspid; + referenced.objectSubId = 0; + + recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO); + } + } + + /* + * Update the shared dependency ACL info + */ + nnewmembers = aclmembers(new_acl, &newmembers); + + updateAclDependencies(DefaultAclRelationId, HeapTupleGetOid(newtuple), 0, + iacls->roleid, iacls->is_grant, + noldmembers, oldmembers, + nnewmembers, newmembers); + + pfree(new_acl); + + if (HeapTupleIsValid(tuple)) + ReleaseSysCache(tuple); + + heap_close(rel, RowExclusiveLock); +} + + +/* + * RemoveRoleFromObjectACL + * + * Used by shdepDropOwned to remove mentions of a role in ACLs + */ +void +RemoveRoleFromObjectACL(Oid roleid, Oid classid, Oid objid) +{ + if (classid == DefaultAclRelationId) + { + InternalDefaultACL iacls; + Form_pg_default_acl pg_default_acl_tuple; + Relation rel; + ScanKeyData skey[1]; + SysScanDesc scan; + HeapTuple tuple; + + /* first fetch info needed by SetDefaultACL */ + rel = heap_open(DefaultAclRelationId, AccessShareLock); + + ScanKeyInit(&skey[0], + ObjectIdAttributeNumber, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(objid)); + + scan = systable_beginscan(rel, DefaultAclOidIndexId, true, + SnapshotNow, 1, skey); + + tuple = systable_getnext(scan); + + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "could not find tuple for default ACL %u", objid); + + pg_default_acl_tuple = (Form_pg_default_acl) GETSTRUCT(tuple); + + iacls.roleid = pg_default_acl_tuple->defaclrole; + iacls.nspid = pg_default_acl_tuple->defaclnamespace; + + switch (pg_default_acl_tuple->defaclobjtype) + { + case DEFACLOBJ_RELATION: + iacls.objtype = ACL_OBJECT_RELATION; + break; + case ACL_OBJECT_SEQUENCE: + iacls.objtype = ACL_OBJECT_SEQUENCE; + break; + case DEFACLOBJ_FUNCTION: + iacls.objtype = ACL_OBJECT_FUNCTION; + break; + default: + /* Shouldn't get here */ + elog(ERROR, "unexpected default ACL type %d", + pg_default_acl_tuple->defaclobjtype); + break; + } + + systable_endscan(scan); + heap_close(rel, AccessShareLock); + + 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 */ + SetDefaultACL(&iacls); + } + else + { + 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 class %u", 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); + } +} + + +/* + * Remove a pg_default_acl entry + */ +void +RemoveDefaultACLById(Oid defaclOid) +{ + Relation rel; + ScanKeyData skey[1]; + SysScanDesc scan; + HeapTuple tuple; + + rel = heap_open(DefaultAclRelationId, RowExclusiveLock); + + ScanKeyInit(&skey[0], + ObjectIdAttributeNumber, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(defaclOid)); + + scan = systable_beginscan(rel, DefaultAclOidIndexId, true, + SnapshotNow, 1, skey); + + tuple = systable_getnext(scan); + + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "could not find tuple for default ACL %u", defaclOid); + + simple_heap_delete(rel, &tuple->t_self); + + systable_endscan(scan); + heap_close(rel, RowExclusiveLock); +} + + /* * expand_col_privileges * @@ -3532,3 +4136,106 @@ pg_conversion_ownercheck(Oid conv_oid, Oid roleid) return has_privs_of_role(roleid, ownerId); } + +/* + * Fetch pg_default_acl entry for given role, namespace and object type + * (object type must be given in pg_default_acl's encoding). + * Returns NULL if no such entry. + */ +static Acl * +get_default_acl_internal(Oid roleId, Oid nsp_oid, char objtype) +{ + Acl *result = NULL; + HeapTuple tuple; + + tuple = SearchSysCache(DEFACLROLENSPOBJ, + ObjectIdGetDatum(roleId), + ObjectIdGetDatum(nsp_oid), + CharGetDatum(objtype), + 0); + + if (HeapTupleIsValid(tuple)) + { + Datum aclDatum; + bool isNull; + + aclDatum = SysCacheGetAttr(DEFACLROLENSPOBJ, tuple, + Anum_pg_default_acl_defaclacl, + &isNull); + if (!isNull) + result = DatumGetAclPCopy(aclDatum); + ReleaseSysCache(tuple); + } + + return result; +} + +/* + * Get default permissions for newly created object within given schema + * + * Returns NULL if built-in system defaults should be used + */ +Acl * +get_user_default_acl(GrantObjectType objtype, Oid ownerId, Oid nsp_oid) +{ + Acl *result; + Acl *glob_acl; + Acl *schema_acl; + Acl *def_acl; + char defaclobjtype; + + /* + * Use NULL during bootstrap, since pg_default_acl probably isn't there + * yet. + */ + if (IsBootstrapProcessingMode()) + return NULL; + + /* Check if object type is supported in pg_default_acl */ + switch (objtype) + { + case ACL_OBJECT_RELATION: + defaclobjtype = DEFACLOBJ_RELATION; + break; + + case ACL_OBJECT_SEQUENCE: + defaclobjtype = DEFACLOBJ_SEQUENCE; + break; + + case ACL_OBJECT_FUNCTION: + defaclobjtype = DEFACLOBJ_FUNCTION; + break; + + default: + return NULL; + } + + /* Look up the relevant pg_default_acl entries */ + glob_acl = get_default_acl_internal(ownerId, InvalidOid, defaclobjtype); + schema_acl = get_default_acl_internal(ownerId, nsp_oid, defaclobjtype); + + /* Quick out if neither entry exists */ + if (glob_acl == NULL && schema_acl == NULL) + return NULL; + + /* We need to know the hard-wired default value, too */ + def_acl = acldefault(objtype, ownerId); + + /* If there's no global entry, substitute the hard-wired default */ + if (glob_acl == NULL) + glob_acl = def_acl; + + /* Merge in any per-schema privileges */ + result = aclmerge(glob_acl, schema_acl, ownerId); + + /* + * For efficiency, we want to return NULL if the result equals default. + * This requires sorting both arrays to get an accurate comparison. + */ + aclitemsort(result); + aclitemsort(def_acl); + if (aclequal(result, def_acl)) + result = NULL; + + return result; +} diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c index c3d87ef59e..8a07c69c7e 100644 --- a/src/backend/catalog/dependency.c +++ b/src/backend/catalog/dependency.c @@ -8,7 +8,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/catalog/dependency.c,v 1.91 2009/09/22 15:46:34 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/catalog/dependency.c,v 1.92 2009/10/05 19:24:35 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -32,6 +32,7 @@ #include "catalog/pg_conversion.h" #include "catalog/pg_conversion_fn.h" #include "catalog/pg_database.h" +#include "catalog/pg_default_acl.h" #include "catalog/pg_depend.h" #include "catalog/pg_foreign_data_wrapper.h" #include "catalog/pg_foreign_server.h" @@ -64,6 +65,7 @@ #include "parser/parsetree.h" #include "rewrite/rewriteRemove.h" #include "storage/lmgr.h" +#include "utils/acl.h" #include "utils/builtins.h" #include "utils/fmgroids.h" #include "utils/guc.h" @@ -146,7 +148,8 @@ static const Oid object_classes[MAX_OCLASS] = { TableSpaceRelationId, /* OCLASS_TBLSPACE */ ForeignDataWrapperRelationId, /* OCLASS_FDW */ ForeignServerRelationId, /* OCLASS_FOREIGN_SERVER */ - UserMappingRelationId /* OCLASS_USER_MAPPING */ + UserMappingRelationId, /* OCLASS_USER_MAPPING */ + DefaultAclRelationId /* OCLASS_DEFACL */ }; @@ -1136,6 +1139,10 @@ doDeletion(const ObjectAddress *object) RemoveUserMappingById(object->objectId); break; + case OCLASS_DEFACL: + RemoveDefaultACLById(object->objectId); + break; + default: elog(ERROR, "unrecognized object class: %u", object->classId); @@ -2055,6 +2062,10 @@ getObjectClass(const ObjectAddress *object) case UserMappingRelationId: Assert(object->objectSubId == 0); return OCLASS_USER_MAPPING; + + case DefaultAclRelationId: + Assert(object->objectSubId == 0); + return OCLASS_DEFACL; } /* shouldn't get here */ @@ -2597,6 +2608,69 @@ getObjectDescription(const ObjectAddress *object) break; } + case OCLASS_DEFACL: + { + Relation defaclrel; + ScanKeyData skey[1]; + SysScanDesc rcscan; + HeapTuple tup; + Form_pg_default_acl defacl; + + defaclrel = heap_open(DefaultAclRelationId, AccessShareLock); + + ScanKeyInit(&skey[0], + ObjectIdAttributeNumber, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(object->objectId)); + + rcscan = systable_beginscan(defaclrel, DefaultAclOidIndexId, + true, SnapshotNow, 1, skey); + + tup = systable_getnext(rcscan); + + if (!HeapTupleIsValid(tup)) + elog(ERROR, "could not find tuple for default ACL %u", + object->objectId); + + defacl = (Form_pg_default_acl) GETSTRUCT(tup); + + switch (defacl->defaclobjtype) + { + case DEFACLOBJ_RELATION: + appendStringInfo(&buffer, + _("default privileges on new relations belonging to role %s"), + GetUserNameFromId(defacl->defaclrole)); + break; + case DEFACLOBJ_SEQUENCE: + appendStringInfo(&buffer, + _("default privileges on new sequences belonging to role %s"), + GetUserNameFromId(defacl->defaclrole)); + break; + case DEFACLOBJ_FUNCTION: + appendStringInfo(&buffer, + _("default privileges on new functions belonging to role %s"), + GetUserNameFromId(defacl->defaclrole)); + break; + default: + /* shouldn't get here */ + appendStringInfo(&buffer, + _("default privileges belonging to role %s"), + GetUserNameFromId(defacl->defaclrole)); + break; + } + + if (OidIsValid(defacl->defaclnamespace)) + { + appendStringInfo(&buffer, + _(" in schema %s"), + get_namespace_name(defacl->defaclnamespace)); + } + + systable_endscan(rcscan); + heap_close(defaclrel, AccessShareLock); + break; + } + default: appendStringInfo(&buffer, "unrecognized object %u %u %d", object->classId, diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c index e9bdc88223..a013306284 100644 --- a/src/backend/catalog/heap.c +++ b/src/backend/catalog/heap.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/catalog/heap.c,v 1.359 2009/09/26 22:42:01 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/catalog/heap.c,v 1.360 2009/10/05 19:24:35 tgl Exp $ * * * INTERFACE ROUTINES @@ -59,6 +59,7 @@ #include "storage/bufmgr.h" #include "storage/freespace.h" #include "storage/smgr.h" +#include "utils/acl.h" #include "utils/builtins.h" #include "utils/fmgroids.h" #include "utils/inval.h" @@ -74,6 +75,7 @@ static void AddNewRelationTuple(Relation pg_class_desc, Oid new_rel_oid, Oid new_type_oid, Oid relowner, char relkind, + Datum relacl, Datum reloptions); static Oid AddNewRelationType(const char *typeName, Oid typeNamespace, @@ -636,14 +638,16 @@ AddNewAttributeTuples(Oid new_rel_oid, * 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). + * relacl and reloptions are passed in Datum form (to avoid having + * to reference the data types in heap.h). Pass (Datum) 0 to set them + * to NULL. * -------------------------------- */ void InsertPgClassTuple(Relation pg_class_desc, Relation new_rel_desc, Oid new_rel_oid, + Datum relacl, Datum reloptions) { Form_pg_class rd_rel = new_rel_desc->rd_rel; @@ -678,8 +682,10 @@ InsertPgClassTuple(Relation pg_class_desc, 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 (relacl != (Datum) 0) + values[Anum_pg_class_relacl - 1] = relacl; + else + nulls[Anum_pg_class_relacl - 1] = true; if (reloptions != (Datum) 0) values[Anum_pg_class_reloptions - 1] = reloptions; else @@ -715,6 +721,7 @@ AddNewRelationTuple(Relation pg_class_desc, Oid new_type_oid, Oid relowner, char relkind, + Datum relacl, Datum reloptions) { Form_pg_class new_rel_reltup; @@ -775,7 +782,8 @@ AddNewRelationTuple(Relation pg_class_desc, new_rel_desc->rd_att->tdtypeid = new_type_oid; /* Now build and insert the tuple */ - InsertPgClassTuple(pg_class_desc, new_rel_desc, new_rel_oid, reloptions); + InsertPgClassTuple(pg_class_desc, new_rel_desc, new_rel_oid, + relacl, reloptions); } @@ -831,6 +839,27 @@ AddNewRelationType(const char *typeName, * heap_create_with_catalog * * creates a new cataloged relation. see comments above. + * + * Arguments: + * relname: name to give to new rel + * relnamespace: OID of namespace it goes in + * reltablespace: OID of tablespace it goes in + * relid: OID to assign to new rel, or InvalidOid to select a new OID + * reltypeid: OID to assign to rel's rowtype, or InvalidOid to select one + * ownerid: OID of new rel's owner + * tupdesc: tuple descriptor (source of column definitions) + * cooked_constraints: list of precooked check constraints and defaults + * relkind: relkind for new rel + * shared_relation: TRUE if it's to be a shared relation + * oidislocal: TRUE if oid column (if any) should be marked attislocal + * oidinhcount: attinhcount to assign to oid column (if any) + * oncommit: ON COMMIT marking (only relevant if it's a temp table) + * reloptions: reloptions in Datum form, or (Datum) 0 if none + * use_user_acl: TRUE if should look for user-defined default permissions; + * if FALSE, relacl is always set NULL + * allow_system_table_mods: TRUE to allow creation in system namespaces + * + * Returns the OID of the new relation * -------------------------------- */ Oid @@ -848,10 +877,12 @@ heap_create_with_catalog(const char *relname, int oidinhcount, OnCommitAction oncommit, Datum reloptions, + bool use_user_acl, bool allow_system_table_mods) { Relation pg_class_desc; Relation new_rel_desc; + Acl *relacl; Oid old_type_oid; Oid new_type_oid; Oid new_array_oid = InvalidOid; @@ -920,6 +951,30 @@ heap_create_with_catalog(const char *relname, relid = GetNewRelFileNode(reltablespace, shared_relation, pg_class_desc); + /* + * Determine the relation's initial permissions. + */ + if (use_user_acl) + { + switch (relkind) + { + case RELKIND_RELATION: + case RELKIND_VIEW: + relacl = get_user_default_acl(ACL_OBJECT_RELATION, ownerid, + relnamespace); + break; + case RELKIND_SEQUENCE: + relacl = get_user_default_acl(ACL_OBJECT_SEQUENCE, ownerid, + relnamespace); + break; + default: + relacl = NULL; + break; + } + } + else + relacl = NULL; + /* * Create the relcache entry (mostly dummy at this point) and the physical * disk file. (If we fail further down, it's the smgr's responsibility to @@ -1027,6 +1082,7 @@ heap_create_with_catalog(const char *relname, new_type_oid, ownerid, relkind, + PointerGetDatum(relacl), reloptions); /* @@ -1037,12 +1093,15 @@ heap_create_with_catalog(const char *relname, /* * Make a dependency link to force the relation to be deleted if its - * namespace is. Also make a dependency link to its owner. + * namespace is. Also make a dependency link to its owner, as well + * as dependencies for any roles mentioned in the default ACL. * * For composite types, these dependencies are tracked for the pg_type * entry, so we needn't record them here. Likewise, TOAST tables don't * need a namespace dependency (they live in a pinned namespace) nor an - * owner dependency (they depend indirectly through the parent table). + * owner dependency (they depend indirectly through the parent table), + * nor should they have any ACL entries. + * * Also, skip this in bootstrap mode, since we don't make dependencies * while bootstrapping. */ @@ -1062,6 +1121,18 @@ heap_create_with_catalog(const char *relname, recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); recordDependencyOnOwner(RelationRelationId, relid, ownerid); + + if (relacl != NULL) + { + int nnewmembers; + Oid *newmembers; + + nnewmembers = aclmembers(relacl, &newmembers); + updateAclDependencies(RelationRelationId, relid, 0, + ownerid, true, + 0, NULL, + nnewmembers, newmembers); + } } /* diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c index 759950b226..b43b2910e7 100644 --- a/src/backend/catalog/index.c +++ b/src/backend/catalog/index.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/catalog/index.c,v 1.321 2009/08/02 22:14:52 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/catalog/index.c,v 1.322 2009/10/05 19:24:35 tgl Exp $ * * * INTERFACE ROUTINES @@ -664,6 +664,7 @@ index_create(Oid heapRelationId, */ InsertPgClassTuple(pg_class, indexRelation, RelationGetRelid(indexRelation), + (Datum) 0, reloptions); /* done with pg_class */ diff --git a/src/backend/catalog/pg_proc.c b/src/backend/catalog/pg_proc.c index 1fda61cc16..db69c127ad 100644 --- a/src/backend/catalog/pg_proc.c +++ b/src/backend/catalog/pg_proc.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/catalog/pg_proc.c,v 1.166 2009/10/02 18:13:04 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/catalog/pg_proc.c,v 1.167 2009/10/05 19:24:36 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -90,6 +90,7 @@ ProcedureCreate(const char *procedureName, bool internalOutParam = false; Oid variadicType = InvalidOid; Oid proowner = GetUserId(); + Acl *proacl = NULL; Relation rel; HeapTuple tup; HeapTuple oldtup; @@ -331,8 +332,7 @@ ProcedureCreate(const char *procedureName, 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; + /* proacl will be determined later */ rel = heap_open(ProcedureRelationId, RowExclusiveLock); tupDesc = RelationGetDescr(rel); @@ -489,6 +489,15 @@ ProcedureCreate(const char *procedureName, else { /* Creating a new procedure */ + + /* First, get default permissions and set up proacl */ + proacl = get_user_default_acl(ACL_OBJECT_FUNCTION, proowner, + procNamespace); + if (proacl != NULL) + values[Anum_pg_proc_proacl - 1] = PointerGetDatum(proacl); + else + nulls[Anum_pg_proc_proacl - 1] = true; + tup = heap_form_tuple(tupDesc, values, nulls); simple_heap_insert(rel, tup); is_update = false; @@ -543,6 +552,19 @@ ProcedureCreate(const char *procedureName, if (!is_update) recordDependencyOnOwner(ProcedureRelationId, retval, proowner); + /* dependency on any roles mentioned in ACL */ + if (!is_update && proacl != NULL) + { + int nnewmembers; + Oid *newmembers; + + nnewmembers = aclmembers(proacl, &newmembers); + updateAclDependencies(ProcedureRelationId, retval, 0, + proowner, true, + 0, NULL, + nnewmembers, newmembers); + } + heap_freetuple(tup); heap_close(rel, RowExclusiveLock); diff --git a/src/backend/catalog/pg_shdepend.c b/src/backend/catalog/pg_shdepend.c index 08977c3863..869ec1fdd5 100644 --- a/src/backend/catalog/pg_shdepend.c +++ b/src/backend/catalog/pg_shdepend.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/catalog/pg_shdepend.c,v 1.34 2009/06/11 14:48:55 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/catalog/pg_shdepend.c,v 1.35 2009/10/05 19:24:36 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -23,6 +23,7 @@ #include "catalog/pg_authid.h" #include "catalog/pg_conversion.h" #include "catalog/pg_database.h" +#include "catalog/pg_default_acl.h" #include "catalog/pg_language.h" #include "catalog/pg_namespace.h" #include "catalog/pg_operator.h" @@ -1180,7 +1181,6 @@ shdepDropOwned(List *roleids, DropBehavior behavior) 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 */ @@ -1195,42 +1195,9 @@ shdepDropOwned(List *roleids, DropBehavior behavior) 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); + RemoveRoleFromObjectACL(roleid, + sdepForm->classid, + sdepForm->objid); break; case SHARED_DEPENDENCY_OWNER: /* Save it for deletion below */ @@ -1365,8 +1332,15 @@ shdepReassignOwned(List *roleids, Oid newrole) AlterLanguageOwner_oid(sdepForm->objid, newrole); break; + case DefaultAclRelationId: + /* + * Ignore default ACLs; they should be handled by + * DROP OWNED, not REASSIGN OWNED. + */ + break; + default: - elog(ERROR, "unexpected classid %d", sdepForm->classid); + elog(ERROR, "unexpected classid %u", sdepForm->classid); break; } /* Make sure the next iteration will see my changes */ diff --git a/src/backend/catalog/toasting.c b/src/backend/catalog/toasting.c index ac7b52734d..dfc178724e 100644 --- a/src/backend/catalog/toasting.c +++ b/src/backend/catalog/toasting.c @@ -8,7 +8,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/catalog/toasting.c,v 1.19 2009/09/26 22:42:01 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/catalog/toasting.c,v 1.20 2009/10/05 19:24:36 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -213,6 +213,7 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid, 0, ONCOMMIT_NOOP, reloptions, + false, true); /* make the toast relation visible, else index creation will fail */ diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c index 5c483614e3..a05fb4736c 100644 --- a/src/backend/commands/cluster.c +++ b/src/backend/commands/cluster.c @@ -11,7 +11,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/commands/cluster.c,v 1.187 2009/09/26 22:42:01 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/commands/cluster.c,v 1.188 2009/10/05 19:24:36 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -713,6 +713,7 @@ make_new_heap(Oid OIDOldHeap, const char *NewName, Oid NewTableSpace) 0, ONCOMMIT_NOOP, reloptions, + false, allowSystemTableMods); ReleaseSysCache(tuple); diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 07b4af611c..697b80d1b4 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/commands/tablecmds.c,v 1.299 2009/09/26 22:42:01 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/commands/tablecmds.c,v 1.300 2009/10/05 19:24:37 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -521,6 +521,7 @@ DefineRelation(CreateStmt *stmt, char relkind) parentOidCount, stmt->oncommit, reloptions, + true, allowSystemTableMods); StoreCatalogInheritance(relationId, inheritOids); @@ -6098,6 +6099,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel, case OCLASS_FDW: case OCLASS_FOREIGN_SERVER: case OCLASS_USER_MAPPING: + case OCLASS_DEFACL: /* * We don't expect any of these sorts of objects to depend on diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c index d18d66c64a..493ec2b1af 100644 --- a/src/backend/executor/execMain.c +++ b/src/backend/executor/execMain.c @@ -26,7 +26,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/executor/execMain.c,v 1.329 2009/09/27 20:09:57 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/executor/execMain.c,v 1.330 2009/10/05 19:24:37 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -2909,6 +2909,7 @@ OpenIntoRel(QueryDesc *queryDesc) 0, into->onCommit, reloptions, + true, allowSystemTableMods); FreeTupleDesc(tupdesc); diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 5feff5d169..0264b2b339 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -15,7 +15,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.438 2009/09/22 23:43:37 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.439 2009/10/05 19:24:38 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -2345,6 +2345,17 @@ _copyGrantRoleStmt(GrantRoleStmt *from) return newnode; } +static AlterDefaultPrivilegesStmt * +_copyAlterDefaultPrivilegesStmt(AlterDefaultPrivilegesStmt *from) +{ + AlterDefaultPrivilegesStmt *newnode = makeNode(AlterDefaultPrivilegesStmt); + + COPY_NODE_FIELD(options); + COPY_NODE_FIELD(action); + + return newnode; +} + static DeclareCursorStmt * _copyDeclareCursorStmt(DeclareCursorStmt *from) { @@ -3760,6 +3771,9 @@ copyObject(void *from) case T_GrantRoleStmt: retval = _copyGrantRoleStmt(from); break; + case T_AlterDefaultPrivilegesStmt: + retval = _copyAlterDefaultPrivilegesStmt(from); + break; case T_DeclareCursorStmt: retval = _copyDeclareCursorStmt(from); break; diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index d7ed08cc68..5d3cbbda1e 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -22,7 +22,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.361 2009/09/22 23:43:38 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.362 2009/10/05 19:24:38 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -1028,6 +1028,15 @@ _equalGrantRoleStmt(GrantRoleStmt *a, GrantRoleStmt *b) return true; } +static bool +_equalAlterDefaultPrivilegesStmt(AlterDefaultPrivilegesStmt *a, AlterDefaultPrivilegesStmt *b) +{ + COMPARE_NODE_FIELD(options); + COMPARE_NODE_FIELD(action); + + return true; +} + static bool _equalDeclareCursorStmt(DeclareCursorStmt *a, DeclareCursorStmt *b) { @@ -2537,6 +2546,9 @@ equal(void *a, void *b) case T_GrantRoleStmt: retval = _equalGrantRoleStmt(a, b); break; + case T_AlterDefaultPrivilegesStmt: + retval = _equalAlterDefaultPrivilegesStmt(a, b); + break; case T_DeclareCursorStmt: retval = _equalDeclareCursorStmt(a, b); break; diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 9a203a5a48..af2f080b63 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -11,7 +11,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.679 2009/09/22 23:43:38 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.680 2009/10/05 19:24:38 tgl Exp $ * * HISTORY * AUTHOR DATE MAJOR EVENT @@ -188,7 +188,9 @@ static TypeName *TableFuncTypeName(List *columns); AlterDatabaseStmt AlterDatabaseSetStmt AlterDomainStmt AlterFdwStmt AlterForeignServerStmt AlterGroupStmt AlterObjectSchemaStmt AlterOwnerStmt AlterSeqStmt AlterTableStmt - AlterUserStmt AlterUserMappingStmt AlterUserSetStmt AlterRoleStmt AlterRoleSetStmt + AlterUserStmt AlterUserMappingStmt AlterUserSetStmt + AlterRoleStmt AlterRoleSetStmt + AlterDefaultPrivilegesStmt DefACLAction AnalyzeStmt ClosePortalStmt ClusterStmt CommentStmt ConstraintsSetStmt CopyStmt CreateAsStmt CreateCastStmt CreateDomainStmt CreateGroupStmt CreateOpClassStmt @@ -269,6 +271,9 @@ static TypeName *TableFuncTypeName(List *columns); %type privilege_target %type function_with_argtypes %type function_with_argtypes_list +%type defacl_privilege_target +%type DefACLOption +%type DefACLOptionList %type stmtblock stmtmulti OptTableElementList TableElementList OptInherit definition @@ -625,6 +630,7 @@ stmtmulti: stmtmulti ';' stmt stmt : AlterDatabaseStmt | AlterDatabaseSetStmt + | AlterDefaultPrivilegesStmt | AlterDomainStmt | AlterFdwStmt | AlterForeignServerStmt @@ -1891,7 +1897,7 @@ reloption_list: ; /* This should match def_elem and also allow qualified names */ -reloption_elem: +reloption_elem: ColLabel '=' def_arg { $$ = makeDefElem($1, (Node *) $3); @@ -4576,6 +4582,93 @@ opt_granted_by: GRANTED BY RoleId { $$ = $3; } | /*EMPTY*/ { $$ = NULL; } ; +/***************************************************************************** + * + * ALTER DEFAULT PRIVILEGES statement + * + *****************************************************************************/ + +AlterDefaultPrivilegesStmt: + ALTER DEFAULT PRIVILEGES DefACLOptionList DefACLAction + { + AlterDefaultPrivilegesStmt *n = makeNode(AlterDefaultPrivilegesStmt); + n->options = $4; + n->action = (GrantStmt *) $5; + $$ = (Node*)n; + } + ; + +DefACLOptionList: + DefACLOptionList DefACLOption { $$ = lappend($1, $2); } + | /* EMPTY */ { $$ = NIL; } + ; + +DefACLOption: + 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); + } + ; + +/* + * This should match GRANT/REVOKE, except that target objects are missing + * and we only allow a subset of object types. + */ +DefACLAction: + 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; } + ; + /***************************************************************************** * @@ -8632,10 +8725,11 @@ a_expr: c_expr { $$ = $1; } $$ = (Node *) makeSimpleA_Expr(AEXPR_OF, "<>", $1, (Node *) $6, @2); } /* - * Ideally we would not use hard-wired operators below but instead use - * opclasses. However, mixed data types and other issues make this - * difficult: http://archives.postgresql.org/pgsql-hackers/2008-08/msg01142.php - */ + * Ideally we would not use hard-wired operators below but + * instead use opclasses. However, mixed data types and other + * issues make this difficult: + * http://archives.postgresql.org/pgsql-hackers/2008-08/msg01142.php + */ | a_expr BETWEEN opt_asymmetric b_expr AND b_expr %prec BETWEEN { $$ = (Node *) makeA_Expr(AEXPR_AND, NIL, diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 0d2079fc0a..4d7b3c2e9d 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -10,7 +10,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/tcop/utility.c,v 1.314 2009/09/22 23:43:38 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/tcop/utility.c,v 1.315 2009/10/05 19:24:41 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -199,6 +199,7 @@ check_xact_readonly(Node *parsetree) case T_DropPropertyStmt: case T_GrantStmt: case T_GrantRoleStmt: + case T_AlterDefaultPrivilegesStmt: case T_TruncateStmt: case T_DropOwnedStmt: case T_ReassignOwnedStmt: @@ -701,6 +702,10 @@ ProcessUtility(Node *parsetree, GrantRole((GrantRoleStmt *) parsetree); break; + case T_AlterDefaultPrivilegesStmt: + ExecAlterDefaultPrivilegesStmt((AlterDefaultPrivilegesStmt *) parsetree); + break; + /* * **************** object creation / destruction ***************** */ @@ -1687,6 +1692,10 @@ CreateCommandTag(Node *parsetree) } break; + case T_AlterDefaultPrivilegesStmt: + tag = "ALTER DEFAULT PRIVILEGES"; + break; + case T_DefineStmt: switch (((DefineStmt *) parsetree)->kind) { @@ -2240,6 +2249,10 @@ GetCommandLogLevel(Node *parsetree) lev = LOGSTMT_DDL; break; + case T_AlterDefaultPrivilegesStmt: + lev = LOGSTMT_DDL; + break; + case T_DefineStmt: lev = LOGSTMT_DDL; break; diff --git a/src/backend/utils/adt/acl.c b/src/backend/utils/adt/acl.c index 83d93ad348..1264dfddb3 100644 --- a/src/backend/utils/adt/acl.c +++ b/src/backend/utils/adt/acl.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/utils/adt/acl.c,v 1.149 2009/08/03 21:11:39 joe Exp $ + * $PostgreSQL: pgsql/src/backend/utils/adt/acl.c,v 1.150 2009/10/05 19:24:41 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -77,6 +77,7 @@ static Acl *allocacl(int n); static void check_acl(const Acl *acl); static const char *aclparse(const char *s, AclItem *aip); static bool aclitem_match(const AclItem *a1, const AclItem *a2); +static int aclitemComparator(const void *arg1, const void *arg2); static void check_circularity(const Acl *old_acl, const AclItem *mod_aip, Oid ownerId); static Acl *recursive_revoke(Acl *acl, Oid grantee, AclMode revoke_privs, @@ -382,6 +383,15 @@ allocacl(int n) return new_acl; } +/* + * Create a zero-entry ACL + */ +Acl * +make_empty_acl(void) +{ + return allocacl(0); +} + /* * Copy an ACL */ @@ -423,6 +433,98 @@ aclconcat(const Acl *left_acl, const Acl *right_acl) return result_acl; } +/* + * Merge two ACLs + * + * This produces a properly merged ACL with no redundant entries. + * Returns NULL on NULL input. + */ +Acl * +aclmerge(const Acl *left_acl, const Acl *right_acl, Oid ownerId) +{ + Acl *result_acl; + AclItem *aip; + int i, + num; + + /* Check for cases where one or both are empty/null */ + if (left_acl == NULL || ACL_NUM(left_acl) == 0) + { + if (right_acl == NULL || ACL_NUM(right_acl) == 0) + return NULL; + else + return aclcopy(right_acl); + } + else + { + if (right_acl == NULL || ACL_NUM(right_acl) == 0) + return aclcopy(left_acl); + } + + /* Merge them the hard way, one item at a time */ + result_acl = aclcopy(left_acl); + + aip = ACL_DAT(right_acl); + num = ACL_NUM(right_acl); + + for (i = 0; i < num; i++, aip++) + { + Acl *tmp_acl; + + tmp_acl = aclupdate(result_acl, aip, ACL_MODECHG_ADD, + ownerId, DROP_RESTRICT); + pfree(result_acl); + result_acl = tmp_acl; + } + + return result_acl; +} + +/* + * Sort the items in an ACL (into an arbitrary but consistent order) + */ +void +aclitemsort(Acl *acl) +{ + if (acl != NULL && ACL_NUM(acl) > 1) + qsort(ACL_DAT(acl), ACL_NUM(acl), sizeof(AclItem), aclitemComparator); +} + +/* + * Check if two ACLs are exactly equal + * + * This will not detect equality if the two arrays contain the same items + * in different orders. To handle that case, sort both inputs first, + * using aclitemsort(). + */ +bool +aclequal(const Acl *left_acl, const Acl *right_acl) +{ + /* Check for cases where one or both are empty/null */ + if (left_acl == NULL || ACL_NUM(left_acl) == 0) + { + if (right_acl == NULL || ACL_NUM(right_acl) == 0) + return true; + else + return false; + } + else + { + if (right_acl == NULL || ACL_NUM(right_acl) == 0) + return false; + } + + if (ACL_NUM(left_acl) != ACL_NUM(right_acl)) + return false; + + if (memcmp(ACL_DAT(left_acl), + ACL_DAT(right_acl), + ACL_NUM(left_acl) * sizeof(AclItem)) == 0) + return true; + + return false; +} + /* * Verify that an ACL array is acceptable (one-dimensional and has no nulls) */ @@ -555,6 +657,31 @@ aclitem_match(const AclItem *a1, const AclItem *a2) a1->ai_grantor == a2->ai_grantor; } +/* + * aclitemComparator + * qsort comparison function for AclItems + */ +static int +aclitemComparator(const void *arg1, const void *arg2) +{ + const AclItem *a1 = (const AclItem *) arg1; + const AclItem *a2 = (const AclItem *) arg2; + + if (a1->ai_grantee > a2->ai_grantee) + return 1; + if (a1->ai_grantee < a2->ai_grantee) + return -1; + if (a1->ai_grantor > a2->ai_grantor) + return 1; + if (a1->ai_grantor < a2->ai_grantor) + return -1; + if (a1->ai_privs > a2->ai_privs) + return 1; + if (a1->ai_privs < a2->ai_privs) + return -1; + return 0; +} + /* * aclitem equality operator */ @@ -593,6 +720,9 @@ hash_aclitem(PG_FUNCTION_ARGS) * * Change this routine if you want to alter the default access policy for * newly-created objects (or any object with a NULL acl entry). + * + * Note that these are the hard-wired "defaults" that are used in the + * absence of any pg_default_acl entry. */ Acl * acldefault(GrantObjectType objtype, Oid ownerId) diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c index 922c4a626f..efed498f12 100644 --- a/src/backend/utils/cache/syscache.c +++ b/src/backend/utils/cache/syscache.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/utils/cache/syscache.c,v 1.120 2009/06/11 14:49:05 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/utils/cache/syscache.c,v 1.121 2009/10/05 19:24:45 tgl Exp $ * * NOTES * These routines allow the parser/planner/executor to perform @@ -31,6 +31,7 @@ #include "catalog/pg_constraint.h" #include "catalog/pg_conversion.h" #include "catalog/pg_database.h" +#include "catalog/pg_default_acl.h" #include "catalog/pg_enum.h" #include "catalog/pg_foreign_data_wrapper.h" #include "catalog/pg_foreign_server.h" @@ -344,6 +345,18 @@ static const struct cachedesc cacheinfo[] = { }, 4 }, + {DefaultAclRelationId, /* DEFACLROLENSPOBJ */ + DefaultAclRoleNspObjIndexId, + 0, + 3, + { + Anum_pg_default_acl_defaclrole, + Anum_pg_default_acl_defaclnamespace, + Anum_pg_default_acl_defaclobjtype, + 0 + }, + 256 + }, {EnumRelationId, /* ENUMOID */ EnumOidIndexId, 0, diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c index 789638fec4..ea314be0fa 100644 --- a/src/bin/pg_dump/common.c +++ b/src/bin/pg_dump/common.c @@ -11,7 +11,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/bin/pg_dump/common.c,v 1.107 2009/06/11 14:49:07 momjian Exp $ + * $PostgreSQL: pgsql/src/bin/pg_dump/common.c,v 1.108 2009/10/05 19:24:45 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -93,6 +93,7 @@ getSchemaData(int *numTablesPtr) TSConfigInfo *cfginfo; FdwInfo *fdwinfo; ForeignServerInfo *srvinfo; + DefaultACLInfo *daclinfo; int numNamespaces; int numAggregates; int numInherits; @@ -108,6 +109,7 @@ getSchemaData(int *numTablesPtr) int numTSConfigs; int numForeignDataWrappers; int numForeignServers; + int numDefaultACLs; if (g_verbose) write_msg(NULL, "reading schemas\n"); @@ -166,6 +168,10 @@ getSchemaData(int *numTablesPtr) write_msg(NULL, "reading user-defined foreign servers\n"); srvinfo = getForeignServers(&numForeignServers); + if (g_verbose) + write_msg(NULL, "reading default privileges\n"); + daclinfo = getDefaultACLs(&numDefaultACLs); + if (g_verbose) write_msg(NULL, "reading user-defined operator families\n"); opfinfo = getOpfamilies(&numOpfamilies); diff --git a/src/bin/pg_dump/dumputils.c b/src/bin/pg_dump/dumputils.c index 8037c1d7e1..a9a2762570 100644 --- a/src/bin/pg_dump/dumputils.c +++ b/src/bin/pg_dump/dumputils.c @@ -8,7 +8,7 @@ * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/bin/pg_dump/dumputils.c,v 1.48 2009/08/04 21:56:08 tgl Exp $ + * $PostgreSQL: pgsql/src/bin/pg_dump/dumputils.c,v 1.49 2009/10/05 19:24:45 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -490,18 +490,22 @@ parsePGArray(const char *atext, char ***itemarray, int *nitems) * acls: the ACL string fetched from the database * owner: username of object owner (will be passed through fmtId); can be * NULL or empty string to indicate "no owner known" + * prefix: string to prefix to each generated command; typically empty * remoteVersion: version of database * * Returns TRUE if okay, FALSE if could not parse the acl string. * The resulting commands (if any) are appended to the contents of 'sql'. * + * Note: when processing a default ACL, prefix is "ALTER DEFAULT PRIVILEGES " + * or something similar, and name is an empty string. + * * Note: beware of passing a fmtId() result directly as 'name' or 'subname', * since this routine uses fmtId() internally. */ bool buildACLCommands(const char *name, const char *subname, const char *type, const char *acls, const char *owner, - int remoteVersion, + const char *prefix, int remoteVersion, PQExpBuffer sql) { char **aclitems; @@ -549,7 +553,7 @@ buildACLCommands(const char *name, const char *subname, * wire-in knowledge about the default public privileges for different * kinds of objects. */ - appendPQExpBuffer(firstsql, "REVOKE ALL"); + appendPQExpBuffer(firstsql, "%sREVOKE ALL", prefix); if (subname) appendPQExpBuffer(firstsql, "(%s)", subname); appendPQExpBuffer(firstsql, " ON %s %s FROM PUBLIC;\n", type, name); @@ -564,8 +568,8 @@ buildACLCommands(const char *name, const char *subname, if (remoteVersion < 80200 && strcmp(type, "DATABASE") == 0) { /* database CONNECT priv didn't exist before 8.2 */ - appendPQExpBuffer(firstsql, "GRANT CONNECT ON %s %s TO PUBLIC;\n", - type, name); + appendPQExpBuffer(firstsql, "%sGRANT CONNECT ON %s %s TO PUBLIC;\n", + prefix, type, name); } /* Scan individual ACL items */ @@ -594,20 +598,20 @@ buildACLCommands(const char *name, const char *subname, ? strcmp(privswgo->data, "ALL") != 0 : strcmp(privs->data, "ALL") != 0) { - appendPQExpBuffer(firstsql, "REVOKE ALL"); + appendPQExpBuffer(firstsql, "%sREVOKE ALL", prefix); if (subname) appendPQExpBuffer(firstsql, "(%s)", subname); appendPQExpBuffer(firstsql, " ON %s %s FROM %s;\n", type, name, fmtId(grantee->data)); if (privs->len > 0) appendPQExpBuffer(firstsql, - "GRANT %s ON %s %s TO %s;\n", - privs->data, type, name, + "%sGRANT %s ON %s %s TO %s;\n", + prefix, privs->data, type, name, fmtId(grantee->data)); if (privswgo->len > 0) appendPQExpBuffer(firstsql, - "GRANT %s ON %s %s TO %s WITH GRANT OPTION;\n", - privswgo->data, type, name, + "%sGRANT %s ON %s %s TO %s WITH GRANT OPTION;\n", + prefix, privswgo->data, type, name, fmtId(grantee->data)); } } @@ -623,8 +627,8 @@ buildACLCommands(const char *name, const char *subname, if (privs->len > 0) { - appendPQExpBuffer(secondsql, "GRANT %s ON %s %s TO ", - privs->data, type, name); + appendPQExpBuffer(secondsql, "%sGRANT %s ON %s %s TO ", + prefix, privs->data, type, name); if (grantee->len == 0) appendPQExpBuffer(secondsql, "PUBLIC;\n"); else if (strncmp(grantee->data, "group ", @@ -636,8 +640,8 @@ buildACLCommands(const char *name, const char *subname, } if (privswgo->len > 0) { - appendPQExpBuffer(secondsql, "GRANT %s ON %s %s TO ", - privswgo->data, type, name); + appendPQExpBuffer(secondsql, "%sGRANT %s ON %s %s TO ", + prefix, privswgo->data, type, name); if (grantee->len == 0) appendPQExpBuffer(secondsql, "PUBLIC"); else if (strncmp(grantee->data, "group ", @@ -661,7 +665,7 @@ buildACLCommands(const char *name, const char *subname, */ if (!found_owner_privs && owner) { - appendPQExpBuffer(firstsql, "REVOKE ALL"); + appendPQExpBuffer(firstsql, "%sREVOKE ALL", prefix); if (subname) appendPQExpBuffer(firstsql, "(%s)", subname); appendPQExpBuffer(firstsql, " ON %s %s FROM %s;\n", @@ -682,6 +686,50 @@ buildACLCommands(const char *name, const char *subname, return true; } +/* + * Build ALTER DEFAULT PRIVILEGES command(s) for single pg_default_acl entry. + * + * type: the object type (as seen in GRANT command) + * nspname: schema name, or NULL for global default privileges + * acls: the ACL string fetched from the database + * owner: username of privileges owner (will be passed through fmtId) + * remoteVersion: version of database + * + * Returns TRUE if okay, FALSE if could not parse the acl string. + * The resulting commands (if any) are appended to the contents of 'sql'. + */ +bool +buildDefaultACLCommands(const char *type, const char *nspname, + const char *acls, const char *owner, + int remoteVersion, + PQExpBuffer sql) +{ + bool result; + PQExpBuffer prefix; + + prefix = createPQExpBuffer(); + + /* + * We incorporate the target role directly into the command, rather than + * playing around with SET ROLE or anything like that. This is so that + * a permissions error leads to nothing happening, rather than + * changing default privileges for the wrong user. + */ + appendPQExpBuffer(prefix, "ALTER DEFAULT PRIVILEGES FOR ROLE %s ", + fmtId(owner)); + if (nspname) + appendPQExpBuffer(prefix, "IN SCHEMA %s ", fmtId(nspname)); + + result = buildACLCommands("", NULL, + type, acls, owner, + prefix->data, remoteVersion, + sql); + + destroyPQExpBuffer(prefix); + + return result; +} + /* * This will parse an aclitem string, having the general form * username=privilegecodes/grantor diff --git a/src/bin/pg_dump/dumputils.h b/src/bin/pg_dump/dumputils.h index 3c56c3f9a4..a5bfe1bcfd 100644 --- a/src/bin/pg_dump/dumputils.h +++ b/src/bin/pg_dump/dumputils.h @@ -8,7 +8,7 @@ * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/bin/pg_dump/dumputils.h,v 1.25 2009/08/04 21:56:08 tgl Exp $ + * $PostgreSQL: pgsql/src/bin/pg_dump/dumputils.h,v 1.26 2009/10/05 19:24:45 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -34,8 +34,12 @@ extern int parse_version(const char *versionString); extern bool parsePGArray(const char *atext, char ***itemarray, int *nitems); extern bool buildACLCommands(const char *name, const char *subname, const char *type, const char *acls, const char *owner, - int remoteVersion, + const char *prefix, int remoteVersion, PQExpBuffer sql); +extern bool buildDefaultACLCommands(const char *type, const char *nspname, + const char *acls, const char *owner, + int remoteVersion, + PQExpBuffer sql); extern void processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern, bool have_where, bool force_escape, diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c index 20bd3eb7ea..e15e4dbdb9 100644 --- a/src/bin/pg_dump/pg_backup_archiver.c +++ b/src/bin/pg_dump/pg_backup_archiver.c @@ -15,7 +15,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/bin/pg_dump/pg_backup_archiver.c,v 1.175 2009/08/07 22:48:34 tgl Exp $ + * $PostgreSQL: pgsql/src/bin/pg_dump/pg_backup_archiver.c,v 1.176 2009/10/05 19:24:45 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -2072,7 +2072,8 @@ ReadToc(ArchiveHandle *AH) * the entries into sections */ if (strcmp(te->desc, "COMMENT") == 0 || - strcmp(te->desc, "ACL") == 0) + strcmp(te->desc, "ACL") == 0 || + strcmp(te->desc, "DEFAULT ACL") == 0) te->section = SECTION_NONE; else if (strcmp(te->desc, "TABLE DATA") == 0 || strcmp(te->desc, "BLOBS") == 0 || @@ -2227,7 +2228,8 @@ _tocEntryRequired(TocEntry *te, RestoreOptions *ropt, bool include_acls) return 0; /* If it's an ACL, maybe ignore it */ - if ((!include_acls || ropt->aclsSkip) && strcmp(te->desc, "ACL") == 0) + if ((!include_acls || ropt->aclsSkip) && + (strcmp(te->desc, "ACL") == 0 || strcmp(te->desc, "DEFAULT ACL") == 0)) return 0; if (!ropt->create && strcmp(te->desc, "DATABASE") == 0) @@ -2721,12 +2723,14 @@ _printTocEntry(ArchiveHandle *AH, TocEntry *te, RestoreOptions *ropt, bool isDat /* ACLs are dumped only during acl pass */ if (acl_pass) { - if (strcmp(te->desc, "ACL") != 0) + if (!(strcmp(te->desc, "ACL") == 0 || + strcmp(te->desc, "DEFAULT ACL") == 0)) return; } else { - if (strcmp(te->desc, "ACL") == 0) + if (strcmp(te->desc, "ACL") == 0 || + strcmp(te->desc, "DEFAULT ACL") == 0) return; } diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index 34ebc27168..d1715eccce 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -12,7 +12,7 @@ * by PostgreSQL * * IDENTIFICATION - * $PostgreSQL: pgsql/src/bin/pg_dump/pg_dump.c,v 1.548 2009/09/22 23:43:38 tgl Exp $ + * $PostgreSQL: pgsql/src/bin/pg_dump/pg_dump.c,v 1.549 2009/10/05 19:24:45 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -34,6 +34,7 @@ #include "access/sysattr.h" #include "catalog/pg_cast.h" #include "catalog/pg_class.h" +#include "catalog/pg_default_acl.h" #include "catalog/pg_largeobject.h" #include "catalog/pg_proc.h" #include "catalog/pg_trigger.h" @@ -162,6 +163,7 @@ static void dumpForeignServer(Archive *fout, ForeignServerInfo *srvinfo); static void dumpUserMappings(Archive *fout, const char *target, const char *servername, const char *namespace, const char *owner, CatalogId catalogId, DumpId dumpId); +static void dumpDefaultACL(Archive *fout, DefaultACLInfo *daclinfo); static void dumpACL(Archive *fout, CatalogId objCatId, DumpId objDumpId, const char *type, const char *name, const char *subname, @@ -1049,6 +1051,23 @@ selectDumpableType(TypeInfo *tinfo) tinfo->dobj.dump = true; } +/* + * selectDumpableDefaultACL: policy-setting subroutine + * Mark a default ACL as to be dumped or not + * + * For per-schema default ACLs, dump if the schema is to be dumped. + * Otherwise dump if we are dumping "everything". Note that dataOnly + * and aclsSkip are checked separately. + */ +static void +selectDumpableDefaultACL(DefaultACLInfo *dinfo) +{ + if (dinfo->dobj.namespace) + dinfo->dobj.dump = dinfo->dobj.namespace->dobj.dump; + else + dinfo->dobj.dump = include_everything; +} + /* * selectDumpableObject: policy-setting subroutine * Mark a generic dumpable object as to be dumped or not @@ -1779,7 +1798,7 @@ dumpDatabase(Archive *AH) PQExpBuffer loFrozenQry = createPQExpBuffer(); PQExpBuffer loOutQry = createPQExpBuffer(); int i_relfrozenxid; - + appendPQExpBuffer(loFrozenQry, "SELECT relfrozenxid\n" "FROM pg_catalog.pg_class\n" "WHERE oid = %d;\n", @@ -1808,7 +1827,7 @@ dumpDatabase(Archive *AH) loOutQry->data, "", NULL, NULL, 0, NULL, NULL); - + PQclear(lo_res); destroyPQExpBuffer(loFrozenQry); destroyPQExpBuffer(loOutQry); @@ -5645,6 +5664,94 @@ getForeignServers(int *numForeignServers) return srvinfo; } +/* + * getDefaultACLs: + * read all default ACL information in the system catalogs and return + * them in the DefaultACLInfo structure + * + * numDefaultACLs is set to the number of ACLs read in + */ +DefaultACLInfo * +getDefaultACLs(int *numDefaultACLs) +{ + DefaultACLInfo *daclinfo; + PQExpBuffer query; + PGresult *res; + int i_oid; + int i_tableoid; + int i_defaclrole; + int i_defaclnamespace; + int i_defaclobjtype; + int i_defaclacl; + int i, + ntups; + + if (g_fout->remoteVersion < 80500) + { + *numDefaultACLs = 0; + return NULL; + } + + query = createPQExpBuffer(); + + /* Make sure we are in proper schema */ + selectSourceSchema("pg_catalog"); + + appendPQExpBuffer(query, "SELECT oid, tableoid, " + "(%s defaclrole) AS defaclrole, " + "defaclnamespace, " + "defaclobjtype, " + "defaclacl " + "FROM pg_default_acl", + username_subquery); + + res = PQexec(g_conn, query->data); + check_sql_result(res, g_conn, query->data, PGRES_TUPLES_OK); + + ntups = PQntuples(res); + *numDefaultACLs = ntups; + + daclinfo = (DefaultACLInfo *) malloc(ntups * sizeof(DefaultACLInfo)); + + i_oid = PQfnumber(res, "oid"); + i_tableoid = PQfnumber(res, "tableoid"); + i_defaclrole = PQfnumber(res, "defaclrole"); + i_defaclnamespace = PQfnumber(res, "defaclnamespace"); + i_defaclobjtype = PQfnumber(res, "defaclobjtype"); + i_defaclacl = PQfnumber(res, "defaclacl"); + + for (i = 0; i < ntups; i++) + { + Oid nspid = atooid(PQgetvalue(res, i, i_defaclnamespace)); + + daclinfo[i].dobj.objType = DO_DEFAULT_ACL; + daclinfo[i].dobj.catId.tableoid = atooid(PQgetvalue(res, i, i_tableoid)); + daclinfo[i].dobj.catId.oid = atooid(PQgetvalue(res, i, i_oid)); + AssignDumpId(&daclinfo[i].dobj); + /* cheesy ... is it worth coming up with a better object name? */ + daclinfo[i].dobj.name = strdup(PQgetvalue(res, i, i_defaclobjtype)); + + if (nspid != InvalidOid) + daclinfo[i].dobj.namespace = findNamespace(nspid, + daclinfo[i].dobj.catId.oid); + else + daclinfo[i].dobj.namespace = NULL; + + daclinfo[i].defaclrole = strdup(PQgetvalue(res, i, i_defaclrole)); + daclinfo[i].defaclobjtype = *(PQgetvalue(res, i, i_defaclobjtype)); + daclinfo[i].defaclacl = strdup(PQgetvalue(res, i, i_defaclacl)); + + /* Decide whether we want to dump it */ + selectDumpableDefaultACL(&(daclinfo[i])); + } + + PQclear(res); + + destroyPQExpBuffer(query); + + return daclinfo; +} + /* * dumpComment -- * @@ -6058,6 +6165,9 @@ dumpDumpableObject(Archive *fout, DumpableObject *dobj) case DO_FOREIGN_SERVER: dumpForeignServer(fout, (ForeignServerInfo *) dobj); break; + case DO_DEFAULT_ACL: + dumpDefaultACL(fout, (DefaultACLInfo *) dobj); + break; case DO_BLOBS: ArchiveEntry(fout, dobj->catId, dobj->dumpId, dobj->name, NULL, NULL, "", @@ -9791,6 +9901,72 @@ dumpUserMappings(Archive *fout, const char *target, destroyPQExpBuffer(q); } +/* + * Write out default privileges information + */ +static void +dumpDefaultACL(Archive *fout, DefaultACLInfo *daclinfo) +{ + PQExpBuffer q; + PQExpBuffer tag; + const char *type; + + /* Skip if not to be dumped */ + if (!daclinfo->dobj.dump || dataOnly || aclsSkip) + return; + + q = createPQExpBuffer(); + tag = createPQExpBuffer(); + + switch (daclinfo->defaclobjtype) + { + case DEFACLOBJ_RELATION: + type = "TABLE"; + break; + case DEFACLOBJ_SEQUENCE: + type = "SEQUENCE"; + break; + case DEFACLOBJ_FUNCTION: + type = "FUNCTION"; + break; + default: + /* shouldn't get here */ + write_msg(NULL, "unknown object type (%d) in default privileges\n", + (int) daclinfo->defaclobjtype); + exit_nicely(); + type = ""; /* keep compiler quiet */ + } + + appendPQExpBuffer(tag, "DEFAULT %s PRIVILEGES", type); + + /* build the actual command(s) for this tuple */ + if (!buildDefaultACLCommands(type, + daclinfo->dobj.namespace != NULL ? + daclinfo->dobj.namespace->dobj.name : NULL, + daclinfo->defaclacl, + daclinfo->defaclrole, + fout->remoteVersion, + q)) + { + write_msg(NULL, "could not parse default ACL list (%s)\n", + daclinfo->defaclacl); + exit_nicely(); + } + + ArchiveEntry(fout, daclinfo->dobj.catId, daclinfo->dobj.dumpId, + tag->data, + daclinfo->dobj.namespace ? daclinfo->dobj.namespace->dobj.name : NULL, + NULL, + daclinfo->defaclrole, + false, "DEFAULT ACL", SECTION_NONE, + q->data, "", NULL, + daclinfo->dobj.dependencies, daclinfo->dobj.nDeps, + NULL, NULL); + + destroyPQExpBuffer(tag); + destroyPQExpBuffer(q); +} + /*---------- * Write out grant/revoke information * @@ -9820,7 +9996,8 @@ dumpACL(Archive *fout, CatalogId objCatId, DumpId objDumpId, sql = createPQExpBuffer(); - if (!buildACLCommands(name, subname, type, acls, owner, fout->remoteVersion, sql)) + if (!buildACLCommands(name, subname, type, acls, owner, + "", fout->remoteVersion, sql)) { write_msg(NULL, "could not parse ACL list (%s) for object \"%s\" (%s)\n", acls, name, type); @@ -10263,7 +10440,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo) fmtId(tbinfo->dobj.name)); appendPQExpBuffer(q, "ALTER COLUMN %s ", fmtId(tbinfo->attnames[j])); - appendPQExpBuffer(q, "SET STATISTICS DISTINCT %g;\n", + appendPQExpBuffer(q, "SET STATISTICS DISTINCT %g;\n", tbinfo->attdistinct[j]); } diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h index beec160110..5b2c3d1e6b 100644 --- a/src/bin/pg_dump/pg_dump.h +++ b/src/bin/pg_dump/pg_dump.h @@ -6,7 +6,7 @@ * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/bin/pg_dump/pg_dump.h,v 1.157 2009/09/22 23:43:40 tgl Exp $ + * $PostgreSQL: pgsql/src/bin/pg_dump/pg_dump.h,v 1.158 2009/10/05 19:24:46 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -114,6 +114,7 @@ typedef enum DO_TSCONFIG, DO_FDW, DO_FOREIGN_SERVER, + DO_DEFAULT_ACL, DO_BLOBS, DO_BLOB_COMMENTS } DumpableObjectType; @@ -432,6 +433,14 @@ typedef struct _foreignServerInfo char *srvoptions; } ForeignServerInfo; +typedef struct _defaultACLInfo +{ + DumpableObject dobj; + char *defaclrole; + char defaclobjtype; + char *defaclacl; +} DefaultACLInfo; + /* global decls */ extern bool force_quotes; /* double-quotes for identifiers flag */ extern bool g_verbose; /* verbose flag */ @@ -516,5 +525,6 @@ extern TSTemplateInfo *getTSTemplates(int *numTSTemplates); extern TSConfigInfo *getTSConfigurations(int *numTSConfigs); extern FdwInfo *getForeignDataWrappers(int *numForeignDataWrappers); extern ForeignServerInfo *getForeignServers(int *numForeignServers); +extern DefaultACLInfo *getDefaultACLs(int *numDefaultACLs); #endif /* PG_DUMP_H */ diff --git a/src/bin/pg_dump/pg_dump_sort.c b/src/bin/pg_dump/pg_dump_sort.c index a56f429a7a..4e95618318 100644 --- a/src/bin/pg_dump/pg_dump_sort.c +++ b/src/bin/pg_dump/pg_dump_sort.c @@ -9,7 +9,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/bin/pg_dump/pg_dump_sort.c,v 1.25 2009/06/11 14:49:07 momjian Exp $ + * $PostgreSQL: pgsql/src/bin/pg_dump/pg_dump_sort.c,v 1.26 2009/10/05 19:24:46 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -23,8 +23,8 @@ static const char *modulename = gettext_noop("sorter"); * Objects are sorted by priority levels, and within an equal priority level * by OID. (This is a relatively crude hack to provide semi-reasonable * behavior for old databases without full dependency info.) Note: text - * search and foreign-data objects can't really happen here, so the rather - * bogus priorities for them don't matter. + * search, foreign-data, and default ACL objects can't really happen here, + * so the rather bogus priorities for them don't matter. */ static const int oldObjectTypePriority[] = { @@ -54,6 +54,7 @@ static const int oldObjectTypePriority[] = 5, /* DO_TSCONFIG */ 3, /* DO_FDW */ 4, /* DO_FOREIGN_SERVER */ + 17, /* DO_DEFAULT_ACL */ 10, /* DO_BLOBS */ 11 /* DO_BLOB_COMMENTS */ }; @@ -90,6 +91,7 @@ static const int newObjectTypePriority[] = 13, /* DO_TSCONFIG */ 14, /* DO_FDW */ 15, /* DO_FOREIGN_SERVER */ + 27, /* DO_DEFAULT_ACL */ 20, /* DO_BLOBS */ 21 /* DO_BLOB_COMMENTS */ }; @@ -1139,6 +1141,11 @@ describeDumpableObject(DumpableObject *obj, char *buf, int bufsize) "FOREIGN SERVER %s (ID %d OID %u)", obj->name, obj->dumpId, obj->catId.oid); return; + case DO_DEFAULT_ACL: + snprintf(buf, bufsize, + "DEFAULT ACL %s (ID %d OID %u)", + obj->name, obj->dumpId, obj->catId.oid); + return; case DO_BLOBS: snprintf(buf, bufsize, "BLOBS (ID %d)", diff --git a/src/bin/pg_dump/pg_dumpall.c b/src/bin/pg_dump/pg_dumpall.c index 454b95c106..f0a4d67d2a 100644 --- a/src/bin/pg_dump/pg_dumpall.c +++ b/src/bin/pg_dump/pg_dumpall.c @@ -6,7 +6,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * - * $PostgreSQL: pgsql/src/bin/pg_dump/pg_dumpall.c,v 1.126 2009/06/11 14:49:07 momjian Exp $ + * $PostgreSQL: pgsql/src/bin/pg_dump/pg_dumpall.c,v 1.127 2009/10/05 19:24:46 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -989,7 +989,7 @@ dumpTablespaces(PGconn *conn) if (!skip_acls && !buildACLCommands(fspcname, NULL, "TABLESPACE", spcacl, spcowner, - server_version, buf)) + "", server_version, buf)) { fprintf(stderr, _("%s: could not parse ACL list (%s) for tablespace \"%s\"\n"), progname, spcacl, fspcname); @@ -1289,7 +1289,7 @@ dumpCreateDB(PGconn *conn) if (!skip_acls && !buildACLCommands(fdbname, NULL, "DATABASE", dbacl, dbowner, - server_version, buf)) + "", server_version, buf)) { fprintf(stderr, _("%s: could not parse ACL list (%s) for database \"%s\"\n"), progname, dbacl, fdbname); diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index 67f05a89de..d94d8b80c5 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -3,7 +3,7 @@ * * Copyright (c) 2000-2009, PostgreSQL Global Development Group * - * $PostgreSQL: pgsql/src/bin/psql/command.c,v 1.207 2009/09/13 22:18:22 tgl Exp $ + * $PostgreSQL: pgsql/src/bin/psql/command.c,v 1.208 2009/10/05 19:24:46 tgl Exp $ */ #include "postgres_fe.h" #include "command.h" @@ -361,7 +361,10 @@ exec_command(const char *cmd, success = listCasts(pattern); break; case 'd': - success = objectDescription(pattern, show_system); + if (strcmp(cmd, "ddp") == 0) + success = listDefaultACLs(pattern); + else + success = objectDescription(pattern, show_system); break; case 'D': success = listDomains(pattern, show_system); diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c index 6e288da67a..1644623812 100644 --- a/src/bin/psql/describe.c +++ b/src/bin/psql/describe.c @@ -8,7 +8,7 @@ * * Copyright (c) 2000-2009, PostgreSQL Global Development Group * - * $PostgreSQL: pgsql/src/bin/psql/describe.c,v 1.226 2009/07/29 20:56:19 tgl Exp $ + * $PostgreSQL: pgsql/src/bin/psql/describe.c,v 1.227 2009/10/05 19:24:46 tgl Exp $ */ #include "postgres_fe.h" @@ -732,6 +732,73 @@ permissionsList(const char *pattern) } +/* + * \ddp + * + * List DefaultACLs. The pattern can match either schema or role name. + */ +bool +listDefaultACLs(const char *pattern) +{ + PQExpBufferData buf; + PGresult *res; + printQueryOpt myopt = pset.popt; + static const bool translate_columns[] = {false, false, true, false}; + + if (pset.sversion < 80500) + { + fprintf(stderr, _("The server (version %d.%d) does not support altering default privileges.\n"), + pset.sversion / 10000, (pset.sversion / 100) % 100); + return true; + } + + initPQExpBuffer(&buf); + + printfPQExpBuffer(&buf, + "SELECT pg_catalog.pg_get_userbyid(d.defaclrole) AS \"%s\",\n" + " n.nspname AS \"%s\",\n" + " CASE d.defaclobjtype WHEN 'r' THEN '%s' WHEN 'S' THEN '%s' WHEN 'f' THEN '%s' END AS \"%s\",\n" + " ", + gettext_noop("Owner"), + gettext_noop("Schema"), + gettext_noop("table"), + gettext_noop("sequence"), + gettext_noop("function"), + gettext_noop("Type")); + + printACLColumn(&buf, "d.defaclacl"); + + appendPQExpBuffer(&buf, "\nFROM pg_catalog.pg_default_acl d\n" + " LEFT JOIN pg_catalog.pg_namespace n ON n.oid = d.defaclnamespace\n"); + + processSQLNamePattern(pset.db, &buf, pattern, false, false, + NULL, + "n.nspname", + "pg_catalog.pg_get_userbyid(d.defaclrole)", + NULL); + + appendPQExpBuffer(&buf, "ORDER BY 1, 2, 3;"); + + res = PSQLexec(buf.data, false); + if (!res) + { + termPQExpBuffer(&buf); + return false; + } + + myopt.nullPrint = NULL; + printfPQExpBuffer(&buf, _("Default access privileges")); + myopt.title = buf.data; + myopt.translate_header = true; + myopt.translate_columns = translate_columns; + + printQuery(res, &myopt, pset.queryFout, pset.logfile); + + termPQExpBuffer(&buf); + PQclear(res); + return true; +} + /* * Get object comments diff --git a/src/bin/psql/describe.h b/src/bin/psql/describe.h index 769ee9e975..169ceb3739 100644 --- a/src/bin/psql/describe.h +++ b/src/bin/psql/describe.h @@ -3,7 +3,7 @@ * * Copyright (c) 2000-2009, PostgreSQL Global Development Group * - * $PostgreSQL: pgsql/src/bin/psql/describe.h,v 1.40 2009/04/21 15:49:06 momjian Exp $ + * $PostgreSQL: pgsql/src/bin/psql/describe.h,v 1.41 2009/10/05 19:24:46 tgl Exp $ */ #ifndef DESCRIBE_H #define DESCRIBE_H @@ -30,6 +30,9 @@ extern bool describeRoles(const char *pattern, bool verbose); /* \z (or \dp) */ extern bool permissionsList(const char *pattern); +/* \ddp */ +extern bool listDefaultACLs(const char *pattern); + /* \dd */ extern bool objectDescription(const char *pattern, bool showSystem); diff --git a/src/bin/psql/help.c b/src/bin/psql/help.c index 947eff050a..f21099a692 100644 --- a/src/bin/psql/help.c +++ b/src/bin/psql/help.c @@ -3,7 +3,7 @@ * * Copyright (c) 2000-2009, PostgreSQL Global Development Group * - * $PostgreSQL: pgsql/src/bin/psql/help.c,v 1.152 2009/09/18 05:00:42 petere Exp $ + * $PostgreSQL: pgsql/src/bin/psql/help.c,v 1.153 2009/10/05 19:24:46 tgl Exp $ */ #include "postgres_fe.h" @@ -201,6 +201,7 @@ slashUsage(unsigned short int pager) fprintf(output, _(" \\dc[S] [PATTERN] list conversions\n")); fprintf(output, _(" \\dC [PATTERN] list casts\n")); fprintf(output, _(" \\dd[S] [PATTERN] show comments on objects\n")); + fprintf(output, _(" \\ddp [PATTERN] list default privileges\n")); fprintf(output, _(" \\dD[S] [PATTERN] list domains\n")); fprintf(output, _(" \\des[+] [PATTERN] list foreign servers\n")); fprintf(output, _(" \\deu[+] [PATTERN] list user mappings\n")); diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h index 4ae9157556..1d3bfb0726 100644 --- a/src/include/catalog/catversion.h +++ b/src/include/catalog/catversion.h @@ -37,7 +37,7 @@ * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.540 2009/09/26 22:42:01 tgl Exp $ + * $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.541 2009/10/05 19:24:46 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -53,6 +53,6 @@ */ /* yyyymmddN */ -#define CATALOG_VERSION_NO 200909261 +#define CATALOG_VERSION_NO 200910051 #endif diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h index fe04aab964..954d3808a8 100644 --- a/src/include/catalog/dependency.h +++ b/src/include/catalog/dependency.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/catalog/dependency.h,v 1.40 2009/06/11 14:49:09 momjian Exp $ + * $PostgreSQL: pgsql/src/include/catalog/dependency.h,v 1.41 2009/10/05 19:24:48 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -146,6 +146,7 @@ typedef enum ObjectClass OCLASS_FDW, /* pg_foreign_data_wrapper */ OCLASS_FOREIGN_SERVER, /* pg_foreign_server */ OCLASS_USER_MAPPING, /* pg_user_mapping */ + OCLASS_DEFACL, /* pg_default_acl */ MAX_OCLASS /* MUST BE LAST */ } ObjectClass; diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h index 0d42cf1196..5faf9c1c01 100644 --- a/src/include/catalog/heap.h +++ b/src/include/catalog/heap.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/catalog/heap.h,v 1.93 2009/09/26 22:42:02 tgl Exp $ + * $PostgreSQL: pgsql/src/include/catalog/heap.h,v 1.94 2009/10/05 19:24:48 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -57,6 +57,7 @@ extern Oid heap_create_with_catalog(const char *relname, int oidinhcount, OnCommitAction oncommit, Datum reloptions, + bool use_user_acl, bool allow_system_table_mods); extern void heap_drop_with_catalog(Oid relid); @@ -76,6 +77,7 @@ extern void InsertPgAttributeTuple(Relation pg_attribute_rel, extern void InsertPgClassTuple(Relation pg_class_desc, Relation new_rel_desc, Oid new_rel_oid, + Datum relacl, Datum reloptions); extern List *AddRelationNewConstraints(Relation rel, diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h index ce117a8eec..0272334f2e 100644 --- a/src/include/catalog/indexing.h +++ b/src/include/catalog/indexing.h @@ -8,7 +8,7 @@ * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/catalog/indexing.h,v 1.108 2009/06/11 14:49:09 momjian Exp $ + * $PostgreSQL: pgsql/src/include/catalog/indexing.h,v 1.109 2009/10/05 19:24:48 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -267,6 +267,11 @@ DECLARE_UNIQUE_INDEX(pg_user_mapping_oid_index, 174, on pg_user_mapping using bt 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_acl_role_nsp_obj_index, 827, on pg_default_acl using btree(defaclrole oid_ops, defaclnamespace oid_ops, defaclobjtype char_ops)); +#define DefaultAclRoleNspObjIndexId 827 +DECLARE_UNIQUE_INDEX(pg_default_acl_oid_index, 828, on pg_default_acl using btree(oid oid_ops)); +#define DefaultAclOidIndexId 828 + /* last step of initialization script: build the indexes declared above */ BUILD_INDICES diff --git a/src/include/catalog/pg_default_acl.h b/src/include/catalog/pg_default_acl.h new file mode 100644 index 0000000000..cfd1f2b3a9 --- /dev/null +++ b/src/include/catalog/pg_default_acl.h @@ -0,0 +1,75 @@ +/*------------------------------------------------------------------------- + * + * pg_default_acl.h + * definition of default ACLs for new objects. + * + * + * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * $PostgreSQL: pgsql/src/include/catalog/pg_default_acl.h,v 1.1 2009/10/05 19:24:48 tgl Exp $ + * + * NOTES + * the genbki.sh script reads this file and generates .bki + * information from the DATA() statements. + * + *------------------------------------------------------------------------- + */ +#ifndef PG_DEFAULT_ACL_H +#define PG_DEFAULT_ACL_H + +#include "catalog/genbki.h" + +/* ---------------- + * pg_default_acl definition. cpp turns this into + * typedef struct FormData_pg_default_acl + * ---------------- + */ +#define DefaultAclRelationId 826 + +CATALOG(pg_default_acl,826) +{ + Oid defaclrole; /* OID of role owning this ACL */ + Oid defaclnamespace; /* OID of namespace, or 0 for all */ + char defaclobjtype; /* see DEFACLOBJ_xxx constants below */ + + /* + * VARIABLE LENGTH FIELDS start here. + */ + + aclitem defaclacl[1]; /* permissions to add at CREATE time */ +} FormData_pg_default_acl; + +/* ---------------- + * Form_pg_default_acl corresponds to a pointer to a tuple with + * the format of pg_default_acl relation. + * ---------------- + */ +typedef FormData_pg_default_acl *Form_pg_default_acl; + +/* ---------------- + * compiler constants for pg_default_acl + * ---------------- + */ + +#define Natts_pg_default_acl 4 +#define Anum_pg_default_acl_defaclrole 1 +#define Anum_pg_default_acl_defaclnamespace 2 +#define Anum_pg_default_acl_defaclobjtype 3 +#define Anum_pg_default_acl_defaclacl 4 + +/* ---------------- + * pg_default_acl has no initial contents + * ---------------- + */ + +/* + * Types of objects for which the user is allowed to specify default + * permissions through pg_default_acl. These codes are used in the + * defaclobjtype column. + */ +#define DEFACLOBJ_RELATION 'r' /* table, view */ +#define DEFACLOBJ_SEQUENCE 'S' /* sequence */ +#define DEFACLOBJ_FUNCTION 'f' /* function */ + +#endif /* PG_DEFAULT_ACL_H */ diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h index d842c9c319..5fd046e95b 100644 --- a/src/include/nodes/nodes.h +++ b/src/include/nodes/nodes.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/nodes/nodes.h,v 1.226 2009/09/22 23:43:41 tgl Exp $ + * $PostgreSQL: pgsql/src/include/nodes/nodes.h,v 1.227 2009/10/05 19:24:48 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -261,6 +261,7 @@ typedef enum NodeTag T_SetOperationStmt, T_GrantStmt, T_GrantRoleStmt, + T_AlterDefaultPrivilegesStmt, T_ClosePortalStmt, T_ClusterStmt, T_CopyStmt, diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 1ce28b7754..24066b9b38 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -13,7 +13,7 @@ * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/nodes/parsenodes.h,v 1.402 2009/09/22 23:43:41 tgl Exp $ + * $PostgreSQL: pgsql/src/include/nodes/parsenodes.h,v 1.403 2009/10/05 19:24:48 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -1259,6 +1259,17 @@ typedef struct GrantRoleStmt DropBehavior behavior; /* drop behavior (for REVOKE) */ } GrantRoleStmt; +/* ---------------------- + * Alter Default Privileges Statement + * ---------------------- + */ +typedef struct AlterDefaultPrivilegesStmt +{ + NodeTag type; + List *options; /* list of DefElem */ + GrantStmt *action; /* GRANT/REVOKE action (with objects=NIL) */ +} AlterDefaultPrivilegesStmt; + /* ---------------------- * Copy Statement * diff --git a/src/include/utils/acl.h b/src/include/utils/acl.h index 5c38822d5d..977b00de79 100644 --- a/src/include/utils/acl.h +++ b/src/include/utils/acl.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/utils/acl.h,v 1.108 2009/06/11 14:49:13 momjian Exp $ + * $PostgreSQL: pgsql/src/include/utils/acl.h,v 1.109 2009/10/05 19:24:49 tgl Exp $ * * NOTES * An ACL array is simply an array of AclItems, representing the union @@ -193,41 +193,23 @@ typedef enum AclObjectKind MAX_ACL_KIND /* MUST BE LAST */ } AclObjectKind; -/* - * The information about one Grant/Revoke statement, in internal format: object - * and grantees names have been turned into Oids, the privilege list is an - * AclMode bitmask. If 'privileges' is ACL_NO_RIGHTS (the 0 value) and - * all_privs is true, 'privileges' will be internally set to the right kind of - * ACL_ALL_RIGHTS_*, depending on the object type (NB - this will modify the - * InternalGrant struct!) - * - * Note: 'all_privs' and 'privileges' represent object-level privileges only. - * There might also be column-level privilege specifications, which are - * represented in col_privs (this is a list of untransformed AccessPriv nodes). - * Column privileges are only valid for objtype ACL_OBJECT_RELATION. - */ -typedef struct -{ - bool is_grant; - GrantObjectType objtype; - List *objects; - bool all_privs; - AclMode privileges; - List *col_privs; - List *grantees; - bool grant_option; - DropBehavior behavior; -} InternalGrant; /* * routines used internally */ extern Acl *acldefault(GrantObjectType objtype, Oid ownerId); +extern Acl *get_user_default_acl(GrantObjectType objtype, Oid ownerId, + Oid nsp_oid); + extern Acl *aclupdate(const Acl *old_acl, const AclItem *mod_aip, int modechg, Oid ownerId, DropBehavior behavior); extern Acl *aclnewowner(const Acl *old_acl, Oid oldOwnerId, Oid newOwnerId); +extern Acl *make_empty_acl(void); extern Acl *aclcopy(const Acl *orig_acl); extern Acl *aclconcat(const Acl *left_acl, const Acl *right_acl); +extern Acl *aclmerge(const Acl *left_acl, const Acl *right_acl, Oid ownerId); +extern void aclitemsort(Acl *acl); +extern bool aclequal(const Acl *left_acl, const Acl *right_acl); extern AclMode aclmask(const Acl *acl, Oid roleid, Oid ownerId, AclMode mask, AclMaskHow how); @@ -261,7 +243,10 @@ extern Datum hash_aclitem(PG_FUNCTION_ARGS); * prototypes for functions in aclchk.c */ extern void ExecuteGrantStmt(GrantStmt *stmt); -extern void ExecGrantStmt_oids(InternalGrant *istmt); +extern void ExecAlterDefaultPrivilegesStmt(AlterDefaultPrivilegesStmt *stmt); + +extern void RemoveRoleFromObjectACL(Oid roleid, Oid classid, Oid objid); +extern void RemoveDefaultACLById(Oid defaclOid); extern AclMode pg_attribute_aclmask(Oid table_oid, AttrNumber attnum, Oid roleid, AclMode mask, AclMaskHow how); diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h index 1428b28d15..f0647e3419 100644 --- a/src/include/utils/syscache.h +++ b/src/include/utils/syscache.h @@ -9,7 +9,7 @@ * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/utils/syscache.h,v 1.74 2009/01/01 17:24:02 momjian Exp $ + * $PostgreSQL: pgsql/src/include/utils/syscache.h,v 1.75 2009/10/05 19:24:49 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -49,6 +49,7 @@ enum SysCacheIdentifier CONSTROID, CONVOID, DATABASEOID, + DEFACLROLENSPOBJ, ENUMOID, ENUMTYPOIDNAME, FOREIGNDATAWRAPPERNAME, diff --git a/src/test/regress/expected/privileges.out b/src/test/regress/expected/privileges.out index 809b656217..24239276db 100644 --- a/src/test/regress/expected/privileges.out +++ b/src/test/regress/expected/privileges.out @@ -836,6 +836,117 @@ SELECT has_sequence_privilege('x_seq', 'USAGE'); t (1 row) +-- test default ACLs +\c - +CREATE SCHEMA testns; +GRANT ALL ON SCHEMA testns TO regressuser1; +CREATE TABLE testns.acltest1 (x int); +SELECT has_table_privilege('regressuser1', 'testns.acltest1', 'SELECT'); -- no + has_table_privilege +--------------------- + f +(1 row) + +SELECT has_table_privilege('regressuser1', 'testns.acltest1', 'INSERT'); -- no + has_table_privilege +--------------------- + f +(1 row) + +ALTER DEFAULT PRIVILEGES IN SCHEMA testns GRANT SELECT ON TABLE TO public; +SELECT has_table_privilege('regressuser1', 'testns.acltest1', 'SELECT'); -- no + has_table_privilege +--------------------- + f +(1 row) + +SELECT has_table_privilege('regressuser1', 'testns.acltest1', 'INSERT'); -- no + has_table_privilege +--------------------- + f +(1 row) + +DROP TABLE testns.acltest1; +CREATE TABLE testns.acltest1 (x int); +SELECT has_table_privilege('regressuser1', 'testns.acltest1', 'SELECT'); -- yes + has_table_privilege +--------------------- + t +(1 row) + +SELECT has_table_privilege('regressuser1', 'testns.acltest1', 'INSERT'); -- no + has_table_privilege +--------------------- + f +(1 row) + +ALTER DEFAULT PRIVILEGES IN SCHEMA testns GRANT INSERT ON TABLE TO regressuser1; +DROP TABLE testns.acltest1; +CREATE TABLE testns.acltest1 (x int); +SELECT has_table_privilege('regressuser1', 'testns.acltest1', 'SELECT'); -- yes + has_table_privilege +--------------------- + t +(1 row) + +SELECT has_table_privilege('regressuser1', 'testns.acltest1', 'INSERT'); -- yes + has_table_privilege +--------------------- + t +(1 row) + +ALTER DEFAULT PRIVILEGES IN SCHEMA testns REVOKE INSERT ON TABLE FROM regressuser1; +DROP TABLE testns.acltest1; +CREATE TABLE testns.acltest1 (x int); +SELECT has_table_privilege('regressuser1', 'testns.acltest1', 'SELECT'); -- yes + has_table_privilege +--------------------- + t +(1 row) + +SELECT has_table_privilege('regressuser1', 'testns.acltest1', 'INSERT'); -- no + has_table_privilege +--------------------- + f +(1 row) + +ALTER DEFAULT PRIVILEGES FOR ROLE regressuser1 REVOKE EXECUTE ON FUNCTION FROM public; +SET ROLE regressuser1; +CREATE FUNCTION testns.foo() RETURNS int AS 'select 1' LANGUAGE sql; +SELECT has_function_privilege('regressuser2', 'testns.foo()', 'EXECUTE'); -- no + has_function_privilege +------------------------ + f +(1 row) + +ALTER DEFAULT PRIVILEGES IN SCHEMA testns GRANT EXECUTE ON FUNCTION to public; +DROP FUNCTION testns.foo(); +CREATE FUNCTION testns.foo() RETURNS int AS 'select 1' LANGUAGE sql; +SELECT has_function_privilege('regressuser2', 'testns.foo()', 'EXECUTE'); -- yes + has_function_privilege +------------------------ + t +(1 row) + +DROP FUNCTION testns.foo(); +RESET ROLE; +SELECT count(*) + FROM pg_default_acl d LEFT JOIN pg_namespace n ON defaclnamespace = n.oid + WHERE nspname = 'testns'; + count +------- + 2 +(1 row) + +DROP SCHEMA testns CASCADE; +NOTICE: drop cascades to table testns.acltest1 +SELECT d.* -- check that entries went away + FROM pg_default_acl d LEFT JOIN pg_namespace n ON defaclnamespace = n.oid + WHERE nspname IS NULL AND defaclnamespace != 0; + defaclrole | defaclnamespace | defaclobjtype | defaclacl +------------+-----------------+---------------+----------- +(0 rows) + -- clean up \c drop sequence x_seq; @@ -860,7 +971,9 @@ DROP TABLE atestp1; DROP TABLE atestp2; DROP GROUP regressgroup1; DROP GROUP regressgroup2; +-- these are needed to clean up permissions REVOKE USAGE ON LANGUAGE sql FROM regressuser1; +DROP OWNED BY regressuser1; DROP USER regressuser1; DROP USER regressuser2; DROP USER regressuser3; diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out index 7213192d5f..1994edc905 100644 --- a/src/test/regress/expected/sanity_check.out +++ b/src/test/regress/expected/sanity_check.out @@ -95,6 +95,7 @@ SELECT relname, relhasindex pg_constraint | t pg_conversion | t pg_database | t + pg_default_acl | t pg_depend | t pg_description | t pg_enum | t @@ -151,7 +152,7 @@ SELECT relname, relhasindex timetz_tbl | f tinterval_tbl | f varchar_tbl | f -(140 rows) +(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 917e8e5da4..eaa879efa2 100644 --- a/src/test/regress/sql/privileges.sql +++ b/src/test/regress/sql/privileges.sql @@ -484,6 +484,73 @@ SET SESSION AUTHORIZATION regressuser2; SELECT has_sequence_privilege('x_seq', 'USAGE'); + +-- test default ACLs +\c - + +CREATE SCHEMA testns; +GRANT ALL ON SCHEMA testns TO regressuser1; + +CREATE TABLE testns.acltest1 (x int); +SELECT has_table_privilege('regressuser1', 'testns.acltest1', 'SELECT'); -- no +SELECT has_table_privilege('regressuser1', 'testns.acltest1', 'INSERT'); -- no + +ALTER DEFAULT PRIVILEGES IN SCHEMA testns GRANT SELECT ON TABLE TO public; + +SELECT has_table_privilege('regressuser1', 'testns.acltest1', 'SELECT'); -- no +SELECT has_table_privilege('regressuser1', 'testns.acltest1', 'INSERT'); -- no + +DROP TABLE testns.acltest1; +CREATE TABLE testns.acltest1 (x int); + +SELECT has_table_privilege('regressuser1', 'testns.acltest1', 'SELECT'); -- yes +SELECT has_table_privilege('regressuser1', 'testns.acltest1', 'INSERT'); -- no + +ALTER DEFAULT PRIVILEGES IN SCHEMA testns GRANT INSERT ON TABLE TO regressuser1; + +DROP TABLE testns.acltest1; +CREATE TABLE testns.acltest1 (x int); + +SELECT has_table_privilege('regressuser1', 'testns.acltest1', 'SELECT'); -- yes +SELECT has_table_privilege('regressuser1', 'testns.acltest1', 'INSERT'); -- yes + +ALTER DEFAULT PRIVILEGES IN SCHEMA testns REVOKE INSERT ON TABLE FROM regressuser1; + +DROP TABLE testns.acltest1; +CREATE TABLE testns.acltest1 (x int); + +SELECT has_table_privilege('regressuser1', 'testns.acltest1', 'SELECT'); -- yes +SELECT has_table_privilege('regressuser1', 'testns.acltest1', 'INSERT'); -- no + +ALTER DEFAULT PRIVILEGES FOR ROLE regressuser1 REVOKE EXECUTE ON FUNCTION FROM public; + +SET ROLE regressuser1; + +CREATE FUNCTION testns.foo() RETURNS int AS 'select 1' LANGUAGE sql; + +SELECT has_function_privilege('regressuser2', 'testns.foo()', 'EXECUTE'); -- no + +ALTER DEFAULT PRIVILEGES IN SCHEMA testns GRANT EXECUTE ON FUNCTION to public; + +DROP FUNCTION testns.foo(); +CREATE FUNCTION testns.foo() RETURNS int AS 'select 1' LANGUAGE sql; + +SELECT has_function_privilege('regressuser2', 'testns.foo()', 'EXECUTE'); -- yes + +DROP FUNCTION testns.foo(); + +RESET ROLE; + +SELECT count(*) + FROM pg_default_acl d LEFT JOIN pg_namespace n ON defaclnamespace = n.oid + WHERE nspname = 'testns'; + +DROP SCHEMA testns CASCADE; + +SELECT d.* -- check that entries went away + FROM pg_default_acl d LEFT JOIN pg_namespace n ON defaclnamespace = n.oid + WHERE nspname IS NULL AND defaclnamespace != 0; + -- clean up \c @@ -513,7 +580,10 @@ DROP TABLE atestp2; DROP GROUP regressgroup1; DROP GROUP regressgroup2; +-- these are needed to clean up permissions REVOKE USAGE ON LANGUAGE sql FROM regressuser1; +DROP OWNED BY regressuser1; + DROP USER regressuser1; DROP USER regressuser2; DROP USER regressuser3;