diff options
author | Tom Lane <tgl@sss.pgh.pa.us> | 2009-10-05 19:24:49 +0000 |
---|---|---|
committer | Tom Lane <tgl@sss.pgh.pa.us> | 2009-10-05 19:24:49 +0000 |
commit | 249724cb014bd341cf51a8c4284fca9767a556d1 (patch) | |
tree | c165eeb00764af4ee34157d7dc1cdc8d2a23593b | |
parent | 41f89e3bbc3138d82fe26084236f9687414091e4 (diff) | |
download | postgresql-249724cb014bd341cf51a8c4284fca9767a556d1.tar.gz postgresql-249724cb014bd341cf51a8c4284fca9767a556d1.zip |
Create an ALTER DEFAULT PRIVILEGES command, which allows users to adjust
the privileges that will be applied to subsequently-created objects.
Such adjustments are always per owning role, and can be restricted to objects
created in particular schemas too. A notable benefit is that users can
override the traditional default privilege settings, eg, the PUBLIC EXECUTE
privilege traditionally granted by default for functions.
Petr Jelinek
48 files changed, 2240 insertions, 180 deletions
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml index dd103573a5d..15dab71cc0d 100644 --- a/doc/src/sgml/catalogs.sgml +++ b/doc/src/sgml/catalogs.sgml @@ -1,4 +1,4 @@ -<!-- $PostgreSQL: pgsql/doc/src/sgml/catalogs.sgml,v 2.207 2009/09/22 23:43:37 tgl Exp $ --> +<!-- $PostgreSQL: pgsql/doc/src/sgml/catalogs.sgml,v 2.208 2009/10/05 19:24:32 tgl Exp $ --> <!-- Documentation of the system catalogs, directed toward PostgreSQL developers --> @@ -114,6 +114,11 @@ </row> <row> + <entry><link linkend="catalog-pg-default-acl"><structname>pg_default_acl</structname></link></entry> + <entry>default privileges for object types</entry> + </row> + + <row> <entry><link linkend="catalog-pg-depend"><structname>pg_depend</structname></link></entry> <entry>dependencies between database objects</entry> </row> @@ -2155,6 +2160,93 @@ </sect1> + <sect1 id="catalog-pg-default-acl"> + <title><structname>pg_default_acl</structname></title> + + <indexterm zone="catalog-pg-default-acl"> + <primary>pg_default_acl</primary> + </indexterm> + + <para> + The catalog <structname>pg_default_acl</> stores initial + privileges to be assigned to newly created objects. + </para> + + <table> + <title><structname>pg_default_acl</> Columns</title> + + <tgroup cols="4"> + <thead> + <row> + <entry>Name</entry> + <entry>Type</entry> + <entry>References</entry> + <entry>Description</entry> + </row> + </thead> + + <tbody> + <row> + <entry><structfield>defaclrole</structfield></entry> + <entry><type>oid</type></entry> + <entry><literal><link linkend="catalog-pg-authid"><structname>pg_authid</structname></link>.oid</literal></entry> + <entry>The OID of the role associated with this entry</entry> + </row> + + <row> + <entry><structfield>defaclnamespace</structfield></entry> + <entry><type>oid</type></entry> + <entry><literal><link linkend="catalog-pg-namespace"><structname>pg_namespace</structname></link>.oid</literal></entry> + <entry>The OID of the namespace associated with this entry, + or 0 if none</entry> + </row> + + <row> + <entry><structfield>defaclobjtype</structfield></entry> + <entry><type>char</type></entry> + <entry></entry> + <entry> + Type of object this entry is for: + <literal>r</> = relation (table, view), + <literal>S</> = sequence, + <literal>f</> = function + </entry> + </row> + + <row> + <entry><structfield>defaclacl</structfield></entry> + <entry><type>aclitem[]</type></entry> + <entry></entry> + <entry> + Access privileges that this type of object should have on creation + </entry> + </row> + </tbody> + </tgroup> + </table> + + <para> + A <structname>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: <quote>global</> entries with + <structfield>defaclnamespace</> = 0, and <quote>per-schema</> entries + that reference a particular schema. If a global entry is present then + it <emphasis>overrides</> the normal hard-wired default privileges + for the object type. A per-schema entry, if present, represents privileges + to be <emphasis>added to</> the global or hard-wired default privileges. + </para> + + <para> + Note that when an ACL entry in another catalog is NULL, it is taken + to represent the hard-wired default privileges for its object, + <emphasis>not</> whatever might be in <structname>pg_default_acl</> + at the moment. <structname>pg_default_acl</> is only consulted during + object creation. + </para> + + </sect1> + + <sect1 id="catalog-pg-depend"> <title><structname>pg_depend</structname></title> diff --git a/doc/src/sgml/ref/allfiles.sgml b/doc/src/sgml/ref/allfiles.sgml index 845033b6b66..c15579c5164 100644 --- a/doc/src/sgml/ref/allfiles.sgml +++ b/doc/src/sgml/ref/allfiles.sgml @@ -1,5 +1,5 @@ <!-- -$PostgreSQL: pgsql/doc/src/sgml/ref/allfiles.sgml,v 1.75 2009/09/22 23:43:37 tgl Exp $ +$PostgreSQL: pgsql/doc/src/sgml/ref/allfiles.sgml,v 1.76 2009/10/05 19:24:33 tgl Exp $ PostgreSQL documentation Complete list of usable sgml source files in this directory. --> @@ -9,6 +9,7 @@ Complete list of usable sgml source files in this directory. <!entity alterAggregate system "alter_aggregate.sgml"> <!entity alterConversion system "alter_conversion.sgml"> <!entity alterDatabase system "alter_database.sgml"> +<!entity alterDefaultPrivileges system "alter_default_privileges.sgml"> <!entity alterDomain system "alter_domain.sgml"> <!entity alterForeignDataWrapper system "alter_foreign_data_wrapper.sgml"> <!entity alterFunction system "alter_function.sgml"> 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 00000000000..b2054b17804 --- /dev/null +++ b/doc/src/sgml/ref/alter_default_privileges.sgml @@ -0,0 +1,211 @@ +<!-- +$PostgreSQL: pgsql/doc/src/sgml/ref/alter_default_privileges.sgml,v 1.1 2009/10/05 19:24:33 tgl Exp $ +PostgreSQL documentation +--> + +<refentry id="SQL-ALTERDEFAULTPRIVILEGES"> + <refmeta> + <refentrytitle id="SQL-ALTERDEFAULTPRIVILEGES-TITLE">ALTER DEFAULT PRIVILEGES</refentrytitle> + <manvolnum>7</manvolnum> + <refmiscinfo>SQL - Language Statements</refmiscinfo> + </refmeta> + + <refnamediv> + <refname>ALTER DEFAULT PRIVILEGES</refname> + <refpurpose>define default access privileges</refpurpose> + </refnamediv> + + <indexterm zone="sql-alterdefaultprivileges"> + <primary>ALTER DEFAULT PRIVILEGES</primary> + </indexterm> + + <refsynopsisdiv> +<synopsis> +ALTER DEFAULT PRIVILEGES + [ FOR { ROLE | USER } <replaceable>target_role</replaceable> [, ...] ] + [ IN SCHEMA <replaceable>schema_name</replaceable> [, ...] ] + <replaceable class="parameter">abbreviated_grant_or_revoke</replaceable> + +<phrase>where <replaceable class="parameter">abbreviated_grant_or_revoke</replaceable> is one of:</phrase> + +GRANT { { SELECT | INSERT | UPDATE | DELETE | TRUNCATE | REFERENCES | TRIGGER } + [,...] | ALL [ PRIVILEGES ] } + ON TABLE + TO { [ GROUP ] <replaceable class="PARAMETER">role_name</replaceable> | PUBLIC } [, ...] [ WITH GRANT OPTION ] + +GRANT { { USAGE | SELECT | UPDATE } + [,...] | ALL [ PRIVILEGES ] } + ON SEQUENCE + TO { [ GROUP ] <replaceable class="PARAMETER">role_name</replaceable> | PUBLIC } [, ...] [ WITH GRANT OPTION ] + +GRANT { EXECUTE | ALL [ PRIVILEGES ] } + ON FUNCTION + TO { [ GROUP ] <replaceable class="PARAMETER">role_name</replaceable> | PUBLIC } [, ...] [ WITH GRANT OPTION ] + +REVOKE [ GRANT OPTION FOR ] + { { SELECT | INSERT | UPDATE | DELETE | TRUNCATE | REFERENCES | TRIGGER } + [,...] | ALL [ PRIVILEGES ] } + ON TABLE + FROM { [ GROUP ] <replaceable class="PARAMETER">role_name</replaceable> | PUBLIC } [, ...] + [ CASCADE | RESTRICT ] + +REVOKE [ GRANT OPTION FOR ] + { { USAGE | SELECT | UPDATE } + [,...] | ALL [ PRIVILEGES ] } + ON SEQUENCE + FROM { [ GROUP ] <replaceable class="PARAMETER">role_name</replaceable> | PUBLIC } [, ...] + [ CASCADE | RESTRICT ] + +REVOKE [ GRANT OPTION FOR ] + { EXECUTE | ALL [ PRIVILEGES ] } + ON FUNCTION + FROM { [ GROUP ] <replaceable class="PARAMETER">role_name</replaceable> | PUBLIC } [, ...] + [ CASCADE | RESTRICT ] +</synopsis> + </refsynopsisdiv> + + <refsect1 id="sql-alterdefaultprivileges-description"> + <title>Description</title> + + <para> + <command>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. + </para> + + <para> + 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. + </para> + + <para> + As explained under <xref linkend="sql-grant" endterm="sql-grant-title">, + the default privileges for any object type normally grant all grantable + permissions to the object owner, and may grant some privileges to + <literal>PUBLIC</> as well. However, this behavior can be changed by + altering the global default privileges with + <command>ALTER DEFAULT PRIVILEGES</>. + </para> + + <refsect2> + <title>Parameters</title> + + <variablelist> + <varlistentry> + <term><replaceable>target_role</replaceable></term> + <listitem> + <para> + The name of an existing role of which the current role is a member. + If <literal>FOR ROLE</> is omitted, the current role is assumed. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><replaceable>schema_name</replaceable></term> + <listitem> + <para> + The name of an existing schema. Each <replaceable>target_role</> + must have <literal>CREATE</> privileges for each specified schema. + If <literal>IN SCHEMA</> is omitted, the global default privileges + are altered. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><replaceable>role_name</replaceable></term> + <listitem> + <para> + The name of an existing role to grant or revoke privileges for. + This parameter, and all the other parameters in + <replaceable class="parameter">abbreviated_grant_or_revoke</>, + act as described under + <xref linkend="sql-grant" endterm="sql-grant-title"> or + <xref linkend="sql-revoke" endterm="sql-revoke-title">, + except that one is setting permissions for a whole class of objects + rather than specific named objects. + </para> + </listitem> + </varlistentry> + </variablelist> + </refsect2> + </refsect1> + + <refsect1 id="sql-alterdefaultprivileges-notes"> + <title>Notes</title> + + <para> + Use <xref linkend="app-psql">'s <command>\ddp</command> command + to obtain information about existing assignments of default privileges. + The meaning of the privilege values is the same as explained for + <command>\dp</command> under + <xref linkend="sql-grant" endterm="sql-grant-title">. + </para> + + <para> + If you wish to drop a role that has had its global default privileges + altered, it is necessary to use <command>DROP OWNED BY</> first, + to get rid of the default privileges entry for the role. + </para> + </refsect1> + + <refsect1 id="sql-alterdefaultprivileges-examples"> + <title>Examples</title> + + <para> + Grant SELECT privilege to everyone for all tables (and views) you + subsequently create in schema <literal>myschema</literal>, and allow + role <literal>webuser</> to INSERT into them too: + +<programlisting> +ALTER DEFAULT PRIVILEGES IN SCHEMA myschema GRANT SELECT ON TABLE TO PUBLIC; +ALTER DEFAULT PRIVILEGES IN SCHEMA myschema GRANT INSERT ON TABLE TO webuser; +</programlisting> + </para> + + <para> + Undo the above, so that subsequently-created tables won't have any + more permissions than normal: + +<programlisting> +ALTER DEFAULT PRIVILEGES IN SCHEMA myschema REVOKE SELECT ON TABLE FROM PUBLIC; +ALTER DEFAULT PRIVILEGES IN SCHEMA myschema REVOKE INSERT ON TABLE FROM webuser; +</programlisting> + </para> + + <para> + Remove the public EXECUTE permission that is normally granted on functions, + for all functions subsequently created by role <literal>admin</>: + +<programlisting> +ALTER DEFAULT PRIVILEGES FOR ROLE admin REVOKE EXECUTE ON FUNCTION FROM PUBLIC; +</programlisting> + </para> + </refsect1> + + <refsect1> + <title>Compatibility</title> + + <para> + There is no <command>ALTER DEFAULT PRIVILEGES</command> statement in the SQL + standard. + </para> + </refsect1> + + <refsect1> + <title>See Also</title> + + <simplelist type="inline"> + <member><xref linkend="sql-grant" endterm="sql-grant-title"></member> + <member><xref linkend="sql-revoke" endterm="sql-revoke-title"></member> + </simplelist> + </refsect1> + +</refentry> diff --git a/doc/src/sgml/ref/grant.sgml b/doc/src/sgml/ref/grant.sgml index 4dddde27b91..2dcf4aa0f0b 100644 --- a/doc/src/sgml/ref/grant.sgml +++ b/doc/src/sgml/ref/grant.sgml @@ -1,5 +1,5 @@ <!-- -$PostgreSQL: pgsql/doc/src/sgml/ref/grant.sgml,v 1.77 2009/09/19 10:23:27 petere Exp $ +$PostgreSQL: pgsql/doc/src/sgml/ref/grant.sgml,v 1.78 2009/10/05 19:24:34 tgl Exp $ PostgreSQL documentation --> @@ -80,14 +80,6 @@ GRANT <replaceable class="PARAMETER">role_name</replaceable> [, ...] TO <replace they are different enough to be described separately. </para> - <para> - As of <productname>PostgreSQL</productname> 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 <literal>GROUP</> - to identify whether a grantee is a user or a group. <literal>GROUP</> - is still allowed in the command, but it is a noise word. - </para> - <refsect2 id="sql-grant-description-objects"> <title>GRANT on Database Objects</title> @@ -145,6 +137,9 @@ GRANT <replaceable class="PARAMETER">role_name</replaceable> [, ...] TO <replace security, issue the <command>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 + <xref linkend="sql-alterdefaultprivileges" endterm="sql-alterdefaultprivileges-title"> + command. </para> <para> @@ -389,6 +384,14 @@ GRANT <replaceable class="PARAMETER">role_name</replaceable> [, ...] TO <replace </para> <para> + Since <productname>PostgreSQL</productname> 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 <literal>GROUP</> + to identify whether a grantee is a user or a group. <literal>GROUP</> + is still allowed in the command, but it is a noise word. + </para> + + <para> A user may perform <command>SELECT</>, <command>INSERT</>, etc. on a column if he holds that privilege for either the specific column or its whole table. Granting the privilege at the table level and then @@ -518,8 +521,13 @@ GRANT SELECT (col1), UPDATE (col1) ON mytable TO miriam_rw; <command>REVOKE</> on an object will instantiate the default privileges (producing, for example, <literal>{miriam=arwdDxt/miriam}</>) and then modify them per the - specified request. Entries are shown in <quote>Column access + specified request. Similarly, entries are shown in <quote>Column access privileges</> only for columns with nondefault privileges. + (Note: for this purpose, <quote>default privileges</> always means the + built-in default privileges for the object's type. An object whose + privileges have been affected by an <command>ALTER DEFAULT PRIVILEGES</> + command will always be shown with an explicit privilege entry that + includes the effects of the <command>ALTER</>.) </para> <para> @@ -602,9 +610,10 @@ GRANT admins TO joe; <refsect1> <title>See Also</title> - <simpara> - <xref linkend="sql-revoke" endterm="sql-revoke-title"> - </simpara> + <simplelist type="inline"> + <member><xref linkend="sql-revoke" endterm="sql-revoke-title"></member> + <member><xref linkend="sql-alterdefaultprivileges" endterm="sql-alterdefaultprivileges-title"></member> + </simplelist> </refsect1> </refentry> diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index f60c3150e9f..e689d275cb4 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -1,5 +1,5 @@ <!-- -$PostgreSQL: pgsql/doc/src/sgml/ref/psql-ref.sgml,v 1.229 2009/08/11 12:02:58 momjian Exp $ +$PostgreSQL: pgsql/doc/src/sgml/ref/psql-ref.sgml,v 1.230 2009/10/05 19:24:34 tgl Exp $ PostgreSQL documentation --> @@ -979,6 +979,29 @@ testdb=> <varlistentry> + <term><literal>\ddp [ <replaceable class="parameter">pattern</replaceable> ]</literal></term> + <listitem> + <para> + 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 <replaceable class="parameter">pattern</replaceable> is + specified, only entries whose role name or schema name matches + the pattern are listed. + </para> + + <para> + The <xref linkend="sql-alterdefaultprivileges" + endterm="sql-alterdefaultprivileges-title"> command is used to set + default access privileges. The meaning of the + privilege display is explained under + <xref linkend="sql-grant" endterm="sql-grant-title">. + </para> + </listitem> + </varlistentry> + + + <varlistentry> <term><literal>\dD[S] [ <replaceable class="parameter">pattern</replaceable> ]</literal></term> <listitem> <para> @@ -1142,8 +1165,8 @@ testdb=> class="parameter">pattern</replaceable> is specified, only those roles whose names match the pattern are listed. (This command is now effectively the same as <literal>\du</literal>). - If the form <literal>\dg+</literal> is used, additional information - is shown about each role, including the comment for each role. + If the form <literal>\dg+</literal> is used, additional information + is shown about each role, including the comment for each role. </para> </listitem> </varlistentry> @@ -1235,7 +1258,9 @@ testdb=> <para> The <xref linkend="sql-grant" endterm="sql-grant-title"> and <xref linkend="sql-revoke" endterm="sql-revoke-title"> - commands are used to set access privileges. + commands are used to set access privileges. The meaning of the + privilege display is explained under + <xref linkend="sql-grant" endterm="sql-grant-title">. </para> </listitem> </varlistentry> @@ -2046,12 +2071,6 @@ lo_import 152801 </para> <para> - The <xref linkend="sql-grant" endterm="sql-grant-title"> and - <xref linkend="sql-revoke" endterm="sql-revoke-title"> - commands are used to set access privileges. - </para> - - <para> This is an alias for <command>\dp</command> (<quote>display privileges</quote>). </para> diff --git a/doc/src/sgml/reference.sgml b/doc/src/sgml/reference.sgml index 48f8040541d..0e72fc5475b 100644 --- a/doc/src/sgml/reference.sgml +++ b/doc/src/sgml/reference.sgml @@ -1,4 +1,4 @@ -<!-- $PostgreSQL: pgsql/doc/src/sgml/reference.sgml,v 1.68 2009/09/22 23:43:37 tgl Exp $ --> +<!-- $PostgreSQL: pgsql/doc/src/sgml/reference.sgml,v 1.69 2009/10/05 19:24:33 tgl Exp $ --> <part id="reference"> <title>Reference</title> @@ -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 b7624547aac..9d6f854d126 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 e4414ed2bf5..53784e9c54b 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 bb15e78d1c9..b06e587a1b1 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) @@ -610,6 +657,563 @@ objectNamesToOids(GrantObjectType objtype, List *objnames) } /* + * 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 * * OR the specified privilege(s) into per-column array entries for each @@ -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 c3d87ef59eb..8a07c69c7e8 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 e9bdc882238..a0133062847 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; @@ -921,6 +952,30 @@ heap_create_with_catalog(const char *relname, 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 * remove the disk file again.) @@ -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 759950b2265..b43b2910e73 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 1fda61cc160..db69c127ad4 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 08977c38633..869ec1fdd57 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 ac7b52734dd..dfc178724ef 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 5c483614e33..a05fb4736cc 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 07b4af611c4..697b80d1b41 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 d18d66c64a9..493ec2b1af1 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 5feff5d1691..0264b2b3392 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 d7ed08cc68b..5d3cbbda1e4 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 $ * *------------------------------------------------------------------------- */ @@ -1029,6 +1029,15 @@ _equalGrantRoleStmt(GrantRoleStmt *a, GrantRoleStmt *b) } static bool +_equalAlterDefaultPrivilegesStmt(AlterDefaultPrivilegesStmt *a, AlterDefaultPrivilegesStmt *b) +{ + COMPARE_NODE_FIELD(options); + COMPARE_NODE_FIELD(action); + + return true; +} + +static bool _equalDeclareCursorStmt(DeclareCursorStmt *a, DeclareCursorStmt *b) { COMPARE_STRING_FIELD(portalname); @@ -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 9a203a5a48d..af2f080b634 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 <privtarget> privilege_target %type <funwithargs> function_with_argtypes %type <list> function_with_argtypes_list +%type <ival> defacl_privilege_target +%type <defelt> DefACLOption +%type <list> DefACLOptionList %type <list> 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 0d2079fc0aa..4d7b3c2e9de 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 83d93ad3483..1264dfddb32 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, @@ -383,6 +384,15 @@ allocacl(int n) } /* + * Create a zero-entry ACL + */ +Acl * +make_empty_acl(void) +{ + return allocacl(0); +} + +/* * Copy an ACL */ Acl * @@ -424,6 +434,98 @@ aclconcat(const Acl *left_acl, const Acl *right_acl) } /* + * Merge two ACLs + * + * This produces a properly merged ACL with no redundant entries. + * Returns NULL on NULL input. + */ +Acl * +aclmerge(const Acl *left_acl, const Acl *right_acl, Oid ownerId) +{ + Acl *result_acl; + AclItem *aip; + int i, + num; + + /* Check for cases where one or both are empty/null */ + if (left_acl == NULL || ACL_NUM(left_acl) == 0) + { + if (right_acl == NULL || ACL_NUM(right_acl) == 0) + return NULL; + else + return aclcopy(right_acl); + } + else + { + if (right_acl == NULL || ACL_NUM(right_acl) == 0) + return aclcopy(left_acl); + } + + /* Merge them the hard way, one item at a time */ + result_acl = aclcopy(left_acl); + + aip = ACL_DAT(right_acl); + num = ACL_NUM(right_acl); + + for (i = 0; i < num; i++, aip++) + { + Acl *tmp_acl; + + tmp_acl = aclupdate(result_acl, aip, ACL_MODECHG_ADD, + ownerId, DROP_RESTRICT); + pfree(result_acl); + result_acl = tmp_acl; + } + + return result_acl; +} + +/* + * Sort the items in an ACL (into an arbitrary but consistent order) + */ +void +aclitemsort(Acl *acl) +{ + if (acl != NULL && ACL_NUM(acl) > 1) + qsort(ACL_DAT(acl), ACL_NUM(acl), sizeof(AclItem), aclitemComparator); +} + +/* + * Check if two ACLs are exactly equal + * + * This will not detect equality if the two arrays contain the same items + * in different orders. To handle that case, sort both inputs first, + * using aclitemsort(). + */ +bool +aclequal(const Acl *left_acl, const Acl *right_acl) +{ + /* Check for cases where one or both are empty/null */ + if (left_acl == NULL || ACL_NUM(left_acl) == 0) + { + if (right_acl == NULL || ACL_NUM(right_acl) == 0) + return true; + else + return false; + } + else + { + if (right_acl == NULL || ACL_NUM(right_acl) == 0) + return false; + } + + if (ACL_NUM(left_acl) != ACL_NUM(right_acl)) + return false; + + if (memcmp(ACL_DAT(left_acl), + ACL_DAT(right_acl), + ACL_NUM(left_acl) * sizeof(AclItem)) == 0) + return true; + + return false; +} + +/* * Verify that an ACL array is acceptable (one-dimensional and has no nulls) */ static void @@ -556,6 +658,31 @@ aclitem_match(const AclItem *a1, const AclItem *a2) } /* + * aclitemComparator + * qsort comparison function for AclItems + */ +static int +aclitemComparator(const void *arg1, const void *arg2) +{ + const AclItem *a1 = (const AclItem *) arg1; + const AclItem *a2 = (const AclItem *) arg2; + + if (a1->ai_grantee > a2->ai_grantee) + return 1; + if (a1->ai_grantee < a2->ai_grantee) + return -1; + if (a1->ai_grantor > a2->ai_grantor) + return 1; + if (a1->ai_grantor < a2->ai_grantor) + return -1; + if (a1->ai_privs > a2->ai_privs) + return 1; + if (a1->ai_privs < a2->ai_privs) + return -1; + return 0; +} + +/* * aclitem equality operator */ Datum @@ -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 922c4a626f7..efed498f120 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 789638fec45..ea314be0fa5 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"); @@ -167,6 +169,10 @@ getSchemaData(int *numTablesPtr) 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 8037c1d7e16..a9a27625707 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", @@ -683,6 +687,50 @@ buildACLCommands(const char *name, const char *subname, } /* + * 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 * or diff --git a/src/bin/pg_dump/dumputils.h b/src/bin/pg_dump/dumputils.h index 3c56c3f9a4d..a5bfe1bcfda 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 20bd3eb7eac..e15e4dbdb9e 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 34ebc27168d..d1715eccce8 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, @@ -1050,6 +1052,23 @@ selectDumpableType(TypeInfo *tinfo) } /* + * 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); @@ -5646,6 +5665,94 @@ getForeignServers(int *numForeignServers) } /* + * 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 -- * * This routine is used to dump any comments associated with the @@ -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 beec160110c..5b2c3d1e6bf 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 a56f429a7a8..4e95618318b 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 454b95c106c..f0a4d67d2af 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 67f05a89de0..d94d8b80c52 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 6e288da67a8..1644623812c 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 769ee9e975d..169ceb3739a 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 947eff050a1..f21099a6923 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 4ae91575566..1d3bfb0726c 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 fe04aab9643..954d3808a85 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 0d42cf11963..5faf9c1c014 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 ce117a8eecd..0272334f2e5 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 00000000000..cfd1f2b3a9b --- /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 d842c9c3199..5fd046e95b8 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 1ce28b77541..24066b9b38e 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 $ * *------------------------------------------------------------------------- */ @@ -1260,6 +1260,17 @@ typedef struct GrantRoleStmt } 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 * * We support "COPY relation FROM file", "COPY relation TO file", and diff --git a/src/include/utils/acl.h b/src/include/utils/acl.h index 5c38822d5d7..977b00de798 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 1428b28d154..f0647e34194 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 809b6562171..24239276dbf 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 7213192d5f3..1994edc905e 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 917e8e5da4e..eaa879efa22 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; |