<!--
-$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
-->
<para>
If <literal>WITH GRANT OPTION</literal> is specified, the recipient
- of the privilege may in turn grant it to others. By default this
- is not allowed. Grant options can only be granted to individual
- users, not to groups or <literal>PUBLIC</literal>.
+ of the privilege may in turn grant it to others. Without a grant
+ option, the recipient cannot do that. At present, grant options can
+ only be granted to individual users, not to groups or
+ <literal>PUBLIC</literal>.
</para>
<para>
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
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
- grant options to be revoked, either.
+ and cannot be granted or revoked. The owner implicitly has all grant
+ options for the object, too.
</para>
<para>
<term>RULE</term>
<listitem>
<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.)
</para>
</listitem>
<term>TRIGGER</term>
<listitem>
<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.)
</para>
</listitem>
<term>ALL PRIVILEGES</term>
<listitem>
<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
<productname>PostgreSQL</productname>, though it is required by
strict SQL.
</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>
It should be noted that database superusers can access
all objects regardless of object privilege settings. This
is comparable to the rights of <literal>root</> in a Unix system.
</para>
<para>
- Currently, to grant privileges in <productname>PostgreSQL</productname>
- to only a few columns, you must
- create a view having the desired columns and then grant privileges
- to that view.
+ Currently, <productname>PostgreSQL</productname> does not support
+ granting or revoking privileges for individual columns of a table.
+ One possible workaround is to create a view having just the desired
+ columns and then grant privileges to that view.
</para>
<para>
=> \z mytable
Access privileges for database "lusitania"
- Schema | Name | Type | Access privileges
---------+---------+-------+-----------------------------------------------------------------
- public | mytable | table | {=r/postgres,miriam=arwdRxt/postgres,"group todos=arw/postgres"}
+ Schema | Name | Type | Access privileges
+--------+---------+-------+------------------------------------------------------------
+ public | mytable | table | {miriam=arwdRxt/miriam,=r/miriam,"group todos=arw/miriam"}
(1 row)
</programlisting>
The entries shown by <command>\z</command> are interpreted thus:
object type, as explained above. The first <command>GRANT</> or
<command>REVOKE</> on an object
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>
</refsect1>
</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>
GRANT ALL PRIVILEGES ON kinds TO manuel;
</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>
</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
-->
<para>
If <literal>GRANT OPTION FOR</literal> is specified, only the grant
option for the privilege is revoked, not the privilege itself.
+ Otherwise, both the privilege and the grant option are revoked.
</para>
<para>
<para>
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.
</para>
C, then user A cannot revoke the privilege directly from C.
Instead, user A could revoke the grant option from user B and use
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>
+ 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>
If a superuser chooses to issue a <command>GRANT</> or <command>REVOKE</>
command, the command is performed as though it were issued by the
owner of the affected object. Since all privileges ultimately come
</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;
</programlisting>
+
+ Note that this actually means <quote>revoke all privileges that I
+ granted</>.
</para>
</refsect1>
*
*
* 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
* See acl.h.
static const char *privilege_to_string(AclMode privilege);
-static AclMode aclmask(Acl *acl, AclId userid,
- AclMode mask, AclMaskHow how);
-
#ifdef ACLDEBUG
static
AclItem aclitem;
uint32 idtype;
Acl *newer_acl;
- bool grantee_is_owner = false;
if (grantee->username)
{
aclitem.ai_grantee = get_usesysid(grantee->username);
idtype = ACL_IDTYPE_UID;
-
- grantee_is_owner = (aclitem.ai_grantee == owner_uid);
}
else if (grantee->groupname)
{
(errcode(ERRCODE_INVALID_GRANT_OPERATION),
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;
+ /*
+ * 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,
(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);
- 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 */
pfree(new_acl);
ExecuteGrantStmt_Relation(GrantStmt *stmt)
{
AclMode privileges;
+ bool all_privs;
ListCell *i;
if (linitial_int(stmt->privileges) == ACL_ALL_RIGHTS)
+ {
+ all_privs = true;
privileges = ACL_ALL_RIGHTS_RELATION;
+ }
else
{
+ all_privs = false;
privileges = ACL_NO_RIGHTS;
foreach(i, stmt->privileges)
{
Form_pg_class pg_class_tuple;
Datum aclDatum;
bool isNull;
+ AclMode my_goptions;
+ AclMode this_privileges;
Acl *old_acl;
Acl *new_acl;
AclId grantorId;
elog(ERROR, "cache lookup failed for relation %u", relOid);
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 */
if (pg_class_tuple->relkind == RELKIND_INDEX)
ereport(ERROR,
errmsg("\"%s\" is an index",
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.
*/
new_acl = merge_acl_with_grant(old_acl, stmt->is_grant,
stmt->grant_option, stmt->behavior,
- stmt->grantees, privileges,
+ stmt->grantees, this_privileges,
grantorId, ownerId);
/* finished building new ACL value, now insert it */
ExecuteGrantStmt_Database(GrantStmt *stmt)
{
AclMode privileges;
+ bool all_privs;
ListCell *i;
if (linitial_int(stmt->privileges) == ACL_ALL_RIGHTS)
+ {
+ all_privs = true;
privileges = ACL_ALL_RIGHTS_DATABASE;
+ }
else
{
+ all_privs = false;
privileges = ACL_NO_RIGHTS;
foreach(i, stmt->privileges)
{
Form_pg_database pg_database_tuple;
Datum aclDatum;
bool isNull;
+ AclMode my_goptions;
+ AclMode this_privileges;
Acl *old_acl;
Acl *new_acl;
AclId grantorId;
ownerId = pg_database_tuple->datdba;
grantorId = select_grantor(ownerId);
- if (stmt->is_grant
- && !pg_database_ownercheck(HeapTupleGetOid(tuple), GetUserId())
- && pg_database_aclcheck(HeapTupleGetOid(tuple), GetUserId(),
- ACL_GRANT_OPTION_FOR(privileges)) != ACLCHECK_OK)
- aclcheck_error(ACLCHECK_NO_PRIV, ACL_KIND_DATABASE,
- NameStr(pg_database_tuple->datname));
+ /*
+ * 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_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.
new_acl = merge_acl_with_grant(old_acl, stmt->is_grant,
stmt->grant_option, stmt->behavior,
- stmt->grantees, privileges,
+ stmt->grantees, this_privileges,
grantorId, ownerId);
/* finished building new ACL value, now insert it */
ExecuteGrantStmt_Function(GrantStmt *stmt)
{
AclMode privileges;
+ bool all_privs;
ListCell *i;
if (linitial_int(stmt->privileges) == ACL_ALL_RIGHTS)
+ {
+ all_privs = true;
privileges = ACL_ALL_RIGHTS_FUNCTION;
+ }
else
{
+ all_privs = false;
privileges = ACL_NO_RIGHTS;
foreach(i, stmt->privileges)
{
Form_pg_proc pg_proc_tuple;
Datum aclDatum;
bool isNull;
+ AclMode my_goptions;
+ AclMode this_privileges;
Acl *old_acl;
Acl *new_acl;
AclId grantorId;
ownerId = pg_proc_tuple->proowner;
grantorId = select_grantor(ownerId);
- if (stmt->is_grant
- && !pg_proc_ownercheck(oid, GetUserId())
- && pg_proc_aclcheck(oid, GetUserId(),
- ACL_GRANT_OPTION_FOR(privileges)) != ACLCHECK_OK)
- aclcheck_error(ACLCHECK_NO_PRIV, ACL_KIND_PROC,
- NameStr(pg_proc_tuple->proname));
+ /*
+ * 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_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.
new_acl = merge_acl_with_grant(old_acl, stmt->is_grant,
stmt->grant_option, stmt->behavior,
- stmt->grantees, privileges,
+ stmt->grantees, this_privileges,
grantorId, ownerId);
/* finished building new ACL value, now insert it */
ExecuteGrantStmt_Language(GrantStmt *stmt)
{
AclMode privileges;
+ bool all_privs;
ListCell *i;
if (linitial_int(stmt->privileges) == ACL_ALL_RIGHTS)
+ {
+ all_privs = true;
privileges = ACL_ALL_RIGHTS_LANGUAGE;
+ }
else
{
+ all_privs = false;
privileges = ACL_NO_RIGHTS;
foreach(i, stmt->privileges)
{
Form_pg_language pg_language_tuple;
Datum aclDatum;
bool isNull;
+ AclMode my_goptions;
+ AclMode this_privileges;
Acl *old_acl;
Acl *new_acl;
AclId grantorId;
errmsg("language \"%s\" does not exist", langname)));
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
* user. We should add an owner column to pg_language instead.
ownerId = BOOTSTRAP_USESYSID;
grantorId = select_grantor(ownerId);
- if (stmt->is_grant
- && !superuser() /* XXX no ownercheck() available */
- && pg_language_aclcheck(HeapTupleGetOid(tuple), GetUserId(),
- ACL_GRANT_OPTION_FOR(privileges)) != ACLCHECK_OK)
- aclcheck_error(ACLCHECK_NO_PRIV, ACL_KIND_LANGUAGE,
- NameStr(pg_language_tuple->lanname));
+ /*
+ * 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 (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,
- (errcode(ERRCODE_WRONG_OBJECT_TYPE),
- errmsg("language \"%s\" is not trusted", langname)));
+ /*
+ * 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.
new_acl = merge_acl_with_grant(old_acl, stmt->is_grant,
stmt->grant_option, stmt->behavior,
- stmt->grantees, privileges,
+ stmt->grantees, this_privileges,
grantorId, ownerId);
/* finished building new ACL value, now insert it */
ExecuteGrantStmt_Namespace(GrantStmt *stmt)
{
AclMode privileges;
+ bool all_privs;
ListCell *i;
if (linitial_int(stmt->privileges) == ACL_ALL_RIGHTS)
+ {
+ all_privs = true;
privileges = ACL_ALL_RIGHTS_NAMESPACE;
+ }
else
{
+ all_privs = false;
privileges = ACL_NO_RIGHTS;
foreach(i, stmt->privileges)
{
Form_pg_namespace pg_namespace_tuple;
Datum aclDatum;
bool isNull;
+ AclMode my_goptions;
+ AclMode this_privileges;
Acl *old_acl;
Acl *new_acl;
AclId grantorId;
ownerId = pg_namespace_tuple->nspowner;
grantorId = select_grantor(ownerId);
- if (stmt->is_grant
- && !pg_namespace_ownercheck(HeapTupleGetOid(tuple), GetUserId())
- && pg_namespace_aclcheck(HeapTupleGetOid(tuple), GetUserId(),
- ACL_GRANT_OPTION_FOR(privileges)) != ACLCHECK_OK)
- aclcheck_error(ACLCHECK_NO_PRIV, ACL_KIND_NAMESPACE,
- nspname);
+ /*
+ * 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_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.
new_acl = merge_acl_with_grant(old_acl, stmt->is_grant,
stmt->grant_option, stmt->behavior,
- stmt->grantees, privileges,
+ stmt->grantees, this_privileges,
grantorId, ownerId);
/* finished building new ACL value, now insert it */
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.
Datum aclDatum;
bool isNull;
Acl *acl;
+ AclId ownerId;
/*
* Validate userid, find out if he is superuser, also get usecatupd
/*
* Normal case: get the relation's ACL from pg_class
*/
+ ownerId = classForm->relowner;
+
aclDatum = SysCacheGetAttr(RELOID, tuple, Anum_pg_class_relacl,
&isNull);
if (isNull)
{
/* No ACL, so build default ACL */
- AclId ownerId = classForm->relowner;
-
acl = acldefault(ACL_OBJECT_RELATION, ownerId);
aclDatum = (Datum) 0;
}
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 (acl && (Pointer) acl != DatumGetPointer(aclDatum))
Datum aclDatum;
bool isNull;
Acl *acl;
+ AclId ownerId;
/* Superusers bypass all permission checking. */
if (superuser_arg(userid))
(errcode(ERRCODE_UNDEFINED_DATABASE),
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,
RelationGetDescr(pg_database), &isNull);
if (isNull)
{
/* No ACL, so build default ACL */
- AclId ownerId;
-
- ownerId = ((Form_pg_database) GETSTRUCT(tuple))->datdba;
acl = acldefault(ACL_OBJECT_DATABASE, ownerId);
aclDatum = (Datum) 0;
}
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 (acl && (Pointer) acl != DatumGetPointer(aclDatum))
Datum aclDatum;
bool isNull;
Acl *acl;
+ AclId ownerId;
/* Superusers bypass all permission checking. */
if (superuser_arg(userid))
(errcode(ERRCODE_UNDEFINED_FUNCTION),
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,
&isNull);
if (isNull)
{
/* No ACL, so build default ACL */
- AclId ownerId;
-
- ownerId = ((Form_pg_proc) GETSTRUCT(tuple))->proowner;
acl = acldefault(ACL_OBJECT_FUNCTION, ownerId);
aclDatum = (Datum) 0;
}
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 (acl && (Pointer) acl != DatumGetPointer(aclDatum))
Datum aclDatum;
bool isNull;
Acl *acl;
+ AclId ownerId;
/* Superusers bypass all permission checking. */
if (superuser_arg(userid))
(errcode(ERRCODE_UNDEFINED_OBJECT),
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,
&isNull);
if (isNull)
{
/* No ACL, so build default ACL */
- /* XXX pg_language should have an owner column, but doesn't */
- acl = acldefault(ACL_OBJECT_LANGUAGE, BOOTSTRAP_USESYSID);
+ acl = acldefault(ACL_OBJECT_LANGUAGE, ownerId);
aclDatum = (Datum) 0;
}
else
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 (acl && (Pointer) acl != DatumGetPointer(aclDatum))
Datum aclDatum;
bool isNull;
Acl *acl;
+ AclId ownerId;
/* Superusers bypass all permission checking. */
if (superuser_arg(userid))
(errcode(ERRCODE_UNDEFINED_SCHEMA),
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,
&isNull);
if (isNull)
{
/* No ACL, so build default ACL */
- AclId ownerId;
-
- ownerId = ((Form_pg_namespace) GETSTRUCT(tuple))->nspowner;
acl = acldefault(ACL_OBJECT_NAMESPACE, ownerId);
aclDatum = (Datum) 0;
}
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 (acl && (Pointer) acl != DatumGetPointer(aclDatum))
*
*
* 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 $
*
*-------------------------------------------------------------------------
*/
#include <ctype.h>
#include "catalog/namespace.h"
+#include "catalog/pg_group.h"
#include "catalog/pg_shadow.h"
#include "catalog/pg_type.h"
#include "commands/dbcommands.h"
static Acl *allocacl(int n);
static const char *aclparse(const char *s, AclItem *aip);
static bool aclitem_match(const AclItem *a1, const AclItem *a2);
-static Acl *recursive_revoke(Acl *acl, AclId grantee,
- AclMode revoke_privs, DropBehavior behavior);
+static void check_circularity(const Acl *old_acl, const AclItem *mod_aip,
+ 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);
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_grantor = ownerid;
- /* owner gets default privileges with grant option */
- ACLITEM_SET_PRIVS_IDTYPE(*aip, owner_default, owner_default,
+ ACLITEM_SET_PRIVS_IDTYPE(*aip, owner_default, ACL_NO_RIGHTS,
ACL_IDTYPE_UID);
return acl;
/*
- * Add or replace an item in an ACL array. The result is a modified copy;
- * the input object is not changed.
+ * Update an ACL array to add or remove specified privileges.
+ *
+ * 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.
*/
Acl *
-aclinsert3(const Acl *old_acl, const AclItem *mod_aip,
- unsigned modechg, DropBehavior behavior)
+aclupdate(const Acl *old_acl, const AclItem *mod_aip,
+ int modechg, AclId ownerid, DropBehavior behavior)
{
Acl *new_acl = NULL;
AclItem *old_aip,
*new_aip = NULL;
- AclMode old_privs,
+ AclMode old_rights,
old_goptions,
- new_privs,
+ new_rights,
new_goptions;
int dst,
num;
if (!mod_aip)
{
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;
}
+ /* 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);
old_aip = ACL_DAT(old_acl);
/* initialize the new entry with no permissions */
new_aip[dst].ai_grantee = mod_aip->ai_grantee;
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));
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]);
/* apply the specified permissions change */
switch (modechg)
{
case ACL_MODECHG_ADD:
- ACLITEM_SET_PRIVS(new_aip[dst],
- old_privs | ACLITEM_GET_PRIVS(*mod_aip));
- ACLITEM_SET_GOPTIONS(new_aip[dst],
- old_goptions | ACLITEM_GET_GOPTIONS(*mod_aip));
+ ACLITEM_SET_RIGHTS(new_aip[dst],
+ old_rights | ACLITEM_GET_RIGHTS(*mod_aip));
break;
case ACL_MODECHG_DEL:
- ACLITEM_SET_PRIVS(new_aip[dst],
- old_privs & ~ACLITEM_GET_PRIVS(*mod_aip));
- ACLITEM_SET_GOPTIONS(new_aip[dst],
- old_goptions & ~ACLITEM_GET_GOPTIONS(*mod_aip));
+ ACLITEM_SET_RIGHTS(new_aip[dst],
+ old_rights & ~ACLITEM_GET_RIGHTS(*mod_aip));
break;
case ACL_MODECHG_EQL:
- ACLITEM_SET_PRIVS_IDTYPE(new_aip[dst],
- ACLITEM_GET_PRIVS(*mod_aip),
- ACLITEM_GET_GOPTIONS(*mod_aip),
- ACLITEM_GET_IDTYPE(new_aip[dst]));
+ ACLITEM_SET_RIGHTS(new_aip[dst],
+ ACLITEM_GET_RIGHTS(*mod_aip));
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]);
/*
* 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,
new_aip + dst + 1,
* Remove abandoned privileges (cascading revoke). Currently we
* can only handle this when the grantee is a user.
*/
- if ((old_goptions & ~new_goptions) != 0
- && ACLITEM_GET_IDTYPE(*mod_aip) == ACL_IDTYPE_UID)
+ if ((old_goptions & ~new_goptions) != 0)
+ {
+ Assert(ACLITEM_GET_IDTYPE(*mod_aip) == ACL_IDTYPE_UID);
new_acl = recursive_revoke(new_acl, mod_aip->ai_grantee,
(old_goptions & ~new_goptions),
- behavior);
+ ownerid, behavior);
+ }
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
* if the user that granted the privilege loses the grant option. (So
* the chain through which it was granted is broken.) Either the
* abandoned privileges are revoked as well, or an error message is
* 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 *
recursive_revoke(Acl *acl,
AclId grantee,
AclMode revoke_privs,
+ AclId ownerid,
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:
- 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
&& (ACLITEM_GET_PRIVS(aip[i]) & revoke_privs) != 0)
{
AclItem mod_acl;
+ Acl *new_acl;
if (behavior == DROP_RESTRICT)
ereport(ERROR,
revoke_privs,
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;
}
}
/*
- * 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
-aclinsert(PG_FUNCTION_ARGS)
+AclMode
+aclmask(const Acl *acl, AclId userid, AclId ownerid,
+ AclMode mask, AclMaskHow how)
{
- Acl *old_acl = PG_GETARG_ACL_P(0);
- AclItem *mod_aip = PG_GETARG_ACLITEM_P(1);
+ AclMode result;
+ 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
-aclremove(PG_FUNCTION_ARGS)
-{
- 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;
+ /* Quick exit for mask == 0 */
+ if (mask == 0)
+ return 0;
- /* These checks for null input should be dead code, but... */
- if (!old_acl || ACL_NUM(old_acl) < 0)
- old_acl = allocacl(0);
- if (!mod_aip)
+ result = 0;
+
+ /* Owner always implicitly has all grant options */
+ if (userid == ownerid)
{
- new_acl = allocacl(ACL_NUM(old_acl));
- memcpy((char *) new_acl, (char *) old_acl, ACL_SIZE(old_acl));
- PG_RETURN_ACL_P(new_acl);
+ result = mask & ACLITEM_ALL_GOPTION_BITS;
+ if (result == mask)
+ return result;
}
- old_num = ACL_NUM(old_acl);
- old_aip = ACL_DAT(old_acl);
+ num = ACL_NUM(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 */
- for (dst = 0;
- dst < old_num && !aclitem_match(mod_aip, old_aip + dst);
- ++dst)
- /* continue */ ;
+ 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;
+ }
+ }
- 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 */
- new_acl = allocacl(old_num);
- memcpy((char *) new_acl, (char *) old_acl, ACL_SIZE(old_acl));
+ 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);
+ }
}
- 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;
- new_acl = allocacl(new_num);
- new_aip = ACL_DAT(new_acl);
- if (dst > 0)
- memcpy((char *) new_aip,
- (char *) old_aip,
- dst * sizeof(AclItem));
- if (dst < new_num)
- memcpy((char *) (new_aip + dst),
- (char *) (old_aip + dst + 1),
- (new_num - dst) * sizeof(AclItem));
+ 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;
+}
+
+
+/*
+ * 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
if (aip->ai_grantee == aidat[i].ai_grantee
&& ACLITEM_GET_IDTYPE(*aip) == ACLITEM_GET_IDTYPE(aidat[i])
&& aip->ai_grantor == aidat[i].ai_grantor
- && (ACLITEM_GET_PRIVS(*aip) & ACLITEM_GET_PRIVS(aidat[i])) == ACLITEM_GET_PRIVS(*aip)
- && (ACLITEM_GET_GOPTIONS(*aip) & ACLITEM_GET_GOPTIONS(aidat[i])) == ACLITEM_GET_GOPTIONS(*aip))
+ && (ACLITEM_GET_RIGHTS(*aip) & ACLITEM_GET_RIGHTS(aidat[i])) == ACLITEM_GET_RIGHTS(*aip))
PG_RETURN_BOOL(true);
}
PG_RETURN_BOOL(false);
* Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
* 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
* An ACL array is simply an array of AclItems, representing the union
/*
* 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,
- * 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_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 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) \
((item).ai_privs = ((item).ai_privs & ~((AclMode) 0x7FFF)) | \
#define ACLITEM_SET_GOPTIONS(item,goptions) \
((item).ai_privs = ((item).ai_privs & ~(((AclMode) 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) \
((item).ai_privs = ((item).ai_privs & ~(((AclMode) 0x03) << 30)) | \
(((AclMode) (idtype) & 0x03) << 30))
(((AclMode) (goption) & 0x7FFF) << 15) | \
((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
/*
- * ACL modification opcodes
+ * ACL modification opcodes for aclupdate
*/
#define ACL_MODECHG_ADD 1
#define ACL_MODECHG_DEL 2
* routines used internally
*/
extern Acl *acldefault(GrantObjectType objtype, AclId ownerid);
-extern Acl *aclinsert3(const Acl *old_acl, const AclItem *mod_aip,
- unsigned modechg, DropBehavior behavior);
+extern Acl *aclupdate(const Acl *old_acl, const AclItem *mod_aip,
+ 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)
*
* 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 $
*
*-------------------------------------------------------------------------
*/
#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_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_DEPRECATED_FEATURE MAKE_SQLSTATE('0','1', 'P','0','1')
COPY atest2 FROM stdin; -- fail
ERROR: permission denied for relation atest2
GRANT ALL ON atest1 TO PUBLIC; -- fail
-ERROR: permission denied for relation atest1
+WARNING: no privileges were granted
-- checks in subquery, both ok
SELECT * FROM atest1 WHERE ( b IN ( SELECT col1 FROM atest2 ) );
a | b
ERROR: language "c" is not trusted
SET SESSION AUTHORIZATION regressuser1;
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 testfunc2(int) RETURNS int AS 'select 3 * $1;' LANGUAGE sql;
REVOKE ALL ON FUNCTION testfunc1(int), testfunc2(int) FROM PUBLIC;
SET SESSION AUTHORIZATION regressuser2;
GRANT SELECT ON atest4 TO regressuser3;
GRANT UPDATE ON atest4 TO regressuser3; -- fail
-ERROR: permission denied for relation atest4
+WARNING: no privileges were granted
SET SESSION AUTHORIZATION regressuser1;
REVOKE SELECT ON atest4 FROM regressuser3; -- does nothing
SELECT has_table_privilege('regressuser3', 'atest4', 'SELECT'); -- true