@ -38,6 +38,7 @@ static void AddAcl(PQExpBuffer aclbuf, const char *keyword,
* TABLE , SEQUENCE , FUNCTION , LANGUAGE , SCHEMA , DATABASE , TABLESPACE ,
* FOREIGN DATA WRAPPER , SERVER , or LARGE OBJECT )
* acls : the ACL string fetched from the database
* racls : the ACL string of any initial - but - now - revoked privileges
* owner : username of object owner ( will be passed through fmtId ) ; can be
* NULL or empty string to indicate " no owner known "
* prefix : string to prefix to each generated command ; typically empty
@ -54,13 +55,15 @@ static void AddAcl(PQExpBuffer aclbuf, const char *keyword,
*/
bool
buildACLCommands ( const char * name , const char * subname ,
const char * type , const char * acls , const char * owne r,
const char * prefix , int remoteVersion ,
const char * type , const char * acls , const char * racls ,
const char * owner , const char * prefix , int remoteVersion ,
PQExpBuffer sql )
{
bool ok = true ;
char * * aclitems ;
int naclitems ;
char * * aclitems = NULL ;
char * * raclitems = NULL ;
int naclitems = 0 ;
int nraclitems = 0 ;
int i ;
PQExpBuffer grantee ,
grantor ,
@ -70,18 +73,31 @@ buildACLCommands(const char *name, const char *subname,
secondsql ;
bool found_owner_privs = false ;
if ( strlen ( acls ) = = 0 )
if ( strlen ( acls ) = = 0 & & strlen ( racls ) = = 0 )
return true ; /* object has default permissions */
/* treat empty-string owner same as NULL */
if ( owner & & * owner = = ' \0 ' )
owner = NULL ;
if ( ! parsePGArray ( acls , & aclitems , & naclitems ) )
if ( strlen ( acls ) ! = 0 )
{
if ( aclitems )
free ( aclitems ) ;
return false ;
if ( ! parsePGArray ( acls , & aclitems , & naclitems ) )
{
if ( aclitems )
free ( aclitems ) ;
return false ;
}
}
if ( strlen ( racls ) ! = 0 )
{
if ( ! parsePGArray ( racls , & raclitems , & nraclitems ) )
{
if ( raclitems )
free ( raclitems ) ;
return false ;
}
}
grantee = createPQExpBuffer ( ) ;
@ -90,24 +106,101 @@ buildACLCommands(const char *name, const char *subname,
privswgo = createPQExpBuffer ( ) ;
/*
* At the end , these two will be pasted together to form the result . But
* the owner privileges need to go before the other ones to keep the
* dependencies valid . In recent versions this is normally the case , but
* in old versions they come after the PUBLIC privileges and that results
* in problems if we need to run REVOKE on the owner privileges .
* At the end , these two will be pasted together to form the result .
*
* For older systems we use these to ensure that the owner privileges go
* before the other ones , as a GRANT could create the default entry for
* the object , which generally includes all rights for the owner . In more
* recent versions we normally handle this because the owner rights come
* first in the ACLs , but older versions might have them after the PUBLIC
* privileges .
*
* For 9.6 and later systems , much of this changes . With 9.6 , we check
* the default privileges for the objects at dump time and create two sets
* of ACLs - " racls " which are the ACLs to REVOKE from the object ( as the
* object may have initial privileges on it , along with any default ACLs
* which are not part of the current set of privileges ) , and regular
* " acls " , which are the ACLs to GRANT to the object . We handle the
* REVOKEs first , followed by the GRANTs .
*/
firstsql = createPQExpBuffer ( ) ;
secondsql = 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 .
* For pre - 9.6 systems , we always start with REVOKE ALL FROM PUBLIC , as we
* don ' t wish to make any assumptions about what the default ACLs are , and
* we do not collect them during the dump phase ( and racls will always be
* the empty set , see above ) .
*
* For 9.6 and later , if any revoke ACLs have been provided , then include
* them in ' firstsql ' .
*
* Revoke ACLs happen when an object starts out life with a set of
* privileges ( eg : GRANT SELECT ON pg_class TO PUBLIC ; ) and the user has
* decided to revoke those rights . Since those objects come into being
* with those default privileges , we have to revoke them to match what the
* current state of affairs is . Note that we only started explicitly
* tracking such initial rights in 9.6 , and prior to that all initial
* rights are actually handled by the simple ' REVOKE ALL . . FROM PUBLIC '
* case , for initdb - created objects . Prior to 9.6 , we didn ' t handle
* extensions correctly , but we do now by tracking their initial
* privileges , in the same way we track initdb initial privileges , see
* pg_init_privs .
*/
appendPQExpBuffer ( firstsql , " %sREVOKE ALL " , prefix ) ;
if ( subname )
appendPQExpBuffer ( firstsql , " (%s) " , subname ) ;
appendPQExpBuffer ( firstsql , " ON %s %s FROM PUBLIC; \n " , type , name ) ;
if ( remoteVersion < 90600 )
{
Assert ( nraclitems = = 0 ) ;
appendPQExpBuffer ( firstsql , " %sREVOKE ALL " , prefix ) ;
if ( subname )
appendPQExpBuffer ( firstsql , " (%s) " , subname ) ;
appendPQExpBuffer ( firstsql , " ON %s %s FROM PUBLIC; \n " , type , name ) ;
}
else
{
/* Scan individual REVOKE ACL items */
for ( i = 0 ; i < nraclitems ; i + + )
{
if ( ! parseAclItem ( raclitems [ i ] , type , name , subname , remoteVersion ,
grantee , grantor , privs , privswgo ) )
{
ok = false ;
break ;
}
if ( privs - > len > 0 | | privswgo - > len > 0 )
{
if ( privs - > len > 0 )
{
appendPQExpBuffer ( firstsql , " %sREVOKE %s ON %s %s FROM " ,
prefix , privs - > data , type , name ) ;
if ( grantee - > len = = 0 )
appendPQExpBufferStr ( firstsql , " PUBLIC; \n " ) ;
else if ( strncmp ( grantee - > data , " group " ,
strlen ( " group " ) ) = = 0 )
appendPQExpBuffer ( firstsql , " GROUP %s; \n " ,
fmtId ( grantee - > data + strlen ( " group " ) ) ) ;
else
appendPQExpBuffer ( firstsql , " %s; \n " ,
fmtId ( grantee - > data ) ) ;
}
if ( privswgo - > len > 0 )
{
appendPQExpBuffer ( firstsql ,
" %sREVOKE GRANT OPTION FOR %s ON %s %s FROM " ,
prefix , privswgo - > data , type , name ) ;
if ( grantee - > len = = 0 )
appendPQExpBufferStr ( firstsql , " PUBLIC " ) ;
else if ( strncmp ( grantee - > data , " group " ,
strlen ( " group " ) ) = = 0 )
appendPQExpBuffer ( firstsql , " GROUP %s " ,
fmtId ( grantee - > data + strlen ( " group " ) ) ) ;
else
appendPQExpBufferStr ( firstsql , fmtId ( grantee - > data ) ) ;
}
}
}
}
/*
* We still need some hacking though to cover the case where new default
@ -138,7 +231,14 @@ buildACLCommands(const char *name, const char *subname,
if ( privs - > len > 0 | | privswgo - > len > 0 )
{
if ( owner
/*
* Prior to 9.6 , we had to handle owner privileges in a special
* manner by first REVOKE ' ing the rights and then GRANT ' ing them
* after . With 9.6 and above , what we need to REVOKE and what we
* need to GRANT is figured out when we dump and stashed into
* " racls " and " acls " , respectivly . See above .
*/
if ( remoteVersion < 90600 & & owner
& & strcmp ( grantee - > data , owner ) = = 0
& & strcmp ( grantor - > data , owner ) = = 0 )
{
@ -172,7 +272,14 @@ buildACLCommands(const char *name, const char *subname,
else
{
/*
* Otherwise can assume we are starting from no privs .
* For systems prior to 9.6 , we can assume we are starting
* from no privs at this point .
*
* For 9.6 and above , at this point we have issued REVOKE
* statements for all initial and default privileges which are
* no longer present on the object ( as they were passed in as
* ' racls ' ) and we can simply GRANT the rights which are in
* ' acls ' .
*/
if ( grantor - > len > 0
& & ( ! owner | | strcmp ( owner , grantor - > data ) ! = 0 ) )
@ -215,9 +322,12 @@ buildACLCommands(const char *name, const char *subname,
}
/*
* If we didn ' t find any owner privs , the owner must have revoked ' em all
* For systems prior to 9.6 , if we didn ' t find any owner privs , the owner
* must have revoked ' em all .
*
* For 9.6 and above , we handle this through the ' racls ' . See above .
*/
if ( ! found_owner_privs & & owner )
if ( remoteVersion < 90600 & & ! found_owner_privs & & owner )
{
appendPQExpBuffer ( firstsql , " %sREVOKE ALL " , prefix ) ;
if ( subname )
@ -235,7 +345,11 @@ buildACLCommands(const char *name, const char *subname,
destroyPQExpBuffer ( firstsql ) ;
destroyPQExpBuffer ( secondsql ) ;
free ( aclitems ) ;
if ( aclitems )
free ( aclitems ) ;
if ( raclitems )
free ( raclitems ) ;
return ok ;
}
@ -275,7 +389,7 @@ buildDefaultACLCommands(const char *type, const char *nspname,
appendPQExpBuffer ( prefix , " IN SCHEMA %s " , fmtId ( nspname ) ) ;
result = buildACLCommands ( " " , NULL ,
type , acls , owner ,
type , acls , " " , owner ,
prefix - > data , remoteVersion ,
sql ) ;
@ -555,3 +669,109 @@ emitShSecLabels(PGconn *conn, PGresult *res, PQExpBuffer buffer,
appendPQExpBufferStr ( buffer , " ; \n " ) ;
}
}
/*
* buildACLQueries
*
* Build the subqueries to extract out the correct set of ACLs to be
* GRANT ' d and REVOKE ' d for the specific kind of object , accounting for any
* initial privileges ( from pg_init_privs ) and based on if we are in binary
* upgrade mode or not .
*
* Also builds subqueries to extract out the set of ACLs to go from the object
* default privileges to the privileges in pg_init_privs , if we are in binary
* upgrade mode , so that those privileges can be set up and recorded in the new
* cluster before the regular privileges are added on top of those .
*/
void
buildACLQueries ( PQExpBuffer acl_subquery , PQExpBuffer racl_subquery ,
PQExpBuffer init_acl_subquery , PQExpBuffer init_racl_subquery ,
const char * acl_column , const char * acl_owner ,
const char * obj_kind , bool binary_upgrade )
{
/*
* To get the delta from what the permissions were at creation time
* ( either initdb or CREATE EXTENSION ) vs . what they are now , we have to
* look at two things :
*
* What privileges have been added , which we calculate by extracting all
* the current privileges ( using the set of default privileges for the
* object type if current privileges are NULL ) and then removing those
* which existed at creation time ( again , using the set of default
* privileges for the object type if there were no creation time
* privileges ) .
*
* What privileges have been removed , which we calculate by extracting the
* privileges as they were at creation time ( or the default privileges , as
* above ) , and then removing the current privileges ( or the default
* privileges , if current privileges are NULL ) .
*
* As a good cross - check , both directions of these checks should result in
* the empty set if both the current ACL and the initial privs are NULL
* ( meaning , in practice , that the default ACLs were there at init time
* and is what the current privileges are ) .
*
* We always perform this delta on all ACLs and expect that by the time
* these are run the initial privileges will be in place , even in a
* binary upgrade situation ( see below ) .
*/
printfPQExpBuffer ( acl_subquery , " (SELECT array_agg(acl) FROM "
" (SELECT unnest(coalesce(%s,acldefault(%s,%s))) AS acl "
" EXCEPT "
" SELECT unnest(coalesce(pip.initprivs,acldefault(%s,%s)))) as foo) " ,
acl_column ,
obj_kind ,
acl_owner ,
obj_kind ,
acl_owner ) ;
printfPQExpBuffer ( racl_subquery , " (SELECT array_agg(acl) FROM "
" (SELECT unnest(coalesce(pip.initprivs,acldefault(%s,%s))) AS acl "
" EXCEPT "
" SELECT unnest(coalesce(%s,acldefault(%s,%s)))) as foo) " ,
obj_kind ,
acl_owner ,
acl_column ,
obj_kind ,
acl_owner ) ;
/*
* In binary upgrade mode we don ' t run the extension script but instead
* dump out the objects independently and then recreate them . To preserve
* the initial privileges which were set on extension objects , we need to
* grab the set of GRANT and REVOKE commands necessary to get from the
* default privileges of an object to the initial privileges as recorded
* in pg_init_privs .
*
* These will then be run ahead of the regular ACL commands , which were
* calculated using the queries above , inside of a block which sets a flag
* to indicate that the backend should record the results of these GRANT
* and REVOKE statements into pg_init_privs . This is how we preserve the
* contents of that catalog across binary upgrades .
*/
if ( binary_upgrade )
{
printfPQExpBuffer ( init_acl_subquery ,
" CASE WHEN privtype = 'e' THEN "
" (SELECT array_agg(acl) FROM "
" (SELECT unnest(pip.initprivs) AS acl "
" EXCEPT "
" SELECT unnest(acldefault(%s,%s))) as foo) END " ,
obj_kind ,
acl_owner ) ;
printfPQExpBuffer ( init_racl_subquery ,
" CASE WHEN privtype = 'e' THEN "
" (SELECT array_agg(acl) FROM "
" (SELECT unnest(acldefault(%s,%s)) AS acl "
" EXCEPT "
" SELECT unnest(pip.initprivs)) as foo) END " ,
obj_kind ,
acl_owner ) ;
}
else
{
printfPQExpBuffer ( init_acl_subquery , " NULL " ) ;
printfPQExpBuffer ( init_racl_subquery , " NULL " ) ;
}
}