diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index 830d71779a..b3de02ef06 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -1,5 +1,5 @@ @@ -5786,6 +5786,12 @@ SELECT has_table_privilege('myschema.mytable', 'select'); USAGE. + + To evaluate whether a user holds a grant option on the privilege, + append WITH GRANT OPTION to the privilege key + word; for example 'UPDATE WITH GRANT OPTION'. + + shows functions that determine whether a certain object is visible in the diff --git a/doc/src/sgml/ref/grant.sgml b/doc/src/sgml/ref/grant.sgml index d8830498c6..5457a950ed 100644 --- a/doc/src/sgml/ref/grant.sgml +++ b/doc/src/sgml/ref/grant.sgml @@ -1,5 +1,5 @@ @@ -19,23 +19,23 @@ PostgreSQL documentation GRANT { { SELECT | INSERT | UPDATE | DELETE | RULE | REFERENCES | TRIGGER } [,...] | ALL [ PRIVILEGES ] } ON [ TABLE ] tablename [, ...] - TO { username | GROUP groupname | PUBLIC } [, ...] + TO { username | GROUP groupname | PUBLIC } [, ...] [ WITH GRANT OPTION ] GRANT { { CREATE | TEMPORARY | TEMP } [,...] | ALL [ PRIVILEGES ] } ON DATABASE dbname [, ...] - TO { username | GROUP groupname | PUBLIC } [, ...] + TO { username | GROUP groupname | PUBLIC } [, ...] [ WITH GRANT OPTION ] GRANT { EXECUTE | ALL [ PRIVILEGES ] } ON FUNCTION funcname ([type, ...]) [, ...] - TO { username | GROUP groupname | PUBLIC } [, ...] + TO { username | GROUP groupname | PUBLIC } [, ...] [ WITH GRANT OPTION ] GRANT { USAGE | ALL [ PRIVILEGES ] } ON LANGUAGE langname [, ...] - TO { username | GROUP groupname | PUBLIC } [, ...] + TO { username | GROUP groupname | PUBLIC } [, ...] [ WITH GRANT OPTION ] GRANT { { CREATE | USAGE } [,...] | ALL [ PRIVILEGES ] } ON SCHEMA schemaname [, ...] - TO { username | GROUP groupname | PUBLIC } [, ...] + TO { username | GROUP groupname | PUBLIC } [, ...] [ WITH GRANT OPTION ] @@ -63,13 +63,18 @@ GRANT { { CREATE | USAGE } [,...] | ALL [ PRIVILEGES ] } There is no need to grant privileges to the creator of an object, - as the creator has all privileges by default. - (The creator could, however, choose to revoke - some of his own privileges for safety.) Note that the ability to - grant and revoke privileges is inherent in the creator and cannot - be lost. The right to drop an object, or to alter it in any way - not described by a grantable right, is likewise inherent in the - creator, and cannot be granted or revoked. + as the creator has all privileges by default. (The creator could, + however, choose to revoke some of his own privileges for safety.) + Note that 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 creator, + 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 possible. Grant options can only be granted to individual + users, not groups or PUBLIC. @@ -269,7 +274,7 @@ lusitania=> \dp mytable Access privileges for database "lusitania" Schema | Table | Access privileges --------+---------+--------------------------------------- - public | mytable | {=r,miriam=arwdRxt,"group todos=arw"} + public | mytable | {=r/postgres,miriam=arwdRxt/postgres,"group todos=arw/postgres"} (1 row) The entries shown by \dp are interpreted thus: @@ -290,6 +295,9 @@ lusitania=> \dp mytable C -- CREATE T -- TEMPORARY arwdRxt -- ALL PRIVILEGES (for tables) + * -- grant option for preceding privilege + + /yyyy -- user who granted this privilege The above example display would be seen by user miriam after @@ -346,13 +354,12 @@ GRANT ALL PRIVILEGES ON kinds TO manuel; - The SQL92 syntax for GRANT allows setting - privileges for individual columns within a table, and allows - setting a privilege to grant the same privileges to others: + The SQL syntax for GRANT + allows setting privileges for individual columns within a table: GRANT privilege [, ...] - ON object [ ( column [, ...] ) ] [, ...] + ON table [ ( column [, ...] ) ] [, ...] TO { PUBLIC | username [, ...] } [ WITH GRANT OPTION ] diff --git a/doc/src/sgml/ref/revoke.sgml b/doc/src/sgml/ref/revoke.sgml index 345024bb7c..e2ec87d5e7 100644 --- a/doc/src/sgml/ref/revoke.sgml +++ b/doc/src/sgml/ref/revoke.sgml @@ -1,5 +1,5 @@ @@ -16,31 +16,36 @@ PostgreSQL documentation -REVOKE { { SELECT | INSERT | UPDATE | DELETE | RULE | REFERENCES | TRIGGER } +REVOKE [ GRANT OPTION FOR ] + { { SELECT | INSERT | UPDATE | DELETE | RULE | REFERENCES | TRIGGER } [,...] | ALL [ PRIVILEGES ] } ON [ TABLE ] tablename [, ...] FROM { username | GROUP groupname | PUBLIC } [, ...] - [ RESTRICT ] + [ CASCADE | RESTRICT ] -REVOKE { { CREATE | TEMPORARY | TEMP } [,...] | ALL [ PRIVILEGES ] } +REVOKE [ GRANT OPTION FOR ] + { { CREATE | TEMPORARY | TEMP } [,...] | ALL [ PRIVILEGES ] } ON DATABASE dbname [, ...] FROM { username | GROUP groupname | PUBLIC } [, ...] - [ RESTRICT ] + [ CASCADE | RESTRICT ] -REVOKE { EXECUTE | ALL [ PRIVILEGES ] } +REVOKE [ GRANT OPTION FOR ] + { EXECUTE | ALL [ PRIVILEGES ] } ON FUNCTION funcname ([type, ...]) [, ...] FROM { username | GROUP groupname | PUBLIC } [, ...] - [ RESTRICT ] + [ CASCADE | RESTRICT ] -REVOKE { USAGE | ALL [ PRIVILEGES ] } +REVOKE [ GRANT OPTION FOR ] + { USAGE | ALL [ PRIVILEGES ] } ON LANGUAGE langname [, ...] FROM { username | GROUP groupname | PUBLIC } [, ...] - [ RESTRICT ] + [ CASCADE | RESTRICT ] -REVOKE { { CREATE | USAGE } [,...] | ALL [ PRIVILEGES ] } +REVOKE [ GRANT OPTION FOR ] + { { CREATE | USAGE } [,...] | ALL [ PRIVILEGES ] } ON SCHEMA schemaname [, ...] FROM { username | GROUP groupname | PUBLIC } [, ...] - [ RESTRICT ] + [ CASCADE | RESTRICT ] @@ -70,8 +75,22 @@ REVOKE { { CREATE | USAGE } [,...] | ALL [ PRIVILEGES ] } - The RESTRICT key word is currently only noise. - See also the compatibility notes below. + If GRANT OPTION FOR is specified, only the grant + option for the privilege is revoked, not the privilege itself. + + + + If a user holds a privilege with grant option and has granted it to + other users then the privileges held by those other users are + called dependent privileges. If the privilege or the grant option + held by the first user is being revoked and dependent privileges + exist, those dependent privileges are also revoked if + CASCADE is specified, else the revoke action + will fail. This recursive revocation only affects privileges that + were granted through a chain of users that is traceable to the user + that is the subject of this REVOKE command. + Thus, the affected users may effectively keep the privilege if it + was also granted through other users. @@ -83,6 +102,16 @@ REVOKE { { CREATE | USAGE } [,...] | ALL [ PRIVILEGES ] } display the privileges granted on existing objects. See also for information about the format. + + + A user can only revoke privileges that were granted directly by + that user. If, for example, user A has granted a privilege with + grant option to user B, and user B has in turned granted it to user + 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 CASCADE option so that the privilege is + automatically revoked from user C. + @@ -122,16 +151,8 @@ REVOKE [ GRANT OPTION FOR ] { SELECT | INSERT | UPDATE | DELETE | REFERENCES } FROM { PUBLIC | username [, ...] } { RESTRICT | CASCADE } - - - - If user1 gives a privilege WITH GRANT OPTION to user2, - and user2 gives it to user3 then user1 can revoke - this privilege in cascade using the CASCADE keyword. - If user1 gives a privilege WITH GRANT OPTION to user2, - and user2 gives it to user3, then if user1 tries to revoke - this privilege it fails if he specifies the RESTRICT - keyword. + One of RESTRICT or CASCADE + is required. diff --git a/doc/src/sgml/release.sgml b/doc/src/sgml/release.sgml index 2911e1828f..18d71f5866 100644 --- a/doc/src/sgml/release.sgml +++ b/doc/src/sgml/release.sgml @@ -1,5 +1,5 @@ @@ -38,6 +38,7 @@ ON COMMIT options for temp tables extra_float_digits option allows pg_dump to dump float data accurately Long options for psql and pg_dump are now available on all platforms Read-only transactions +Object owners can allow grantees to grant the privilege to others (grant option) ]]> diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c index 0b1ce10e1a..639f464bc3 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.79 2002/12/04 05:18:31 momjian Exp $ + * $Header: /cvsroot/pgsql/src/backend/catalog/aclchk.c,v 1.80 2003/01/23 23:38:55 petere Exp $ * * NOTES * See acl.h. @@ -47,7 +47,7 @@ static void ExecuteGrantStmt_Namespace(GrantStmt *stmt); static const char *privilege_to_string(AclMode privilege); -static AclResult aclcheck(Acl *acl, AclId id, uint32 idtype, AclMode mode); +static AclResult aclcheck(Acl *acl, AclId userid, AclMode mode); #ifdef ACLDEBUG @@ -75,7 +75,8 @@ dumpacl(Acl *acl) */ static Acl * merge_acl_with_grant(Acl *old_acl, bool is_grant, - List *grantees, AclMode privileges) + List *grantees, AclMode privileges, + bool grant_option, DropBehavior behavior) { unsigned modechg; List *j; @@ -96,26 +97,38 @@ merge_acl_with_grant(Acl *old_acl, bool is_grant, if (grantee->username) { - aclitem. ai_id = get_usesysid(grantee->username); - + aclitem.ai_grantee = get_usesysid(grantee->username); idtype = ACL_IDTYPE_UID; } else if (grantee->groupname) { - aclitem. ai_id = get_grosysid(grantee->groupname); - + aclitem.ai_grantee = get_grosysid(grantee->groupname); idtype = ACL_IDTYPE_GID; } else { - aclitem. ai_id = ACL_ID_WORLD; - + aclitem.ai_grantee = ACL_ID_WORLD; idtype = ACL_IDTYPE_WORLD; } - ACLITEM_SET_PRIVS_IDTYPE(aclitem, privileges, idtype); + /* + * Grant options can only be granted to individual users, not + * groups or public. The reason is that if a user would + * re-grant a privilege that he held through a group having a + * grant option, 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) + elog(ERROR, "grant options can only be granted to individual users"); - new_acl = aclinsert3(new_acl, &aclitem, modechg); + aclitem.ai_grantor = GetUserId(); + + ACLITEM_SET_PRIVS_IDTYPE(aclitem, + (is_grant || !grant_option) ? privileges : ACL_NO_RIGHTS, + (grant_option || !is_grant) ? privileges : ACL_NO_RIGHTS, + idtype); + + new_acl = aclinsert3(new_acl, &aclitem, modechg, behavior); #ifdef ACLDEBUG dumpacl(new_acl); @@ -202,8 +215,10 @@ ExecuteGrantStmt_Relation(GrantStmt *stmt) elog(ERROR, "relation %u not found", relOid); pg_class_tuple = (Form_pg_class) GETSTRUCT(tuple); - if (!pg_class_ownercheck(relOid, GetUserId())) - aclcheck_error(ACLCHECK_NOT_OWNER, relvar->relname); + 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, relvar->relname); if (pg_class_tuple->relkind == RELKIND_INDEX) elog(ERROR, "\"%s\" is an index", @@ -223,7 +238,8 @@ ExecuteGrantStmt_Relation(GrantStmt *stmt) old_acl = DatumGetAclPCopy(aclDatum); new_acl = merge_acl_with_grant(old_acl, stmt->is_grant, - stmt->grantees, privileges); + stmt->grantees, privileges, + stmt->grant_option, stmt->behavior); /* finished building new ACL value, now insert it */ MemSet(values, 0, sizeof(values)); @@ -298,8 +314,10 @@ ExecuteGrantStmt_Database(GrantStmt *stmt) elog(ERROR, "database \"%s\" not found", dbname); pg_database_tuple = (Form_pg_database) GETSTRUCT(tuple); - if (!superuser() && pg_database_tuple->datdba != GetUserId()) - elog(ERROR, "permission denied"); + if (stmt->is_grant + && pg_database_tuple->datdba != GetUserId() + && pg_database_aclcheck(HeapTupleGetOid(tuple), GetUserId(), ACL_GRANT_OPTION_FOR(privileges)) != ACLCHECK_OK) + aclcheck_error(ACLCHECK_NO_PRIV, NameStr(pg_database_tuple->datname)); /* * If there's no ACL, create a default. @@ -314,7 +332,8 @@ ExecuteGrantStmt_Database(GrantStmt *stmt) old_acl = DatumGetAclPCopy(aclDatum); new_acl = merge_acl_with_grant(old_acl, stmt->is_grant, - stmt->grantees, privileges); + stmt->grantees, privileges, + stmt->grant_option, stmt->behavior); /* finished building new ACL value, now insert it */ MemSet(values, 0, sizeof(values)); @@ -389,8 +408,10 @@ ExecuteGrantStmt_Function(GrantStmt *stmt) elog(ERROR, "function %u not found", oid); pg_proc_tuple = (Form_pg_proc) GETSTRUCT(tuple); - if (!pg_proc_ownercheck(oid, GetUserId())) - aclcheck_error(ACLCHECK_NOT_OWNER, + 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, NameStr(pg_proc_tuple->proname)); /* @@ -407,7 +428,8 @@ ExecuteGrantStmt_Function(GrantStmt *stmt) old_acl = DatumGetAclPCopy(aclDatum); new_acl = merge_acl_with_grant(old_acl, stmt->is_grant, - stmt->grantees, privileges); + stmt->grantees, privileges, + stmt->grant_option, stmt->behavior); /* finished building new ACL value, now insert it */ MemSet(values, 0, sizeof(values)); @@ -470,9 +492,6 @@ ExecuteGrantStmt_Language(GrantStmt *stmt) char nulls[Natts_pg_language]; char replaces[Natts_pg_language]; - if (!superuser()) - elog(ERROR, "permission denied"); - relation = heap_openr(LanguageRelationName, RowExclusiveLock); tuple = SearchSysCache(LANGNAME, PointerGetDatum(langname), @@ -484,6 +503,11 @@ ExecuteGrantStmt_Language(GrantStmt *stmt) if (!pg_language_tuple->lanpltrusted && stmt->is_grant) elog(ERROR, "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, NameStr(pg_language_tuple->lanname)); + /* * If there's no ACL, create a default. */ @@ -497,7 +521,8 @@ ExecuteGrantStmt_Language(GrantStmt *stmt) old_acl = DatumGetAclPCopy(aclDatum); new_acl = merge_acl_with_grant(old_acl, stmt->is_grant, - stmt->grantees, privileges); + stmt->grantees, privileges, + stmt->grant_option, stmt->behavior); /* finished building new ACL value, now insert it */ MemSet(values, 0, sizeof(values)); @@ -568,8 +593,10 @@ ExecuteGrantStmt_Namespace(GrantStmt *stmt) elog(ERROR, "namespace \"%s\" not found", nspname); pg_namespace_tuple = (Form_pg_namespace) GETSTRUCT(tuple); - if (!pg_namespace_ownercheck(HeapTupleGetOid(tuple), GetUserId())) - aclcheck_error(ACLCHECK_NOT_OWNER, nspname); + 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, nspname); /* * If there's no ACL, create a default using the @@ -586,7 +613,8 @@ ExecuteGrantStmt_Namespace(GrantStmt *stmt) old_acl = DatumGetAclPCopy(aclDatum); new_acl = merge_acl_with_grant(old_acl, stmt->is_grant, - stmt->grantees, privileges); + stmt->grantees, privileges, + stmt->grant_option, stmt->behavior); /* finished building new ACL value, now insert it */ MemSet(values, 0, sizeof(values)); @@ -741,125 +769,53 @@ in_group(AclId uid, AclId gid) /* * aclcheck * - * Returns ACLCHECK_OK if the 'id' of type 'idtype' has ACL entries in 'acl' - * to satisfy any one of the requirements of 'mode'. Returns an appropriate - * ACLCHECK_* error code otherwise. - * - * The ACL list is expected to be sorted in standard order. + * Returns ACLCHECK_OK if the 'userid' has ACL entries in 'acl' to + * satisfy any one of the requirements of 'mode'. Returns an + * appropriate ACLCHECK_* error code otherwise. */ static AclResult -aclcheck(Acl *acl, AclId id, uint32 idtype, AclMode mode) +aclcheck(Acl *acl, AclId userid, AclMode mode) { - AclItem *aip, - *aidat; + AclItem *aidat; int i, num; /* - * If ACL is null, default to "OK" --- this should not happen, since - * caller should have inserted appropriate default + * Null ACL should not happen, since caller should have inserted + * appropriate default */ - if (!acl) + if (acl == NULL) { - elog(DEBUG1, "aclcheck: null ACL, returning OK"); - return ACLCHECK_OK; + elog(ERROR, "aclcheck: internal error -- null ACL"); + return ACLCHECK_NO_PRIV; } num = ACL_NUM(acl); aidat = ACL_DAT(acl); /* - * We'll treat the empty ACL like that, too, although this is more - * like an error (i.e., you manually blew away your ACL array) -- the - * system never creates an empty ACL, since there must always be a - * "world" entry in the first slot. + * See if privilege is granted directly to user or to public */ - if (num < 1) - { - elog(DEBUG1, "aclcheck: zero-length ACL, returning OK"); - return ACLCHECK_OK; - } - + for (i = 0; i < num; i++) + if (ACLITEM_GET_IDTYPE(aidat[i]) == ACL_IDTYPE_WORLD + || (ACLITEM_GET_IDTYPE(aidat[i]) == ACL_IDTYPE_UID + && aidat[i].ai_grantee == userid)) + { + if (aidat[i].ai_privs & mode) + return ACLCHECK_OK; + } + /* - * "World" rights are applicable regardless of the passed-in ID, and - * since they're much the cheapest to check, check 'em first. + * See if he has the permission via any group (do this in a + * separate pass to avoid expensive(?) lookups in pg_group) */ - if (ACLITEM_GET_IDTYPE(*aidat) != ACL_IDTYPE_WORLD) - elog(ERROR, "aclcheck: first entry in ACL is not 'world' entry"); - if (aidat->ai_privs & mode) - { -#ifdef ACLDEBUG - elog(DEBUG1, "aclcheck: using world=%d", ACLITEM_GET_PRIVS(*aidat)); -#endif - return ACLCHECK_OK; - } + for (i = 0; i < num; i++) + if (ACLITEM_GET_IDTYPE(aidat[i]) == ACL_IDTYPE_GID + && aidat[i].ai_privs & mode + && in_group(userid, aidat[i].ai_grantee)) + return ACLCHECK_OK; - switch (idtype) - { - case ACL_IDTYPE_UID: - /* See if permission is granted directly to user */ - for (i = 1, aip = aidat + 1; /* skip world entry */ - i < num && ACLITEM_GET_IDTYPE(*aip) == ACL_IDTYPE_UID; - ++i, ++aip) - { - if (aip->ai_id == id) - { -#ifdef ACLDEBUG - elog(DEBUG1, "aclcheck: found user %u/%d", - aip->ai_id, ACLITEM_GET_PRIVS(*aip)); -#endif - if (aip->ai_privs & mode) - return ACLCHECK_OK; - } - } - /* See if he has the permission via any group */ - for (; - i < num && ACLITEM_GET_IDTYPE(*aip) == ACL_IDTYPE_GID; - ++i, ++aip) - { - if (aip->ai_privs & mode) - { - if (in_group(id, aip->ai_id)) - { -#ifdef ACLDEBUG - elog(DEBUG1, "aclcheck: found group %u/%d", - aip->ai_id, ACLITEM_GET_PRIVS(*aip)); -#endif - return ACLCHECK_OK; - } - } - } - break; - case ACL_IDTYPE_GID: - /* Look for this group ID */ - for (i = 1, aip = aidat + 1; /* skip world entry */ - i < num && ACLITEM_GET_IDTYPE(*aip) == ACL_IDTYPE_UID; - ++i, ++aip) - /* skip UID entry */ ; - for (; - i < num && ACLITEM_GET_IDTYPE(*aip) == ACL_IDTYPE_GID; - ++i, ++aip) - { - if (aip->ai_id == id) - { -#ifdef ACLDEBUG - elog(DEBUG1, "aclcheck: found group %u/%d", - aip->ai_id, ACLITEM_GET_PRIVS(*aip)); -#endif - if (aip->ai_privs & mode) - return ACLCHECK_OK; - } - } - break; - case ACL_IDTYPE_WORLD: - /* Only check the world entry */ - break; - default: - elog(ERROR, "aclcheck: bogus ACL id type: %d", idtype); - break; - } - - /* If get here, he doesn't have the privilege nohow */ + /* If here, doesn't have the privilege. */ return ACLCHECK_NO_PRIV; } @@ -976,7 +932,7 @@ pg_class_aclcheck(Oid table_oid, AclId userid, AclMode mode) acl = DatumGetAclP(aclDatum); } - result = aclcheck(acl, userid, ACL_IDTYPE_UID, mode); + result = aclcheck(acl, userid, mode); /* if we have a detoasted copy, free it */ if (acl && (Pointer) acl != DatumGetPointer(aclDatum)) @@ -1038,7 +994,7 @@ pg_database_aclcheck(Oid db_oid, AclId userid, AclMode mode) acl = DatumGetAclP(aclDatum); } - result = aclcheck(acl, userid, ACL_IDTYPE_UID, mode); + result = aclcheck(acl, userid, mode); /* if we have a detoasted copy, free it */ if (acl && (Pointer) acl != DatumGetPointer(aclDatum)) @@ -1092,7 +1048,7 @@ pg_proc_aclcheck(Oid proc_oid, AclId userid, AclMode mode) acl = DatumGetAclP(aclDatum); } - result = aclcheck(acl, userid, ACL_IDTYPE_UID, mode); + result = aclcheck(acl, userid, mode); /* if we have a detoasted copy, free it */ if (acl && (Pointer) acl != DatumGetPointer(aclDatum)) @@ -1142,7 +1098,7 @@ pg_language_aclcheck(Oid lang_oid, AclId userid, AclMode mode) acl = DatumGetAclP(aclDatum); } - result = aclcheck(acl, userid, ACL_IDTYPE_UID, mode); + result = aclcheck(acl, userid, mode); /* if we have a detoasted copy, free it */ if (acl && (Pointer) acl != DatumGetPointer(aclDatum)) @@ -1202,7 +1158,7 @@ pg_namespace_aclcheck(Oid nsp_oid, AclId userid, AclMode mode) acl = DatumGetAclP(aclDatum); } - result = aclcheck(acl, userid, ACL_IDTYPE_UID, mode); + result = aclcheck(acl, userid, mode); /* if we have a detoasted copy, free it */ if (acl && (Pointer) acl != DatumGetPointer(aclDatum)) diff --git a/src/backend/catalog/sql_features.txt b/src/backend/catalog/sql_features.txt index 90076de6bd..8c1f0bbf63 100644 --- a/src/backend/catalog/sql_features.txt +++ b/src/backend/catalog/sql_features.txt @@ -72,7 +72,7 @@ E081 Basic Privileges 04 UPDATE privilege at the table level YES E081 Basic Privileges 05 UPDATE privilege at the column level NO E081 Basic Privileges 06 REFERENCES privilege at the table level YES E081 Basic Privileges 07 REFERENCES privilege at the column level NO -E081 Basic Privileges 08 WITH GRANT OPTION NO +E081 Basic Privileges 08 WITH GRANT OPTION YES E091 Set functions YES E091 Set functions 01 AVG YES E091 Set functions 02 COUNT YES @@ -133,10 +133,10 @@ F031 Basic schema manipulation 16 DROP VIEW statement: RESTRICT clause YES F031 Basic schema manipulation 19 REVOKE statement: RESTRICT clause YES F032 CASCADE drop behavior YES F033 ALTER TABLE statement: DROP COLUMN clause YES -F034 Extended REVOKE statement NO -F034 Extended REVOKE statement 01 REVOKE statement performed by other than the owner of a schema object NO -F034 Extended REVOKE statement 02 REVOKE statement: GRANT OPTION FOR clause NO -F034 Extended REVOKE statement 03 REVOKE statement to revoke a privilege that the grantee has WITH GRANT OPTION NO +F034 Extended REVOKE statement YES +F034 Extended REVOKE statement 01 REVOKE statement performed by other than the owner of a schema object YES +F034 Extended REVOKE statement 02 REVOKE statement: GRANT OPTION FOR clause YES +F034 Extended REVOKE statement 03 REVOKE statement to revoke a privilege that the grantee has WITH GRANT OPTION YES F041 Basic joined table YES F041 Basic joined table 01 Inner join (but not necessarily the INNER keyword) YES F041 Basic joined table 02 INNER keyword YES diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index f8e81431ec..667e4f30c2 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -15,7 +15,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/nodes/copyfuncs.c,v 1.237 2003/01/20 18:54:46 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/nodes/copyfuncs.c,v 1.238 2003/01/23 23:38:56 petere Exp $ * *------------------------------------------------------------------------- */ @@ -1563,6 +1563,8 @@ _copyGrantStmt(GrantStmt *from) COPY_NODE_FIELD(objects); COPY_INTLIST_FIELD(privileges); COPY_NODE_FIELD(grantees); + COPY_SCALAR_FIELD(grant_option); + COPY_SCALAR_FIELD(behavior); return newnode; } diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index 5d3e194e3c..1982973696 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -18,7 +18,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/nodes/equalfuncs.c,v 1.181 2003/01/20 18:54:46 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/nodes/equalfuncs.c,v 1.182 2003/01/23 23:38:56 petere Exp $ * *------------------------------------------------------------------------- */ @@ -635,6 +635,8 @@ _equalGrantStmt(GrantStmt *a, GrantStmt *b) COMPARE_NODE_FIELD(objects); COMPARE_INTLIST_FIELD(privileges); COMPARE_NODE_FIELD(grantees); + COMPARE_SCALAR_FIELD(grant_option); + COMPARE_SCALAR_FIELD(behavior); return true; } diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 2703ae8a06..ff79e7a39f 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -11,7 +11,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/parser/gram.y,v 2.395 2003/01/10 22:03:27 petere Exp $ + * $Header: /cvsroot/pgsql/src/backend/parser/gram.y,v 2.396 2003/01/23 23:38:56 petere Exp $ * * HISTORY * AUTHOR DATE MAJOR EVENT @@ -163,6 +163,7 @@ static void doNegateFloat(Value *v); %type opt_lock lock_type cast_context %type opt_force opt_or_replace transaction_access_mode + opt_grant_grant_option opt_revoke_grant_option %type user_list @@ -2737,6 +2738,7 @@ GrantStmt: GRANT privileges ON privilege_target TO grantee_list n->objtype = ($4)->objtype; n->objects = ($4)->objs; n->grantees = $6; + n->grant_option = $7; $$ = (Node*)n; } ; @@ -2750,9 +2752,8 @@ RevokeStmt: REVOKE opt_revoke_grant_option privileges ON privilege_target n->objtype = ($5)->objtype; n->objects = ($5)->objs; n->grantees = $7; - - if ($8 == DROP_CASCADE) - elog(ERROR, "REVOKE ... CASCADE is not implemented"); + n->grant_option = $2; + n->behavior = $8; $$ = (Node *)n; } @@ -2867,19 +2868,13 @@ grantee: ColId opt_grant_grant_option: - WITH GRANT OPTION - { - elog(ERROR, "grant options are not implemented"); - } - | /*EMPTY*/ + WITH GRANT OPTION { $$ = TRUE; } + | /*EMPTY*/ { $$ = FALSE; } ; opt_revoke_grant_option: - GRANT OPTION FOR - { - elog(ERROR, "grant options are not implemented"); - } - | /*EMPTY*/ + GRANT OPTION FOR { $$ = TRUE; } + | /*EMPTY*/ { $$ = FALSE; } ; diff --git a/src/backend/utils/adt/acl.c b/src/backend/utils/adt/acl.c index a0e94b5bbb..5e98556b31 100644 --- a/src/backend/utils/adt/acl.c +++ b/src/backend/utils/adt/acl.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/utils/adt/acl.c,v 1.84 2002/12/05 04:04:42 momjian Exp $ + * $Header: /cvsroot/pgsql/src/backend/utils/adt/acl.c,v 1.85 2003/01/23 23:39:01 petere Exp $ * *------------------------------------------------------------------------- */ @@ -32,9 +32,10 @@ static const char *getid(const char *s, char *n); static Acl *makeacl(int n); -static const char *aclparse(const char *s, AclItem *aip, unsigned *modechg); +static const char *aclparse(const char *s, AclItem *aip); static bool aclitemeq(const AclItem *a1, const AclItem *a2); -static bool aclitemgt(const AclItem *a1, const AclItem *a2); +static Acl *recursive_revoke(Acl *acl, AclId grantee, + AclMode revoke_privs, DropBehavior behavior); static Oid convert_table_name(text *tablename); static AclMode convert_table_priv_string(text *priv_type_text); @@ -102,7 +103,7 @@ getid(const char *s, char *n) /* * aclparse * Consumes and parses an ACL specification of the form: - * [group|user] [A-Za-z0-9]*[+-=][rwaR]* + * [group|user] [A-Za-z0-9]*=[rwaR]* * from string 's', ignoring any leading white space or white space * between the optional id type keyword (group|user) and the actual * ACL specification. @@ -115,25 +116,23 @@ getid(const char *s, char *n) * specification. Also: * - loads the structure pointed to by 'aip' with the appropriate * UID/GID, id type identifier and mode type values. - * - loads 'modechg' with the mode change flag. */ static const char * -aclparse(const char *s, AclItem *aip, unsigned *modechg) +aclparse(const char *s, AclItem *aip) { - AclMode privs; + AclMode privs, goption, read; uint32 idtype; char name[NAMEDATALEN]; + char name2[NAMEDATALEN]; - Assert(s && aip && modechg); + Assert(s && aip); #ifdef ACLDEBUG elog(LOG, "aclparse: input = '%s'", s); #endif idtype = ACL_IDTYPE_UID; s = getid(s, name); - if (*s != ACL_MODECHG_ADD_CHR && - *s != ACL_MODECHG_DEL_CHR && - *s != ACL_MODECHG_EQL_CHR) + if (*s != '=') { /* we just read a keyword, not a name */ if (strncmp(name, ACL_IDTYPE_GID_KEYWORD, sizeof(name)) == 0) @@ -147,87 +146,93 @@ aclparse(const char *s, AclItem *aip, unsigned *modechg) if (name[0] == '\0') idtype = ACL_IDTYPE_WORLD; - switch (*s) - { - case ACL_MODECHG_ADD_CHR: - *modechg = ACL_MODECHG_ADD; - break; - case ACL_MODECHG_DEL_CHR: - *modechg = ACL_MODECHG_DEL; - break; - case ACL_MODECHG_EQL_CHR: - *modechg = ACL_MODECHG_EQL; - break; - default: - elog(ERROR, "aclparse: mode change flag must use \"%c%c%c\"", - ACL_MODECHG_ADD_CHR, - ACL_MODECHG_DEL_CHR, - ACL_MODECHG_EQL_CHR); - } + if (*s != '=') + elog(ERROR, "aclparse: expecting \"=\" sign"); - privs = ACL_NO_RIGHTS; + privs = goption = ACL_NO_RIGHTS; - while (isalpha((unsigned char) *++s)) + for (++s, read=0; isalpha((unsigned char) *s) || *s == '*'; s++) { switch (*s) { + case '*': + goption |= read; + break; case ACL_INSERT_CHR: - privs |= ACL_INSERT; + read = ACL_INSERT; break; case ACL_SELECT_CHR: - privs |= ACL_SELECT; + read = ACL_SELECT; break; case ACL_UPDATE_CHR: - privs |= ACL_UPDATE; + read = ACL_UPDATE; break; case ACL_DELETE_CHR: - privs |= ACL_DELETE; + read = ACL_DELETE; break; case ACL_RULE_CHR: - privs |= ACL_RULE; + read = ACL_RULE; break; case ACL_REFERENCES_CHR: - privs |= ACL_REFERENCES; + read = ACL_REFERENCES; break; case ACL_TRIGGER_CHR: - privs |= ACL_TRIGGER; + read = ACL_TRIGGER; break; case ACL_EXECUTE_CHR: - privs |= ACL_EXECUTE; + read = ACL_EXECUTE; break; case ACL_USAGE_CHR: - privs |= ACL_USAGE; + read = ACL_USAGE; break; case ACL_CREATE_CHR: - privs |= ACL_CREATE; + read = ACL_CREATE; break; case ACL_CREATE_TEMP_CHR: - privs |= ACL_CREATE_TEMP; + read = ACL_CREATE_TEMP; break; default: elog(ERROR, "aclparse: mode flags must use \"%s\"", ACL_ALL_RIGHTS_STR); } + + privs |= read; } switch (idtype) { case ACL_IDTYPE_UID: - aip->ai_id = get_usesysid(name); + aip->ai_grantee = get_usesysid(name); break; case ACL_IDTYPE_GID: - aip->ai_id = get_grosysid(name); + aip->ai_grantee = get_grosysid(name); break; case ACL_IDTYPE_WORLD: - aip->ai_id = ACL_ID_WORLD; + aip->ai_grantee = ACL_ID_WORLD; break; } - ACLITEM_SET_PRIVS_IDTYPE(*aip, privs, idtype); + /* XXX Allow a degree of backward compatibility by defaulting the + * grantor to the superuser. */ + if (*s == '/') + { + s = getid(s + 1, name2); + if (name2[0] == '\0') + elog(ERROR, "aclparse: a name must follow the \"/\" sign"); + + aip->ai_grantor = get_usesysid(name2); + } + else + { + aip->ai_grantor = BOOTSTRAP_USESYSID; + elog(WARNING, "defaulting grantor to %u", BOOTSTRAP_USESYSID); + } + + ACLITEM_SET_PRIVS_IDTYPE(*aip, privs, goption, idtype); #ifdef ACLDEBUG - elog(LOG, "aclparse: correctly read [%x %d %x], modechg=%x", - idtype, aip->ai_id, privs, *modechg); + elog(LOG, "aclparse: correctly read [%x %d %x]", + idtype, aip->ai_grantee, privs); #endif return s; } @@ -271,12 +276,9 @@ aclitemin(PG_FUNCTION_ARGS) { const char *s = PG_GETARG_CSTRING(0); AclItem *aip; - unsigned modechg; aip = (AclItem *) palloc(sizeof(AclItem)); - s = aclparse(s, aip, &modechg); - if (modechg != ACL_MODECHG_EQL) - elog(ERROR, "aclitemin: cannot accept anything but = ACLs"); + s = aclparse(s, aip); while (isspace((unsigned char) *s)) ++s; if (*s) @@ -302,14 +304,14 @@ aclitemout(PG_FUNCTION_ARGS) unsigned i; char *tmpname; - p = out = palloc(strlen("group = ") + N_ACL_RIGHTS + NAMEDATALEN + 1); + p = out = palloc(strlen("group = ") + 2 * N_ACL_RIGHTS + 2* NAMEDATALEN + 2); *p = '\0'; switch (ACLITEM_GET_IDTYPE(*aip)) { case ACL_IDTYPE_UID: htup = SearchSysCache(SHADOWSYSID, - ObjectIdGetDatum(aip->ai_id), + ObjectIdGetDatum(aip->ai_grantee), 0, 0, 0); if (HeapTupleIsValid(htup)) { @@ -324,14 +326,14 @@ aclitemout(PG_FUNCTION_ARGS) char *tmp; tmp = DatumGetCString(DirectFunctionCall1(int4out, - Int32GetDatum((int32) aip->ai_id))); + Int32GetDatum((int32) aip->ai_grantee))); strcat(p, tmp); pfree(tmp); } break; case ACL_IDTYPE_GID: strcat(p, "group "); - tmpname = get_groname(aip->ai_id); + tmpname = get_groname(aip->ai_grantee); if (tmpname != NULL) strncat(p, tmpname, NAMEDATALEN); else @@ -340,7 +342,7 @@ aclitemout(PG_FUNCTION_ARGS) char *tmp; tmp = DatumGetCString(DirectFunctionCall1(int4out, - Int32GetDatum((int32) aip->ai_id))); + Int32GetDatum((int32) aip->ai_grantee))); strcat(p, tmp); pfree(tmp); } @@ -354,10 +356,43 @@ aclitemout(PG_FUNCTION_ARGS) } while (*p) ++p; + *p++ = '='; + for (i = 0; i < N_ACL_RIGHTS; ++i) - if (aip->ai_privs & (1 << i)) + { + if (ACLITEM_GET_PRIVS(*aip) & (1 << i)) *p++ = ACL_ALL_RIGHTS_STR[i]; + if (ACLITEM_GET_GOPTIONS(*aip) & (1 << i)) + *p++ = '*'; + } + + *p++ = '/'; + *p = '\0'; + + htup = SearchSysCache(SHADOWSYSID, + ObjectIdGetDatum(aip->ai_grantor), + 0, 0, 0); + if (HeapTupleIsValid(htup)) + { + strncat(p, + NameStr(((Form_pg_shadow) GETSTRUCT(htup))->usename), + NAMEDATALEN); + ReleaseSysCache(htup); + } + else + { + /* Generate numeric UID if we don't find an entry */ + char *tmp; + + tmp = DatumGetCString(DirectFunctionCall1(int4out, + Int32GetDatum((int32) aip->ai_grantor))); + strcat(p, tmp); + pfree(tmp); + } + + while (*p) + ++p; *p = '\0'; PG_RETURN_CSTRING(out); @@ -365,29 +400,15 @@ aclitemout(PG_FUNCTION_ARGS) /* * aclitemeq - * aclitemgt - * AclItem equality and greater-than comparison routines. * Two AclItems are considered equal iff they have the same - * identifier (and identifier type); the privileges are ignored. - * Note that these routines are really only useful for sorting - * AclItems into identifier order. - * - * RETURNS: - * a boolean value indicating = or > + * grantee and grantor; the privileges are ignored. */ static bool aclitemeq(const AclItem *a1, const AclItem *a2) { return ACLITEM_GET_IDTYPE(*a1) == ACLITEM_GET_IDTYPE(*a2) && - a1->ai_id == a2->ai_id; -} - -static bool -aclitemgt(const AclItem *a1, const AclItem *a2) -{ - return ((ACLITEM_GET_IDTYPE(*a1) > ACLITEM_GET_IDTYPE(*a2)) || - (ACLITEM_GET_IDTYPE(*a1) == ACLITEM_GET_IDTYPE(*a2) && - a1->ai_id > a2->ai_id)); + a1->ai_grantee == a2->ai_grantee && + a1->ai_grantor == a2->ai_grantor; } @@ -436,15 +457,25 @@ acldefault(GrantObjectType objtype, AclId ownerid) break; } - acl = makeacl(ownerid ? 2 : 1); + acl = makeacl((world_default != ACL_NO_RIGHTS ? 1 : 0) + + (ownerid ? 1 : 0)); aip = ACL_DAT(acl); - aip[0].ai_id = ACL_ID_WORLD; - ACLITEM_SET_PRIVS_IDTYPE(aip[0], world_default, ACL_IDTYPE_WORLD); + if (world_default != ACL_NO_RIGHTS) + { + aip[0].ai_grantee = ACL_ID_WORLD; + aip[0].ai_grantor = ownerid; + ACLITEM_SET_PRIVS_IDTYPE(aip[0], world_default, ACL_NO_RIGHTS, ACL_IDTYPE_WORLD); + } + if (ownerid) { - aip[1].ai_id = ownerid; - ACLITEM_SET_PRIVS_IDTYPE(aip[1], owner_default, ACL_IDTYPE_UID); + int index = (world_default != ACL_NO_RIGHTS ? 1: 0); + + aip[index].ai_grantee = ownerid; + aip[index].ai_grantor = ownerid; + /* owner gets default privileges with grant option */ + ACLITEM_SET_PRIVS_IDTYPE(aip[index], owner_default, owner_default, ACL_IDTYPE_UID); } return acl; @@ -458,7 +489,7 @@ acldefault(GrantObjectType objtype, AclId ownerid) * NB: caller is responsible for having detoasted the input ACL, if needed. */ Acl * -aclinsert3(const Acl *old_acl, const AclItem *mod_aip, unsigned modechg) +aclinsert3(const Acl *old_acl, const AclItem *mod_aip, unsigned modechg, DropBehavior behavior) { Acl *new_acl; AclItem *old_aip, @@ -480,49 +511,35 @@ aclinsert3(const Acl *old_acl, const AclItem *mod_aip, unsigned modechg) old_aip = ACL_DAT(old_acl); /* - * Search the ACL for an existing entry for 'id'. If one exists, just - * modify the entry in-place (well, in the same position, since we - * actually return a copy); otherwise, insert the new entry in - * sort-order. + * Search the ACL for an existing entry for this grantee and + * grantor. If one exists, just modify the entry in-place (well, + * in the same position, since we actually return a copy); + * otherwise, insert the new entry at the end. */ - /* find the first element not less than the element to be inserted */ - for (dst = 0; dst < num && aclitemgt(mod_aip, old_aip + dst); ++dst) - ; - if (dst < num && aclitemeq(mod_aip, old_aip + dst)) + for (dst = 0; dst < num; ++dst) { - /* found a match, so modify existing item */ - new_acl = makeacl(num); - new_aip = ACL_DAT(new_acl); - memcpy((char *) new_acl, (char *) old_acl, ACL_SIZE(old_acl)); + if (aclitemeq(mod_aip, old_aip + dst)) + { + /* found a match, so modify existing item */ + new_acl = makeacl(num); + new_aip = ACL_DAT(new_acl); + memcpy(new_acl, old_acl, ACL_SIZE(old_acl)); + break; + } } - else + + if (dst == num) { - /* need to insert a new item */ + /* need to append a new item */ new_acl = makeacl(num + 1); new_aip = ACL_DAT(new_acl); - if (dst == 0) - { /* start */ - elog(ERROR, "aclinsert3: insertion before world ACL??"); - } - else if (dst >= num) - { /* end */ - memcpy((char *) new_aip, - (char *) old_aip, - num * sizeof(AclItem)); - } - else - { /* middle */ - memcpy((char *) new_aip, - (char *) old_aip, - dst * sizeof(AclItem)); - memcpy((char *) (new_aip + dst + 1), - (char *) (old_aip + dst), - (num - dst) * sizeof(AclItem)); - } + memcpy(new_aip, old_aip, num * sizeof(AclItem)); + /* initialize the new entry with no permissions */ - new_aip[dst].ai_id = mod_aip->ai_id; - ACLITEM_SET_PRIVS_IDTYPE(new_aip[dst], ACL_NO_RIGHTS, + 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_GET_IDTYPE(*mod_aip)); num++; /* set num to the size of new_acl */ } @@ -531,35 +548,89 @@ aclinsert3(const Acl *old_acl, const AclItem *mod_aip, unsigned modechg) switch (modechg) { case ACL_MODECHG_ADD: - new_aip[dst].ai_privs |= ACLITEM_GET_PRIVS(*mod_aip); + ACLITEM_SET_PRIVS(new_aip[dst], ACLITEM_GET_PRIVS(new_aip[dst]) | ACLITEM_GET_PRIVS(*mod_aip)); + ACLITEM_SET_GOPTIONS(new_aip[dst], ACLITEM_GET_GOPTIONS(new_aip[dst]) | ACLITEM_GET_GOPTIONS(*mod_aip)); break; case ACL_MODECHG_DEL: - new_aip[dst].ai_privs &= ~ACLITEM_GET_PRIVS(*mod_aip); + ACLITEM_SET_PRIVS(new_aip[dst], ACLITEM_GET_PRIVS(new_aip[dst]) & ~ACLITEM_GET_PRIVS(*mod_aip)); + ACLITEM_SET_GOPTIONS(new_aip[dst], ACLITEM_GET_GOPTIONS(new_aip[dst]) & ~ACLITEM_GET_GOPTIONS(*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])); break; } /* - * if the adjusted entry has no permissions, delete it from the list. - * For example, this helps in removing entries for users who no longer - * exist. EXCEPTION: never remove the world entry. + * If the adjusted entry has no permissions, delete it from the list. */ - if (ACLITEM_GET_PRIVS(new_aip[dst]) == ACL_NO_RIGHTS && dst > 0) + if (ACLITEM_GET_PRIVS(new_aip[dst]) == ACL_NO_RIGHTS) { - memmove((char *) (new_aip + dst), - (char *) (new_aip + dst + 1), + memmove(new_aip + dst, + new_aip + dst + 1, (num - dst - 1) * sizeof(AclItem)); ARR_DIMS(new_acl)[0] = num - 1; ARR_SIZE(new_acl) -= sizeof(AclItem); } + /* + * Remove abandoned privileges (cascading revoke) + */ + if (modechg != ACL_MODECHG_ADD + && ACLITEM_GET_IDTYPE(*mod_aip) == ACL_IDTYPE_UID + && ACLITEM_GET_GOPTIONS(*mod_aip)) + new_acl = recursive_revoke(new_acl, mod_aip->ai_grantee, ACLITEM_GET_GOPTIONS(*mod_aip), behavior); + return new_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. + */ +static Acl * +recursive_revoke(Acl *acl, + AclId grantee, + AclMode revoke_privs, + DropBehavior behavior) +{ + int i; + +restart: + for (i = 0; i < ACL_NUM(acl); i++) + { + AclItem *aip = ACL_DAT(acl); + + if (aip[i].ai_grantor == grantee + && (ACLITEM_GET_PRIVS(aip[i]) & revoke_privs) != 0) + { + AclItem mod_acl; + + if (behavior == DROP_RESTRICT) + elog(ERROR, "dependent privileges exist (use CASCADE to revoke them too)"); + + mod_acl.ai_grantor = grantee; + mod_acl.ai_grantee = aip[i].ai_grantee; + ACLITEM_SET_PRIVS_IDTYPE(mod_acl, + revoke_privs, + revoke_privs, + ACLITEM_GET_IDTYPE(aip[i])); + + acl = aclinsert3(acl, &mod_acl, ACL_MODECHG_DEL, behavior); + goto restart; + } + } + + return acl; +} + + /* * aclinsert (exported function) */ @@ -569,7 +640,7 @@ aclinsert(PG_FUNCTION_ARGS) Acl *old_acl = PG_GETARG_ACL_P(0); AclItem *mod_aip = PG_GETARG_ACLITEM_P(1); - PG_RETURN_ACL_P(aclinsert3(old_acl, mod_aip, ACL_MODECHG_EQL)); + PG_RETURN_ACL_P(aclinsert3(old_acl, mod_aip, ACL_MODECHG_EQL, DROP_CASCADE)); } Datum @@ -649,7 +720,7 @@ aclcontains(PG_FUNCTION_ARGS) aidat = ACL_DAT(acl); for (i = 0; i < num; ++i) { - if (aip->ai_id == aidat[i].ai_id && + if (aip->ai_grantee == aidat[i].ai_grantee && aip->ai_privs == aidat[i].ai_privs) PG_RETURN_BOOL(true); } @@ -842,24 +913,38 @@ convert_table_priv_string(text *priv_type_text) */ if (strcasecmp(priv_type, "SELECT") == 0) return ACL_SELECT; + if (strcasecmp(priv_type, "SELECT WITH GRANT OPTION") == 0) + return ACL_GRANT_OPTION_FOR(ACL_SELECT); if (strcasecmp(priv_type, "INSERT") == 0) return ACL_INSERT; + if (strcasecmp(priv_type, "INSERT WITH GRANT OPTION") == 0) + return ACL_GRANT_OPTION_FOR(ACL_INSERT); if (strcasecmp(priv_type, "UPDATE") == 0) return ACL_UPDATE; + if (strcasecmp(priv_type, "UPDATE WITH GRANT OPTION") == 0) + return ACL_GRANT_OPTION_FOR(ACL_UPDATE); if (strcasecmp(priv_type, "DELETE") == 0) return ACL_DELETE; + if (strcasecmp(priv_type, "DELETE WITH GRANT OPTION") == 0) + return ACL_GRANT_OPTION_FOR(ACL_DELETE); if (strcasecmp(priv_type, "RULE") == 0) return ACL_RULE; + if (strcasecmp(priv_type, "RULE WITH GRANT OPTION") == 0) + return ACL_GRANT_OPTION_FOR(ACL_RULE); if (strcasecmp(priv_type, "REFERENCES") == 0) return ACL_REFERENCES; + if (strcasecmp(priv_type, "REFERENCES WITH GRANT OPTION") == 0) + return ACL_GRANT_OPTION_FOR(ACL_REFERENCES); if (strcasecmp(priv_type, "TRIGGER") == 0) return ACL_TRIGGER; + if (strcasecmp(priv_type, "TRIGGER WITH GRANT OPTION") == 0) + return ACL_GRANT_OPTION_FOR(ACL_TRIGGER); elog(ERROR, "has_table_privilege: invalid privilege type %s", priv_type); @@ -1057,12 +1142,18 @@ convert_database_priv_string(text *priv_type_text) */ if (strcasecmp(priv_type, "CREATE") == 0) return ACL_CREATE; + if (strcasecmp(priv_type, "CREATE WITH GRANT OPTION") == 0) + return ACL_GRANT_OPTION_FOR(ACL_CREATE); if (strcasecmp(priv_type, "TEMPORARY") == 0) return ACL_CREATE_TEMP; + if (strcasecmp(priv_type, "TEMPORARY WITH GRANT OPTION") == 0) + return ACL_GRANT_OPTION_FOR(ACL_CREATE_TEMP); if (strcasecmp(priv_type, "TEMP") == 0) return ACL_CREATE_TEMP; + if (strcasecmp(priv_type, "TEMP WITH GRANT OPTION") == 0) + return ACL_GRANT_OPTION_FOR(ACL_CREATE_TEMP); elog(ERROR, "has_database_privilege: invalid privilege type %s", priv_type); @@ -1262,6 +1353,8 @@ convert_function_priv_string(text *priv_type_text) */ if (strcasecmp(priv_type, "EXECUTE") == 0) return ACL_EXECUTE; + if (strcasecmp(priv_type, "EXECUTE WITH GRANT OPTION") == 0) + return ACL_GRANT_OPTION_FOR(ACL_EXECUTE); elog(ERROR, "has_function_privilege: invalid privilege type %s", priv_type); @@ -1461,6 +1554,8 @@ convert_language_priv_string(text *priv_type_text) */ if (strcasecmp(priv_type, "USAGE") == 0) return ACL_USAGE; + if (strcasecmp(priv_type, "USAGE WITH GRANT OPTION") == 0) + return ACL_GRANT_OPTION_FOR(ACL_USAGE); elog(ERROR, "has_language_privilege: invalid privilege type %s", priv_type); @@ -1660,9 +1755,13 @@ convert_schema_priv_string(text *priv_type_text) */ if (strcasecmp(priv_type, "CREATE") == 0) return ACL_CREATE; + if (strcasecmp(priv_type, "CREATE WITH GRANT OPTION") == 0) + return ACL_GRANT_OPTION_FOR(ACL_CREATE); if (strcasecmp(priv_type, "USAGE") == 0) return ACL_USAGE; + if (strcasecmp(priv_type, "USAGE WITH GRANT OPTION") == 0) + return ACL_GRANT_OPTION_FOR(ACL_USAGE); elog(ERROR, "has_schema_privilege: invalid privilege type %s", priv_type); diff --git a/src/bin/initdb/initdb.sh b/src/bin/initdb/initdb.sh index 6b5afa0412..5dad4e34a0 100644 --- a/src/bin/initdb/initdb.sh +++ b/src/bin/initdb/initdb.sh @@ -27,7 +27,7 @@ # Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group # Portions Copyright (c) 1994, Regents of the University of California # -# $Header: /cvsroot/pgsql/src/bin/initdb/Attic/initdb.sh,v 1.181 2003/01/21 10:11:52 petere Exp $ +# $Header: /cvsroot/pgsql/src/bin/initdb/Attic/initdb.sh,v 1.182 2003/01/23 23:39:01 petere Exp $ # #------------------------------------------------------------------------- @@ -1029,14 +1029,12 @@ echo "ok" $ECHO_N "setting privileges on built-in objects... "$ECHO_C ( cat < /dev/null || exit_nicely diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index 809fe0216a..32b529b64e 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -12,7 +12,7 @@ * by PostgreSQL * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/bin/pg_dump/pg_dump.c,v 1.314 2003/01/06 18:53:24 petere Exp $ + * $Header: /cvsroot/pgsql/src/bin/pg_dump/pg_dump.c,v 1.315 2003/01/23 23:39:01 petere Exp $ * *------------------------------------------------------------------------- */ @@ -92,7 +92,7 @@ static void dumpFuncACL(Archive *fout, FuncInfo *finfo); static void dumpAggACL(Archive *fout, AggInfo *finfo); static void dumpACL(Archive *fout, const char *type, const char *name, const char *tag, const char *nspname, - const char *usename, const char *acl, const char *objoid); + const char *owner, const char *acl, const char *objoid); static void dumpConstraints(Archive *fout, TableInfo *tblinfo, int numTables); static void dumpTriggers(Archive *fout, TableInfo *tblinfo, int numTables); @@ -114,8 +114,10 @@ static char *getFormattedTypeName(const char *oid, OidOptions opts); static char *myFormatType(const char *typname, int32 typmod); static const char *fmtQualifiedId(const char *schema, const char *id); -static void AddAcl(char *aclbuf, const char *keyword); -static char *GetPrivileges(Archive *AH, const char *s, const char *type); +static void AddAcl(PQExpBuffer aclbuf, const char *keyword); +static void +parseAclItem(const char *item, const char *type, const char *name, int remoteVersion, + PQExpBuffer grantee, PQExpBuffer grantor, PQExpBuffer privs, PQExpBuffer privswgo); static int dumpBlobs(Archive *AH, char *, void *); static int dumpDatabase(Archive *AH); @@ -4777,42 +4779,80 @@ dumpOneAgg(Archive *fout, AggInfo *agginfo) /* - * These are some support functions to fix the acl problem of pg_dump - * - * Matthew C. Aycock 12/02/97 - */ - -/* Append a keyword to a keyword list, inserting comma if needed. - * Caller must make aclbuf big enough for all possible keywords. + * Append a privilege keyword to a keyword list, inserting comma if needed. */ static void -AddAcl(char *aclbuf, const char *keyword) +AddAcl(PQExpBuffer aclbuf, const char *keyword) { - if (*aclbuf) - strcat(aclbuf, ","); - strcat(aclbuf, keyword); + if (aclbuf->len > 0) + appendPQExpBufferChar(aclbuf, ','); + appendPQExpBuffer(aclbuf, "%s", keyword); } + /* - * This will take a string of privilege code letters and return a malloced, - * comma delimited string of keywords for GRANT. + * This will take an aclitem string of privilege code letters and + * parse it into grantee, grantor, and privilege information. The + * privilege information is split between privileges with grant option + * (privswgo) and without (privs). * * Note: for cross-version compatibility, it's important to use ALL when * appropriate. */ -static char * -GetPrivileges(Archive *AH, const char *s, const char *type) +static void +parseAclItem(const char *item, const char *type, const char *name, int remoteVersion, + PQExpBuffer grantee, PQExpBuffer grantor, PQExpBuffer privs, PQExpBuffer privswgo) { - char aclbuf[100]; - bool all = true; + char *buf; + bool all_with_go = true; + bool all_without_go = true; + char *eqpos; + char *slpos; + char *pos; - aclbuf[0] = '\0'; + buf = strdup(item); -#define CONVERT_PRIV(code,keywd) \ - if (strchr(s, code)) \ - AddAcl(aclbuf, keywd); \ + /* user name is string up to = */ + eqpos = strchr(buf, '='); + if (!eqpos) + { + write_msg(NULL, "could not parse ACL list (%s) for object %s (%s)\n", + item, name, type); + exit_nicely(); + } + *eqpos = '\0'; + printfPQExpBuffer(grantee, "%s", buf); + + /* grantor may be listed after / */ + slpos = strchr(eqpos + 1, '/'); + if (slpos) + { + *slpos = '\0'; + printfPQExpBuffer(grantor, "%s", slpos + 1); + } + else + resetPQExpBuffer(grantor); + + /* privilege codes */ +#define CONVERT_PRIV(code, keywd) \ + if ((pos = strchr(eqpos + 1, code))) \ + { \ + if (*(pos + 1) == '*') \ + { \ + AddAcl(privswgo, keywd); \ + all_without_go = false; \ + } \ + else \ + { \ + AddAcl(privs, keywd); \ + all_with_go = false; \ + } \ + } \ else \ - all = false + all_with_go = all_without_go = false + + resetPQExpBuffer(privs); + resetPQExpBuffer(privswgo); if (strcmp(type, "TABLE") == 0) { @@ -4820,7 +4860,7 @@ GetPrivileges(Archive *AH, const char *s, const char *type) CONVERT_PRIV('r', "SELECT"); CONVERT_PRIV('R', "RULE"); - if (AH->remoteVersion >= 70200) + if (remoteVersion >= 70200) { CONVERT_PRIV('w', "UPDATE"); CONVERT_PRIV('d', "DELETE"); @@ -4847,10 +4887,16 @@ GetPrivileges(Archive *AH, const char *s, const char *type) #undef CONVERT_PRIV - if (all) - return strdup("ALL"); - else - return strdup(aclbuf); + if (all_with_go) + { + resetPQExpBuffer(privs); + printfPQExpBuffer(privswgo, "ALL"); + } + else if (all_without_go) + { + resetPQExpBuffer(privswgo); + printfPQExpBuffer(privs, "ALL"); + } } @@ -4861,7 +4907,7 @@ GetPrivileges(Archive *AH, const char *s, const char *type) * 'name' is the formatted name of the object. Must be quoted etc. already. * 'tag' is the tag for the archive entry (typ. unquoted name of object). * 'nspname' is the namespace the object is in (NULL if none). - * 'usename' is the owner, NULL if there is no owner (for languages). + * 'owner' is the owner, NULL if there is no owner (for languages). * 'acls' is the string read out of the fooacl system catalog field; * it will be parsed here. * 'objoid' is the OID of the object for purposes of ordering. @@ -4869,28 +4915,34 @@ GetPrivileges(Archive *AH, const char *s, const char *type) */ static void dumpACL(Archive *fout, const char *type, const char *name, - const char *tag, const char *nspname, const char *usename, + const char *tag, const char *nspname, const char *owner, const char *acls, const char *objoid) { char *aclbuf, - *tok, - *eqpos, - *priv; - PQExpBuffer sql; + *tok; + PQExpBuffer sql, grantee, grantor, privs, privswgo; bool found_owner_privs = false; if (strlen(acls) == 0) return; /* object has default permissions */ +#define MKENTRY(grantor, command) \ + ArchiveEntry(fout, objoid, tag, nspname, grantor ? grantor : "", "ACL", NULL, command, "", NULL, NULL, NULL) + sql = createPQExpBuffer(); + grantee = createPQExpBuffer(); + grantor = createPQExpBuffer(); + privs = createPQExpBuffer(); + privswgo = createPQExpBuffer(); /* * Always start with REVOKE ALL FROM PUBLIC, so that we don't have to * wire-in knowledge about the default public privileges for different * kinds of objects. */ - appendPQExpBuffer(sql, "REVOKE ALL ON %s %s FROM PUBLIC;\n", + printfPQExpBuffer(sql, "REVOKE ALL ON %s %s FROM PUBLIC;\n", type, name); + MKENTRY(owner, sql->data); /* Make a working copy of acls so we can use strtok */ aclbuf = strdup(acls); @@ -4898,6 +4950,10 @@ dumpACL(Archive *fout, const char *type, const char *name, /* Scan comma-separated ACL items */ for (tok = strtok(aclbuf, ","); tok != NULL; tok = strtok(NULL, ",")) { + size_t toklen; + + resetPQExpBuffer(sql); + /* * Token may start with '{' and/or '"'. Actually only the start * of the string should have '{', but we don't verify that. @@ -4906,39 +4962,33 @@ dumpACL(Archive *fout, const char *type, const char *name, tok++; if (*tok == '"') tok++; + toklen = strlen(tok); + while (toklen >=0 && (tok[toklen-1] == '"' || tok[toklen-1] == '}')) + tok[toklen-- - 1] = '\0'; - /* User name is string up to = in tok */ - eqpos = strchr(tok, '='); - if (!eqpos) + parseAclItem(tok, type, name, fout->remoteVersion, + grantee, grantor, privs, privswgo); + if (grantor->len == 0 && owner) + printfPQExpBuffer(grantor, "%s", owner); + + if (privs->len > 0 || privswgo->len > 0) { - write_msg(NULL, "could not parse ACL list (%s) for object %s (%s)\n", - acls, name, type); - exit_nicely(); - } - *eqpos = '\0'; /* it's ok to clobber aclbuf */ - - /* - * Parse the privileges (right-hand side). - */ - priv = GetPrivileges(fout, eqpos + 1, type); - - if (*priv) - { - if (usename && strcmp(tok, usename) == 0) + if (owner && strcmp(grantee->data, owner) == 0) { /* - * For the owner, the default privilege level is ALL. + * For the owner, the default privilege level is ALL WITH GRANT OPTION. */ found_owner_privs = true; - if (strcmp(priv, "ALL") != 0) + if (strcmp(privswgo->data, "ALL") != 0) { - /* NB: only one fmtId per appendPQExpBuffer! */ - appendPQExpBuffer(sql, "REVOKE ALL ON %s %s FROM ", - type, name); - appendPQExpBuffer(sql, "%s;\n", fmtId(tok)); - appendPQExpBuffer(sql, "GRANT %s ON %s %s TO ", - priv, type, name); - appendPQExpBuffer(sql, "%s;\n", fmtId(tok)); + appendPQExpBuffer(sql, "REVOKE ALL ON %s %s FROM %s;\n", + type, name, fmtId(grantee->data)); + if (privs->len > 0) + appendPQExpBuffer(sql, "GRANT %s ON %s %s TO %s;\n", + privs->data, type, name, fmtId(grantee->data)); + if (privswgo->len > 0) + appendPQExpBuffer(sql, "GRANT %s ON %s %s TO %s WITH GRANT OPTION;\n", + privswgo->data, type, name, fmtId(grantee->data)); } } else @@ -4946,57 +4996,69 @@ dumpACL(Archive *fout, const char *type, const char *name, /* * Otherwise can assume we are starting from no privs. */ - appendPQExpBuffer(sql, "GRANT %s ON %s %s TO ", - priv, type, name); - if (eqpos == tok) + if (privs->len > 0) { - /* Empty left-hand side means "PUBLIC" */ - appendPQExpBuffer(sql, "PUBLIC;\n"); + appendPQExpBuffer(sql, "GRANT %s ON %s %s TO ", + privs->data, type, name); + if (grantee->len == 0) + appendPQExpBuffer(sql, "PUBLIC;\n"); + else if (strncmp(grantee->data, "group ", strlen("group ")) == 0) + appendPQExpBuffer(sql, "GROUP %s;\n", + fmtId(grantee->data + strlen("group "))); + else + appendPQExpBuffer(sql, "%s;\n", fmtId(grantee->data)); + } + if (privswgo->len > 0) + { + appendPQExpBuffer(sql, "GRANT %s ON %s %s TO ", + privswgo->data, type, name); + if (grantee->len == 0) + appendPQExpBuffer(sql, "PUBLIC"); + else if (strncmp(grantee->data, "group ", strlen("group ")) == 0) + appendPQExpBuffer(sql, "GROUP %s", + fmtId(grantee->data + strlen("group "))); + else + appendPQExpBuffer(sql, "%s", fmtId(grantee->data)); + appendPQExpBuffer(sql, " WITH GRANT OPTION;\n"); } - else if (strncmp(tok, "group ", strlen("group ")) == 0) - appendPQExpBuffer(sql, "GROUP %s;\n", - fmtId(tok + strlen("group "))); - else - appendPQExpBuffer(sql, "%s;\n", fmtId(tok)); } } else { /* No privileges. Issue explicit REVOKE for safety. */ - if (eqpos == tok) - { - /* Empty left-hand side means "PUBLIC"; already did it */ - } - else if (strncmp(tok, "group ", strlen("group ")) == 0) - { + if (grantee->len == 0) + ; /* Empty left-hand side means "PUBLIC"; already did it */ + else if (strncmp(grantee->data, "group ", strlen("group ")) == 0) appendPQExpBuffer(sql, "REVOKE ALL ON %s %s FROM GROUP %s;\n", type, name, - fmtId(tok + strlen("group "))); - } + fmtId(grantee->data + strlen("group "))); else - { appendPQExpBuffer(sql, "REVOKE ALL ON %s %s FROM %s;\n", - type, name, fmtId(tok)); - } + type, name, fmtId(grantee->data)); } - free(priv); + + if (sql->len > 0) + MKENTRY(grantor->data, sql->data); } /* * If we didn't find any owner privs, the owner must have revoked 'em * all */ - if (!found_owner_privs && usename) + if (!found_owner_privs && owner) { appendPQExpBuffer(sql, "REVOKE ALL ON %s %s FROM %s;\n", - type, name, fmtId(usename)); + type, name, fmtId(owner)); + MKENTRY(owner, sql->data); } - ArchiveEntry(fout, objoid, tag, nspname, usename ? usename : "", - "ACL", NULL, sql->data, "", NULL, NULL, NULL); - free(aclbuf); destroyPQExpBuffer(sql); + destroyPQExpBuffer(grantee); + destroyPQExpBuffer(grantor); + destroyPQExpBuffer(privs); + destroyPQExpBuffer(privswgo); +#undef MKENTRY } diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h index 9e62bd4793..b679fdb5dd 100644 --- a/src/include/catalog/catversion.h +++ b/src/include/catalog/catversion.h @@ -1,10 +1,10 @@ /*------------------------------------------------------------------------- * * catversion.h - * "Catalog version number" for Postgres. + * "Catalog version number" for PostgreSQL. * * The catalog version number is used to flag incompatible changes in - * the Postgres system catalogs. Whenever anyone changes the format of + * the PostgreSQL system catalogs. Whenever anyone changes the format of * a system catalog relation, or adds, deletes, or modifies standard * catalog entries in such a way that an updated backend wouldn't work * with an old database (or vice versa), the catalog version number @@ -37,7 +37,7 @@ * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Id: catversion.h,v 1.172 2003/01/10 21:08:15 tgl Exp $ + * $Id: catversion.h,v 1.173 2003/01/23 23:39:04 petere Exp $ * *------------------------------------------------------------------------- */ @@ -53,6 +53,6 @@ */ /* yyyymmddN */ -#define CATALOG_VERSION_NO 200301101 +#define CATALOG_VERSION_NO 200301241 #endif diff --git a/src/include/catalog/pg_type.h b/src/include/catalog/pg_type.h index fa19d84508..886cc8b095 100644 --- a/src/include/catalog/pg_type.h +++ b/src/include/catalog/pg_type.h @@ -8,7 +8,7 @@ * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Id: pg_type.h,v 1.138 2003/01/08 21:40:39 tgl Exp $ + * $Id: pg_type.h,v 1.139 2003/01/23 23:39:06 petere Exp $ * * NOTES * the genbki.sh script reads this file and generates .bki @@ -412,7 +412,7 @@ DATA(insert OID = 1023 ( _abstime PGNSP PGUID -1 f b t \054 0 702 array_in arr DATA(insert OID = 1024 ( _reltime PGNSP PGUID -1 f b t \054 0 703 array_in array_out i x f 0 -1 0 _null_ _null_ )); DATA(insert OID = 1025 ( _tinterval PGNSP PGUID -1 f b t \054 0 704 array_in array_out i x f 0 -1 0 _null_ _null_ )); DATA(insert OID = 1027 ( _polygon PGNSP PGUID -1 f b t \054 0 604 array_in array_out d x f 0 -1 0 _null_ _null_ )); -DATA(insert OID = 1033 ( aclitem PGNSP PGUID 8 f b t \054 0 0 aclitemin aclitemout i p f 0 -1 0 _null_ _null_ )); +DATA(insert OID = 1033 ( aclitem PGNSP PGUID 12 f b t \054 0 0 aclitemin aclitemout i p f 0 -1 0 _null_ _null_ )); DESCR("access control list"); #define ACLITEMOID 1033 DATA(insert OID = 1034 ( _aclitem PGNSP PGUID -1 f b t \054 0 1033 array_in array_out i x f 0 -1 0 _null_ _null_ )); diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 9bbee593bf..f6709737d6 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Id: parsenodes.h,v 1.226 2003/01/20 18:55:00 tgl Exp $ + * $Id: parsenodes.h,v 1.227 2003/01/23 23:39:07 petere Exp $ * *------------------------------------------------------------------------- */ @@ -750,7 +750,7 @@ typedef enum GrantObjectType /* * Grantable rights are encoded so that we can OR them together in a bitmask. - * The present representation of AclItem limits us to 30 distinct rights. + * The present representation of AclItem limits us to 15 distinct rights. * Caution: changing these codes breaks stored ACLs, hence forces initdb. */ #define ACL_INSERT (1<<0) /* for relations */ @@ -778,6 +778,8 @@ typedef struct GrantStmt * strings) */ List *privileges; /* integer list of privilege codes */ List *grantees; /* list of PrivGrantee nodes */ + bool grant_option; /* grant or revoke grant option */ + DropBehavior behavior; /* drop behavior (for REVOKE) */ } GrantStmt; typedef struct PrivGrantee diff --git a/src/include/utils/acl.h b/src/include/utils/acl.h index ac86a86855..871ed680f5 100644 --- a/src/include/utils/acl.h +++ b/src/include/utils/acl.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Id: acl.h,v 1.50 2003/01/09 18:00:24 tgl Exp $ + * $Id: acl.h,v 1.51 2003/01/23 23:39:07 petere Exp $ * * NOTES * For backward-compatibility purposes we have to allow there @@ -50,27 +50,37 @@ typedef uint32 AclMode; */ typedef struct AclItem { - AclId ai_id; /* ID that this item applies to */ + AclId ai_grantee; /* ID that this item applies to */ + AclId ai_grantor; AclMode ai_privs; /* AclIdType plus privilege bits */ } AclItem; /* - * The AclIdType is stored in the top two bits of the ai_privs field of an - * AclItem, leaving us with thirty usable privilege bits. + * 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. */ -#define ACLITEM_GET_PRIVS(item) ((item).ai_privs & 0x3FFFFFFF) -#define ACLITEM_GET_IDTYPE(item) ((item).ai_privs >> 30) -#define ACLITEM_SET_PRIVS_IDTYPE(item,privs,idtype) \ - ((item).ai_privs = ((privs) & 0x3FFFFFFF) | ((idtype) << 30)) +#define ACLITEM_GET_PRIVS(item) ((item).ai_privs & 0x7FFF) +#define ACLITEM_GET_GOPTIONS(item) (((item).ai_privs >> 15) & 0x7FFF) +#define ACLITEM_GET_IDTYPE(item) ((item).ai_privs >> 30) + +#define ACL_GRANT_OPTION_FOR(privs) (((privs) & 0x7FFF) << 15) + +#define ACLITEM_SET_PRIVS(item,privs) \ + ((item).ai_privs = (ACLITEM_GET_IDTYPE(item)<<30) | (ACLITEM_GET_GOPTIONS(item)<<15) | ((privs) & 0x7FFF)) +#define ACLITEM_SET_GOPTIONS(item,goptions) \ + ((item).ai_privs = (ACLITEM_GET_IDTYPE(item)<<30) | (((goptions) & 0x7FFF) << 15) | ACLITEM_GET_PRIVS(item)) +#define ACLITEM_SET_PRIVS_IDTYPE(item,privs,goption,idtype) \ + ((item).ai_privs = ((privs) & 0x7FFF) |(((goption) & 0x7FFF) << 15) | ((idtype) << 30)) /* * Definitions for convenient access to Acl (array of AclItem) and IdList - * (array of AclId). These are standard Postgres arrays, but are restricted + * (array of AclId). These are standard PostgreSQL arrays, but are restricted * to have one dimension. We also ignore the lower bound when reading, * and set it to zero when writing. * - * CAUTION: as of Postgres 7.1, these arrays are toastable (just like all + * CAUTION: as of PostgreSQL 7.1, these arrays are toastable (just like all * other array types). Therefore, be careful to detoast them with the * macros provided, unless you know for certain that a particular array * can't have been toasted. Presently, we do not provide toast tables for @@ -80,7 +90,7 @@ typedef struct AclItem /* - * Acl a one-dimensional POSTGRES array of AclItem + * Acl a one-dimensional array of AclItem */ typedef ArrayType Acl; @@ -90,7 +100,7 @@ typedef ArrayType Acl; #define ACL_SIZE(ACL) ARR_SIZE(ACL) /* - * IdList a one-dimensional POSTGRES array of AclId + * IdList a one-dimensional array of AclId */ typedef ArrayType IdList; @@ -126,11 +136,6 @@ typedef ArrayType IdList; #define ACL_MODECHG_DEL 2 #define ACL_MODECHG_EQL 3 -/* external representation of mode indicators for I/O */ -#define ACL_MODECHG_ADD_CHR '+' -#define ACL_MODECHG_DEL_CHR '-' -#define ACL_MODECHG_EQL_CHR '=' - /* * External representations of the privilege bits --- aclitemin/aclitemout * represent each possible privilege bit with a distinct 1-character code @@ -173,7 +178,7 @@ typedef enum */ extern Acl *acldefault(GrantObjectType objtype, AclId ownerid); extern Acl *aclinsert3(const Acl *old_acl, const AclItem *mod_aip, - unsigned modechg); + unsigned modechg, DropBehavior behavior); /* * SQL functions (from acl.c) diff --git a/src/test/regress/expected/privileges.out b/src/test/regress/expected/privileges.out index c962879e40..c9fd902a7a 100644 --- a/src/test/regress/expected/privileges.out +++ b/src/test/regress/expected/privileges.out @@ -90,7 +90,7 @@ ERROR: atest2: permission denied COPY atest2 FROM stdin; -- fail ERROR: atest2: permission denied GRANT ALL ON atest1 TO PUBLIC; -- fail -ERROR: atest1: must be owner +ERROR: atest1: permission denied -- checks in subquery, both ok SELECT * FROM atest1 WHERE ( b IN ( SELECT col1 FROM atest2 ) ); a | b @@ -227,7 +227,7 @@ GRANT USAGE ON LANGUAGE c TO PUBLIC; -- fail ERROR: language "c" is not trusted SET SESSION AUTHORIZATION regressuser1; GRANT USAGE ON LANGUAGE sql TO regressuser2; -- fail -ERROR: permission denied +ERROR: sql: permission denied 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; @@ -544,6 +544,46 @@ from (select oid from pg_class where relname = 'atest1') as t1; f (1 row) +-- Grant options +SET SESSION AUTHORIZATION regressuser1; +CREATE TABLE atest4 (a int); +GRANT SELECT ON atest4 TO regressuser2 WITH GRANT OPTION; +GRANT UPDATE ON atest4 TO regressuser2; +GRANT SELECT ON atest4 TO GROUP regressgroup1 WITH GRANT OPTION; -- fail +ERROR: grant options can only be granted to individual users +SET SESSION AUTHORIZATION regressuser2; +GRANT SELECT ON atest4 TO regressuser3; +GRANT UPDATE ON atest4 TO regressuser3; -- fail +ERROR: atest4: permission denied +SET SESSION AUTHORIZATION regressuser1; +REVOKE SELECT ON atest4 FROM regressuser3; -- does nothing +SELECT has_table_privilege('regressuser3', 'atest4', 'SELECT'); -- true + has_table_privilege +--------------------- + t +(1 row) + +REVOKE SELECT ON atest4 FROM regressuser2; -- fail +ERROR: dependent privileges exist (use CASCADE to revoke them too) +REVOKE GRANT OPTION FOR SELECT ON atest4 FROM regressuser2 CASCADE; -- ok +SELECT has_table_privilege('regressuser2', 'atest4', 'SELECT'); -- true + has_table_privilege +--------------------- + t +(1 row) + +SELECT has_table_privilege('regressuser3', 'atest4', 'SELECT'); -- false + has_table_privilege +--------------------- + f +(1 row) + +SELECT has_table_privilege('regressuser1', 'atest4', 'SELECT WITH GRANT OPTION'); -- true + has_table_privilege +--------------------- + t +(1 row) + -- clean up \c regression SET autocommit TO 'on'; @@ -561,6 +601,7 @@ ERROR: view "atestv4" does not exist DROP TABLE atest1; DROP TABLE atest2; DROP TABLE atest3; +DROP TABLE atest4; DROP GROUP regressgroup1; DROP GROUP regressgroup2; DROP USER regressuser1; diff --git a/src/test/regress/sql/privileges.sql b/src/test/regress/sql/privileges.sql index 1d1bde3bdd..d7076d2575 100644 --- a/src/test/regress/sql/privileges.sql +++ b/src/test/regress/sql/privileges.sql @@ -293,6 +293,33 @@ select has_table_privilege(t1.oid,'trigger') from (select oid from pg_class where relname = 'atest1') as t1; +-- Grant options + +SET SESSION AUTHORIZATION regressuser1; + +CREATE TABLE atest4 (a int); + +GRANT SELECT ON atest4 TO regressuser2 WITH GRANT OPTION; +GRANT UPDATE ON atest4 TO regressuser2; +GRANT SELECT ON atest4 TO GROUP regressgroup1 WITH GRANT OPTION; -- fail + +SET SESSION AUTHORIZATION regressuser2; + +GRANT SELECT ON atest4 TO regressuser3; +GRANT UPDATE ON atest4 TO regressuser3; -- fail + +SET SESSION AUTHORIZATION regressuser1; + +REVOKE SELECT ON atest4 FROM regressuser3; -- does nothing +SELECT has_table_privilege('regressuser3', 'atest4', 'SELECT'); -- true +REVOKE SELECT ON atest4 FROM regressuser2; -- fail +REVOKE GRANT OPTION FOR SELECT ON atest4 FROM regressuser2 CASCADE; -- ok +SELECT has_table_privilege('regressuser2', 'atest4', 'SELECT'); -- true +SELECT has_table_privilege('regressuser3', 'atest4', 'SELECT'); -- false + +SELECT has_table_privilege('regressuser1', 'atest4', 'SELECT WITH GRANT OPTION'); -- true + + -- clean up \c regression @@ -311,6 +338,7 @@ DROP VIEW atestv4; DROP TABLE atest1; DROP TABLE atest2; DROP TABLE atest3; +DROP TABLE atest4; DROP GROUP regressgroup1; DROP GROUP regressgroup2;