Commit 4b2dafcc authored by Tom Lane's avatar Tom Lane

Align GRANT/REVOKE behavior more closely with the SQL spec, per discussion

of bug report #1150.  Also, arrange that the object owner's irrevocable
grant-option permissions are handled implicitly by the system rather than
being listed in the ACL as self-granted rights (which was wrong anyway).
I did not take the further step of showing these permissions in an
explicit 'granted by _SYSTEM' ACL entry, as that seemed more likely to
bollix up existing clients than to do anything really useful.  It's still
a possible future direction, though.
parent f35e8d84
<!-- <!--
$PostgreSQL: pgsql/doc/src/sgml/ref/grant.sgml,v 1.39 2004/03/22 03:38:24 momjian Exp $ $PostgreSQL: pgsql/doc/src/sgml/ref/grant.sgml,v 1.40 2004/06/01 21:49:21 tgl Exp $
PostgreSQL documentation PostgreSQL documentation
--> -->
...@@ -67,9 +67,10 @@ GRANT { { CREATE | USAGE } [,...] | ALL [ PRIVILEGES ] } ...@@ -67,9 +67,10 @@ GRANT { { CREATE | USAGE } [,...] | ALL [ PRIVILEGES ] }
<para> <para>
If <literal>WITH GRANT OPTION</literal> is specified, the recipient If <literal>WITH GRANT OPTION</literal> is specified, the recipient
of the privilege may in turn grant it to others. By default this of the privilege may in turn grant it to others. Without a grant
is not allowed. Grant options can only be granted to individual option, the recipient cannot do that. At present, grant options can
users, not to groups or <literal>PUBLIC</literal>. only be granted to individual users, not to groups or
<literal>PUBLIC</literal>.
</para> </para>
<para> <para>
...@@ -79,8 +80,8 @@ GRANT { { CREATE | USAGE } [,...] | ALL [ PRIVILEGES ] } ...@@ -79,8 +80,8 @@ GRANT { { CREATE | USAGE } [,...] | ALL [ PRIVILEGES ] }
however, choose to revoke some of his own privileges for safety.) however, choose to revoke some of his own privileges for safety.)
The right to drop an object, or to alter its definition in any way is The right to drop an object, or to alter its definition in any way is
not described by a grantable privilege; it is inherent in the owner, not described by a grantable privilege; it is inherent in the owner,
and cannot be granted or revoked. It is not possible for the owner's and cannot be granted or revoked. The owner implicitly has all grant
grant options to be revoked, either. options for the object, too.
</para> </para>
<para> <para>
...@@ -150,7 +151,7 @@ GRANT { { CREATE | USAGE } [,...] | ALL [ PRIVILEGES ] } ...@@ -150,7 +151,7 @@ GRANT { { CREATE | USAGE } [,...] | ALL [ PRIVILEGES ] }
<term>RULE</term> <term>RULE</term>
<listitem> <listitem>
<para> <para>
Allows the creation of a rule on the table/view. (See <xref Allows the creation of a rule on the table/view. (See the <xref
linkend="sql-createrule" endterm="sql-createrule-title"> statement.) linkend="sql-createrule" endterm="sql-createrule-title"> statement.)
</para> </para>
</listitem> </listitem>
...@@ -171,7 +172,7 @@ GRANT { { CREATE | USAGE } [,...] | ALL [ PRIVILEGES ] } ...@@ -171,7 +172,7 @@ GRANT { { CREATE | USAGE } [,...] | ALL [ PRIVILEGES ] }
<term>TRIGGER</term> <term>TRIGGER</term>
<listitem> <listitem>
<para> <para>
Allows the creation of a trigger on the specified table. (See Allows the creation of a trigger on the specified table. (See the
<xref linkend="sql-createtrigger" endterm="sql-createtrigger-title"> statement.) <xref linkend="sql-createtrigger" endterm="sql-createtrigger-title"> statement.)
</para> </para>
</listitem> </listitem>
...@@ -234,7 +235,7 @@ GRANT { { CREATE | USAGE } [,...] | ALL [ PRIVILEGES ] } ...@@ -234,7 +235,7 @@ GRANT { { CREATE | USAGE } [,...] | ALL [ PRIVILEGES ] }
<term>ALL PRIVILEGES</term> <term>ALL PRIVILEGES</term>
<listitem> <listitem>
<para> <para>
Grant all of the privileges applicable to the object at once. Grant all of the available privileges at once.
The <literal>PRIVILEGES</literal> key word is optional in The <literal>PRIVILEGES</literal> key word is optional in
<productname>PostgreSQL</productname>, though it is required by <productname>PostgreSQL</productname>, though it is required by
strict SQL. strict SQL.
...@@ -257,6 +258,20 @@ GRANT { { CREATE | USAGE } [,...] | ALL [ PRIVILEGES ] } ...@@ -257,6 +258,20 @@ GRANT { { CREATE | USAGE } [,...] | ALL [ PRIVILEGES ] }
to revoke access privileges. to revoke access privileges.
</para> </para>
<para>
When a non-owner of an object attempts to <command>GRANT</> privileges
on the object, the command will fail outright if the user has no
privileges whatsoever on the object. As long as some privilege is
available, the command will proceed, but it will grant only those
privileges for which the user has grant options. The <command>GRANT ALL
PRIVILEGES</> forms will issue a warning message if no grant options are
held, while the other forms will issue a warning if grant options for
any of the privileges specifically named in the command are not held.
(In principle these statements apply to the object owner as well, but
since the owner is always treated as holding all grant options, the
cases can never occur.)
</para>
<para> <para>
It should be noted that database superusers can access It should be noted that database superusers can access
all objects regardless of object privilege settings. This all objects regardless of object privilege settings. This
...@@ -273,10 +288,10 @@ GRANT { { CREATE | USAGE } [,...] | ALL [ PRIVILEGES ] } ...@@ -273,10 +288,10 @@ GRANT { { CREATE | USAGE } [,...] | ALL [ PRIVILEGES ] }
</para> </para>
<para> <para>
Currently, to grant privileges in <productname>PostgreSQL</productname> Currently, <productname>PostgreSQL</productname> does not support
to only a few columns, you must granting or revoking privileges for individual columns of a table.
create a view having the desired columns and then grant privileges One possible workaround is to create a view having just the desired
to that view. columns and then grant privileges to that view.
</para> </para>
<para> <para>
...@@ -286,9 +301,9 @@ GRANT { { CREATE | USAGE } [,...] | ALL [ PRIVILEGES ] } ...@@ -286,9 +301,9 @@ GRANT { { CREATE | USAGE } [,...] | ALL [ PRIVILEGES ] }
=> \z mytable => \z mytable
Access privileges for database "lusitania" Access privileges for database "lusitania"
Schema | Name | Type | Access privileges Schema | Name | Type | Access privileges
--------+---------+-------+----------------------------------------------------------------- --------+---------+-------+------------------------------------------------------------
public | mytable | table | {=r/postgres,miriam=arwdRxt/postgres,"group todos=arw/postgres"} public | mytable | table | {miriam=arwdRxt/miriam,=r/miriam,"group todos=arw/miriam"}
(1 row) (1 row)
</programlisting> </programlisting>
The entries shown by <command>\z</command> are interpreted thus: The entries shown by <command>\z</command> are interpreted thus:
...@@ -331,7 +346,14 @@ and may include some privileges for <literal>PUBLIC</> depending on the ...@@ -331,7 +346,14 @@ and may include some privileges for <literal>PUBLIC</> depending on the
object type, as explained above. The first <command>GRANT</> or object type, as explained above. The first <command>GRANT</> or
<command>REVOKE</> on an object <command>REVOKE</> on an object
will instantiate the default privileges (producing, for example, will instantiate the default privileges (producing, for example,
<literal>{=,miriam=arwdRxt}</>) and then modify them per the specified request. <literal>{miriam=arwdRxt/miriam}</>) and then modify them per the
specified request.
</para>
<para>
Notice that the owner's implicit grant options are not marked in the
access privileges display. A <literal>*</> will appear only when
grant options have been explicitly granted to someone.
</para> </para>
</refsect1> </refsect1>
...@@ -347,11 +369,17 @@ GRANT INSERT ON films TO PUBLIC; ...@@ -347,11 +369,17 @@ GRANT INSERT ON films TO PUBLIC;
</para> </para>
<para> <para>
Grant all privileges to user <literal>manuel</literal> on view <literal>kinds</literal>: Grant all available privileges to user <literal>manuel</literal> on view
<literal>kinds</literal>:
<programlisting> <programlisting>
GRANT ALL PRIVILEGES ON kinds TO manuel; GRANT ALL PRIVILEGES ON kinds TO manuel;
</programlisting> </programlisting>
Note that while the above will indeed grant all privileges if executed by a
superuser or the owner of <literal>kinds</literal>, when executed by someone
else it will only grant those permissions for which the someone else has
grant options.
</para> </para>
</refsect1> </refsect1>
......
<!-- <!--
$PostgreSQL: pgsql/doc/src/sgml/ref/revoke.sgml,v 1.29 2003/11/29 19:51:39 pgsql Exp $ $PostgreSQL: pgsql/doc/src/sgml/ref/revoke.sgml,v 1.30 2004/06/01 21:49:21 tgl Exp $
PostgreSQL documentation PostgreSQL documentation
--> -->
...@@ -81,6 +81,7 @@ REVOKE [ GRANT OPTION FOR ] ...@@ -81,6 +81,7 @@ REVOKE [ GRANT OPTION FOR ]
<para> <para>
If <literal>GRANT OPTION FOR</literal> is specified, only the grant If <literal>GRANT OPTION FOR</literal> is specified, only the grant
option for the privilege is revoked, not the privilege itself. option for the privilege is revoked, not the privilege itself.
Otherwise, both the privilege and the grant option are revoked.
</para> </para>
<para> <para>
...@@ -103,7 +104,7 @@ REVOKE [ GRANT OPTION FOR ] ...@@ -103,7 +104,7 @@ REVOKE [ GRANT OPTION FOR ]
<para> <para>
Use <xref linkend="app-psql">'s <command>\z</command> command to Use <xref linkend="app-psql">'s <command>\z</command> command to
display the privileges granted on existing objects. See also <xref display the privileges granted on existing objects. See <xref
linkend="sql-grant" endterm="sql-grant-title"> for information about the format. linkend="sql-grant" endterm="sql-grant-title"> for information about the format.
</para> </para>
...@@ -114,9 +115,25 @@ REVOKE [ GRANT OPTION FOR ] ...@@ -114,9 +115,25 @@ REVOKE [ GRANT OPTION FOR ]
C, then user A cannot revoke the privilege directly from C. C, then user A cannot revoke the privilege directly from C.
Instead, user A could revoke the grant option from user B and use Instead, user A could revoke the grant option from user B and use
the <literal>CASCADE</literal> option so that the privilege is the <literal>CASCADE</literal> option so that the privilege is
automatically revoked from user C. in turn revoked from user C. For another example, if both A and B
have granted the same privilege to C, A can revoke his own grant
but not B's grant, so C will still effectively have the privilege.
</para> </para>
<para>
When a non-owner of an object attempts to <command>REVOKE</> privileges
on the object, the command will fail outright if the user has no
privileges whatsoever on the object. As long as some privilege is
available, the command will proceed, but it will revoke only those
privileges for which the user has grant options. The <command>REVOKE ALL
PRIVILEGES</> forms will issue a warning message if no grant options are
held, while the other forms will issue a warning if grant options for
any of the privileges specifically named in the command are not held.
(In principle these statements apply to the object owner as well, but
since the owner is always treated as holding all grant options, the
cases can never occur.)
</para>
<para> <para>
If a superuser chooses to issue a <command>GRANT</> or <command>REVOKE</> If a superuser chooses to issue a <command>GRANT</> or <command>REVOKE</>
command, the command is performed as though it were issued by the command, the command is performed as though it were issued by the
...@@ -140,11 +157,15 @@ REVOKE INSERT ON films FROM PUBLIC; ...@@ -140,11 +157,15 @@ REVOKE INSERT ON films FROM PUBLIC;
</para> </para>
<para> <para>
Revoke all privileges from user <literal>manuel</literal> on view <literal>kinds</literal>: Revoke all privileges from user <literal>manuel</literal> on view
<literal>kinds</literal>:
<programlisting> <programlisting>
REVOKE ALL PRIVILEGES ON kinds FROM manuel; REVOKE ALL PRIVILEGES ON kinds FROM manuel;
</programlisting> </programlisting>
Note that this actually means <quote>revoke all privileges that I
granted</>.
</para> </para>
</refsect1> </refsect1>
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
* *
* *
* IDENTIFICATION * IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/catalog/aclchk.c,v 1.102 2004/05/28 16:37:11 tgl Exp $ * $PostgreSQL: pgsql/src/backend/catalog/aclchk.c,v 1.103 2004/06/01 21:49:22 tgl Exp $
* *
* NOTES * NOTES
* See acl.h. * See acl.h.
...@@ -48,9 +48,6 @@ static void ExecuteGrantStmt_Namespace(GrantStmt *stmt); ...@@ -48,9 +48,6 @@ static void ExecuteGrantStmt_Namespace(GrantStmt *stmt);
static const char *privilege_to_string(AclMode privilege); static const char *privilege_to_string(AclMode privilege);
static AclMode aclmask(Acl *acl, AclId userid,
AclMode mask, AclMaskHow how);
#ifdef ACLDEBUG #ifdef ACLDEBUG
static static
...@@ -126,15 +123,12 @@ merge_acl_with_grant(Acl *old_acl, bool is_grant, ...@@ -126,15 +123,12 @@ merge_acl_with_grant(Acl *old_acl, bool is_grant,
AclItem aclitem; AclItem aclitem;
uint32 idtype; uint32 idtype;
Acl *newer_acl; Acl *newer_acl;
bool grantee_is_owner = false;
if (grantee->username) if (grantee->username)
{ {
aclitem.ai_grantee = get_usesysid(grantee->username); aclitem.ai_grantee = get_usesysid(grantee->username);
idtype = ACL_IDTYPE_UID; idtype = ACL_IDTYPE_UID;
grantee_is_owner = (aclitem.ai_grantee == owner_uid);
} }
else if (grantee->groupname) else if (grantee->groupname)
{ {
...@@ -161,19 +155,21 @@ merge_acl_with_grant(Acl *old_acl, bool is_grant, ...@@ -161,19 +155,21 @@ merge_acl_with_grant(Acl *old_acl, bool is_grant,
(errcode(ERRCODE_INVALID_GRANT_OPERATION), (errcode(ERRCODE_INVALID_GRANT_OPERATION),
errmsg("grant options can only be granted to individual users"))); errmsg("grant options can only be granted to individual users")));
if (!is_grant && grant_option && grantee_is_owner)
ereport(ERROR,
(errcode(ERRCODE_INVALID_GRANT_OPERATION),
errmsg("cannot revoke grant options from owner")));
aclitem.ai_grantor = grantor_uid; aclitem.ai_grantor = grantor_uid;
/*
* The asymmetry in the conditions here comes from the spec. In
* GRANT, the grant_option flag signals WITH GRANT OPTION, which means
* to grant both the basic privilege and its grant option. But in
* REVOKE, plain revoke revokes both the basic privilege and its
* grant option, while REVOKE GRANT OPTION revokes only the option.
*/
ACLITEM_SET_PRIVS_IDTYPE(aclitem, ACLITEM_SET_PRIVS_IDTYPE(aclitem,
(is_grant || !grant_option) ? privileges : ACL_NO_RIGHTS, (is_grant || !grant_option) ? privileges : ACL_NO_RIGHTS,
(grant_option || (!is_grant && !grantee_is_owner)) ? privileges : ACL_NO_RIGHTS, (!is_grant || grant_option) ? privileges : ACL_NO_RIGHTS,
idtype); idtype);
newer_acl = aclinsert3(new_acl, &aclitem, modechg, behavior); newer_acl = aclupdate(new_acl, &aclitem, modechg, owner_uid, behavior);
/* avoid memory leak when there are many grantees */ /* avoid memory leak when there are many grantees */
pfree(new_acl); pfree(new_acl);
...@@ -221,12 +217,17 @@ static void ...@@ -221,12 +217,17 @@ static void
ExecuteGrantStmt_Relation(GrantStmt *stmt) ExecuteGrantStmt_Relation(GrantStmt *stmt)
{ {
AclMode privileges; AclMode privileges;
bool all_privs;
ListCell *i; ListCell *i;
if (linitial_int(stmt->privileges) == ACL_ALL_RIGHTS) if (linitial_int(stmt->privileges) == ACL_ALL_RIGHTS)
{
all_privs = true;
privileges = ACL_ALL_RIGHTS_RELATION; privileges = ACL_ALL_RIGHTS_RELATION;
}
else else
{ {
all_privs = false;
privileges = ACL_NO_RIGHTS; privileges = ACL_NO_RIGHTS;
foreach(i, stmt->privileges) foreach(i, stmt->privileges)
{ {
...@@ -250,6 +251,8 @@ ExecuteGrantStmt_Relation(GrantStmt *stmt) ...@@ -250,6 +251,8 @@ ExecuteGrantStmt_Relation(GrantStmt *stmt)
Form_pg_class pg_class_tuple; Form_pg_class pg_class_tuple;
Datum aclDatum; Datum aclDatum;
bool isNull; bool isNull;
AclMode my_goptions;
AclMode this_privileges;
Acl *old_acl; Acl *old_acl;
Acl *new_acl; Acl *new_acl;
AclId grantorId; AclId grantorId;
...@@ -269,15 +272,6 @@ ExecuteGrantStmt_Relation(GrantStmt *stmt) ...@@ -269,15 +272,6 @@ ExecuteGrantStmt_Relation(GrantStmt *stmt)
elog(ERROR, "cache lookup failed for relation %u", relOid); elog(ERROR, "cache lookup failed for relation %u", relOid);
pg_class_tuple = (Form_pg_class) GETSTRUCT(tuple); pg_class_tuple = (Form_pg_class) GETSTRUCT(tuple);
ownerId = pg_class_tuple->relowner;
grantorId = select_grantor(ownerId);
if (stmt->is_grant
&& !pg_class_ownercheck(relOid, GetUserId())
&& pg_class_aclcheck(relOid, GetUserId(),
ACL_GRANT_OPTION_FOR(privileges)) != ACLCHECK_OK)
aclcheck_error(ACLCHECK_NO_PRIV, ACL_KIND_CLASS, relvar->relname);
/* Not sensible to grant on an index */ /* Not sensible to grant on an index */
if (pg_class_tuple->relkind == RELKIND_INDEX) if (pg_class_tuple->relkind == RELKIND_INDEX)
ereport(ERROR, ereport(ERROR,
...@@ -285,6 +279,69 @@ ExecuteGrantStmt_Relation(GrantStmt *stmt) ...@@ -285,6 +279,69 @@ ExecuteGrantStmt_Relation(GrantStmt *stmt)
errmsg("\"%s\" is an index", errmsg("\"%s\" is an index",
relvar->relname))); relvar->relname)));
/* Composite types aren't tables either */
if (pg_class_tuple->relkind == RELKIND_COMPOSITE_TYPE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is a composite type",
relvar->relname)));
ownerId = pg_class_tuple->relowner;
grantorId = select_grantor(ownerId);
/*
* Must be owner or have some privilege on the object (per spec,
* any privilege will get you by here). The owner is always
* treated as having all grant options.
*/
if (pg_class_ownercheck(relOid, GetUserId()))
my_goptions = ACL_ALL_RIGHTS_RELATION;
else
{
AclMode my_rights;
my_rights = pg_class_aclmask(relOid,
GetUserId(),
ACL_ALL_RIGHTS_RELATION | ACL_GRANT_OPTION_FOR(ACL_ALL_RIGHTS_RELATION),
ACLMASK_ALL);
if (my_rights == ACL_NO_RIGHTS)
aclcheck_error(ACLCHECK_NO_PRIV, ACL_KIND_CLASS,
relvar->relname);
my_goptions = ACL_OPTION_TO_PRIVS(my_rights);
}
/*
* Restrict the operation to what we can actually grant or revoke,
* and issue a warning if appropriate. (For REVOKE this isn't quite
* what the spec says to do: the spec seems to want a warning only
* if no privilege bits actually change in the ACL. In practice
* that behavior seems much too noisy, as well as inconsistent with
* the GRANT case.)
*/
this_privileges = privileges & my_goptions;
if (stmt->is_grant)
{
if (this_privileges == 0)
ereport(WARNING,
(errcode(ERRCODE_WARNING_PRIVILEGE_NOT_GRANTED),
errmsg("no privileges were granted")));
else if (!all_privs && this_privileges != privileges)
ereport(WARNING,
(errcode(ERRCODE_WARNING_PRIVILEGE_NOT_GRANTED),
errmsg("not all privileges were granted")));
}
else
{
if (this_privileges == 0)
ereport(WARNING,
(errcode(ERRCODE_WARNING_PRIVILEGE_NOT_REVOKED),
errmsg("no privileges could be revoked")));
else if (!all_privs && this_privileges != privileges)
ereport(WARNING,
(errcode(ERRCODE_WARNING_PRIVILEGE_NOT_REVOKED),
errmsg("not all privileges could be revoked")));
}
/* /*
* If there's no ACL, substitute the proper default. * If there's no ACL, substitute the proper default.
*/ */
...@@ -298,7 +355,7 @@ ExecuteGrantStmt_Relation(GrantStmt *stmt) ...@@ -298,7 +355,7 @@ ExecuteGrantStmt_Relation(GrantStmt *stmt)
new_acl = merge_acl_with_grant(old_acl, stmt->is_grant, new_acl = merge_acl_with_grant(old_acl, stmt->is_grant,
stmt->grant_option, stmt->behavior, stmt->grant_option, stmt->behavior,
stmt->grantees, privileges, stmt->grantees, this_privileges,
grantorId, ownerId); grantorId, ownerId);
/* finished building new ACL value, now insert it */ /* finished building new ACL value, now insert it */
...@@ -328,12 +385,17 @@ static void ...@@ -328,12 +385,17 @@ static void
ExecuteGrantStmt_Database(GrantStmt *stmt) ExecuteGrantStmt_Database(GrantStmt *stmt)
{ {
AclMode privileges; AclMode privileges;
bool all_privs;
ListCell *i; ListCell *i;
if (linitial_int(stmt->privileges) == ACL_ALL_RIGHTS) if (linitial_int(stmt->privileges) == ACL_ALL_RIGHTS)
{
all_privs = true;
privileges = ACL_ALL_RIGHTS_DATABASE; privileges = ACL_ALL_RIGHTS_DATABASE;
}
else else
{ {
all_privs = false;
privileges = ACL_NO_RIGHTS; privileges = ACL_NO_RIGHTS;
foreach(i, stmt->privileges) foreach(i, stmt->privileges)
{ {
...@@ -358,6 +420,8 @@ ExecuteGrantStmt_Database(GrantStmt *stmt) ...@@ -358,6 +420,8 @@ ExecuteGrantStmt_Database(GrantStmt *stmt)
Form_pg_database pg_database_tuple; Form_pg_database pg_database_tuple;
Datum aclDatum; Datum aclDatum;
bool isNull; bool isNull;
AclMode my_goptions;
AclMode this_privileges;
Acl *old_acl; Acl *old_acl;
Acl *new_acl; Acl *new_acl;
AclId grantorId; AclId grantorId;
...@@ -383,12 +447,58 @@ ExecuteGrantStmt_Database(GrantStmt *stmt) ...@@ -383,12 +447,58 @@ ExecuteGrantStmt_Database(GrantStmt *stmt)
ownerId = pg_database_tuple->datdba; ownerId = pg_database_tuple->datdba;
grantorId = select_grantor(ownerId); grantorId = select_grantor(ownerId);
if (stmt->is_grant /*
&& !pg_database_ownercheck(HeapTupleGetOid(tuple), GetUserId()) * Must be owner or have some privilege on the object (per spec,
&& pg_database_aclcheck(HeapTupleGetOid(tuple), GetUserId(), * any privilege will get you by here). The owner is always
ACL_GRANT_OPTION_FOR(privileges)) != ACLCHECK_OK) * treated as having all grant options.
aclcheck_error(ACLCHECK_NO_PRIV, ACL_KIND_DATABASE, */
NameStr(pg_database_tuple->datname)); if (pg_database_ownercheck(HeapTupleGetOid(tuple), GetUserId()))
my_goptions = ACL_ALL_RIGHTS_DATABASE;
else
{
AclMode my_rights;
my_rights = pg_database_aclmask(HeapTupleGetOid(tuple),
GetUserId(),
ACL_ALL_RIGHTS_DATABASE | ACL_GRANT_OPTION_FOR(ACL_ALL_RIGHTS_DATABASE),
ACLMASK_ALL);
if (my_rights == ACL_NO_RIGHTS)
aclcheck_error(ACLCHECK_NO_PRIV, ACL_KIND_DATABASE,
NameStr(pg_database_tuple->datname));
my_goptions = ACL_OPTION_TO_PRIVS(my_rights);
}
/*
* Restrict the operation to what we can actually grant or revoke,
* and issue a warning if appropriate. (For REVOKE this isn't quite
* what the spec says to do: the spec seems to want a warning only
* if no privilege bits actually change in the ACL. In practice
* that behavior seems much too noisy, as well as inconsistent with
* the GRANT case.)
*/
this_privileges = privileges & my_goptions;
if (stmt->is_grant)
{
if (this_privileges == 0)
ereport(WARNING,
(errcode(ERRCODE_WARNING_PRIVILEGE_NOT_GRANTED),
errmsg("no privileges were granted")));
else if (!all_privs && this_privileges != privileges)
ereport(WARNING,
(errcode(ERRCODE_WARNING_PRIVILEGE_NOT_GRANTED),
errmsg("not all privileges were granted")));
}
else
{
if (this_privileges == 0)
ereport(WARNING,
(errcode(ERRCODE_WARNING_PRIVILEGE_NOT_REVOKED),
errmsg("no privileges could be revoked")));
else if (!all_privs && this_privileges != privileges)
ereport(WARNING,
(errcode(ERRCODE_WARNING_PRIVILEGE_NOT_REVOKED),
errmsg("not all privileges could be revoked")));
}
/* /*
* If there's no ACL, substitute the proper default. * If there's no ACL, substitute the proper default.
...@@ -403,7 +513,7 @@ ExecuteGrantStmt_Database(GrantStmt *stmt) ...@@ -403,7 +513,7 @@ ExecuteGrantStmt_Database(GrantStmt *stmt)
new_acl = merge_acl_with_grant(old_acl, stmt->is_grant, new_acl = merge_acl_with_grant(old_acl, stmt->is_grant,
stmt->grant_option, stmt->behavior, stmt->grant_option, stmt->behavior,
stmt->grantees, privileges, stmt->grantees, this_privileges,
grantorId, ownerId); grantorId, ownerId);
/* finished building new ACL value, now insert it */ /* finished building new ACL value, now insert it */
...@@ -433,12 +543,17 @@ static void ...@@ -433,12 +543,17 @@ static void
ExecuteGrantStmt_Function(GrantStmt *stmt) ExecuteGrantStmt_Function(GrantStmt *stmt)
{ {
AclMode privileges; AclMode privileges;
bool all_privs;
ListCell *i; ListCell *i;
if (linitial_int(stmt->privileges) == ACL_ALL_RIGHTS) if (linitial_int(stmt->privileges) == ACL_ALL_RIGHTS)
{
all_privs = true;
privileges = ACL_ALL_RIGHTS_FUNCTION; privileges = ACL_ALL_RIGHTS_FUNCTION;
}
else else
{ {
all_privs = false;
privileges = ACL_NO_RIGHTS; privileges = ACL_NO_RIGHTS;
foreach(i, stmt->privileges) foreach(i, stmt->privileges)
{ {
...@@ -462,6 +577,8 @@ ExecuteGrantStmt_Function(GrantStmt *stmt) ...@@ -462,6 +577,8 @@ ExecuteGrantStmt_Function(GrantStmt *stmt)
Form_pg_proc pg_proc_tuple; Form_pg_proc pg_proc_tuple;
Datum aclDatum; Datum aclDatum;
bool isNull; bool isNull;
AclMode my_goptions;
AclMode this_privileges;
Acl *old_acl; Acl *old_acl;
Acl *new_acl; Acl *new_acl;
AclId grantorId; AclId grantorId;
...@@ -484,12 +601,58 @@ ExecuteGrantStmt_Function(GrantStmt *stmt) ...@@ -484,12 +601,58 @@ ExecuteGrantStmt_Function(GrantStmt *stmt)
ownerId = pg_proc_tuple->proowner; ownerId = pg_proc_tuple->proowner;
grantorId = select_grantor(ownerId); grantorId = select_grantor(ownerId);
if (stmt->is_grant /*
&& !pg_proc_ownercheck(oid, GetUserId()) * Must be owner or have some privilege on the object (per spec,
&& pg_proc_aclcheck(oid, GetUserId(), * any privilege will get you by here). The owner is always
ACL_GRANT_OPTION_FOR(privileges)) != ACLCHECK_OK) * treated as having all grant options.
aclcheck_error(ACLCHECK_NO_PRIV, ACL_KIND_PROC, */
NameStr(pg_proc_tuple->proname)); if (pg_proc_ownercheck(oid, GetUserId()))
my_goptions = ACL_ALL_RIGHTS_FUNCTION;
else
{
AclMode my_rights;
my_rights = pg_proc_aclmask(oid,
GetUserId(),
ACL_ALL_RIGHTS_FUNCTION | ACL_GRANT_OPTION_FOR(ACL_ALL_RIGHTS_FUNCTION),
ACLMASK_ALL);
if (my_rights == ACL_NO_RIGHTS)
aclcheck_error(ACLCHECK_NO_PRIV, ACL_KIND_PROC,
NameStr(pg_proc_tuple->proname));
my_goptions = ACL_OPTION_TO_PRIVS(my_rights);
}
/*
* Restrict the operation to what we can actually grant or revoke,
* and issue a warning if appropriate. (For REVOKE this isn't quite
* what the spec says to do: the spec seems to want a warning only
* if no privilege bits actually change in the ACL. In practice
* that behavior seems much too noisy, as well as inconsistent with
* the GRANT case.)
*/
this_privileges = privileges & my_goptions;
if (stmt->is_grant)
{
if (this_privileges == 0)
ereport(WARNING,
(errcode(ERRCODE_WARNING_PRIVILEGE_NOT_GRANTED),
errmsg("no privileges were granted")));
else if (!all_privs && this_privileges != privileges)
ereport(WARNING,
(errcode(ERRCODE_WARNING_PRIVILEGE_NOT_GRANTED),
errmsg("not all privileges were granted")));
}
else
{
if (this_privileges == 0)
ereport(WARNING,
(errcode(ERRCODE_WARNING_PRIVILEGE_NOT_REVOKED),
errmsg("no privileges could be revoked")));
else if (!all_privs && this_privileges != privileges)
ereport(WARNING,
(errcode(ERRCODE_WARNING_PRIVILEGE_NOT_REVOKED),
errmsg("not all privileges could be revoked")));
}
/* /*
* If there's no ACL, substitute the proper default. * If there's no ACL, substitute the proper default.
...@@ -504,7 +667,7 @@ ExecuteGrantStmt_Function(GrantStmt *stmt) ...@@ -504,7 +667,7 @@ ExecuteGrantStmt_Function(GrantStmt *stmt)
new_acl = merge_acl_with_grant(old_acl, stmt->is_grant, new_acl = merge_acl_with_grant(old_acl, stmt->is_grant,
stmt->grant_option, stmt->behavior, stmt->grant_option, stmt->behavior,
stmt->grantees, privileges, stmt->grantees, this_privileges,
grantorId, ownerId); grantorId, ownerId);
/* finished building new ACL value, now insert it */ /* finished building new ACL value, now insert it */
...@@ -534,12 +697,17 @@ static void ...@@ -534,12 +697,17 @@ static void
ExecuteGrantStmt_Language(GrantStmt *stmt) ExecuteGrantStmt_Language(GrantStmt *stmt)
{ {
AclMode privileges; AclMode privileges;
bool all_privs;
ListCell *i; ListCell *i;
if (linitial_int(stmt->privileges) == ACL_ALL_RIGHTS) if (linitial_int(stmt->privileges) == ACL_ALL_RIGHTS)
{
all_privs = true;
privileges = ACL_ALL_RIGHTS_LANGUAGE; privileges = ACL_ALL_RIGHTS_LANGUAGE;
}
else else
{ {
all_privs = false;
privileges = ACL_NO_RIGHTS; privileges = ACL_NO_RIGHTS;
foreach(i, stmt->privileges) foreach(i, stmt->privileges)
{ {
...@@ -562,6 +730,8 @@ ExecuteGrantStmt_Language(GrantStmt *stmt) ...@@ -562,6 +730,8 @@ ExecuteGrantStmt_Language(GrantStmt *stmt)
Form_pg_language pg_language_tuple; Form_pg_language pg_language_tuple;
Datum aclDatum; Datum aclDatum;
bool isNull; bool isNull;
AclMode my_goptions;
AclMode this_privileges;
Acl *old_acl; Acl *old_acl;
Acl *new_acl; Acl *new_acl;
AclId grantorId; AclId grantorId;
...@@ -581,6 +751,11 @@ ExecuteGrantStmt_Language(GrantStmt *stmt) ...@@ -581,6 +751,11 @@ ExecuteGrantStmt_Language(GrantStmt *stmt)
errmsg("language \"%s\" does not exist", langname))); errmsg("language \"%s\" does not exist", langname)));
pg_language_tuple = (Form_pg_language) GETSTRUCT(tuple); pg_language_tuple = (Form_pg_language) GETSTRUCT(tuple);
if (!pg_language_tuple->lanpltrusted)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("language \"%s\" is not trusted", langname)));
/* /*
* Note: for now, languages are treated as owned by the bootstrap * Note: for now, languages are treated as owned by the bootstrap
* user. We should add an owner column to pg_language instead. * user. We should add an owner column to pg_language instead.
...@@ -588,17 +763,58 @@ ExecuteGrantStmt_Language(GrantStmt *stmt) ...@@ -588,17 +763,58 @@ ExecuteGrantStmt_Language(GrantStmt *stmt)
ownerId = BOOTSTRAP_USESYSID; ownerId = BOOTSTRAP_USESYSID;
grantorId = select_grantor(ownerId); grantorId = select_grantor(ownerId);
if (stmt->is_grant /*
&& !superuser() /* XXX no ownercheck() available */ * Must be owner or have some privilege on the object (per spec,
&& pg_language_aclcheck(HeapTupleGetOid(tuple), GetUserId(), * any privilege will get you by here). The owner is always
ACL_GRANT_OPTION_FOR(privileges)) != ACLCHECK_OK) * treated as having all grant options.
aclcheck_error(ACLCHECK_NO_PRIV, ACL_KIND_LANGUAGE, */
NameStr(pg_language_tuple->lanname)); if (superuser()) /* XXX no ownercheck() available */
my_goptions = ACL_ALL_RIGHTS_LANGUAGE;
else
{
AclMode my_rights;
my_rights = pg_language_aclmask(HeapTupleGetOid(tuple),
GetUserId(),
ACL_ALL_RIGHTS_LANGUAGE | ACL_GRANT_OPTION_FOR(ACL_ALL_RIGHTS_LANGUAGE),
ACLMASK_ALL);
if (my_rights == ACL_NO_RIGHTS)
aclcheck_error(ACLCHECK_NO_PRIV, ACL_KIND_LANGUAGE,
NameStr(pg_language_tuple->lanname));
my_goptions = ACL_OPTION_TO_PRIVS(my_rights);
}
if (!pg_language_tuple->lanpltrusted) /*
ereport(ERROR, * Restrict the operation to what we can actually grant or revoke,
(errcode(ERRCODE_WRONG_OBJECT_TYPE), * and issue a warning if appropriate. (For REVOKE this isn't quite
errmsg("language \"%s\" is not trusted", langname))); * what the spec says to do: the spec seems to want a warning only
* if no privilege bits actually change in the ACL. In practice
* that behavior seems much too noisy, as well as inconsistent with
* the GRANT case.)
*/
this_privileges = privileges & my_goptions;
if (stmt->is_grant)
{
if (this_privileges == 0)
ereport(WARNING,
(errcode(ERRCODE_WARNING_PRIVILEGE_NOT_GRANTED),
errmsg("no privileges were granted")));
else if (!all_privs && this_privileges != privileges)
ereport(WARNING,
(errcode(ERRCODE_WARNING_PRIVILEGE_NOT_GRANTED),
errmsg("not all privileges were granted")));
}
else
{
if (this_privileges == 0)
ereport(WARNING,
(errcode(ERRCODE_WARNING_PRIVILEGE_NOT_REVOKED),
errmsg("no privileges could be revoked")));
else if (!all_privs && this_privileges != privileges)
ereport(WARNING,
(errcode(ERRCODE_WARNING_PRIVILEGE_NOT_REVOKED),
errmsg("not all privileges could be revoked")));
}
/* /*
* If there's no ACL, substitute the proper default. * If there's no ACL, substitute the proper default.
...@@ -613,7 +829,7 @@ ExecuteGrantStmt_Language(GrantStmt *stmt) ...@@ -613,7 +829,7 @@ ExecuteGrantStmt_Language(GrantStmt *stmt)
new_acl = merge_acl_with_grant(old_acl, stmt->is_grant, new_acl = merge_acl_with_grant(old_acl, stmt->is_grant,
stmt->grant_option, stmt->behavior, stmt->grant_option, stmt->behavior,
stmt->grantees, privileges, stmt->grantees, this_privileges,
grantorId, ownerId); grantorId, ownerId);
/* finished building new ACL value, now insert it */ /* finished building new ACL value, now insert it */
...@@ -643,12 +859,17 @@ static void ...@@ -643,12 +859,17 @@ static void
ExecuteGrantStmt_Namespace(GrantStmt *stmt) ExecuteGrantStmt_Namespace(GrantStmt *stmt)
{ {
AclMode privileges; AclMode privileges;
bool all_privs;
ListCell *i; ListCell *i;
if (linitial_int(stmt->privileges) == ACL_ALL_RIGHTS) if (linitial_int(stmt->privileges) == ACL_ALL_RIGHTS)
{
all_privs = true;
privileges = ACL_ALL_RIGHTS_NAMESPACE; privileges = ACL_ALL_RIGHTS_NAMESPACE;
}
else else
{ {
all_privs = false;
privileges = ACL_NO_RIGHTS; privileges = ACL_NO_RIGHTS;
foreach(i, stmt->privileges) foreach(i, stmt->privileges)
{ {
...@@ -671,6 +892,8 @@ ExecuteGrantStmt_Namespace(GrantStmt *stmt) ...@@ -671,6 +892,8 @@ ExecuteGrantStmt_Namespace(GrantStmt *stmt)
Form_pg_namespace pg_namespace_tuple; Form_pg_namespace pg_namespace_tuple;
Datum aclDatum; Datum aclDatum;
bool isNull; bool isNull;
AclMode my_goptions;
AclMode this_privileges;
Acl *old_acl; Acl *old_acl;
Acl *new_acl; Acl *new_acl;
AclId grantorId; AclId grantorId;
...@@ -693,12 +916,58 @@ ExecuteGrantStmt_Namespace(GrantStmt *stmt) ...@@ -693,12 +916,58 @@ ExecuteGrantStmt_Namespace(GrantStmt *stmt)
ownerId = pg_namespace_tuple->nspowner; ownerId = pg_namespace_tuple->nspowner;
grantorId = select_grantor(ownerId); grantorId = select_grantor(ownerId);
if (stmt->is_grant /*
&& !pg_namespace_ownercheck(HeapTupleGetOid(tuple), GetUserId()) * Must be owner or have some privilege on the object (per spec,
&& pg_namespace_aclcheck(HeapTupleGetOid(tuple), GetUserId(), * any privilege will get you by here). The owner is always
ACL_GRANT_OPTION_FOR(privileges)) != ACLCHECK_OK) * treated as having all grant options.
aclcheck_error(ACLCHECK_NO_PRIV, ACL_KIND_NAMESPACE, */
nspname); if (pg_namespace_ownercheck(HeapTupleGetOid(tuple), GetUserId()))
my_goptions = ACL_ALL_RIGHTS_NAMESPACE;
else
{
AclMode my_rights;
my_rights = pg_namespace_aclmask(HeapTupleGetOid(tuple),
GetUserId(),
ACL_ALL_RIGHTS_NAMESPACE | ACL_GRANT_OPTION_FOR(ACL_ALL_RIGHTS_NAMESPACE),
ACLMASK_ALL);
if (my_rights == ACL_NO_RIGHTS)
aclcheck_error(ACLCHECK_NO_PRIV, ACL_KIND_NAMESPACE,
nspname);
my_goptions = ACL_OPTION_TO_PRIVS(my_rights);
}
/*
* Restrict the operation to what we can actually grant or revoke,
* and issue a warning if appropriate. (For REVOKE this isn't quite
* what the spec says to do: the spec seems to want a warning only
* if no privilege bits actually change in the ACL. In practice
* that behavior seems much too noisy, as well as inconsistent with
* the GRANT case.)
*/
this_privileges = privileges & my_goptions;
if (stmt->is_grant)
{
if (this_privileges == 0)
ereport(WARNING,
(errcode(ERRCODE_WARNING_PRIVILEGE_NOT_GRANTED),
errmsg("no privileges were granted")));
else if (!all_privs && this_privileges != privileges)
ereport(WARNING,
(errcode(ERRCODE_WARNING_PRIVILEGE_NOT_GRANTED),
errmsg("not all privileges were granted")));
}
else
{
if (this_privileges == 0)
ereport(WARNING,
(errcode(ERRCODE_WARNING_PRIVILEGE_NOT_REVOKED),
errmsg("no privileges could be revoked")));
else if (!all_privs && this_privileges != privileges)
ereport(WARNING,
(errcode(ERRCODE_WARNING_PRIVILEGE_NOT_REVOKED),
errmsg("not all privileges could be revoked")));
}
/* /*
* If there's no ACL, substitute the proper default. * If there's no ACL, substitute the proper default.
...@@ -714,7 +983,7 @@ ExecuteGrantStmt_Namespace(GrantStmt *stmt) ...@@ -714,7 +983,7 @@ ExecuteGrantStmt_Namespace(GrantStmt *stmt)
new_acl = merge_acl_with_grant(old_acl, stmt->is_grant, new_acl = merge_acl_with_grant(old_acl, stmt->is_grant,
stmt->grant_option, stmt->behavior, stmt->grant_option, stmt->behavior,
stmt->grantees, privileges, stmt->grantees, this_privileges,
grantorId, ownerId); grantorId, ownerId);
/* finished building new ACL value, now insert it */ /* finished building new ACL value, now insert it */
...@@ -816,147 +1085,6 @@ get_groname(AclId grosysid) ...@@ -816,147 +1085,6 @@ get_groname(AclId grosysid)
return name; return name;
} }
/*
* Is user a member of group?
*/
static bool
in_group(AclId uid, AclId gid)
{
bool result = false;
HeapTuple tuple;
Datum att;
bool isNull;
IdList *glist;
AclId *aidp;
int i,
num;
tuple = SearchSysCache(GROSYSID,
ObjectIdGetDatum(gid),
0, 0, 0);
if (HeapTupleIsValid(tuple))
{
att = SysCacheGetAttr(GROSYSID,
tuple,
Anum_pg_group_grolist,
&isNull);
if (!isNull)
{
/* be sure the IdList is not toasted */
glist = DatumGetIdListP(att);
/* scan it */
num = IDLIST_NUM(glist);
aidp = IDLIST_DAT(glist);
for (i = 0; i < num; ++i)
{
if (aidp[i] == uid)
{
result = true;
break;
}
}
/* if IdList was toasted, free detoasted copy */
if ((Pointer) glist != DatumGetPointer(att))
pfree(glist);
}
ReleaseSysCache(tuple);
}
else
ereport(WARNING,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("group with ID %u does not exist", gid)));
return result;
}
/*
* aclmask --- compute bitmask of all privileges held by userid.
*
* When 'how' = ACLMASK_ALL, this simply returns the privilege bits
* held by the given userid according to the given ACL list, ANDed
* with 'mask'. (The point of passing 'mask' is to let the routine
* exit early if all privileges of interest have been found.)
*
* When 'how' = ACLMASK_ANY, returns as soon as any bit in the mask
* is known true. (This lets us exit soonest in cases where the
* caller is only going to test for zero or nonzero result.)
*
* Usage patterns:
*
* To see if any of a set of privileges are held:
* if (aclmask(acl, userid, privs, ACLMASK_ANY) != 0)
*
* To see if all of a set of privileges are held:
* if (aclmask(acl, userid, privs, ACLMASK_ALL) == privs)
*
* To determine exactly which of a set of privileges are held:
* heldprivs = aclmask(acl, userid, privs, ACLMASK_ALL);
*/
static AclMode
aclmask(Acl *acl, AclId userid, AclMode mask, AclMaskHow how)
{
AclMode result;
AclMode remaining;
AclItem *aidat;
int i,
num;
/*
* Null ACL should not happen, since caller should have inserted
* appropriate default
*/
if (acl == NULL)
elog(ERROR, "null ACL");
/* Quick exit for mask == 0 */
if (mask == 0)
return 0;
num = ACL_NUM(acl);
aidat = ACL_DAT(acl);
result = 0;
/*
* Check privileges granted directly to user or to public
*/
for (i = 0; i < num; i++)
{
AclItem *aidata = &aidat[i];
if (ACLITEM_GET_IDTYPE(*aidata) == ACL_IDTYPE_WORLD
|| (ACLITEM_GET_IDTYPE(*aidata) == ACL_IDTYPE_UID
&& aidata->ai_grantee == userid))
{
result |= (aidata->ai_privs & mask);
if ((how == ACLMASK_ALL) ? (result == mask) : (result != 0))
return result;
}
}
/*
* Check privileges granted via groups. We do this in a separate
* pass to minimize expensive lookups in pg_group.
*/
remaining = (mask & ~result);
for (i = 0; i < num; i++)
{
AclItem *aidata = &aidat[i];
if (ACLITEM_GET_IDTYPE(*aidata) == ACL_IDTYPE_GID
&& (aidata->ai_privs & remaining)
&& in_group(userid, aidata->ai_grantee))
{
result |= (aidata->ai_privs & mask);
if ((how == ACLMASK_ALL) ? (result == mask) : (result != 0))
return result;
remaining = (mask & ~result);
}
}
return result;
}
/* /*
* Standardized reporting of aclcheck permissions failures. * Standardized reporting of aclcheck permissions failures.
...@@ -1058,6 +1186,7 @@ pg_class_aclmask(Oid table_oid, AclId userid, ...@@ -1058,6 +1186,7 @@ pg_class_aclmask(Oid table_oid, AclId userid,
Datum aclDatum; Datum aclDatum;
bool isNull; bool isNull;
Acl *acl; Acl *acl;
AclId ownerId;
/* /*
* Validate userid, find out if he is superuser, also get usecatupd * Validate userid, find out if he is superuser, also get usecatupd
...@@ -1125,13 +1254,13 @@ pg_class_aclmask(Oid table_oid, AclId userid, ...@@ -1125,13 +1254,13 @@ pg_class_aclmask(Oid table_oid, AclId userid,
/* /*
* Normal case: get the relation's ACL from pg_class * Normal case: get the relation's ACL from pg_class
*/ */
ownerId = classForm->relowner;
aclDatum = SysCacheGetAttr(RELOID, tuple, Anum_pg_class_relacl, aclDatum = SysCacheGetAttr(RELOID, tuple, Anum_pg_class_relacl,
&isNull); &isNull);
if (isNull) if (isNull)
{ {
/* No ACL, so build default ACL */ /* No ACL, so build default ACL */
AclId ownerId = classForm->relowner;
acl = acldefault(ACL_OBJECT_RELATION, ownerId); acl = acldefault(ACL_OBJECT_RELATION, ownerId);
aclDatum = (Datum) 0; aclDatum = (Datum) 0;
} }
...@@ -1141,7 +1270,7 @@ pg_class_aclmask(Oid table_oid, AclId userid, ...@@ -1141,7 +1270,7 @@ pg_class_aclmask(Oid table_oid, AclId userid,
acl = DatumGetAclP(aclDatum); acl = DatumGetAclP(aclDatum);
} }
result = aclmask(acl, userid, mask, how); result = aclmask(acl, userid, ownerId, mask, how);
/* if we have a detoasted copy, free it */ /* if we have a detoasted copy, free it */
if (acl && (Pointer) acl != DatumGetPointer(aclDatum)) if (acl && (Pointer) acl != DatumGetPointer(aclDatum))
...@@ -1167,6 +1296,7 @@ pg_database_aclmask(Oid db_oid, AclId userid, ...@@ -1167,6 +1296,7 @@ pg_database_aclmask(Oid db_oid, AclId userid,
Datum aclDatum; Datum aclDatum;
bool isNull; bool isNull;
Acl *acl; Acl *acl;
AclId ownerId;
/* Superusers bypass all permission checking. */ /* Superusers bypass all permission checking. */
if (superuser_arg(userid)) if (superuser_arg(userid))
...@@ -1189,15 +1319,14 @@ pg_database_aclmask(Oid db_oid, AclId userid, ...@@ -1189,15 +1319,14 @@ pg_database_aclmask(Oid db_oid, AclId userid,
(errcode(ERRCODE_UNDEFINED_DATABASE), (errcode(ERRCODE_UNDEFINED_DATABASE),
errmsg("database with OID %u does not exist", db_oid))); errmsg("database with OID %u does not exist", db_oid)));
ownerId = ((Form_pg_database) GETSTRUCT(tuple))->datdba;
aclDatum = heap_getattr(tuple, Anum_pg_database_datacl, aclDatum = heap_getattr(tuple, Anum_pg_database_datacl,
RelationGetDescr(pg_database), &isNull); RelationGetDescr(pg_database), &isNull);
if (isNull) if (isNull)
{ {
/* No ACL, so build default ACL */ /* No ACL, so build default ACL */
AclId ownerId;
ownerId = ((Form_pg_database) GETSTRUCT(tuple))->datdba;
acl = acldefault(ACL_OBJECT_DATABASE, ownerId); acl = acldefault(ACL_OBJECT_DATABASE, ownerId);
aclDatum = (Datum) 0; aclDatum = (Datum) 0;
} }
...@@ -1207,7 +1336,7 @@ pg_database_aclmask(Oid db_oid, AclId userid, ...@@ -1207,7 +1336,7 @@ pg_database_aclmask(Oid db_oid, AclId userid,
acl = DatumGetAclP(aclDatum); acl = DatumGetAclP(aclDatum);
} }
result = aclmask(acl, userid, mask, how); result = aclmask(acl, userid, ownerId, mask, how);
/* if we have a detoasted copy, free it */ /* if we have a detoasted copy, free it */
if (acl && (Pointer) acl != DatumGetPointer(aclDatum)) if (acl && (Pointer) acl != DatumGetPointer(aclDatum))
...@@ -1231,6 +1360,7 @@ pg_proc_aclmask(Oid proc_oid, AclId userid, ...@@ -1231,6 +1360,7 @@ pg_proc_aclmask(Oid proc_oid, AclId userid,
Datum aclDatum; Datum aclDatum;
bool isNull; bool isNull;
Acl *acl; Acl *acl;
AclId ownerId;
/* Superusers bypass all permission checking. */ /* Superusers bypass all permission checking. */
if (superuser_arg(userid)) if (superuser_arg(userid))
...@@ -1247,14 +1377,13 @@ pg_proc_aclmask(Oid proc_oid, AclId userid, ...@@ -1247,14 +1377,13 @@ pg_proc_aclmask(Oid proc_oid, AclId userid,
(errcode(ERRCODE_UNDEFINED_FUNCTION), (errcode(ERRCODE_UNDEFINED_FUNCTION),
errmsg("function with OID %u does not exist", proc_oid))); errmsg("function with OID %u does not exist", proc_oid)));
ownerId = ((Form_pg_proc) GETSTRUCT(tuple))->proowner;
aclDatum = SysCacheGetAttr(PROCOID, tuple, Anum_pg_proc_proacl, aclDatum = SysCacheGetAttr(PROCOID, tuple, Anum_pg_proc_proacl,
&isNull); &isNull);
if (isNull) if (isNull)
{ {
/* No ACL, so build default ACL */ /* No ACL, so build default ACL */
AclId ownerId;
ownerId = ((Form_pg_proc) GETSTRUCT(tuple))->proowner;
acl = acldefault(ACL_OBJECT_FUNCTION, ownerId); acl = acldefault(ACL_OBJECT_FUNCTION, ownerId);
aclDatum = (Datum) 0; aclDatum = (Datum) 0;
} }
...@@ -1264,7 +1393,7 @@ pg_proc_aclmask(Oid proc_oid, AclId userid, ...@@ -1264,7 +1393,7 @@ pg_proc_aclmask(Oid proc_oid, AclId userid,
acl = DatumGetAclP(aclDatum); acl = DatumGetAclP(aclDatum);
} }
result = aclmask(acl, userid, mask, how); result = aclmask(acl, userid, ownerId, mask, how);
/* if we have a detoasted copy, free it */ /* if we have a detoasted copy, free it */
if (acl && (Pointer) acl != DatumGetPointer(aclDatum)) if (acl && (Pointer) acl != DatumGetPointer(aclDatum))
...@@ -1287,6 +1416,7 @@ pg_language_aclmask(Oid lang_oid, AclId userid, ...@@ -1287,6 +1416,7 @@ pg_language_aclmask(Oid lang_oid, AclId userid,
Datum aclDatum; Datum aclDatum;
bool isNull; bool isNull;
Acl *acl; Acl *acl;
AclId ownerId;
/* Superusers bypass all permission checking. */ /* Superusers bypass all permission checking. */
if (superuser_arg(userid)) if (superuser_arg(userid))
...@@ -1303,13 +1433,15 @@ pg_language_aclmask(Oid lang_oid, AclId userid, ...@@ -1303,13 +1433,15 @@ pg_language_aclmask(Oid lang_oid, AclId userid,
(errcode(ERRCODE_UNDEFINED_OBJECT), (errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("language with OID %u does not exist", lang_oid))); errmsg("language with OID %u does not exist", lang_oid)));
/* XXX pg_language should have an owner column, but doesn't */
ownerId = BOOTSTRAP_USESYSID;
aclDatum = SysCacheGetAttr(LANGOID, tuple, Anum_pg_language_lanacl, aclDatum = SysCacheGetAttr(LANGOID, tuple, Anum_pg_language_lanacl,
&isNull); &isNull);
if (isNull) if (isNull)
{ {
/* No ACL, so build default ACL */ /* No ACL, so build default ACL */
/* XXX pg_language should have an owner column, but doesn't */ acl = acldefault(ACL_OBJECT_LANGUAGE, ownerId);
acl = acldefault(ACL_OBJECT_LANGUAGE, BOOTSTRAP_USESYSID);
aclDatum = (Datum) 0; aclDatum = (Datum) 0;
} }
else else
...@@ -1318,7 +1450,7 @@ pg_language_aclmask(Oid lang_oid, AclId userid, ...@@ -1318,7 +1450,7 @@ pg_language_aclmask(Oid lang_oid, AclId userid,
acl = DatumGetAclP(aclDatum); acl = DatumGetAclP(aclDatum);
} }
result = aclmask(acl, userid, mask, how); result = aclmask(acl, userid, ownerId, mask, how);
/* if we have a detoasted copy, free it */ /* if we have a detoasted copy, free it */
if (acl && (Pointer) acl != DatumGetPointer(aclDatum)) if (acl && (Pointer) acl != DatumGetPointer(aclDatum))
...@@ -1341,6 +1473,7 @@ pg_namespace_aclmask(Oid nsp_oid, AclId userid, ...@@ -1341,6 +1473,7 @@ pg_namespace_aclmask(Oid nsp_oid, AclId userid,
Datum aclDatum; Datum aclDatum;
bool isNull; bool isNull;
Acl *acl; Acl *acl;
AclId ownerId;
/* Superusers bypass all permission checking. */ /* Superusers bypass all permission checking. */
if (superuser_arg(userid)) if (superuser_arg(userid))
...@@ -1385,14 +1518,13 @@ pg_namespace_aclmask(Oid nsp_oid, AclId userid, ...@@ -1385,14 +1518,13 @@ pg_namespace_aclmask(Oid nsp_oid, AclId userid,
(errcode(ERRCODE_UNDEFINED_SCHEMA), (errcode(ERRCODE_UNDEFINED_SCHEMA),
errmsg("schema with OID %u does not exist", nsp_oid))); errmsg("schema with OID %u does not exist", nsp_oid)));
ownerId = ((Form_pg_namespace) GETSTRUCT(tuple))->nspowner;
aclDatum = SysCacheGetAttr(NAMESPACEOID, tuple, Anum_pg_namespace_nspacl, aclDatum = SysCacheGetAttr(NAMESPACEOID, tuple, Anum_pg_namespace_nspacl,
&isNull); &isNull);
if (isNull) if (isNull)
{ {
/* No ACL, so build default ACL */ /* No ACL, so build default ACL */
AclId ownerId;
ownerId = ((Form_pg_namespace) GETSTRUCT(tuple))->nspowner;
acl = acldefault(ACL_OBJECT_NAMESPACE, ownerId); acl = acldefault(ACL_OBJECT_NAMESPACE, ownerId);
aclDatum = (Datum) 0; aclDatum = (Datum) 0;
} }
...@@ -1402,7 +1534,7 @@ pg_namespace_aclmask(Oid nsp_oid, AclId userid, ...@@ -1402,7 +1534,7 @@ pg_namespace_aclmask(Oid nsp_oid, AclId userid,
acl = DatumGetAclP(aclDatum); acl = DatumGetAclP(aclDatum);
} }
result = aclmask(acl, userid, mask, how); result = aclmask(acl, userid, ownerId, mask, how);
/* if we have a detoasted copy, free it */ /* if we have a detoasted copy, free it */
if (acl && (Pointer) acl != DatumGetPointer(aclDatum)) if (acl && (Pointer) acl != DatumGetPointer(aclDatum))
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
* *
* *
* IDENTIFICATION * IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/utils/adt/acl.c,v 1.104 2004/05/07 00:24:57 tgl Exp $ * $PostgreSQL: pgsql/src/backend/utils/adt/acl.c,v 1.105 2004/06/01 21:49:22 tgl Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
#include <ctype.h> #include <ctype.h>
#include "catalog/namespace.h" #include "catalog/namespace.h"
#include "catalog/pg_group.h"
#include "catalog/pg_shadow.h" #include "catalog/pg_shadow.h"
#include "catalog/pg_type.h" #include "catalog/pg_type.h"
#include "commands/dbcommands.h" #include "commands/dbcommands.h"
...@@ -35,8 +36,11 @@ static void putid(char *p, const char *s); ...@@ -35,8 +36,11 @@ static void putid(char *p, const char *s);
static Acl *allocacl(int n); static Acl *allocacl(int n);
static const char *aclparse(const char *s, AclItem *aip); static const char *aclparse(const char *s, AclItem *aip);
static bool aclitem_match(const AclItem *a1, const AclItem *a2); static bool aclitem_match(const AclItem *a1, const AclItem *a2);
static Acl *recursive_revoke(Acl *acl, AclId grantee, static void check_circularity(const Acl *old_acl, const AclItem *mod_aip,
AclMode revoke_privs, DropBehavior behavior); AclId ownerid);
static Acl *recursive_revoke(Acl *acl, AclId grantee, AclMode revoke_privs,
AclId ownerid, DropBehavior behavior);
static bool in_group(AclId uid, AclId gid);
static AclMode convert_priv_string(text *priv_type_text); static AclMode convert_priv_string(text *priv_type_text);
...@@ -554,10 +558,19 @@ acldefault(GrantObjectType objtype, AclId ownerid) ...@@ -554,10 +558,19 @@ acldefault(GrantObjectType objtype, AclId ownerid)
aip++; aip++;
} }
/*
* Note that the owner's entry shows all ordinary privileges but no
* grant options. This is because his grant options come "from the
* system" and not from his own efforts. (The SQL spec says that
* the owner's rights come from a "_SYSTEM" authid.) However, we do
* consider that the owner's ordinary privileges are self-granted;
* this lets him revoke them. We implement the owner's grant options
* without any explicit "_SYSTEM"-like ACL entry, by internally
* special-casing the owner whereever we are testing grant options.
*/
aip->ai_grantee = ownerid; aip->ai_grantee = ownerid;
aip->ai_grantor = ownerid; aip->ai_grantor = ownerid;
/* owner gets default privileges with grant option */ ACLITEM_SET_PRIVS_IDTYPE(*aip, owner_default, ACL_NO_RIGHTS,
ACLITEM_SET_PRIVS_IDTYPE(*aip, owner_default, owner_default,
ACL_IDTYPE_UID); ACL_IDTYPE_UID);
return acl; return acl;
...@@ -565,21 +578,31 @@ acldefault(GrantObjectType objtype, AclId ownerid) ...@@ -565,21 +578,31 @@ acldefault(GrantObjectType objtype, AclId ownerid)
/* /*
* Add or replace an item in an ACL array. The result is a modified copy; * Update an ACL array to add or remove specified privileges.
* the input object is not changed. *
* old_acl: the input ACL array
* mod_aip: defines the privileges to be added, removed, or substituted
* modechg: ACL_MODECHG_ADD, ACL_MODECHG_DEL, or ACL_MODECHG_EQL
* ownerid: AclId of object owner
* behavior: RESTRICT or CASCADE behavior for recursive removal
*
* ownerid and behavior are only relevant when the update operation specifies
* deletion of grant options.
*
* The result is a modified copy; the input object is not changed.
* *
* NB: caller is responsible for having detoasted the input ACL, if needed. * NB: caller is responsible for having detoasted the input ACL, if needed.
*/ */
Acl * Acl *
aclinsert3(const Acl *old_acl, const AclItem *mod_aip, aclupdate(const Acl *old_acl, const AclItem *mod_aip,
unsigned modechg, DropBehavior behavior) int modechg, AclId ownerid, DropBehavior behavior)
{ {
Acl *new_acl = NULL; Acl *new_acl = NULL;
AclItem *old_aip, AclItem *old_aip,
*new_aip = NULL; *new_aip = NULL;
AclMode old_privs, AclMode old_rights,
old_goptions, old_goptions,
new_privs, new_rights,
new_goptions; new_goptions;
int dst, int dst,
num; num;
...@@ -590,10 +613,15 @@ aclinsert3(const Acl *old_acl, const AclItem *mod_aip, ...@@ -590,10 +613,15 @@ aclinsert3(const Acl *old_acl, const AclItem *mod_aip,
if (!mod_aip) if (!mod_aip)
{ {
new_acl = allocacl(ACL_NUM(old_acl)); new_acl = allocacl(ACL_NUM(old_acl));
memcpy((char *) new_acl, (char *) old_acl, ACL_SIZE(old_acl)); memcpy(new_acl, old_acl, ACL_SIZE(old_acl));
return new_acl; return new_acl;
} }
/* If granting grant options, check for circularity */
if (modechg != ACL_MODECHG_DEL &&
ACLITEM_GET_GOPTIONS(*mod_aip) != ACL_NO_RIGHTS)
check_circularity(old_acl, mod_aip, ownerid);
num = ACL_NUM(old_acl); num = ACL_NUM(old_acl);
old_aip = ACL_DAT(old_acl); old_aip = ACL_DAT(old_acl);
...@@ -626,44 +654,39 @@ aclinsert3(const Acl *old_acl, const AclItem *mod_aip, ...@@ -626,44 +654,39 @@ aclinsert3(const Acl *old_acl, const AclItem *mod_aip,
/* initialize the new entry with no permissions */ /* initialize the new entry with no permissions */
new_aip[dst].ai_grantee = mod_aip->ai_grantee; new_aip[dst].ai_grantee = mod_aip->ai_grantee;
new_aip[dst].ai_grantor = mod_aip->ai_grantor; new_aip[dst].ai_grantor = mod_aip->ai_grantor;
ACLITEM_SET_PRIVS_IDTYPE(new_aip[dst], ACL_NO_RIGHTS, ACL_NO_RIGHTS, ACLITEM_SET_PRIVS_IDTYPE(new_aip[dst],
ACL_NO_RIGHTS, ACL_NO_RIGHTS,
ACLITEM_GET_IDTYPE(*mod_aip)); ACLITEM_GET_IDTYPE(*mod_aip));
num++; /* set num to the size of new_acl */ num++; /* set num to the size of new_acl */
} }
old_privs = ACLITEM_GET_PRIVS(new_aip[dst]); old_rights = ACLITEM_GET_RIGHTS(new_aip[dst]);
old_goptions = ACLITEM_GET_GOPTIONS(new_aip[dst]); old_goptions = ACLITEM_GET_GOPTIONS(new_aip[dst]);
/* apply the specified permissions change */ /* apply the specified permissions change */
switch (modechg) switch (modechg)
{ {
case ACL_MODECHG_ADD: case ACL_MODECHG_ADD:
ACLITEM_SET_PRIVS(new_aip[dst], ACLITEM_SET_RIGHTS(new_aip[dst],
old_privs | ACLITEM_GET_PRIVS(*mod_aip)); old_rights | ACLITEM_GET_RIGHTS(*mod_aip));
ACLITEM_SET_GOPTIONS(new_aip[dst],
old_goptions | ACLITEM_GET_GOPTIONS(*mod_aip));
break; break;
case ACL_MODECHG_DEL: case ACL_MODECHG_DEL:
ACLITEM_SET_PRIVS(new_aip[dst], ACLITEM_SET_RIGHTS(new_aip[dst],
old_privs & ~ACLITEM_GET_PRIVS(*mod_aip)); old_rights & ~ACLITEM_GET_RIGHTS(*mod_aip));
ACLITEM_SET_GOPTIONS(new_aip[dst],
old_goptions & ~ACLITEM_GET_GOPTIONS(*mod_aip));
break; break;
case ACL_MODECHG_EQL: case ACL_MODECHG_EQL:
ACLITEM_SET_PRIVS_IDTYPE(new_aip[dst], ACLITEM_SET_RIGHTS(new_aip[dst],
ACLITEM_GET_PRIVS(*mod_aip), ACLITEM_GET_RIGHTS(*mod_aip));
ACLITEM_GET_GOPTIONS(*mod_aip),
ACLITEM_GET_IDTYPE(new_aip[dst]));
break; break;
} }
new_privs = ACLITEM_GET_PRIVS(new_aip[dst]); new_rights = ACLITEM_GET_RIGHTS(new_aip[dst]);
new_goptions = ACLITEM_GET_GOPTIONS(new_aip[dst]); new_goptions = ACLITEM_GET_GOPTIONS(new_aip[dst]);
/* /*
* If the adjusted entry has no permissions, delete it from the list. * If the adjusted entry has no permissions, delete it from the list.
*/ */
if (new_privs == ACL_NO_RIGHTS && new_goptions == ACL_NO_RIGHTS) if (new_rights == ACL_NO_RIGHTS)
{ {
memmove(new_aip + dst, memmove(new_aip + dst,
new_aip + dst + 1, new_aip + dst + 1,
...@@ -676,40 +699,143 @@ aclinsert3(const Acl *old_acl, const AclItem *mod_aip, ...@@ -676,40 +699,143 @@ aclinsert3(const Acl *old_acl, const AclItem *mod_aip,
* Remove abandoned privileges (cascading revoke). Currently we * Remove abandoned privileges (cascading revoke). Currently we
* can only handle this when the grantee is a user. * can only handle this when the grantee is a user.
*/ */
if ((old_goptions & ~new_goptions) != 0 if ((old_goptions & ~new_goptions) != 0)
&& ACLITEM_GET_IDTYPE(*mod_aip) == ACL_IDTYPE_UID) {
Assert(ACLITEM_GET_IDTYPE(*mod_aip) == ACL_IDTYPE_UID);
new_acl = recursive_revoke(new_acl, mod_aip->ai_grantee, new_acl = recursive_revoke(new_acl, mod_aip->ai_grantee,
(old_goptions & ~new_goptions), (old_goptions & ~new_goptions),
behavior); ownerid, behavior);
}
return new_acl; return new_acl;
} }
/*
* When granting grant options, we must disallow attempts to set up circular
* chains of grant options. Suppose A (the object owner) grants B some
* privileges with grant option, and B re-grants them to C. If C could
* grant the privileges to B as well, then A would be unable to effectively
* revoke the privileges from B, since recursive_revoke would consider that
* B still has 'em from C.
*
* We check for this by recursively deleting all grant options belonging to
* the target grantee, and then seeing if the would-be grantor still has the
* grant option or not.
*/
static void
check_circularity(const Acl *old_acl, const AclItem *mod_aip,
AclId ownerid)
{
Acl *acl;
AclItem *aip;
int i,
num;
AclMode own_privs;
/*
* For now, grant options can only be granted to users, not groups or
* PUBLIC. Otherwise we'd have to work a bit harder here.
*/
Assert(ACLITEM_GET_IDTYPE(*mod_aip) == ACL_IDTYPE_UID);
/* The owner always has grant options, no need to check */
if (mod_aip->ai_grantor == ownerid)
return;
/* Make a working copy */
acl = allocacl(ACL_NUM(old_acl));
memcpy(acl, old_acl, ACL_SIZE(old_acl));
/* Zap all grant options of target grantee, plus what depends on 'em */
cc_restart:
num = ACL_NUM(acl);
aip = ACL_DAT(acl);
for (i = 0; i < num; i++)
{
if (ACLITEM_GET_IDTYPE(aip[i]) == ACL_IDTYPE_UID &&
aip[i].ai_grantee == mod_aip->ai_grantee &&
ACLITEM_GET_GOPTIONS(aip[i]) != ACL_NO_RIGHTS)
{
Acl *new_acl;
/* We'll actually zap ordinary privs too, but no matter */
new_acl = aclupdate(acl, &aip[i], ACL_MODECHG_DEL,
ownerid, DROP_CASCADE);
pfree(acl);
acl = new_acl;
goto cc_restart;
}
}
/* Now we can compute grantor's independently-derived privileges */
own_privs = aclmask(acl,
mod_aip->ai_grantor,
ownerid,
ACL_GRANT_OPTION_FOR(ACLITEM_GET_GOPTIONS(*mod_aip)),
ACLMASK_ALL);
own_privs = ACL_OPTION_TO_PRIVS(own_privs);
if ((ACLITEM_GET_GOPTIONS(*mod_aip) & ~own_privs) != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_GRANT_OPERATION),
errmsg("grant options cannot be granted back to your own grantor")));
pfree(acl);
}
/* /*
* Ensure that no privilege is "abandoned". A privilege is abandoned * Ensure that no privilege is "abandoned". A privilege is abandoned
* if the user that granted the privilege loses the grant option. (So * if the user that granted the privilege loses the grant option. (So
* the chain through which it was granted is broken.) Either the * the chain through which it was granted is broken.) Either the
* abandoned privileges are revoked as well, or an error message is * abandoned privileges are revoked as well, or an error message is
* printed, depending on the drop behavior option. * printed, depending on the drop behavior option.
*
* acl: the input ACL list
* grantee: the user from whom some grant options have been revoked
* revoke_privs: the grant options being revoked
* ownerid: AclId of object owner
* behavior: RESTRICT or CASCADE behavior for recursive removal
*
* The input Acl object is pfree'd if replaced.
*/ */
static Acl * static Acl *
recursive_revoke(Acl *acl, recursive_revoke(Acl *acl,
AclId grantee, AclId grantee,
AclMode revoke_privs, AclMode revoke_privs,
AclId ownerid,
DropBehavior behavior) DropBehavior behavior)
{ {
int i; AclMode still_has;
AclItem *aip;
int i,
num;
/* The owner can never truly lose grant options, so short-circuit */
if (grantee == ownerid)
return acl;
/* The grantee might still have the privileges via another grantor */
still_has = aclmask(acl, grantee, ownerid,
ACL_GRANT_OPTION_FOR(revoke_privs),
ACLMASK_ALL);
revoke_privs &= ~still_has;
if (revoke_privs == ACL_NO_RIGHTS)
return acl;
restart: restart:
for (i = 0; i < ACL_NUM(acl); i++) num = ACL_NUM(acl);
aip = ACL_DAT(acl);
for (i = 0; i < num; i++)
{ {
AclItem *aip = ACL_DAT(acl);
if (aip[i].ai_grantor == grantee if (aip[i].ai_grantor == grantee
&& (ACLITEM_GET_PRIVS(aip[i]) & revoke_privs) != 0) && (ACLITEM_GET_PRIVS(aip[i]) & revoke_privs) != 0)
{ {
AclItem mod_acl; AclItem mod_acl;
Acl *new_acl;
if (behavior == DROP_RESTRICT) if (behavior == DROP_RESTRICT)
ereport(ERROR, ereport(ERROR,
...@@ -724,7 +850,12 @@ restart: ...@@ -724,7 +850,12 @@ restart:
revoke_privs, revoke_privs,
ACLITEM_GET_IDTYPE(aip[i])); ACLITEM_GET_IDTYPE(aip[i]));
acl = aclinsert3(acl, &mod_acl, ACL_MODECHG_DEL, behavior); new_acl = aclupdate(acl, &mod_acl, ACL_MODECHG_DEL,
ownerid, behavior);
pfree(acl);
acl = new_acl;
goto restart; goto restart;
} }
} }
...@@ -734,70 +865,177 @@ restart: ...@@ -734,70 +865,177 @@ restart:
/* /*
* aclinsert (exported function) * aclmask --- compute bitmask of all privileges held by userid.
*
* When 'how' = ACLMASK_ALL, this simply returns the privilege bits
* held by the given userid according to the given ACL list, ANDed
* with 'mask'. (The point of passing 'mask' is to let the routine
* exit early if all privileges of interest have been found.)
*
* When 'how' = ACLMASK_ANY, returns as soon as any bit in the mask
* is known true. (This lets us exit soonest in cases where the
* caller is only going to test for zero or nonzero result.)
*
* Usage patterns:
*
* To see if any of a set of privileges are held:
* if (aclmask(acl, userid, ownerid, privs, ACLMASK_ANY) != 0)
*
* To see if all of a set of privileges are held:
* if (aclmask(acl, userid, ownerid, privs, ACLMASK_ALL) == privs)
*
* To determine exactly which of a set of privileges are held:
* heldprivs = aclmask(acl, userid, ownerid, privs, ACLMASK_ALL);
*/ */
Datum AclMode
aclinsert(PG_FUNCTION_ARGS) aclmask(const Acl *acl, AclId userid, AclId ownerid,
AclMode mask, AclMaskHow how)
{ {
Acl *old_acl = PG_GETARG_ACL_P(0); AclMode result;
AclItem *mod_aip = PG_GETARG_ACLITEM_P(1); AclMode remaining;
AclItem *aidat;
int i,
num;
PG_RETURN_ACL_P(aclinsert3(old_acl, mod_aip, ACL_MODECHG_EQL, DROP_CASCADE)); /*
} * Null ACL should not happen, since caller should have inserted
* appropriate default
*/
if (acl == NULL)
elog(ERROR, "null ACL");
Datum /* Quick exit for mask == 0 */
aclremove(PG_FUNCTION_ARGS) if (mask == 0)
{ return 0;
Acl *old_acl = PG_GETARG_ACL_P(0);
AclItem *mod_aip = PG_GETARG_ACLITEM_P(1);
Acl *new_acl;
AclItem *old_aip,
*new_aip;
int dst,
old_num,
new_num;
/* These checks for null input should be dead code, but... */ result = 0;
if (!old_acl || ACL_NUM(old_acl) < 0)
old_acl = allocacl(0); /* Owner always implicitly has all grant options */
if (!mod_aip) if (userid == ownerid)
{ {
new_acl = allocacl(ACL_NUM(old_acl)); result = mask & ACLITEM_ALL_GOPTION_BITS;
memcpy((char *) new_acl, (char *) old_acl, ACL_SIZE(old_acl)); if (result == mask)
PG_RETURN_ACL_P(new_acl); return result;
} }
old_num = ACL_NUM(old_acl); num = ACL_NUM(acl);
old_aip = ACL_DAT(old_acl); aidat = ACL_DAT(acl);
/*
* Check privileges granted directly to user or to public
*/
for (i = 0; i < num; i++)
{
AclItem *aidata = &aidat[i];
/* Search for the matching entry */ if (ACLITEM_GET_IDTYPE(*aidata) == ACL_IDTYPE_WORLD
for (dst = 0; || (ACLITEM_GET_IDTYPE(*aidata) == ACL_IDTYPE_UID
dst < old_num && !aclitem_match(mod_aip, old_aip + dst); && aidata->ai_grantee == userid))
++dst) {
/* continue */ ; result |= (aidata->ai_privs & mask);
if ((how == ACLMASK_ALL) ? (result == mask) : (result != 0))
return result;
}
}
if (dst >= old_num) /*
* Check privileges granted via groups. We do this in a separate
* pass to minimize expensive lookups in pg_group.
*/
remaining = (mask & ~result);
for (i = 0; i < num; i++)
{ {
/* Not found, so return copy of source ACL */ AclItem *aidata = &aidat[i];
new_acl = allocacl(old_num);
memcpy((char *) new_acl, (char *) old_acl, ACL_SIZE(old_acl)); if (ACLITEM_GET_IDTYPE(*aidata) == ACL_IDTYPE_GID
&& (aidata->ai_privs & remaining)
&& in_group(userid, aidata->ai_grantee))
{
result |= (aidata->ai_privs & mask);
if ((how == ACLMASK_ALL) ? (result == mask) : (result != 0))
return result;
remaining = (mask & ~result);
}
} }
else
return result;
}
/*
* Is user a member of group?
*/
static bool
in_group(AclId uid, AclId gid)
{
bool result = false;
HeapTuple tuple;
Datum att;
bool isNull;
IdList *glist;
AclId *aidp;
int i,
num;
tuple = SearchSysCache(GROSYSID,
ObjectIdGetDatum(gid),
0, 0, 0);
if (HeapTupleIsValid(tuple))
{ {
new_num = old_num - 1; att = SysCacheGetAttr(GROSYSID,
new_acl = allocacl(new_num); tuple,
new_aip = ACL_DAT(new_acl); Anum_pg_group_grolist,
if (dst > 0) &isNull);
memcpy((char *) new_aip, if (!isNull)
(char *) old_aip, {
dst * sizeof(AclItem)); /* be sure the IdList is not toasted */
if (dst < new_num) glist = DatumGetIdListP(att);
memcpy((char *) (new_aip + dst), /* scan it */
(char *) (old_aip + dst + 1), num = IDLIST_NUM(glist);
(new_num - dst) * sizeof(AclItem)); aidp = IDLIST_DAT(glist);
for (i = 0; i < num; ++i)
{
if (aidp[i] == uid)
{
result = true;
break;
}
}
/* if IdList was toasted, free detoasted copy */
if ((Pointer) glist != DatumGetPointer(att))
pfree(glist);
}
ReleaseSysCache(tuple);
} }
else
ereport(WARNING,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("group with ID %u does not exist", gid)));
return result;
}
/*
* aclinsert (exported function)
*/
Datum
aclinsert(PG_FUNCTION_ARGS)
{
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("aclinsert is no longer supported")));
PG_RETURN_NULL(); /* keep compiler quiet */
}
Datum
aclremove(PG_FUNCTION_ARGS)
{
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("aclremove is no longer supported")));
PG_RETURN_ACL_P(new_acl); PG_RETURN_NULL(); /* keep compiler quiet */
} }
Datum Datum
...@@ -816,8 +1054,7 @@ aclcontains(PG_FUNCTION_ARGS) ...@@ -816,8 +1054,7 @@ aclcontains(PG_FUNCTION_ARGS)
if (aip->ai_grantee == aidat[i].ai_grantee if (aip->ai_grantee == aidat[i].ai_grantee
&& ACLITEM_GET_IDTYPE(*aip) == ACLITEM_GET_IDTYPE(aidat[i]) && ACLITEM_GET_IDTYPE(*aip) == ACLITEM_GET_IDTYPE(aidat[i])
&& aip->ai_grantor == aidat[i].ai_grantor && aip->ai_grantor == aidat[i].ai_grantor
&& (ACLITEM_GET_PRIVS(*aip) & ACLITEM_GET_PRIVS(aidat[i])) == ACLITEM_GET_PRIVS(*aip) && (ACLITEM_GET_RIGHTS(*aip) & ACLITEM_GET_RIGHTS(aidat[i])) == ACLITEM_GET_RIGHTS(*aip))
&& (ACLITEM_GET_GOPTIONS(*aip) & ACLITEM_GET_GOPTIONS(aidat[i])) == ACLITEM_GET_GOPTIONS(*aip))
PG_RETURN_BOOL(true); PG_RETURN_BOOL(true);
} }
PG_RETURN_BOOL(false); PG_RETURN_BOOL(false);
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California * Portions Copyright (c) 1994, Regents of the University of California
* *
* $PostgreSQL: pgsql/src/include/utils/acl.h,v 1.69 2004/05/11 17:36:13 tgl Exp $ * $PostgreSQL: pgsql/src/include/utils/acl.h,v 1.70 2004/06/01 21:49:22 tgl Exp $
* *
* NOTES * NOTES
* An ACL array is simply an array of AclItems, representing the union * An ACL array is simply an array of AclItems, representing the union
...@@ -63,13 +63,16 @@ typedef struct AclItem ...@@ -63,13 +63,16 @@ typedef struct AclItem
/* /*
* The AclIdType is stored in the top two bits of the ai_privs field * The AclIdType is stored in the top two bits of the ai_privs field
* of an AclItem. The middle 15 bits are the grant option markers, * of an AclItem. The middle 15 bits are the grant option markers,
* and the lower 15 bits are the actual privileges. * and the lower 15 bits are the actual privileges. We use "rights"
* to mean the combined grant option and privilege bits fields.
*/ */
#define ACLITEM_GET_PRIVS(item) ((item).ai_privs & 0x7FFF) #define ACLITEM_GET_PRIVS(item) ((item).ai_privs & 0x7FFF)
#define ACLITEM_GET_GOPTIONS(item) (((item).ai_privs >> 15) & 0x7FFF) #define ACLITEM_GET_GOPTIONS(item) (((item).ai_privs >> 15) & 0x7FFF)
#define ACLITEM_GET_RIGHTS(item) ((item).ai_privs & 0x3FFFFFFF)
#define ACLITEM_GET_IDTYPE(item) ((item).ai_privs >> 30) #define ACLITEM_GET_IDTYPE(item) ((item).ai_privs >> 30)
#define ACL_GRANT_OPTION_FOR(privs) (((AclMode) (privs) & 0x7FFF) << 15) #define ACL_GRANT_OPTION_FOR(privs) (((AclMode) (privs) & 0x7FFF) << 15)
#define ACL_OPTION_TO_PRIVS(privs) (((AclMode) (privs) >> 15) & 0x7FFF)
#define ACLITEM_SET_PRIVS(item,privs) \ #define ACLITEM_SET_PRIVS(item,privs) \
((item).ai_privs = ((item).ai_privs & ~((AclMode) 0x7FFF)) | \ ((item).ai_privs = ((item).ai_privs & ~((AclMode) 0x7FFF)) | \
...@@ -77,6 +80,9 @@ typedef struct AclItem ...@@ -77,6 +80,9 @@ typedef struct AclItem
#define ACLITEM_SET_GOPTIONS(item,goptions) \ #define ACLITEM_SET_GOPTIONS(item,goptions) \
((item).ai_privs = ((item).ai_privs & ~(((AclMode) 0x7FFF) << 15)) | \ ((item).ai_privs = ((item).ai_privs & ~(((AclMode) 0x7FFF) << 15)) | \
(((AclMode) (goptions) & 0x7FFF) << 15)) (((AclMode) (goptions) & 0x7FFF) << 15))
#define ACLITEM_SET_RIGHTS(item,rights) \
((item).ai_privs = ((item).ai_privs & ~((AclMode) 0x3FFFFFFF)) | \
((AclMode) (rights) & 0x3FFFFFFF))
#define ACLITEM_SET_IDTYPE(item,idtype) \ #define ACLITEM_SET_IDTYPE(item,idtype) \
((item).ai_privs = ((item).ai_privs & ~(((AclMode) 0x03) << 30)) | \ ((item).ai_privs = ((item).ai_privs & ~(((AclMode) 0x03) << 30)) | \
(((AclMode) (idtype) & 0x03) << 30)) (((AclMode) (idtype) & 0x03) << 30))
...@@ -86,6 +92,8 @@ typedef struct AclItem ...@@ -86,6 +92,8 @@ typedef struct AclItem
(((AclMode) (goption) & 0x7FFF) << 15) | \ (((AclMode) (goption) & 0x7FFF) << 15) | \
((AclMode) (idtype) << 30)) ((AclMode) (idtype) << 30))
#define ACLITEM_ALL_PRIV_BITS ((AclMode) 0x7FFF)
#define ACLITEM_ALL_GOPTION_BITS ((AclMode) 0x7FFF << 15)
/* /*
* Definitions for convenient access to Acl (array of AclItem) and IdList * Definitions for convenient access to Acl (array of AclItem) and IdList
...@@ -143,7 +151,7 @@ typedef ArrayType IdList; ...@@ -143,7 +151,7 @@ typedef ArrayType IdList;
/* /*
* ACL modification opcodes * ACL modification opcodes for aclupdate
*/ */
#define ACL_MODECHG_ADD 1 #define ACL_MODECHG_ADD 1
#define ACL_MODECHG_DEL 2 #define ACL_MODECHG_DEL 2
...@@ -212,8 +220,10 @@ typedef enum AclObjectKind ...@@ -212,8 +220,10 @@ typedef enum AclObjectKind
* routines used internally * routines used internally
*/ */
extern Acl *acldefault(GrantObjectType objtype, AclId ownerid); extern Acl *acldefault(GrantObjectType objtype, AclId ownerid);
extern Acl *aclinsert3(const Acl *old_acl, const AclItem *mod_aip, extern Acl *aclupdate(const Acl *old_acl, const AclItem *mod_aip,
unsigned modechg, DropBehavior behavior); int modechg, AclId ownerid, DropBehavior behavior);
extern AclMode aclmask(const Acl *acl, AclId userid, AclId ownerid,
AclMode mask, AclMaskHow how);
/* /*
* SQL functions (from acl.c) * SQL functions (from acl.c)
......
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
* *
* Copyright (c) 2003, PostgreSQL Global Development Group * Copyright (c) 2003, PostgreSQL Global Development Group
* *
* $PostgreSQL: pgsql/src/include/utils/errcodes.h,v 1.11 2004/05/16 23:18:55 neilc Exp $ * $PostgreSQL: pgsql/src/include/utils/errcodes.h,v 1.12 2004/06/01 21:49:22 tgl Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
...@@ -59,6 +59,8 @@ ...@@ -59,6 +59,8 @@
#define ERRCODE_WARNING_DYNAMIC_RESULT_SETS_RETURNED MAKE_SQLSTATE('0','1', '0','0','C') #define ERRCODE_WARNING_DYNAMIC_RESULT_SETS_RETURNED MAKE_SQLSTATE('0','1', '0','0','C')
#define ERRCODE_WARNING_IMPLICIT_ZERO_BIT_PADDING MAKE_SQLSTATE('0','1', '0','0','8') #define ERRCODE_WARNING_IMPLICIT_ZERO_BIT_PADDING MAKE_SQLSTATE('0','1', '0','0','8')
#define ERRCODE_WARNING_NULL_VALUE_ELIMINATED_IN_SET_FUNCTION MAKE_SQLSTATE('0','1', '0','0','3') #define ERRCODE_WARNING_NULL_VALUE_ELIMINATED_IN_SET_FUNCTION MAKE_SQLSTATE('0','1', '0','0','3')
#define ERRCODE_WARNING_PRIVILEGE_NOT_GRANTED MAKE_SQLSTATE('0','1', '0','0','7')
#define ERRCODE_WARNING_PRIVILEGE_NOT_REVOKED MAKE_SQLSTATE('0','1', '0','0','6')
#define ERRCODE_WARNING_STRING_DATA_RIGHT_TRUNCATION MAKE_SQLSTATE('0','1', '0','0','4') #define ERRCODE_WARNING_STRING_DATA_RIGHT_TRUNCATION MAKE_SQLSTATE('0','1', '0','0','4')
#define ERRCODE_WARNING_DEPRECATED_FEATURE MAKE_SQLSTATE('0','1', 'P','0','1') #define ERRCODE_WARNING_DEPRECATED_FEATURE MAKE_SQLSTATE('0','1', 'P','0','1')
......
...@@ -89,7 +89,7 @@ ERROR: permission denied for relation atest2 ...@@ -89,7 +89,7 @@ ERROR: permission denied for relation atest2
COPY atest2 FROM stdin; -- fail COPY atest2 FROM stdin; -- fail
ERROR: permission denied for relation atest2 ERROR: permission denied for relation atest2
GRANT ALL ON atest1 TO PUBLIC; -- fail GRANT ALL ON atest1 TO PUBLIC; -- fail
ERROR: permission denied for relation atest1 WARNING: no privileges were granted
-- checks in subquery, both ok -- checks in subquery, both ok
SELECT * FROM atest1 WHERE ( b IN ( SELECT col1 FROM atest2 ) ); SELECT * FROM atest1 WHERE ( b IN ( SELECT col1 FROM atest2 ) );
a | b a | b
...@@ -225,7 +225,7 @@ GRANT USAGE ON LANGUAGE c TO PUBLIC; -- fail ...@@ -225,7 +225,7 @@ GRANT USAGE ON LANGUAGE c TO PUBLIC; -- fail
ERROR: language "c" is not trusted ERROR: language "c" is not trusted
SET SESSION AUTHORIZATION regressuser1; SET SESSION AUTHORIZATION regressuser1;
GRANT USAGE ON LANGUAGE sql TO regressuser2; -- fail GRANT USAGE ON LANGUAGE sql TO regressuser2; -- fail
ERROR: permission denied for language sql WARNING: no privileges were granted
CREATE FUNCTION testfunc1(int) RETURNS int AS 'select 2 * $1;' LANGUAGE sql; CREATE FUNCTION testfunc1(int) RETURNS int AS 'select 2 * $1;' LANGUAGE sql;
CREATE FUNCTION testfunc2(int) RETURNS int AS 'select 3 * $1;' LANGUAGE sql; CREATE FUNCTION testfunc2(int) RETURNS int AS 'select 3 * $1;' LANGUAGE sql;
REVOKE ALL ON FUNCTION testfunc1(int), testfunc2(int) FROM PUBLIC; REVOKE ALL ON FUNCTION testfunc1(int), testfunc2(int) FROM PUBLIC;
...@@ -550,7 +550,7 @@ ERROR: grant options can only be granted to individual users ...@@ -550,7 +550,7 @@ ERROR: grant options can only be granted to individual users
SET SESSION AUTHORIZATION regressuser2; SET SESSION AUTHORIZATION regressuser2;
GRANT SELECT ON atest4 TO regressuser3; GRANT SELECT ON atest4 TO regressuser3;
GRANT UPDATE ON atest4 TO regressuser3; -- fail GRANT UPDATE ON atest4 TO regressuser3; -- fail
ERROR: permission denied for relation atest4 WARNING: no privileges were granted
SET SESSION AUTHORIZATION regressuser1; SET SESSION AUTHORIZATION regressuser1;
REVOKE SELECT ON atest4 FROM regressuser3; -- does nothing REVOKE SELECT ON atest4 FROM regressuser3; -- does nothing
SELECT has_table_privilege('regressuser3', 'atest4', 'SELECT'); -- true SELECT has_table_privilege('regressuser3', 'atest4', 'SELECT'); -- true
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment