Fix the problem of GRANTs creating "dangling" privileges not directly
traceable to grant options. As per my earlier proposal, a GRANT made by a role member has to be recorded as being granted by the role that actually holds the grant option, and not the member.
This commit is contained in:
parent
d7527540f2
commit
9178306151
@ -8,7 +8,7 @@
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/catalog/aclchk.c,v 1.118 2005/08/17 19:45:51 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/catalog/aclchk.c,v 1.119 2005/10/10 18:49:01 tgl Exp $
|
||||
*
|
||||
* NOTES
|
||||
* See acl.h.
|
||||
@ -70,32 +70,6 @@ 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 Oid
|
||||
select_grantor(Oid ownerId)
|
||||
{
|
||||
Oid 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
|
||||
@ -243,7 +217,7 @@ ExecuteGrantStmt_Relation(GrantStmt *stmt)
|
||||
Form_pg_class pg_class_tuple;
|
||||
Datum aclDatum;
|
||||
bool isNull;
|
||||
AclMode my_goptions;
|
||||
AclMode avail_goptions;
|
||||
AclMode this_privileges;
|
||||
Acl *old_acl;
|
||||
Acl *new_acl;
|
||||
@ -282,28 +256,36 @@ ExecuteGrantStmt_Relation(GrantStmt *stmt)
|
||||
errmsg("\"%s\" is a composite type",
|
||||
relvar->relname)));
|
||||
|
||||
/*
|
||||
* Get owner ID and working copy of existing ACL.
|
||||
* If there's no ACL, substitute the proper default.
|
||||
*/
|
||||
ownerId = pg_class_tuple->relowner;
|
||||
grantorId = select_grantor(ownerId);
|
||||
aclDatum = SysCacheGetAttr(RELOID, tuple, Anum_pg_class_relacl,
|
||||
&isNull);
|
||||
if (isNull)
|
||||
old_acl = acldefault(ACL_OBJECT_RELATION, ownerId);
|
||||
else
|
||||
old_acl = DatumGetAclPCopy(aclDatum);
|
||||
|
||||
/* Determine ID to do the grant as, and available grant options */
|
||||
select_best_grantor(GetUserId(), privileges,
|
||||
old_acl, ownerId,
|
||||
&grantorId, &avail_goptions);
|
||||
|
||||
/*
|
||||
* Must be owner or have some privilege on the object (per spec,
|
||||
* any privilege will get you by here). The owner is always
|
||||
* treated as having all grant options.
|
||||
* If we found no grant options, consider whether to issue a hard
|
||||
* error. Per spec, having any privilege at all on the object
|
||||
* will get you by here.
|
||||
*/
|
||||
if (pg_class_ownercheck(relOid, GetUserId()))
|
||||
my_goptions = ACL_ALL_RIGHTS_RELATION;
|
||||
else
|
||||
if (avail_goptions == ACL_NO_RIGHTS)
|
||||
{
|
||||
AclMode my_rights;
|
||||
|
||||
my_rights = pg_class_aclmask(relOid,
|
||||
GetUserId(),
|
||||
ACL_ALL_RIGHTS_RELATION | ACL_GRANT_OPTION_FOR(ACL_ALL_RIGHTS_RELATION),
|
||||
ACLMASK_ALL);
|
||||
if (my_rights == ACL_NO_RIGHTS)
|
||||
if (pg_class_aclmask(relOid,
|
||||
grantorId,
|
||||
ACL_ALL_RIGHTS_RELATION | ACL_GRANT_OPTION_FOR(ACL_ALL_RIGHTS_RELATION),
|
||||
ACLMASK_ANY) == ACL_NO_RIGHTS)
|
||||
aclcheck_error(ACLCHECK_NO_PRIV, ACL_KIND_CLASS,
|
||||
relvar->relname);
|
||||
my_goptions = ACL_OPTION_TO_PRIVS(my_rights);
|
||||
}
|
||||
|
||||
/*
|
||||
@ -314,7 +296,7 @@ ExecuteGrantStmt_Relation(GrantStmt *stmt)
|
||||
* In practice that behavior seems much too noisy, as well as
|
||||
* inconsistent with the GRANT case.)
|
||||
*/
|
||||
this_privileges = privileges & my_goptions;
|
||||
this_privileges = privileges & ACL_OPTION_TO_PRIVS(avail_goptions);
|
||||
if (stmt->is_grant)
|
||||
{
|
||||
if (this_privileges == 0)
|
||||
@ -339,17 +321,8 @@ ExecuteGrantStmt_Relation(GrantStmt *stmt)
|
||||
}
|
||||
|
||||
/*
|
||||
* 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, ownerId);
|
||||
else
|
||||
/* get a detoasted copy of the ACL */
|
||||
old_acl = DatumGetAclPCopy(aclDatum);
|
||||
|
||||
/*
|
||||
* Generate new ACL.
|
||||
*
|
||||
* We need the members of both old and new ACLs so we can correct
|
||||
* the shared dependency information.
|
||||
*/
|
||||
@ -434,7 +407,7 @@ ExecuteGrantStmt_Database(GrantStmt *stmt)
|
||||
Form_pg_database pg_database_tuple;
|
||||
Datum aclDatum;
|
||||
bool isNull;
|
||||
AclMode my_goptions;
|
||||
AclMode avail_goptions;
|
||||
AclMode this_privileges;
|
||||
Acl *old_acl;
|
||||
Acl *new_acl;
|
||||
@ -462,28 +435,36 @@ ExecuteGrantStmt_Database(GrantStmt *stmt)
|
||||
errmsg("database \"%s\" does not exist", dbname)));
|
||||
pg_database_tuple = (Form_pg_database) GETSTRUCT(tuple);
|
||||
|
||||
/*
|
||||
* Get owner ID and working copy of existing ACL.
|
||||
* If there's no ACL, substitute the proper default.
|
||||
*/
|
||||
ownerId = pg_database_tuple->datdba;
|
||||
grantorId = select_grantor(ownerId);
|
||||
aclDatum = heap_getattr(tuple, Anum_pg_database_datacl,
|
||||
RelationGetDescr(relation), &isNull);
|
||||
if (isNull)
|
||||
old_acl = acldefault(ACL_OBJECT_DATABASE, ownerId);
|
||||
else
|
||||
old_acl = DatumGetAclPCopy(aclDatum);
|
||||
|
||||
/* Determine ID to do the grant as, and available grant options */
|
||||
select_best_grantor(GetUserId(), privileges,
|
||||
old_acl, ownerId,
|
||||
&grantorId, &avail_goptions);
|
||||
|
||||
/*
|
||||
* Must be owner or have some privilege on the object (per spec,
|
||||
* any privilege will get you by here). The owner is always
|
||||
* treated as having all grant options.
|
||||
* If we found no grant options, consider whether to issue a hard
|
||||
* error. Per spec, having any privilege at all on the object
|
||||
* will get you by here.
|
||||
*/
|
||||
if (pg_database_ownercheck(HeapTupleGetOid(tuple), GetUserId()))
|
||||
my_goptions = ACL_ALL_RIGHTS_DATABASE;
|
||||
else
|
||||
if (avail_goptions == ACL_NO_RIGHTS)
|
||||
{
|
||||
AclMode my_rights;
|
||||
|
||||
my_rights = pg_database_aclmask(HeapTupleGetOid(tuple),
|
||||
GetUserId(),
|
||||
ACL_ALL_RIGHTS_DATABASE | ACL_GRANT_OPTION_FOR(ACL_ALL_RIGHTS_DATABASE),
|
||||
ACLMASK_ALL);
|
||||
if (my_rights == ACL_NO_RIGHTS)
|
||||
if (pg_database_aclmask(HeapTupleGetOid(tuple),
|
||||
grantorId,
|
||||
ACL_ALL_RIGHTS_DATABASE | ACL_GRANT_OPTION_FOR(ACL_ALL_RIGHTS_DATABASE),
|
||||
ACLMASK_ANY) == ACL_NO_RIGHTS)
|
||||
aclcheck_error(ACLCHECK_NO_PRIV, ACL_KIND_DATABASE,
|
||||
NameStr(pg_database_tuple->datname));
|
||||
my_goptions = ACL_OPTION_TO_PRIVS(my_rights);
|
||||
}
|
||||
|
||||
/*
|
||||
@ -494,7 +475,7 @@ ExecuteGrantStmt_Database(GrantStmt *stmt)
|
||||
* In practice that behavior seems much too noisy, as well as
|
||||
* inconsistent with the GRANT case.)
|
||||
*/
|
||||
this_privileges = privileges & my_goptions;
|
||||
this_privileges = privileges & ACL_OPTION_TO_PRIVS(avail_goptions);
|
||||
if (stmt->is_grant)
|
||||
{
|
||||
if (this_privileges == 0)
|
||||
@ -519,17 +500,8 @@ ExecuteGrantStmt_Database(GrantStmt *stmt)
|
||||
}
|
||||
|
||||
/*
|
||||
* 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, ownerId);
|
||||
else
|
||||
/* get a detoasted copy of the ACL */
|
||||
old_acl = DatumGetAclPCopy(aclDatum);
|
||||
|
||||
/*
|
||||
* Generate new ACL.
|
||||
*
|
||||
* We need the members of both old and new ACLs so we can correct
|
||||
* the shared dependency information.
|
||||
*/
|
||||
@ -613,7 +585,7 @@ ExecuteGrantStmt_Function(GrantStmt *stmt)
|
||||
Form_pg_proc pg_proc_tuple;
|
||||
Datum aclDatum;
|
||||
bool isNull;
|
||||
AclMode my_goptions;
|
||||
AclMode avail_goptions;
|
||||
AclMode this_privileges;
|
||||
Acl *old_acl;
|
||||
Acl *new_acl;
|
||||
@ -638,28 +610,36 @@ ExecuteGrantStmt_Function(GrantStmt *stmt)
|
||||
elog(ERROR, "cache lookup failed for function %u", oid);
|
||||
pg_proc_tuple = (Form_pg_proc) GETSTRUCT(tuple);
|
||||
|
||||
/*
|
||||
* Get owner ID and working copy of existing ACL.
|
||||
* If there's no ACL, substitute the proper default.
|
||||
*/
|
||||
ownerId = pg_proc_tuple->proowner;
|
||||
grantorId = select_grantor(ownerId);
|
||||
aclDatum = SysCacheGetAttr(PROCOID, tuple, Anum_pg_proc_proacl,
|
||||
&isNull);
|
||||
if (isNull)
|
||||
old_acl = acldefault(ACL_OBJECT_FUNCTION, ownerId);
|
||||
else
|
||||
old_acl = DatumGetAclPCopy(aclDatum);
|
||||
|
||||
/* Determine ID to do the grant as, and available grant options */
|
||||
select_best_grantor(GetUserId(), privileges,
|
||||
old_acl, ownerId,
|
||||
&grantorId, &avail_goptions);
|
||||
|
||||
/*
|
||||
* Must be owner or have some privilege on the object (per spec,
|
||||
* any privilege will get you by here). The owner is always
|
||||
* treated as having all grant options.
|
||||
* If we found no grant options, consider whether to issue a hard
|
||||
* error. Per spec, having any privilege at all on the object
|
||||
* will get you by here.
|
||||
*/
|
||||
if (pg_proc_ownercheck(oid, GetUserId()))
|
||||
my_goptions = ACL_ALL_RIGHTS_FUNCTION;
|
||||
else
|
||||
if (avail_goptions == ACL_NO_RIGHTS)
|
||||
{
|
||||
AclMode my_rights;
|
||||
|
||||
my_rights = pg_proc_aclmask(oid,
|
||||
GetUserId(),
|
||||
ACL_ALL_RIGHTS_FUNCTION | ACL_GRANT_OPTION_FOR(ACL_ALL_RIGHTS_FUNCTION),
|
||||
ACLMASK_ALL);
|
||||
if (my_rights == ACL_NO_RIGHTS)
|
||||
if (pg_proc_aclmask(oid,
|
||||
grantorId,
|
||||
ACL_ALL_RIGHTS_FUNCTION | ACL_GRANT_OPTION_FOR(ACL_ALL_RIGHTS_FUNCTION),
|
||||
ACLMASK_ANY) == ACL_NO_RIGHTS)
|
||||
aclcheck_error(ACLCHECK_NO_PRIV, ACL_KIND_PROC,
|
||||
NameStr(pg_proc_tuple->proname));
|
||||
my_goptions = ACL_OPTION_TO_PRIVS(my_rights);
|
||||
}
|
||||
|
||||
/*
|
||||
@ -670,7 +650,7 @@ ExecuteGrantStmt_Function(GrantStmt *stmt)
|
||||
* In practice that behavior seems much too noisy, as well as
|
||||
* inconsistent with the GRANT case.)
|
||||
*/
|
||||
this_privileges = privileges & my_goptions;
|
||||
this_privileges = privileges & ACL_OPTION_TO_PRIVS(avail_goptions);
|
||||
if (stmt->is_grant)
|
||||
{
|
||||
if (this_privileges == 0)
|
||||
@ -695,17 +675,8 @@ ExecuteGrantStmt_Function(GrantStmt *stmt)
|
||||
}
|
||||
|
||||
/*
|
||||
* 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, ownerId);
|
||||
else
|
||||
/* get a detoasted copy of the ACL */
|
||||
old_acl = DatumGetAclPCopy(aclDatum);
|
||||
|
||||
/*
|
||||
* Generate new ACL.
|
||||
*
|
||||
* We need the members of both old and new ACLs so we can correct
|
||||
* the shared dependency information.
|
||||
*/
|
||||
@ -788,7 +759,7 @@ ExecuteGrantStmt_Language(GrantStmt *stmt)
|
||||
Form_pg_language pg_language_tuple;
|
||||
Datum aclDatum;
|
||||
bool isNull;
|
||||
AclMode my_goptions;
|
||||
AclMode avail_goptions;
|
||||
AclMode this_privileges;
|
||||
Acl *old_acl;
|
||||
Acl *new_acl;
|
||||
@ -820,31 +791,38 @@ ExecuteGrantStmt_Language(GrantStmt *stmt)
|
||||
errhint("Only superusers may use untrusted languages.")));
|
||||
|
||||
/*
|
||||
* Get owner ID and working copy of existing ACL.
|
||||
* If there's no ACL, substitute the proper default.
|
||||
*
|
||||
* Note: for now, languages are treated as owned by the bootstrap
|
||||
* user. We should add an owner column to pg_language instead.
|
||||
*/
|
||||
ownerId = BOOTSTRAP_SUPERUSERID;
|
||||
grantorId = select_grantor(ownerId);
|
||||
aclDatum = SysCacheGetAttr(LANGNAME, tuple, Anum_pg_language_lanacl,
|
||||
&isNull);
|
||||
if (isNull)
|
||||
old_acl = acldefault(ACL_OBJECT_LANGUAGE, ownerId);
|
||||
else
|
||||
old_acl = DatumGetAclPCopy(aclDatum);
|
||||
|
||||
/* Determine ID to do the grant as, and available grant options */
|
||||
select_best_grantor(GetUserId(), privileges,
|
||||
old_acl, ownerId,
|
||||
&grantorId, &avail_goptions);
|
||||
|
||||
/*
|
||||
* Must be owner or have some privilege on the object (per spec,
|
||||
* any privilege will get you by here). The owner is always
|
||||
* treated as having all grant options.
|
||||
* If we found no grant options, consider whether to issue a hard
|
||||
* error. Per spec, having any privilege at all on the object
|
||||
* will get you by here.
|
||||
*/
|
||||
if (superuser()) /* XXX no ownercheck() available */
|
||||
my_goptions = ACL_ALL_RIGHTS_LANGUAGE;
|
||||
else
|
||||
if (avail_goptions == ACL_NO_RIGHTS)
|
||||
{
|
||||
AclMode my_rights;
|
||||
|
||||
my_rights = pg_language_aclmask(HeapTupleGetOid(tuple),
|
||||
GetUserId(),
|
||||
ACL_ALL_RIGHTS_LANGUAGE | ACL_GRANT_OPTION_FOR(ACL_ALL_RIGHTS_LANGUAGE),
|
||||
ACLMASK_ALL);
|
||||
if (my_rights == ACL_NO_RIGHTS)
|
||||
if (pg_language_aclmask(HeapTupleGetOid(tuple),
|
||||
grantorId,
|
||||
ACL_ALL_RIGHTS_LANGUAGE | ACL_GRANT_OPTION_FOR(ACL_ALL_RIGHTS_LANGUAGE),
|
||||
ACLMASK_ANY) == ACL_NO_RIGHTS)
|
||||
aclcheck_error(ACLCHECK_NO_PRIV, ACL_KIND_LANGUAGE,
|
||||
NameStr(pg_language_tuple->lanname));
|
||||
my_goptions = ACL_OPTION_TO_PRIVS(my_rights);
|
||||
}
|
||||
|
||||
/*
|
||||
@ -855,7 +833,7 @@ ExecuteGrantStmt_Language(GrantStmt *stmt)
|
||||
* In practice that behavior seems much too noisy, as well as
|
||||
* inconsistent with the GRANT case.)
|
||||
*/
|
||||
this_privileges = privileges & my_goptions;
|
||||
this_privileges = privileges & ACL_OPTION_TO_PRIVS(avail_goptions);
|
||||
if (stmt->is_grant)
|
||||
{
|
||||
if (this_privileges == 0)
|
||||
@ -880,17 +858,8 @@ ExecuteGrantStmt_Language(GrantStmt *stmt)
|
||||
}
|
||||
|
||||
/*
|
||||
* 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, ownerId);
|
||||
else
|
||||
/* get a detoasted copy of the ACL */
|
||||
old_acl = DatumGetAclPCopy(aclDatum);
|
||||
|
||||
/*
|
||||
* Generate new ACL.
|
||||
*
|
||||
* We need the members of both old and new ACLs so we can correct
|
||||
* the shared dependency information.
|
||||
*/
|
||||
@ -973,7 +942,7 @@ ExecuteGrantStmt_Namespace(GrantStmt *stmt)
|
||||
Form_pg_namespace pg_namespace_tuple;
|
||||
Datum aclDatum;
|
||||
bool isNull;
|
||||
AclMode my_goptions;
|
||||
AclMode avail_goptions;
|
||||
AclMode this_privileges;
|
||||
Acl *old_acl;
|
||||
Acl *new_acl;
|
||||
@ -998,28 +967,37 @@ ExecuteGrantStmt_Namespace(GrantStmt *stmt)
|
||||
errmsg("schema \"%s\" does not exist", nspname)));
|
||||
pg_namespace_tuple = (Form_pg_namespace) GETSTRUCT(tuple);
|
||||
|
||||
/*
|
||||
* Get owner ID and working copy of existing ACL.
|
||||
* If there's no ACL, substitute the proper default.
|
||||
*/
|
||||
ownerId = pg_namespace_tuple->nspowner;
|
||||
grantorId = select_grantor(ownerId);
|
||||
aclDatum = SysCacheGetAttr(NAMESPACENAME, tuple,
|
||||
Anum_pg_namespace_nspacl,
|
||||
&isNull);
|
||||
if (isNull)
|
||||
old_acl = acldefault(ACL_OBJECT_NAMESPACE, ownerId);
|
||||
else
|
||||
old_acl = DatumGetAclPCopy(aclDatum);
|
||||
|
||||
/* Determine ID to do the grant as, and available grant options */
|
||||
select_best_grantor(GetUserId(), privileges,
|
||||
old_acl, ownerId,
|
||||
&grantorId, &avail_goptions);
|
||||
|
||||
/*
|
||||
* Must be owner or have some privilege on the object (per spec,
|
||||
* any privilege will get you by here). The owner is always
|
||||
* treated as having all grant options.
|
||||
* If we found no grant options, consider whether to issue a hard
|
||||
* error. Per spec, having any privilege at all on the object
|
||||
* will get you by here.
|
||||
*/
|
||||
if (pg_namespace_ownercheck(HeapTupleGetOid(tuple), GetUserId()))
|
||||
my_goptions = ACL_ALL_RIGHTS_NAMESPACE;
|
||||
else
|
||||
if (avail_goptions == ACL_NO_RIGHTS)
|
||||
{
|
||||
AclMode my_rights;
|
||||
|
||||
my_rights = pg_namespace_aclmask(HeapTupleGetOid(tuple),
|
||||
GetUserId(),
|
||||
ACL_ALL_RIGHTS_NAMESPACE | ACL_GRANT_OPTION_FOR(ACL_ALL_RIGHTS_NAMESPACE),
|
||||
ACLMASK_ALL);
|
||||
if (my_rights == ACL_NO_RIGHTS)
|
||||
if (pg_namespace_aclmask(HeapTupleGetOid(tuple),
|
||||
grantorId,
|
||||
ACL_ALL_RIGHTS_NAMESPACE | ACL_GRANT_OPTION_FOR(ACL_ALL_RIGHTS_NAMESPACE),
|
||||
ACLMASK_ANY) == ACL_NO_RIGHTS)
|
||||
aclcheck_error(ACLCHECK_NO_PRIV, ACL_KIND_NAMESPACE,
|
||||
nspname);
|
||||
my_goptions = ACL_OPTION_TO_PRIVS(my_rights);
|
||||
}
|
||||
|
||||
/*
|
||||
@ -1030,7 +1008,7 @@ ExecuteGrantStmt_Namespace(GrantStmt *stmt)
|
||||
* In practice that behavior seems much too noisy, as well as
|
||||
* inconsistent with the GRANT case.)
|
||||
*/
|
||||
this_privileges = privileges & my_goptions;
|
||||
this_privileges = privileges & ACL_OPTION_TO_PRIVS(avail_goptions);
|
||||
if (stmt->is_grant)
|
||||
{
|
||||
if (this_privileges == 0)
|
||||
@ -1055,18 +1033,8 @@ ExecuteGrantStmt_Namespace(GrantStmt *stmt)
|
||||
}
|
||||
|
||||
/*
|
||||
* 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, ownerId);
|
||||
else
|
||||
/* get a detoasted copy of the ACL */
|
||||
old_acl = DatumGetAclPCopy(aclDatum);
|
||||
|
||||
/*
|
||||
* Generate new ACL.
|
||||
*
|
||||
* We need the members of both old and new ACLs so we can correct
|
||||
* the shared dependency information.
|
||||
*/
|
||||
@ -1151,7 +1119,7 @@ ExecuteGrantStmt_Tablespace(GrantStmt *stmt)
|
||||
Form_pg_tablespace pg_tablespace_tuple;
|
||||
Datum aclDatum;
|
||||
bool isNull;
|
||||
AclMode my_goptions;
|
||||
AclMode avail_goptions;
|
||||
AclMode this_privileges;
|
||||
Acl *old_acl;
|
||||
Acl *new_acl;
|
||||
@ -1179,28 +1147,36 @@ ExecuteGrantStmt_Tablespace(GrantStmt *stmt)
|
||||
errmsg("tablespace \"%s\" does not exist", spcname)));
|
||||
pg_tablespace_tuple = (Form_pg_tablespace) GETSTRUCT(tuple);
|
||||
|
||||
/*
|
||||
* Get owner ID and working copy of existing ACL.
|
||||
* If there's no ACL, substitute the proper default.
|
||||
*/
|
||||
ownerId = pg_tablespace_tuple->spcowner;
|
||||
grantorId = select_grantor(ownerId);
|
||||
aclDatum = heap_getattr(tuple, Anum_pg_tablespace_spcacl,
|
||||
RelationGetDescr(relation), &isNull);
|
||||
if (isNull)
|
||||
old_acl = acldefault(ACL_OBJECT_TABLESPACE, ownerId);
|
||||
else
|
||||
old_acl = DatumGetAclPCopy(aclDatum);
|
||||
|
||||
/* Determine ID to do the grant as, and available grant options */
|
||||
select_best_grantor(GetUserId(), privileges,
|
||||
old_acl, ownerId,
|
||||
&grantorId, &avail_goptions);
|
||||
|
||||
/*
|
||||
* Must be owner or have some privilege on the object (per spec,
|
||||
* any privilege will get you by here). The owner is always
|
||||
* treated as having all grant options.
|
||||
* If we found no grant options, consider whether to issue a hard
|
||||
* error. Per spec, having any privilege at all on the object
|
||||
* will get you by here.
|
||||
*/
|
||||
if (pg_tablespace_ownercheck(HeapTupleGetOid(tuple), GetUserId()))
|
||||
my_goptions = ACL_ALL_RIGHTS_TABLESPACE;
|
||||
else
|
||||
if (avail_goptions == ACL_NO_RIGHTS)
|
||||
{
|
||||
AclMode my_rights;
|
||||
|
||||
my_rights = pg_tablespace_aclmask(HeapTupleGetOid(tuple),
|
||||
GetUserId(),
|
||||
ACL_ALL_RIGHTS_TABLESPACE | ACL_GRANT_OPTION_FOR(ACL_ALL_RIGHTS_TABLESPACE),
|
||||
ACLMASK_ALL);
|
||||
if (my_rights == ACL_NO_RIGHTS)
|
||||
if (pg_tablespace_aclmask(HeapTupleGetOid(tuple),
|
||||
grantorId,
|
||||
ACL_ALL_RIGHTS_TABLESPACE | ACL_GRANT_OPTION_FOR(ACL_ALL_RIGHTS_TABLESPACE),
|
||||
ACLMASK_ANY) == ACL_NO_RIGHTS)
|
||||
aclcheck_error(ACLCHECK_NO_PRIV, ACL_KIND_TABLESPACE,
|
||||
spcname);
|
||||
my_goptions = ACL_OPTION_TO_PRIVS(my_rights);
|
||||
}
|
||||
|
||||
/*
|
||||
@ -1211,7 +1187,7 @@ ExecuteGrantStmt_Tablespace(GrantStmt *stmt)
|
||||
* In practice that behavior seems much too noisy, as well as
|
||||
* inconsistent with the GRANT case.)
|
||||
*/
|
||||
this_privileges = privileges & my_goptions;
|
||||
this_privileges = privileges & ACL_OPTION_TO_PRIVS(avail_goptions);
|
||||
if (stmt->is_grant)
|
||||
{
|
||||
if (this_privileges == 0)
|
||||
@ -1236,17 +1212,8 @@ ExecuteGrantStmt_Tablespace(GrantStmt *stmt)
|
||||
}
|
||||
|
||||
/*
|
||||
* If there's no ACL, substitute the proper default.
|
||||
*/
|
||||
aclDatum = heap_getattr(tuple, Anum_pg_tablespace_spcacl,
|
||||
RelationGetDescr(relation), &isNull);
|
||||
if (isNull)
|
||||
old_acl = acldefault(ACL_OBJECT_TABLESPACE, ownerId);
|
||||
else
|
||||
/* get a detoasted copy of the ACL */
|
||||
old_acl = DatumGetAclPCopy(aclDatum);
|
||||
|
||||
/*
|
||||
* Generate new ACL.
|
||||
*
|
||||
* We need the members of both old and new ACLs so we can correct
|
||||
* the shared dependency information.
|
||||
*/
|
||||
|
@ -8,7 +8,7 @@
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/utils/adt/acl.c,v 1.124 2005/10/07 19:59:34 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/utils/adt/acl.c,v 1.125 2005/10/10 18:49:03 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -1070,6 +1070,65 @@ aclmask(const Acl *acl, Oid roleid, Oid ownerId,
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* aclmask_direct --- compute bitmask of all privileges held by roleid.
|
||||
*
|
||||
* This is exactly like aclmask() except that we consider only privileges
|
||||
* held *directly* by roleid, not those inherited via role membership.
|
||||
*/
|
||||
static AclMode
|
||||
aclmask_direct(const Acl *acl, Oid roleid, Oid ownerId,
|
||||
AclMode mask, AclMaskHow how)
|
||||
{
|
||||
AclMode result;
|
||||
AclItem *aidat;
|
||||
int i,
|
||||
num;
|
||||
|
||||
/*
|
||||
* Null ACL should not happen, since caller should have inserted
|
||||
* appropriate default
|
||||
*/
|
||||
if (acl == NULL)
|
||||
elog(ERROR, "null ACL");
|
||||
|
||||
/* Quick exit for mask == 0 */
|
||||
if (mask == 0)
|
||||
return 0;
|
||||
|
||||
result = 0;
|
||||
|
||||
/* Owner always implicitly has all grant options */
|
||||
if ((mask & ACLITEM_ALL_GOPTION_BITS) &&
|
||||
roleid == ownerId)
|
||||
{
|
||||
result = mask & ACLITEM_ALL_GOPTION_BITS;
|
||||
if ((how == ACLMASK_ALL) ? (result == mask) : (result != 0))
|
||||
return result;
|
||||
}
|
||||
|
||||
num = ACL_NUM(acl);
|
||||
aidat = ACL_DAT(acl);
|
||||
|
||||
/*
|
||||
* Check privileges granted directly to roleid (and not to public)
|
||||
*/
|
||||
for (i = 0; i < num; i++)
|
||||
{
|
||||
AclItem *aidata = &aidat[i];
|
||||
|
||||
if (aidata->ai_grantee == roleid)
|
||||
{
|
||||
result |= aidata->ai_privs & mask;
|
||||
if ((how == ACLMASK_ALL) ? (result == mask) : (result != 0))
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* aclmembers
|
||||
* Find out all the roleids mentioned in an Acl.
|
||||
@ -2778,37 +2837,33 @@ has_rolinherit(Oid roleid)
|
||||
|
||||
|
||||
/*
|
||||
* Does member have the privileges of role (directly or indirectly)?
|
||||
* Get a list of roles that the specified roleid has the privileges of
|
||||
*
|
||||
* This is defined not to recurse through roles that don't have rolinherit
|
||||
* set; for such roles, membership implies the ability to do SET ROLE, but
|
||||
* the privileges are not available until you've done so.
|
||||
*
|
||||
* Since indirect membership testing is relatively expensive, we cache
|
||||
* a list of memberships.
|
||||
* a list of memberships. Hence, the result is only guaranteed good until
|
||||
* the next call of roles_has_privs_of()!
|
||||
*
|
||||
* For the benefit of select_best_grantor, the result is defined to be
|
||||
* in breadth-first order, ie, closer relationships earlier.
|
||||
*/
|
||||
bool
|
||||
has_privs_of_role(Oid member, Oid role)
|
||||
static List *
|
||||
roles_has_privs_of(Oid roleid)
|
||||
{
|
||||
List *roles_list;
|
||||
ListCell *l;
|
||||
List *new_cached_privs_roles;
|
||||
MemoryContext oldctx;
|
||||
|
||||
/* Fast path for simple case */
|
||||
if (member == role)
|
||||
return true;
|
||||
|
||||
/* Superusers have every privilege, so are part of every role */
|
||||
if (superuser_arg(member))
|
||||
return true;
|
||||
|
||||
/* If cache is already valid, just use the list */
|
||||
if (OidIsValid(cached_privs_role) && cached_privs_role == member)
|
||||
return list_member_oid(cached_privs_roles, role);
|
||||
/* If cache is already valid, just return the list */
|
||||
if (OidIsValid(cached_privs_role) && cached_privs_role == roleid)
|
||||
return cached_privs_roles;
|
||||
|
||||
/*
|
||||
* Find all the roles that member is a member of,
|
||||
* Find all the roles that roleid is a member of,
|
||||
* including multi-level recursion. The role itself will always
|
||||
* be the first element of the resulting list.
|
||||
*
|
||||
@ -2818,7 +2873,7 @@ has_privs_of_role(Oid member, Oid role)
|
||||
* This is a bit tricky but works because the foreach() macro doesn't
|
||||
* fetch the next list element until the bottom of the loop.
|
||||
*/
|
||||
roles_list = list_make1_oid(member);
|
||||
roles_list = list_make1_oid(roleid);
|
||||
|
||||
foreach(l, roles_list)
|
||||
{
|
||||
@ -2863,43 +2918,36 @@ has_privs_of_role(Oid member, Oid role)
|
||||
cached_privs_role = InvalidOid; /* just paranoia */
|
||||
list_free(cached_privs_roles);
|
||||
cached_privs_roles = new_cached_privs_roles;
|
||||
cached_privs_role = member;
|
||||
cached_privs_role = roleid;
|
||||
|
||||
/* And now we can return the answer */
|
||||
return list_member_oid(cached_privs_roles, role);
|
||||
return cached_privs_roles;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Is member a member of role (directly or indirectly)?
|
||||
* Get a list of roles that the specified roleid is a member of
|
||||
*
|
||||
* This is defined to recurse through roles regardless of rolinherit.
|
||||
*
|
||||
* Since indirect membership testing is relatively expensive, we cache
|
||||
* a list of memberships.
|
||||
* a list of memberships. Hence, the result is only guaranteed good until
|
||||
* the next call of roles_is_member_of()!
|
||||
*/
|
||||
bool
|
||||
is_member_of_role(Oid member, Oid role)
|
||||
static List *
|
||||
roles_is_member_of(Oid roleid)
|
||||
{
|
||||
List *roles_list;
|
||||
ListCell *l;
|
||||
List *new_cached_membership_roles;
|
||||
MemoryContext oldctx;
|
||||
|
||||
/* Fast path for simple case */
|
||||
if (member == role)
|
||||
return true;
|
||||
|
||||
/* Superusers have every privilege, so are part of every role */
|
||||
if (superuser_arg(member))
|
||||
return true;
|
||||
|
||||
/* If cache is already valid, just use the list */
|
||||
if (OidIsValid(cached_member_role) && cached_member_role == member)
|
||||
return list_member_oid(cached_membership_roles, role);
|
||||
/* If cache is already valid, just return the list */
|
||||
if (OidIsValid(cached_member_role) && cached_member_role == roleid)
|
||||
return cached_membership_roles;
|
||||
|
||||
/*
|
||||
* Find all the roles that member is a member of,
|
||||
* Find all the roles that roleid is a member of,
|
||||
* including multi-level recursion. The role itself will always
|
||||
* be the first element of the resulting list.
|
||||
*
|
||||
@ -2909,7 +2957,7 @@ is_member_of_role(Oid member, Oid role)
|
||||
* This is a bit tricky but works because the foreach() macro doesn't
|
||||
* fetch the next list element until the bottom of the loop.
|
||||
*/
|
||||
roles_list = list_make1_oid(member);
|
||||
roles_list = list_make1_oid(roleid);
|
||||
|
||||
foreach(l, roles_list)
|
||||
{
|
||||
@ -2950,10 +2998,60 @@ is_member_of_role(Oid member, Oid role)
|
||||
cached_member_role = InvalidOid; /* just paranoia */
|
||||
list_free(cached_membership_roles);
|
||||
cached_membership_roles = new_cached_membership_roles;
|
||||
cached_member_role = member;
|
||||
cached_member_role = roleid;
|
||||
|
||||
/* And now we can return the answer */
|
||||
return list_member_oid(cached_membership_roles, role);
|
||||
return cached_membership_roles;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Does member have the privileges of role (directly or indirectly)?
|
||||
*
|
||||
* This is defined not to recurse through roles that don't have rolinherit
|
||||
* set; for such roles, membership implies the ability to do SET ROLE, but
|
||||
* the privileges are not available until you've done so.
|
||||
*/
|
||||
bool
|
||||
has_privs_of_role(Oid member, Oid role)
|
||||
{
|
||||
/* Fast path for simple case */
|
||||
if (member == role)
|
||||
return true;
|
||||
|
||||
/* Superusers have every privilege, so are part of every role */
|
||||
if (superuser_arg(member))
|
||||
return true;
|
||||
|
||||
/*
|
||||
* Find all the roles that member has the privileges of, including
|
||||
* multi-level recursion, then see if target role is any one of them.
|
||||
*/
|
||||
return list_member_oid(roles_has_privs_of(member), role);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Is member a member of role (directly or indirectly)?
|
||||
*
|
||||
* This is defined to recurse through roles regardless of rolinherit.
|
||||
*/
|
||||
bool
|
||||
is_member_of_role(Oid member, Oid role)
|
||||
{
|
||||
/* Fast path for simple case */
|
||||
if (member == role)
|
||||
return true;
|
||||
|
||||
/* Superusers have every privilege, so are part of every role */
|
||||
if (superuser_arg(member))
|
||||
return true;
|
||||
|
||||
/*
|
||||
* Find all the roles that member is a member of, including multi-level
|
||||
* recursion, then see if target role is any one of them.
|
||||
*/
|
||||
return list_member_oid(roles_is_member_of(member), role);
|
||||
}
|
||||
|
||||
/*
|
||||
@ -3034,3 +3132,113 @@ is_admin_of_role(Oid member, Oid role)
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/* does what it says ... */
|
||||
static int
|
||||
count_one_bits(AclMode mask)
|
||||
{
|
||||
int nbits = 0;
|
||||
|
||||
/* this code relies on AclMode being an unsigned type */
|
||||
while (mask)
|
||||
{
|
||||
if (mask & 1)
|
||||
nbits++;
|
||||
mask >>= 1;
|
||||
}
|
||||
return nbits;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Select the effective grantor ID for a GRANT or REVOKE operation.
|
||||
*
|
||||
* The grantor must always be either the object owner or some role that has
|
||||
* been explicitly granted grant options. This ensures that all granted
|
||||
* privileges appear to flow from the object owner, and there are never
|
||||
* multiple "original sources" of a privilege. Therefore, if the would-be
|
||||
* grantor is a member of a role that has the needed grant options, we have
|
||||
* to do the grant as that role instead.
|
||||
*
|
||||
* It is possible that the would-be grantor is a member of several roles
|
||||
* that have different subsets of the desired grant options, but no one
|
||||
* role has 'em all. In this case we pick a role with the largest number
|
||||
* of desired options. Ties are broken in favor of closer ancestors.
|
||||
*
|
||||
* roleId: the role attempting to do the GRANT/REVOKE
|
||||
* privileges: the privileges to be granted/revoked
|
||||
* acl: the ACL of the object in question
|
||||
* ownerId: the role owning the object in question
|
||||
* *grantorId: receives the OID of the role to do the grant as
|
||||
* *grantOptions: receives the grant options actually held by grantorId
|
||||
*
|
||||
* If no grant options exist, we set grantorId to roleId, grantOptions to 0.
|
||||
*/
|
||||
void
|
||||
select_best_grantor(Oid roleId, AclMode privileges,
|
||||
const Acl *acl, Oid ownerId,
|
||||
Oid *grantorId, AclMode *grantOptions)
|
||||
{
|
||||
AclMode needed_goptions = ACL_GRANT_OPTION_FOR(privileges);
|
||||
List *roles_list;
|
||||
int nrights;
|
||||
ListCell *l;
|
||||
|
||||
/*
|
||||
* The object owner is always treated as having all grant options,
|
||||
* so if roleId is the owner it's easy. Also, if roleId is a superuser
|
||||
* it's easy: superusers are implicitly members of every role, so they
|
||||
* act as the object owner.
|
||||
*/
|
||||
if (roleId == ownerId || superuser_arg(roleId))
|
||||
{
|
||||
*grantorId = ownerId;
|
||||
*grantOptions = needed_goptions;
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Otherwise we have to do a careful search to see if roleId has the
|
||||
* privileges of any suitable role. Note: we can hang onto the result
|
||||
* of roles_has_privs_of() throughout this loop, because aclmask_direct()
|
||||
* doesn't query any role memberships.
|
||||
*/
|
||||
roles_list = roles_has_privs_of(roleId);
|
||||
|
||||
/* initialize candidate result as default */
|
||||
*grantorId = roleId;
|
||||
*grantOptions = ACL_NO_RIGHTS;
|
||||
nrights = 0;
|
||||
|
||||
foreach(l, roles_list)
|
||||
{
|
||||
Oid otherrole = lfirst_oid(l);
|
||||
AclMode otherprivs;
|
||||
|
||||
otherprivs = aclmask_direct(acl, otherrole, ownerId,
|
||||
needed_goptions, ACLMASK_ALL);
|
||||
if (otherprivs == needed_goptions)
|
||||
{
|
||||
/* Found a suitable grantor */
|
||||
*grantorId = otherrole;
|
||||
*grantOptions = otherprivs;
|
||||
return;
|
||||
}
|
||||
/*
|
||||
* If it has just some of the needed privileges, remember best
|
||||
* candidate.
|
||||
*/
|
||||
if (otherprivs != ACL_NO_RIGHTS)
|
||||
{
|
||||
int nnewrights = count_one_bits(otherprivs);
|
||||
|
||||
if (nnewrights > nrights)
|
||||
{
|
||||
*grantorId = otherrole;
|
||||
*grantOptions = otherprivs;
|
||||
nrights = nnewrights;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,7 +7,7 @@
|
||||
* Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
|
||||
* Portions Copyright (c) 1994, Regents of the University of California
|
||||
*
|
||||
* $PostgreSQL: pgsql/src/include/utils/acl.h,v 1.83 2005/07/26 16:38:29 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/include/utils/acl.h,v 1.84 2005/10/10 18:49:04 tgl Exp $
|
||||
*
|
||||
* NOTES
|
||||
* An ACL array is simply an array of AclItems, representing the union
|
||||
@ -215,6 +215,10 @@ extern bool is_member_of_role(Oid member, Oid role);
|
||||
extern bool is_admin_of_role(Oid member, Oid role);
|
||||
extern void check_is_member_of_role(Oid member, Oid role);
|
||||
|
||||
extern void select_best_grantor(Oid roleId, AclMode privileges,
|
||||
const Acl *acl, Oid ownerId,
|
||||
Oid *grantorId, AclMode *grantOptions);
|
||||
|
||||
extern void initialize_acl(void);
|
||||
|
||||
/*
|
||||
|
Loading…
x
Reference in New Issue
Block a user