Altering default privileges on schemas

Extend ALTER DEFAULT PRIVILEGES command to schemas.

Author: Matheus Oliveira
Reviewed-by: Petr Jelínek, Ashutosh Sharma

https://commitfest.postgresql.org/13/887/
This commit is contained in:
Teodor Sigaev 2017-03-28 18:58:55 +03:00
parent 85163641f8
commit ab89e465cb
13 changed files with 176 additions and 12 deletions

View File

@ -46,6 +46,10 @@ GRANT { USAGE | ALL [ PRIVILEGES ] }
ON TYPES
TO { [ GROUP ] <replaceable class="PARAMETER">role_name</replaceable> | PUBLIC } [, ...] [ WITH GRANT OPTION ]
GRANT { USAGE | CREATE | ALL [ PRIVILEGES ] }
ON SCHEMAS
TO { [ GROUP ] <replaceable class="PARAMETER">role_name</replaceable> | PUBLIC } [, ...] [ WITH GRANT OPTION ]
REVOKE [ GRANT OPTION FOR ]
{ { SELECT | INSERT | UPDATE | DELETE | TRUNCATE | REFERENCES | TRIGGER }
[, ...] | ALL [ PRIVILEGES ] }
@ -71,6 +75,12 @@ REVOKE [ GRANT OPTION FOR ]
ON TYPES
FROM { [ GROUP ] <replaceable class="PARAMETER">role_name</replaceable> | PUBLIC } [, ...]
[ CASCADE | RESTRICT ]
REVOKE [ GRANT OPTION FOR ]
{ USAGE | CREATE | ALL [ PRIVILEGES ] }
ON SCHEMAS
FROM { [ GROUP ] <replaceable class="PARAMETER">role_name</replaceable> | PUBLIC } [, ...]
[ CASCADE | RESTRICT ]
</synopsis>
</refsynopsisdiv>
@ -81,8 +91,9 @@ REVOKE [ GRANT OPTION FOR ]
<command>ALTER DEFAULT PRIVILEGES</> allows you to set the privileges
that will be applied to objects created in the future. (It does not
affect privileges assigned to already-existing objects.) Currently,
only the privileges for tables (including views and foreign tables),
sequences, functions, and types (including domains) can be altered.
only the privileges for schemas, tables (including views and foreign
tables), sequences, functions, and types (including domains) can be
altered.
</para>
<para>
@ -125,6 +136,8 @@ REVOKE [ GRANT OPTION FOR ]
are altered for objects later created in that schema.
If <literal>IN SCHEMA</> is omitted, the global default privileges
are altered.
<literal>IN SCHEMA</> is not allowed when using <literal>ON SCHEMAS</>
as schemas can't be nested.
</para>
</listitem>
</varlistentry>

View File

@ -959,6 +959,10 @@ ExecAlterDefaultPrivilegesStmt(ParseState *pstate, AlterDefaultPrivilegesStmt *s
all_privileges = ACL_ALL_RIGHTS_TYPE;
errormsg = gettext_noop("invalid privilege type %s for type");
break;
case ACL_OBJECT_NAMESPACE:
all_privileges = ACL_ALL_RIGHTS_NAMESPACE;
errormsg = gettext_noop("invalid privilege type %s for schema");
break;
default:
elog(ERROR, "unrecognized GrantStmt.objtype: %d",
(int) action->objtype);
@ -1146,6 +1150,16 @@ SetDefaultACL(InternalDefaultACL *iacls)
this_privileges = ACL_ALL_RIGHTS_TYPE;
break;
case ACL_OBJECT_NAMESPACE:
if (OidIsValid(iacls->nspid))
ereport(ERROR,
(errcode(ERRCODE_INVALID_GRANT_OPERATION),
errmsg("cannot use IN SCHEMA clause when using GRANT/REVOKE ON SCHEMAS")));
objtype = DEFACLOBJ_NAMESPACE;
if (iacls->all_privs && this_privileges == ACL_NO_RIGHTS)
this_privileges = ACL_ALL_RIGHTS_NAMESPACE;
break;
default:
elog(ERROR, "unrecognized objtype: %d",
(int) iacls->objtype);
@ -1369,6 +1383,9 @@ RemoveRoleFromObjectACL(Oid roleid, Oid classid, Oid objid)
case DEFACLOBJ_TYPE:
iacls.objtype = ACL_OBJECT_TYPE;
break;
case DEFACLOBJ_NAMESPACE:
iacls.objtype = ACL_OBJECT_NAMESPACE;
break;
default:
/* Shouldn't get here */
elog(ERROR, "unexpected default ACL type: %d",
@ -5259,6 +5276,10 @@ get_user_default_acl(GrantObjectType objtype, Oid ownerId, Oid nsp_oid)
defaclobjtype = DEFACLOBJ_TYPE;
break;
case ACL_OBJECT_NAMESPACE:
defaclobjtype = DEFACLOBJ_NAMESPACE;
break;
default:
return NULL;
}

View File

@ -1843,11 +1843,14 @@ get_object_address_defacl(List *object, bool missing_ok)
case DEFACLOBJ_TYPE:
objtype_str = "types";
break;
case DEFACLOBJ_NAMESPACE:
objtype_str = "schemas";
break;
default:
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("unrecognized default ACL object type %c", objtype),
errhint("Valid object types are \"r\", \"S\", \"f\", and \"T\".")));
errhint("Valid object types are \"r\", \"S\", \"f\", \"T\" and \"s\".")));
}
/*
@ -3255,6 +3258,11 @@ getObjectDescription(const ObjectAddress *object)
_("default privileges on new types belonging to role %s"),
GetUserNameFromId(defacl->defaclrole, false));
break;
case DEFACLOBJ_NAMESPACE:
appendStringInfo(&buffer,
_("default privileges on new schemas belonging to role %s"),
GetUserNameFromId(defacl->defaclrole, false));
break;
default:
/* shouldn't get here */
appendStringInfo(&buffer,
@ -4762,6 +4770,10 @@ getObjectIdentityParts(const ObjectAddress *object,
appendStringInfoString(&buffer,
" on types");
break;
case DEFACLOBJ_NAMESPACE:
appendStringInfoString(&buffer,
" on schemas");
break;
}
if (objname)

View File

@ -31,10 +31,11 @@
* Create a namespace (schema) with the given name and owner OID.
*
* If isTemp is true, this schema is a per-backend schema for holding
* temporary tables. Currently, the only effect of that is to prevent it
* from being linked as a member of any active extension. (If someone
* does CREATE TEMP TABLE in an extension script, we don't want the temp
* schema to become part of the extension.)
* temporary tables. Currently, it is used to prevent it from being
* linked as a member of any active extension. (If someone does CREATE
* TEMP TABLE in an extension script, we don't want the temp schema to
* become part of the extension). And to avoid checking for default ACL
* for temp namespace (as it is not necessary).
* ---------------
*/
Oid
@ -49,6 +50,7 @@ NamespaceCreate(const char *nspName, Oid ownerId, bool isTemp)
TupleDesc tupDesc;
ObjectAddress myself;
int i;
Acl *nspacl;
/* sanity checks */
if (!nspName)
@ -60,6 +62,12 @@ NamespaceCreate(const char *nspName, Oid ownerId, bool isTemp)
(errcode(ERRCODE_DUPLICATE_SCHEMA),
errmsg("schema \"%s\" already exists", nspName)));
if (!isTemp)
nspacl = get_user_default_acl(ACL_OBJECT_NAMESPACE, ownerId,
InvalidOid);
else
nspacl = NULL;
/* initialize nulls and values */
for (i = 0; i < Natts_pg_namespace; i++)
{
@ -69,7 +77,10 @@ NamespaceCreate(const char *nspName, Oid ownerId, bool isTemp)
namestrcpy(&nname, nspName);
values[Anum_pg_namespace_nspname - 1] = NameGetDatum(&nname);
values[Anum_pg_namespace_nspowner - 1] = ObjectIdGetDatum(ownerId);
nulls[Anum_pg_namespace_nspacl - 1] = true;
if (nspacl != NULL)
values[Anum_pg_namespace_nspacl - 1] = PointerGetDatum(nspacl);
else
nulls[Anum_pg_namespace_nspacl - 1] = true;
nspdesc = heap_open(NamespaceRelationId, RowExclusiveLock);
tupDesc = nspdesc->rd_att;

View File

@ -668,7 +668,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
RESET RESTART RESTRICT RETURNING RETURNS REVOKE RIGHT ROLE ROLLBACK ROLLUP
ROW ROWS RULE
SAVEPOINT SCHEMA SCROLL SEARCH SECOND_P SECURITY SELECT SEQUENCE SEQUENCES
SAVEPOINT SCHEMA SCHEMAS SCROLL SEARCH SECOND_P SECURITY SELECT SEQUENCE SEQUENCES
SERIALIZABLE SERVER SESSION SESSION_USER SET SETS SETOF SHARE SHOW
SIMILAR SIMPLE SKIP SLOT SMALLINT SNAPSHOT SOME SQL_P STABLE STANDALONE_P
START STATEMENT STATISTICS STDIN STDOUT STORAGE STRICT_P STRIP_P
@ -7035,6 +7035,7 @@ defacl_privilege_target:
| FUNCTIONS { $$ = ACL_OBJECT_FUNCTION; }
| SEQUENCES { $$ = ACL_OBJECT_SEQUENCE; }
| TYPES_P { $$ = ACL_OBJECT_TYPE; }
| SCHEMAS { $$ = ACL_OBJECT_NAMESPACE; }
;
@ -14713,6 +14714,7 @@ unreserved_keyword:
| RULE
| SAVEPOINT
| SCHEMA
| SCHEMAS
| SCROLL
| SEARCH
| SECOND_P

View File

@ -520,7 +520,9 @@ do { \
CONVERT_PRIV('X', "EXECUTE");
else if (strcmp(type, "LANGUAGE") == 0)
CONVERT_PRIV('U', "USAGE");
else if (strcmp(type, "SCHEMA") == 0)
else if (strcmp(type, "SCHEMA") == 0 ||
strcmp(type, "SCHEMAS") == 0
)
{
CONVERT_PRIV('C', "CREATE");
CONVERT_PRIV('U', "USAGE");

View File

@ -14295,6 +14295,9 @@ dumpDefaultACL(Archive *fout, DefaultACLInfo *daclinfo)
case DEFACLOBJ_TYPE:
type = "TYPES";
break;
case DEFACLOBJ_NAMESPACE:
type = "SCHEMAS";
break;
default:
/* shouldn't get here */
exit_horribly(NULL,

View File

@ -1028,7 +1028,7 @@ listDefaultACLs(const char *pattern)
printfPQExpBuffer(&buf,
"SELECT pg_catalog.pg_get_userbyid(d.defaclrole) AS \"%s\",\n"
" n.nspname AS \"%s\",\n"
" CASE d.defaclobjtype WHEN '%c' THEN '%s' WHEN '%c' THEN '%s' WHEN '%c' THEN '%s' WHEN '%c' THEN '%s' END AS \"%s\",\n"
" CASE d.defaclobjtype WHEN '%c' THEN '%s' WHEN '%c' THEN '%s' WHEN '%c' THEN '%s' WHEN '%c' THEN '%s' WHEN '%c' THEN '%s' END AS \"%s\",\n"
" ",
gettext_noop("Owner"),
gettext_noop("Schema"),
@ -1040,6 +1040,8 @@ listDefaultACLs(const char *pattern)
gettext_noop("function"),
DEFACLOBJ_TYPE,
gettext_noop("type"),
DEFACLOBJ_NAMESPACE,
gettext_noop("schema"),
gettext_noop("Type"));
printACLColumn(&buf, "d.defaclacl");

View File

@ -2796,7 +2796,7 @@ psql_completion(const char *text, int start, int end)
* to the kinds of objects supported.
*/
if (HeadMatches3("ALTER","DEFAULT","PRIVILEGES"))
COMPLETE_WITH_LIST4("TABLES", "SEQUENCES", "FUNCTIONS", "TYPES");
COMPLETE_WITH_LIST5("TABLES", "SEQUENCES", "FUNCTIONS", "TYPES", "SCHEMAS");
else
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf,
" UNION SELECT 'ALL FUNCTIONS IN SCHEMA'"

View File

@ -70,5 +70,6 @@ typedef FormData_pg_default_acl *Form_pg_default_acl;
#define DEFACLOBJ_SEQUENCE 'S' /* sequence */
#define DEFACLOBJ_FUNCTION 'f' /* function */
#define DEFACLOBJ_TYPE 'T' /* type */
#define DEFACLOBJ_NAMESPACE 'n' /* namespace */
#endif /* PG_DEFAULT_ACL_H */

View File

@ -344,6 +344,7 @@ PG_KEYWORD("rows", ROWS, UNRESERVED_KEYWORD)
PG_KEYWORD("rule", RULE, UNRESERVED_KEYWORD)
PG_KEYWORD("savepoint", SAVEPOINT, UNRESERVED_KEYWORD)
PG_KEYWORD("schema", SCHEMA, UNRESERVED_KEYWORD)
PG_KEYWORD("schemas", SCHEMAS, UNRESERVED_KEYWORD)
PG_KEYWORD("scroll", SCROLL, UNRESERVED_KEYWORD)
PG_KEYWORD("search", SEARCH, UNRESERVED_KEYWORD)
PG_KEYWORD("second", SECOND_P, UNRESERVED_KEYWORD)

View File

@ -1356,6 +1356,64 @@ SELECT has_table_privilege('regress_user1', 'testns.acltest1', 'INSERT'); -- no
(1 row)
ALTER DEFAULT PRIVILEGES FOR ROLE regress_user1 REVOKE EXECUTE ON FUNCTIONS FROM public;
ALTER DEFAULT PRIVILEGES IN SCHEMA testns GRANT USAGE ON SCHEMAS TO regress_user2; -- error
ERROR: cannot use IN SCHEMA clause when using GRANT/REVOKE ON SCHEMAS
ALTER DEFAULT PRIVILEGES GRANT USAGE ON SCHEMAS TO regress_user2;
CREATE SCHEMA testns2;
SELECT has_schema_privilege('regress_user2', 'testns2', 'USAGE'); -- yes
has_schema_privilege
----------------------
t
(1 row)
SELECT has_schema_privilege('regress_user2', 'testns2', 'CREATE'); -- no
has_schema_privilege
----------------------
f
(1 row)
ALTER DEFAULT PRIVILEGES REVOKE USAGE ON SCHEMAS FROM regress_user2;
CREATE SCHEMA testns3;
SELECT has_schema_privilege('regress_user2', 'testns3', 'USAGE'); -- no
has_schema_privilege
----------------------
f
(1 row)
SELECT has_schema_privilege('regress_user2', 'testns3', 'CREATE'); -- no
has_schema_privilege
----------------------
f
(1 row)
ALTER DEFAULT PRIVILEGES GRANT ALL ON SCHEMAS TO regress_user2;
CREATE SCHEMA testns4;
SELECT has_schema_privilege('regress_user2', 'testns4', 'USAGE'); -- yes
has_schema_privilege
----------------------
t
(1 row)
SELECT has_schema_privilege('regress_user2', 'testns4', 'CREATE'); -- yes
has_schema_privilege
----------------------
t
(1 row)
ALTER DEFAULT PRIVILEGES REVOKE ALL ON SCHEMAS FROM regress_user2;
CREATE SCHEMA testns5;
SELECT has_schema_privilege('regress_user2', 'testns5', 'USAGE'); -- no
has_schema_privilege
----------------------
f
(1 row)
SELECT has_schema_privilege('regress_user2', 'testns5', 'CREATE'); -- no
has_schema_privilege
----------------------
f
(1 row)
SET ROLE regress_user1;
CREATE FUNCTION testns.foo() RETURNS int AS 'select 1' LANGUAGE sql;
SELECT has_function_privilege('regress_user2', 'testns.foo()', 'EXECUTE'); -- no
@ -1403,6 +1461,10 @@ SELECT count(*)
DROP SCHEMA testns CASCADE;
NOTICE: drop cascades to table testns.acltest1
DROP SCHEMA testns2 CASCADE;
DROP SCHEMA testns3 CASCADE;
DROP SCHEMA testns4 CASCADE;
DROP SCHEMA testns5 CASCADE;
SELECT d.* -- check that entries went away
FROM pg_default_acl d LEFT JOIN pg_namespace n ON defaclnamespace = n.oid
WHERE nspname IS NULL AND defaclnamespace != 0;

View File

@ -816,6 +816,36 @@ SELECT has_table_privilege('regress_user1', 'testns.acltest1', 'INSERT'); -- no
ALTER DEFAULT PRIVILEGES FOR ROLE regress_user1 REVOKE EXECUTE ON FUNCTIONS FROM public;
ALTER DEFAULT PRIVILEGES IN SCHEMA testns GRANT USAGE ON SCHEMAS TO regress_user2; -- error
ALTER DEFAULT PRIVILEGES GRANT USAGE ON SCHEMAS TO regress_user2;
CREATE SCHEMA testns2;
SELECT has_schema_privilege('regress_user2', 'testns2', 'USAGE'); -- yes
SELECT has_schema_privilege('regress_user2', 'testns2', 'CREATE'); -- no
ALTER DEFAULT PRIVILEGES REVOKE USAGE ON SCHEMAS FROM regress_user2;
CREATE SCHEMA testns3;
SELECT has_schema_privilege('regress_user2', 'testns3', 'USAGE'); -- no
SELECT has_schema_privilege('regress_user2', 'testns3', 'CREATE'); -- no
ALTER DEFAULT PRIVILEGES GRANT ALL ON SCHEMAS TO regress_user2;
CREATE SCHEMA testns4;
SELECT has_schema_privilege('regress_user2', 'testns4', 'USAGE'); -- yes
SELECT has_schema_privilege('regress_user2', 'testns4', 'CREATE'); -- yes
ALTER DEFAULT PRIVILEGES REVOKE ALL ON SCHEMAS FROM regress_user2;
CREATE SCHEMA testns5;
SELECT has_schema_privilege('regress_user2', 'testns5', 'USAGE'); -- no
SELECT has_schema_privilege('regress_user2', 'testns5', 'CREATE'); -- no
SET ROLE regress_user1;
CREATE FUNCTION testns.foo() RETURNS int AS 'select 1' LANGUAGE sql;
@ -853,6 +883,10 @@ SELECT count(*)
WHERE nspname = 'testns';
DROP SCHEMA testns CASCADE;
DROP SCHEMA testns2 CASCADE;
DROP SCHEMA testns3 CASCADE;
DROP SCHEMA testns4 CASCADE;
DROP SCHEMA testns5 CASCADE;
SELECT d.* -- check that entries went away
FROM pg_default_acl d LEFT JOIN pg_namespace n ON defaclnamespace = n.oid