diff --git a/doc/src/sgml/ref/grant.sgml b/doc/src/sgml/ref/grant.sgml index b2ad6310525f0dc9f9038ae8ac40fba3b0442a94..a43de6356d94e46269ac4220bf8bcaca0fe9c918 100644 --- a/doc/src/sgml/ref/grant.sgml +++ b/doc/src/sgml/ref/grant.sgml @@ -1,5 +1,5 @@ <!-- -$Header: /cvsroot/pgsql/doc/src/sgml/ref/grant.sgml,v 1.36 2003/09/20 20:12:05 tgl Exp $ +$Header: /cvsroot/pgsql/doc/src/sgml/ref/grant.sgml,v 1.37 2003/10/31 20:00:48 tgl Exp $ PostgreSQL documentation --> @@ -66,19 +66,21 @@ GRANT { { CREATE | USAGE } [,...] | ALL [ PRIVILEGES ] } </para> <para> - There is no need to grant privileges to the owner of an object (usually the user that created it), - as the owner has all privileges by default. (The owner could, - however, choose to revoke some of his own privileges for safety.) - The right to drop an object, or to alter it in any way is - not described by a grantable right; it is inherent in the owner, - and cannot be granted or revoked. + 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>. </para> <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 possible. Grant options can only be granted to individual - users, not groups or <literal>PUBLIC</literal>. + There is no need to grant privileges to the owner of an object + (usually the user that created it), + as the owner has all privileges by default. (The owner could, + 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. </para> <para> @@ -263,6 +265,13 @@ GRANT { { CREATE | USAGE } [,...] | ALL [ PRIVILEGES ] } except when absolutely necessary. </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. In particular, privileges granted via + such a command will appear to have been granted by the object owner. + </para> + <para> Currently, to grant privileges in <productname>PostgreSQL</productname> to only a few columns, you must diff --git a/doc/src/sgml/ref/revoke.sgml b/doc/src/sgml/ref/revoke.sgml index 557a219f7733f51bf288fef3e2f2de3a8994fc8c..cb69c707b7a20d59b54d3ee481539a19355f2621 100644 --- a/doc/src/sgml/ref/revoke.sgml +++ b/doc/src/sgml/ref/revoke.sgml @@ -1,5 +1,5 @@ <!-- -$Header: /cvsroot/pgsql/doc/src/sgml/ref/revoke.sgml,v 1.27 2003/08/31 17:32:24 petere Exp $ +$Header: /cvsroot/pgsql/doc/src/sgml/ref/revoke.sgml,v 1.28 2003/10/31 20:00:48 tgl Exp $ PostgreSQL documentation --> @@ -63,6 +63,11 @@ REVOKE [ GRANT OPTION FOR ] all users. </para> + <para> + See the description of the <xref linkend="sql-grant" endterm="sql-grant-title"> command for + the meaning of the privilege types. + </para> + <para> Note that any particular user will have the sum of privileges granted directly to him, privileges granted to any group he @@ -73,11 +78,6 @@ REVOKE [ GRANT OPTION FOR ] directly or via a group will still have it. </para> - <para> - See the description of the <xref linkend="sql-grant" endterm="sql-grant-title"> command for - the meaning of the privilege types. - </para> - <para> If <literal>GRANT OPTION FOR</literal> is specified, only the grant option for the privilege is revoked, not the privilege itself. @@ -116,6 +116,15 @@ REVOKE [ GRANT OPTION FOR ] the <literal>CASCADE</literal> option so that the privilege is automatically revoked from user C. </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 + from the object owner (possibly indirectly via chains of grant options), + it is possible for a superuser to revoke all privileges, but this may + require use of <literal>CASCADE</literal> as stated above. + </para> </refsect1> <refsect1 id="SQL-REVOKE-examples"> @@ -153,7 +162,8 @@ REVOKE [ GRANT OPTION FOR ] <replaceable class="PARAMETER">privileges</replaceab { RESTRICT | CASCADE } </synopsis> One of <literal>RESTRICT</literal> or <literal>CASCADE</literal> - is required. + is required according to the standard, but <productname>PostgreSQL</> + assumes <literal>RESTRICT</literal> by default. </para> </refsect1> diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c index 437453a03be6c8e55483a167612b182b1eefdcca..bf43769d7062bccf7f3839072111f3b22f306c00 100644 --- a/src/backend/catalog/aclchk.c +++ b/src/backend/catalog/aclchk.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/catalog/aclchk.c,v 1.90 2003/10/29 22:20:54 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/catalog/aclchk.c,v 1.91 2003/10/31 20:00:49 tgl Exp $ * * NOTES * See acl.h. @@ -68,6 +68,32 @@ dumpacl(Acl *acl) #endif /* ACLDEBUG */ +/* + * Determine the effective grantor ID for a GRANT or REVOKE operation. + * + * Ordinarily this is just the current user, but when a superuser does + * GRANT or REVOKE, we pretend he is the object owner. This ensures that + * all granted privileges appear to flow from the object owner, and there + * are never multiple "original sources" of a privilege. + */ +static AclId +select_grantor(AclId ownerId) +{ + AclId grantorId; + + grantorId = GetUserId(); + + /* fast path if no difference */ + if (grantorId == ownerId) + return grantorId; + + if (superuser()) + grantorId = ownerId; + + return grantorId; +} + + /* * If is_grant is true, adds the given privileges for the list of * grantees to the existing old_acl. If is_grant is false, the @@ -77,9 +103,9 @@ dumpacl(Acl *acl) */ static Acl * merge_acl_with_grant(Acl *old_acl, bool is_grant, - List *grantees, AclMode privileges, bool grant_option, DropBehavior behavior, - AclId owner_uid) + List *grantees, AclMode privileges, + AclId grantor_uid, AclId owner_uid) { unsigned modechg; List *j; @@ -128,7 +154,7 @@ merge_acl_with_grant(Acl *old_acl, bool is_grant, * and later the user is removed from the group, the situation is * impossible to clean up. */ - if (is_grant && idtype != ACL_IDTYPE_UID && grant_option) + if (is_grant && grant_option && idtype != ACL_IDTYPE_UID) ereport(ERROR, (errcode(ERRCODE_INVALID_GRANT_OPERATION), errmsg("grant options can only be granted to individual users"))); @@ -138,7 +164,7 @@ merge_acl_with_grant(Acl *old_acl, bool is_grant, (errcode(ERRCODE_INVALID_GRANT_OPERATION), errmsg("cannot revoke grant options from owner"))); - aclitem.ai_grantor = GetUserId(); + aclitem.ai_grantor = grantor_uid; ACLITEM_SET_PRIVS_IDTYPE(aclitem, (is_grant || !grant_option) ? privileges : ACL_NO_RIGHTS, @@ -224,6 +250,8 @@ ExecuteGrantStmt_Relation(GrantStmt *stmt) bool isNull; Acl *old_acl; Acl *new_acl; + AclId grantorId; + AclId ownerId; HeapTuple newtuple; Datum values[Natts_pg_class]; char nulls[Natts_pg_class]; @@ -239,9 +267,13 @@ ExecuteGrantStmt_Relation(GrantStmt *stmt) 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) + && 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 */ @@ -252,22 +284,20 @@ ExecuteGrantStmt_Relation(GrantStmt *stmt) relvar->relname))); /* - * If there's no ACL, create a default using the pg_class.relowner - * field. + * If there's no ACL, substitute the proper default. */ aclDatum = SysCacheGetAttr(RELOID, tuple, Anum_pg_class_relacl, &isNull); if (isNull) - old_acl = acldefault(ACL_OBJECT_RELATION, - pg_class_tuple->relowner); + old_acl = acldefault(ACL_OBJECT_RELATION, ownerId); else /* get a detoasted copy of the ACL */ old_acl = DatumGetAclPCopy(aclDatum); new_acl = merge_acl_with_grant(old_acl, stmt->is_grant, - stmt->grantees, privileges, stmt->grant_option, stmt->behavior, - pg_class_tuple->relowner); + stmt->grantees, privileges, + grantorId, ownerId); /* finished building new ACL value, now insert it */ MemSet(values, 0, sizeof(values)); @@ -328,6 +358,8 @@ ExecuteGrantStmt_Database(GrantStmt *stmt) bool isNull; Acl *old_acl; Acl *new_acl; + AclId grantorId; + AclId ownerId; HeapTuple newtuple; Datum values[Natts_pg_database]; char nulls[Natts_pg_database]; @@ -345,28 +377,31 @@ ExecuteGrantStmt_Database(GrantStmt *stmt) errmsg("database \"%s\" does not exist", dbname))); pg_database_tuple = (Form_pg_database) GETSTRUCT(tuple); + ownerId = pg_database_tuple->datdba; + grantorId = select_grantor(ownerId); + if (stmt->is_grant - && pg_database_tuple->datdba != GetUserId() - && pg_database_aclcheck(HeapTupleGetOid(tuple), GetUserId(), ACL_GRANT_OPTION_FOR(privileges)) != ACLCHECK_OK) + && !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)); /* - * If there's no ACL, create a default. + * If there's no ACL, substitute the proper default. */ aclDatum = heap_getattr(tuple, Anum_pg_database_datacl, RelationGetDescr(relation), &isNull); if (isNull) - old_acl = acldefault(ACL_OBJECT_DATABASE, - pg_database_tuple->datdba); + old_acl = acldefault(ACL_OBJECT_DATABASE, ownerId); else /* get a detoasted copy of the ACL */ old_acl = DatumGetAclPCopy(aclDatum); new_acl = merge_acl_with_grant(old_acl, stmt->is_grant, - stmt->grantees, privileges, stmt->grant_option, stmt->behavior, - pg_database_tuple->datdba); + stmt->grantees, privileges, + grantorId, ownerId); /* finished building new ACL value, now insert it */ MemSet(values, 0, sizeof(values)); @@ -426,6 +461,8 @@ ExecuteGrantStmt_Function(GrantStmt *stmt) bool isNull; Acl *old_acl; Acl *new_acl; + AclId grantorId; + AclId ownerId; HeapTuple newtuple; Datum values[Natts_pg_proc]; char nulls[Natts_pg_proc]; @@ -441,29 +478,31 @@ ExecuteGrantStmt_Function(GrantStmt *stmt) elog(ERROR, "cache lookup failed for function %u", oid); pg_proc_tuple = (Form_pg_proc) GETSTRUCT(tuple); + 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) + && 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)); /* - * If there's no ACL, create a default using the pg_proc.proowner - * field. + * If there's no ACL, substitute the proper default. */ aclDatum = SysCacheGetAttr(PROCOID, tuple, Anum_pg_proc_proacl, &isNull); if (isNull) - old_acl = acldefault(ACL_OBJECT_FUNCTION, - pg_proc_tuple->proowner); + old_acl = acldefault(ACL_OBJECT_FUNCTION, ownerId); else /* get a detoasted copy of the ACL */ old_acl = DatumGetAclPCopy(aclDatum); new_acl = merge_acl_with_grant(old_acl, stmt->is_grant, - stmt->grantees, privileges, stmt->grant_option, stmt->behavior, - pg_proc_tuple->proowner); + stmt->grantees, privileges, + grantorId, ownerId); /* finished building new ACL value, now insert it */ MemSet(values, 0, sizeof(values)); @@ -522,6 +561,8 @@ ExecuteGrantStmt_Language(GrantStmt *stmt) bool isNull; Acl *old_acl; Acl *new_acl; + AclId grantorId; + AclId ownerId; HeapTuple newtuple; Datum values[Natts_pg_language]; char nulls[Natts_pg_language]; @@ -537,36 +578,40 @@ ExecuteGrantStmt_Language(GrantStmt *stmt) errmsg("language \"%s\" does not exist", langname))); pg_language_tuple = (Form_pg_language) GETSTRUCT(tuple); - if (!pg_language_tuple->lanpltrusted && stmt->is_grant) - 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() - && pg_language_aclcheck(HeapTupleGetOid(tuple), GetUserId(), ACL_GRANT_OPTION_FOR(privileges)) != ACLCHECK_OK) + && !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)); + if (!pg_language_tuple->lanpltrusted && stmt->is_grant) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("language \"%s\" is not trusted", langname))); + /* - * If there's no ACL, create a default. - * - * Note: for now, languages are treated as owned by the bootstrap - * user. We should add an owner column to pg_language instead. + * If there's no ACL, substitute the proper default. */ aclDatum = SysCacheGetAttr(LANGNAME, tuple, Anum_pg_language_lanacl, &isNull); if (isNull) - old_acl = acldefault(ACL_OBJECT_LANGUAGE, - BOOTSTRAP_USESYSID); + old_acl = acldefault(ACL_OBJECT_LANGUAGE, ownerId); else /* get a detoasted copy of the ACL */ old_acl = DatumGetAclPCopy(aclDatum); new_acl = merge_acl_with_grant(old_acl, stmt->is_grant, - stmt->grantees, privileges, stmt->grant_option, stmt->behavior, - BOOTSTRAP_USESYSID); + stmt->grantees, privileges, + grantorId, ownerId); /* finished building new ACL value, now insert it */ MemSet(values, 0, sizeof(values)); @@ -625,6 +670,8 @@ ExecuteGrantStmt_Namespace(GrantStmt *stmt) bool isNull; Acl *old_acl; Acl *new_acl; + AclId grantorId; + AclId ownerId; HeapTuple newtuple; Datum values[Natts_pg_namespace]; char nulls[Natts_pg_namespace]; @@ -640,30 +687,32 @@ ExecuteGrantStmt_Namespace(GrantStmt *stmt) errmsg("schema \"%s\" does not exist", nspname))); pg_namespace_tuple = (Form_pg_namespace) GETSTRUCT(tuple); + 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) + && !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); /* - * If there's no ACL, create a default using the - * pg_namespace.nspowner field. + * If there's no ACL, substitute the proper default. */ aclDatum = SysCacheGetAttr(NAMESPACENAME, tuple, Anum_pg_namespace_nspacl, &isNull); if (isNull) - old_acl = acldefault(ACL_OBJECT_NAMESPACE, - pg_namespace_tuple->nspowner); + old_acl = acldefault(ACL_OBJECT_NAMESPACE, ownerId); else /* get a detoasted copy of the ACL */ old_acl = DatumGetAclPCopy(aclDatum); new_acl = merge_acl_with_grant(old_acl, stmt->is_grant, - stmt->grantees, privileges, stmt->grant_option, stmt->behavior, - pg_namespace_tuple->nspowner); + stmt->grantees, privileges, + grantorId, ownerId); /* finished building new ACL value, now insert it */ MemSet(values, 0, sizeof(values)); @@ -1032,7 +1081,7 @@ pg_class_aclcheck(Oid table_oid, AclId userid, AclMode mode) &isNull); if (isNull) { - /* No ACL, so build default ACL for rel */ + /* No ACL, so build default ACL */ AclId ownerId; ownerId = ((Form_pg_class) GETSTRUCT(tuple))->relowner;