diff options
author | Tom Lane | 2009-10-05 19:24:49 +0000 |
---|---|---|
committer | Tom Lane | 2009-10-05 19:24:49 +0000 |
commit | a60d707a14eff8cebe1425794b9f34bdb5190f13 (patch) | |
tree | 3d4d63479cbe4cb887f39d8b0d0e9a676d133914 | |
parent | e99f626551fbc55ea3f3352485cf8126b6ba9204 (diff) |
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, 2197 insertions, 137 deletions
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml index cc40c140ff..ef50e65f47 100644 --- a/doc/src/sgml/catalogs.sgml +++ b/doc/src/sgml/catalogs.sgml @@ -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 dbf3a3a953..87f33c8f5a 100644 --- a/doc/src/sgml/ref/allfiles.sgml +++ b/doc/src/sgml/ref/allfiles.sgml @@ -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 0000000000..ce17662156 --- /dev/null +++ b/doc/src/sgml/ref/alter_default_privileges.sgml @@ -0,0 +1,211 @@ +<!-- +$PostgreSQL$ +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 8aaedb3008..4a993b6ed4 100644 --- a/doc/src/sgml/ref/grant.sgml +++ b/doc/src/sgml/ref/grant.sgml @@ -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 85e937556d..8dba804c6c 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -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 723bb9c519..ac95ec8469 100644 --- a/doc/src/sgml/reference.sgml +++ b/doc/src/sgml/reference.sgml @@ -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 3a3f504756..1203892267 100644 --- a/src/backend/bootstrap/bootparse.y +++ b/src/backend/bootstrap/bootparse.y @@ -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 861cb1debc..cb77a769ce 100644 --- a/src/backend/catalog/Makefile +++ b/src/backend/catalog/Makefile @@ -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 ec4aaf0bf1..25a8f6ae66 100644 --- a/src/backend/catalog/aclchk.c +++ b/src/backend/catalog/aclchk.c @@ -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 cb9a9c2675..7051caf54f 100644 --- a/src/backend/catalog/dependency.c +++ b/src/backend/catalog/dependency.c @@ -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 e404dbe40e..c258bd69c2 100644 --- a/src/backend/catalog/heap.c +++ b/src/backend/catalog/heap.c @@ -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 824d4efead..c397bcb760 100644 --- a/src/backend/catalog/index.c +++ b/src/backend/catalog/index.c @@ -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 d9e1e21a17..4c1ecb1561 100644 --- a/src/backend/catalog/pg_proc.c +++ b/src/backend/catalog/pg_proc.c @@ -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 cd04053759..c4b6ebf7c5 100644 --- a/src/backend/catalog/pg_shdepend.c +++ b/src/backend/catalog/pg_shdepend.c @@ -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 be171503f3..b16f986265 100644 --- a/src/backend/catalog/toasting.c +++ b/src/backend/catalog/toasting.c @@ -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 29602a4eea..a20a909cf5 100644 --- a/src/backend/commands/cluster.c +++ b/src/backend/commands/cluster.c @@ -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 90281d38e7..d3b89b12d7 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -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 6c2fd67734..fc9785e3a7 100644 --- a/src/backend/executor/execMain.c +++ b/src/backend/executor/execMain.c @@ -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 6420e4adb8..067df4709b 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -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 f125719a3c..ac641e6aa1 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -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 732b1d7adc..548b2f7e23 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -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 ddf87b4e5d..1d05072437 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -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 e19e96dce6..4c5e31f6f9 100644 --- a/src/backend/utils/adt/acl.c +++ b/src/backend/utils/adt/acl.c @@ -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 dbd3619648..1b6b22683e 100644 --- a/src/backend/utils/cache/syscache.c +++ b/src/backend/utils/cache/syscache.c @@ -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 2d43e80fcd..02b4510ade 100644 --- a/src/bin/pg_dump/common.c +++ b/src/bin/pg_dump/common.c @@ -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 5a3f9c34ce..e4e6c8d715 100644 --- a/src/bin/pg_dump/dumputils.c +++ b/src/bin/pg_dump/dumputils.c @@ -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 f693922cf4..ce0a37a325 100644 --- a/src/bin/pg_dump/dumputils.h +++ b/src/bin/pg_dump/dumputils.h @@ -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 5b68a779b4..47e0ac0d53 100644 --- a/src/bin/pg_dump/pg_backup_archiver.c +++ b/src/bin/pg_dump/pg_backup_archiver.c @@ -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 b5c2f9355e..68705b2eae 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -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 ea489e84f2..1b4b182a3a 100644 --- a/src/bin/pg_dump/pg_dump.h +++ b/src/bin/pg_dump/pg_dump.h @@ -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 bafabe9fa4..109e49b4c6 100644 --- a/src/bin/pg_dump/pg_dump_sort.c +++ b/src/bin/pg_dump/pg_dump_sort.c @@ -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 b265398f13..2da7919bd8 100644 --- a/src/bin/pg_dump/pg_dumpall.c +++ b/src/bin/pg_dump/pg_dumpall.c @@ -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 da57eb4b29..d260010957 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -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 a193ea0734..01787717c1 100644 --- a/src/bin/psql/describe.c +++ b/src/bin/psql/describe.c @@ -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 0b32ddf90a..1dafb475c5 100644 --- a/src/bin/psql/describe.h +++ b/src/bin/psql/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 d498d580b3..c2c3b4a6be 100644 --- a/src/bin/psql/help.c +++ b/src/bin/psql/help.c @@ -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 64d62e41e7..9ffab6d131 100644 --- a/src/include/catalog/catversion.h +++ b/src/include/catalog/catversion.h @@ -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 a2f676199b..3d3e1fbaca 100644 --- a/src/include/catalog/dependency.h +++ b/src/include/catalog/dependency.h @@ -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 e276b25b1f..ee1d3c95b8 100644 --- a/src/include/catalog/heap.h +++ b/src/include/catalog/heap.h @@ -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 81e18a1e7a..64d15fa3b3 100644 --- a/src/include/catalog/indexing.h +++ b/src/include/catalog/indexing.h @@ -267,6 +267,11 @@ DECLARE_UNIQUE_INDEX(pg_user_mapping_oid_index, 174, on pg_user_mapping using bt DECLARE_UNIQUE_INDEX(pg_user_mapping_user_server_index, 175, on pg_user_mapping using btree(umuser oid_ops, umserver oid_ops)); #define UserMappingUserServerIndexId 175 +DECLARE_UNIQUE_INDEX(pg_default_acl_role_nsp_obj_index, 827, on pg_default_acl using btree(defaclrole oid_ops, defaclnamespace oid_ops, defaclobjtype char_ops)); +#define DefaultAclRoleNspObjIndexId 827 +DECLARE_UNIQUE_INDEX(pg_default_acl_oid_index, 828, on pg_default_acl using btree(oid oid_ops)); +#define DefaultAclOidIndexId 828 + /* last step of initialization script: build the indexes declared above */ BUILD_INDICES diff --git a/src/include/catalog/pg_default_acl.h b/src/include/catalog/pg_default_acl.h new file mode 100644 index 0000000000..192face51c --- /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$ + * + * 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 3977081e7d..6c3e89570c 100644 --- a/src/include/nodes/nodes.h +++ b/src/include/nodes/nodes.h @@ -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 076ed8b03a..b7a144b405 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -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 bde872749d..0d857731cf 100644 --- a/src/include/utils/acl.h +++ b/src/include/utils/acl.h @@ -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 0e4527795a..8b13782dc7 100644 --- a/src/include/utils/syscache.h +++ b/src/include/utils/syscache.h @@ -49,6 +49,7 @@ enum SysCacheIdentifier CONSTROID, CONVOID, DATABASEOID, + DEFACLROLENSPOBJ, ENUMOID, ENUMTYPOIDNAME, FOREIGNDATAWRAPPERNAME, diff --git a/src/test/regress/expected/privileges.out b/src/test/regress/expected/privileges.out index 809b656217..24239276db 100644 --- a/src/test/regress/expected/privileges.out +++ b/src/test/regress/expected/privileges.out @@ -836,6 +836,117 @@ SELECT has_sequence_privilege('x_seq', 'USAGE'); t (1 row) +-- test default ACLs +\c - +CREATE SCHEMA testns; +GRANT ALL ON SCHEMA testns TO regressuser1; +CREATE TABLE testns.acltest1 (x int); +SELECT has_table_privilege('regressuser1', 'testns.acltest1', 'SELECT'); -- no + has_table_privilege +--------------------- + f +(1 row) + +SELECT has_table_privilege('regressuser1', 'testns.acltest1', 'INSERT'); -- no + has_table_privilege +--------------------- + f +(1 row) + +ALTER DEFAULT PRIVILEGES IN SCHEMA testns GRANT SELECT ON TABLE TO public; +SELECT has_table_privilege('regressuser1', 'testns.acltest1', 'SELECT'); -- no + has_table_privilege +--------------------- + f +(1 row) + +SELECT has_table_privilege('regressuser1', 'testns.acltest1', 'INSERT'); -- no + has_table_privilege +--------------------- + f +(1 row) + +DROP TABLE testns.acltest1; +CREATE TABLE testns.acltest1 (x int); +SELECT has_table_privilege('regressuser1', 'testns.acltest1', 'SELECT'); -- yes + has_table_privilege +--------------------- + t +(1 row) + +SELECT has_table_privilege('regressuser1', 'testns.acltest1', 'INSERT'); -- no + has_table_privilege +--------------------- + f +(1 row) + +ALTER DEFAULT PRIVILEGES IN SCHEMA testns GRANT INSERT ON TABLE TO regressuser1; +DROP TABLE testns.acltest1; +CREATE TABLE testns.acltest1 (x int); +SELECT has_table_privilege('regressuser1', 'testns.acltest1', 'SELECT'); -- yes + has_table_privilege +--------------------- + t +(1 row) + +SELECT has_table_privilege('regressuser1', 'testns.acltest1', 'INSERT'); -- yes + has_table_privilege +--------------------- + t +(1 row) + +ALTER DEFAULT PRIVILEGES IN SCHEMA testns REVOKE INSERT ON TABLE FROM regressuser1; +DROP TABLE testns.acltest1; +CREATE TABLE testns.acltest1 (x int); +SELECT has_table_privilege('regressuser1', 'testns.acltest1', 'SELECT'); -- yes + has_table_privilege +--------------------- + t +(1 row) + +SELECT has_table_privilege('regressuser1', 'testns.acltest1', 'INSERT'); -- no + has_table_privilege +--------------------- + f +(1 row) + +ALTER DEFAULT PRIVILEGES FOR ROLE regressuser1 REVOKE EXECUTE ON FUNCTION FROM public; +SET ROLE regressuser1; +CREATE FUNCTION testns.foo() RETURNS int AS 'select 1' LANGUAGE sql; +SELECT has_function_privilege('regressuser2', 'testns.foo()', 'EXECUTE'); -- no + has_function_privilege +------------------------ + f +(1 row) + +ALTER DEFAULT PRIVILEGES IN SCHEMA testns GRANT EXECUTE ON FUNCTION to public; +DROP FUNCTION testns.foo(); +CREATE FUNCTION testns.foo() RETURNS int AS 'select 1' LANGUAGE sql; +SELECT has_function_privilege('regressuser2', 'testns.foo()', 'EXECUTE'); -- yes + has_function_privilege +------------------------ + t +(1 row) + +DROP FUNCTION testns.foo(); +RESET ROLE; +SELECT count(*) + FROM pg_default_acl d LEFT JOIN pg_namespace n ON defaclnamespace = n.oid + WHERE nspname = 'testns'; + count +------- + 2 +(1 row) + +DROP SCHEMA testns CASCADE; +NOTICE: drop cascades to table testns.acltest1 +SELECT d.* -- check that entries went away + FROM pg_default_acl d LEFT JOIN pg_namespace n ON defaclnamespace = n.oid + WHERE nspname IS NULL AND defaclnamespace != 0; + defaclrole | defaclnamespace | defaclobjtype | defaclacl +------------+-----------------+---------------+----------- +(0 rows) + -- clean up \c drop sequence x_seq; @@ -860,7 +971,9 @@ DROP TABLE atestp1; DROP TABLE atestp2; DROP GROUP regressgroup1; DROP GROUP regressgroup2; +-- these are needed to clean up permissions REVOKE USAGE ON LANGUAGE sql FROM regressuser1; +DROP OWNED BY regressuser1; DROP USER regressuser1; DROP USER regressuser2; DROP USER regressuser3; diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out index 7213192d5f..1994edc905 100644 --- a/src/test/regress/expected/sanity_check.out +++ b/src/test/regress/expected/sanity_check.out @@ -95,6 +95,7 @@ SELECT relname, relhasindex pg_constraint | t pg_conversion | t pg_database | t + pg_default_acl | t pg_depend | t pg_description | t pg_enum | t @@ -151,7 +152,7 @@ SELECT relname, relhasindex timetz_tbl | f tinterval_tbl | f varchar_tbl | f -(140 rows) +(141 rows) -- -- another sanity check: every system catalog that has OIDs should have diff --git a/src/test/regress/sql/privileges.sql b/src/test/regress/sql/privileges.sql index 917e8e5da4..eaa879efa2 100644 --- a/src/test/regress/sql/privileges.sql +++ b/src/test/regress/sql/privileges.sql @@ -484,6 +484,73 @@ SET SESSION AUTHORIZATION regressuser2; SELECT has_sequence_privilege('x_seq', 'USAGE'); + +-- test default ACLs +\c - + +CREATE SCHEMA testns; +GRANT ALL ON SCHEMA testns TO regressuser1; + +CREATE TABLE testns.acltest1 (x int); +SELECT has_table_privilege('regressuser1', 'testns.acltest1', 'SELECT'); -- no +SELECT has_table_privilege('regressuser1', 'testns.acltest1', 'INSERT'); -- no + +ALTER DEFAULT PRIVILEGES IN SCHEMA testns GRANT SELECT ON TABLE TO public; + +SELECT has_table_privilege('regressuser1', 'testns.acltest1', 'SELECT'); -- no +SELECT has_table_privilege('regressuser1', 'testns.acltest1', 'INSERT'); -- no + +DROP TABLE testns.acltest1; +CREATE TABLE testns.acltest1 (x int); + +SELECT has_table_privilege('regressuser1', 'testns.acltest1', 'SELECT'); -- yes +SELECT has_table_privilege('regressuser1', 'testns.acltest1', 'INSERT'); -- no + +ALTER DEFAULT PRIVILEGES IN SCHEMA testns GRANT INSERT ON TABLE TO regressuser1; + +DROP TABLE testns.acltest1; +CREATE TABLE testns.acltest1 (x int); + +SELECT has_table_privilege('regressuser1', 'testns.acltest1', 'SELECT'); -- yes +SELECT has_table_privilege('regressuser1', 'testns.acltest1', 'INSERT'); -- yes + +ALTER DEFAULT PRIVILEGES IN SCHEMA testns REVOKE INSERT ON TABLE FROM regressuser1; + +DROP TABLE testns.acltest1; +CREATE TABLE testns.acltest1 (x int); + +SELECT has_table_privilege('regressuser1', 'testns.acltest1', 'SELECT'); -- yes +SELECT has_table_privilege('regressuser1', 'testns.acltest1', 'INSERT'); -- no + +ALTER DEFAULT PRIVILEGES FOR ROLE regressuser1 REVOKE EXECUTE ON FUNCTION FROM public; + +SET ROLE regressuser1; + +CREATE FUNCTION testns.foo() RETURNS int AS 'select 1' LANGUAGE sql; + +SELECT has_function_privilege('regressuser2', 'testns.foo()', 'EXECUTE'); -- no + +ALTER DEFAULT PRIVILEGES IN SCHEMA testns GRANT EXECUTE ON FUNCTION to public; + +DROP FUNCTION testns.foo(); +CREATE FUNCTION testns.foo() RETURNS int AS 'select 1' LANGUAGE sql; + +SELECT has_function_privilege('regressuser2', 'testns.foo()', 'EXECUTE'); -- yes + +DROP FUNCTION testns.foo(); + +RESET ROLE; + +SELECT count(*) + FROM pg_default_acl d LEFT JOIN pg_namespace n ON defaclnamespace = n.oid + WHERE nspname = 'testns'; + +DROP SCHEMA testns CASCADE; + +SELECT d.* -- check that entries went away + FROM pg_default_acl d LEFT JOIN pg_namespace n ON defaclnamespace = n.oid + WHERE nspname IS NULL AND defaclnamespace != 0; + -- clean up \c @@ -513,7 +580,10 @@ DROP TABLE atestp2; DROP GROUP regressgroup1; DROP GROUP regressgroup2; +-- these are needed to clean up permissions REVOKE USAGE ON LANGUAGE sql FROM regressuser1; +DROP OWNED BY regressuser1; + DROP USER regressuser1; DROP USER regressuser2; DROP USER regressuser3; |