diff --git a/doc/src/sgml/ref/grant.sgml b/doc/src/sgml/ref/grant.sgml index b2ad631052..a43de6356d 100644 --- a/doc/src/sgml/ref/grant.sgml +++ b/doc/src/sgml/ref/grant.sgml @@ -1,5 +1,5 @@ @@ -66,19 +66,21 @@ GRANT { { CREATE | USAGE } [,...] | ALL [ PRIVILEGES ] } - 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 WITH GRANT OPTION 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 PUBLIC. - If WITH GRANT OPTION 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 PUBLIC. + 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. @@ -263,6 +265,13 @@ GRANT { { CREATE | USAGE } [,...] | ALL [ PRIVILEGES ] } except when absolutely necessary. + + If a superuser chooses to issue a GRANT or 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. + + Currently, to grant privileges in PostgreSQL to only a few columns, you must diff --git a/doc/src/sgml/ref/revoke.sgml b/doc/src/sgml/ref/revoke.sgml index 557a219f77..cb69c707b7 100644 --- a/doc/src/sgml/ref/revoke.sgml +++ b/doc/src/sgml/ref/revoke.sgml @@ -1,5 +1,5 @@ @@ -63,6 +63,11 @@ REVOKE [ GRANT OPTION FOR ] all users. + + See the description of the command for + the meaning of the privilege types. + + 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. - - See the description of the command for - the meaning of the privilege types. - - If GRANT OPTION FOR is specified, only the grant option for the privilege is revoked, not the privilege itself. @@ -116,6 +116,15 @@ REVOKE [ GRANT OPTION FOR ] the CASCADE option so that the privilege is automatically revoked from user C. + + + If a superuser chooses to issue a GRANT or 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 CASCADE as stated above. + @@ -153,7 +162,8 @@ REVOKE [ GRANT OPTION FOR ] privileges One of RESTRICT or CASCADE - is required. + is required according to the standard, but PostgreSQL + assumes RESTRICT by default. diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c index 437453a03b..bf43769d70 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); + /* + * 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)); + if (!pg_language_tuple->lanpltrusted && stmt->is_grant) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("language \"%s\" is not trusted", langname))); - if (stmt->is_grant - && !superuser() - && 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 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;