| | On Thu, 25 Jan 2007, Jeremy Drake wrote:
On Thu, 25 Jan 2007, Jeremy Drake wrote:> I think that an ALTER LANGUAGE OWNER TO is the proper response to these> > things, and unless I hear otherwise I will attempt to add this to my> > patch.> Here is the patch which adds this. It also allows ALTER LANGUAGE RENAME> TO for the owner, which I missed before. I would appreciate someone with> more knowledge of the permissions infrastructure to take a look at it> since I am fairly new to it and may not fully understand its intricacies.> I have refactored the owner checking of languages in the same manner as it is for other owned objects. I have changed to using standard permissions error messages (aclcheck_error) for the language permissions errors.
I consider this patch ready for review, assuming the permissions rules outlined by Tom Lane on -hackers are valid. For reference, here are the rules that this patch is intended to implement:
On Wed, 24 Jan 2007, Tom Lane wrote:
In detail, it'd look something like:> * For an untrusted language: must be superuser to either create or use> the language (no change from current rules). Ownership of the> pg_language entry is really irrelevant, as is its ACL.> * For a trusted language:> * if pg_pltemplate.something is ON: either a superuser or the current> DB's owner can CREATE the language. In either case the pg_language> entry will be marked as owned by the DB owner (pg_database.datdba),> which means that subsequently he (or a superuser) can grant or deny> USAGE within his DB.> * if pg_pltemplate.something is OFF: must be superuser to CREATE the> language; subsequently it will be owned by you, so only you or another> superuser can grant or deny USAGE (same behavior as currently). The only difference from this is, that when superuser is required, the owner of the language is not the superuser who created it, but BOOTSTRAP_SUPERUSERID. This is because my interpretation was that the "same behavior as currently" took precedence. The current behavior in cvs is that languages have no owner, and for purposes where one would be needed it is assumed to be BOOTSTRAP_SUPERUSERID.
Is this valid, or should I instead set the owner to GetUserId() in those cases?
-- Academic politics is the most vicious and bitter form of politics, because the stakes are so low. -- Wallace Sayre
Index: doc/src/sgml/ref/alter_language.sgml =================================================================== RCS file: /data/local/jeremyd/postgres/cvsuproot/pgsql/doc/src/sgml/ref/alter_language.sgml,v retrieving revision 1.6 diff -c -r1.6 alter_language.sgml *** doc/src/sgml/ref/alter_language.sgml16 Sep 2006 00:30:16 -00001.6 --- doc/src/sgml/ref/alter_language.sgml26 Jan 2007 01:01:40 -0000 *************** *** 21,26 **** --- 21,28 ---- <refsynopsisdiv> <synopsis> ALTER LANGUAGE <replaceable>name</replaceable> RENAME TO <replaceable>newname</replaceable> + + ALTER LANGUAGE <replaceable>name</replaceable> OWNER TO <replaceable>new_owner</replaceable> </synopsis> </refsynopsisdiv> *************** *** 48,53 **** --- 50,64 ---- </varlistentry> <varlistentry> + <term><replaceable>new_owner</replaceable></term> + <listitem> + <para> + The new owner of the language. + </para> + </listitem> + </varlistentry> + + <varlistentry> <term><replaceable>newname</replaceable></term> <listitem> <para> Index: src/backend/catalog/aclchk.c =================================================================== RCS file: /data/local/jeremyd/postgres/cvsuproot/pgsql/src/backend/catalog/aclchk.c,v retrieving revision 1.135 diff -c -r1.135 aclchk.c *** src/backend/catalog/aclchk.c23 Jan 2007 05:07:17 -00001.135 --- src/backend/catalog/aclchk.c26 Jan 2007 23:53:03 -0000 *************** *** 1003,1013 **** /* * 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; aclDatum = SysCacheGetAttr(LANGNAME, tuple, Anum_pg_language_lanacl, &isNull); if (isNull) --- 1003,1010 ---- /* * Get owner ID and working copy of existing ACL. If there's no ACL, * substitute the proper default. */ ! ownerId = pg_language_tuple->lanowner; aclDatum = SysCacheGetAttr(LANGNAME, tuple, Anum_pg_language_lanacl, &isNull); if (isNull) *************** *** 1770,1777 **** (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("language with OID %u does not exist", lang_oid))); ! /* XXX pg_language should have an owner column, but doesn't */ ! ownerId = BOOTSTRAP_SUPERUSERID; aclDatum = SysCacheGetAttr(LANGOID, tuple, Anum_pg_language_lanacl, &isNull); --- 1767,1773 ---- (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("language with OID %u does not exist", lang_oid))); ! ownerId = ((Form_pg_language) GETSTRUCT(tuple))->lanowner; aclDatum = SysCacheGetAttr(LANGOID, tuple, Anum_pg_language_lanacl, &isNull); *************** *** 2148,2153 **** --- 2144,2177 ---- } /* + * Ownership check for a procedural language (specified by OID) + */ + bool + pg_language_ownercheck(Oid lan_oid, Oid roleid) + { + HeapTupletuple; + OidownerId; + + /* Superusers bypass all permission checking. */ + if (superuser_arg(roleid)) + return true; + + tuple = SearchSysCache(LANGOID, + ObjectIdGetDatum(lan_oid), + 0, 0, 0); + if (!HeapTupleIsValid(tuple)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("language with OID %u does not exist", lan_oid))); + + ownerId = ((Form_pg_language) GETSTRUCT(tuple))->lanowner; + + ReleaseSysCache(tuple); + + return has_privs_of_role(roleid, ownerId); + } + + /* * Ownership check for a namespace (specified by OID). */ bool Index: src/backend/commands/alter.c =================================================================== RCS file: /data/local/jeremyd/postgres/cvsuproot/pgsql/src/backend/commands/alter.c,v retrieving revision 1.22 diff -c -r1.22 alter.c *** src/backend/commands/alter.c23 Jan 2007 05:07:17 -00001.22 --- src/backend/commands/alter.c25 Jan 2007 23:55:41 -0000 *************** *** 203,208 **** --- 203,212 ---- AlterFunctionOwner(stmt->object, stmt->objarg, newowner); break; + case OBJECT_LANGUAGE: + AlterLanguageOwner((char *) linitial(stmt->object), newowner); + break; + case OBJECT_OPERATOR: Assert(list_length(stmt->objarg) == 2); AlterOperatorOwner(stmt->object, Index: src/backend/commands/proclang.c =================================================================== RCS file: /data/local/jeremyd/postgres/cvsuproot/pgsql/src/backend/commands/proclang.c,v retrieving revision 1.71 diff -c -r1.71 proclang.c *** src/backend/commands/proclang.c22 Jan 2007 01:35:20 -00001.71 --- src/backend/commands/proclang.c27 Jan 2007 00:20:19 -0000 *************** *** 17,22 **** --- 17,24 ---- #include "access/heapam.h" #include "catalog/dependency.h" #include "catalog/indexing.h" + #include "catalog/pg_authid.h" + #include "catalog/pg_database.h" #include "catalog/pg_language.h" #include "catalog/pg_namespace.h" #include "catalog/pg_pltemplate.h" *************** *** 27,32 **** --- 29,35 ---- #include "miscadmin.h" #include "parser/gramparse.h" #include "parser/parse_func.h" + #include "utils/acl.h" #include "utils/builtins.h" #include "utils/fmgroids.h" #include "utils/lsyscache.h" *************** *** 36,49 **** typedef struct { booltmpltrusted;/* trusted? */ char *tmplhandler;/* name of handler function */ char *tmplvalidator;/* name of validator function, or NULL */ char *tmpllibrary;/* path of shared library */ } PLTemplate; static void create_proc_lang(const char *languageName, ! Oid handlerOid, Oid valOid, bool trusted); static PLTemplate *find_language_template(const char *languageName); /* --------------------------------------------------------------------- --- 39,57 ---- typedef struct { booltmpltrusted;/* trusted? */ + booltmpldbaallowed;/* db owner allowed to create? */ char *tmplhandler;/* name of handler function */ char *tmplvalidator;/* name of validator function, or NULL */ char *tmpllibrary;/* path of shared library */ } PLTemplate; static void create_proc_lang(const char *languageName, ! Oid languageOwner, Oid handlerOid, Oid valOid, bool trusted); static PLTemplate *find_language_template(const char *languageName); + static void AlterLanguageOwner_internal(HeapTuple tup, Relation rel, Oid newOwnerId); + + static Oid get_current_datdba(); + static Oid find_desired_language_owner (PLTemplate *pltemplate); /* --------------------------------------------------------------------- *************** *** 61,74 **** Oidfuncargtypes[1]; /* - * Check permission - */ - if (!superuser()) - ereport(ERROR, - (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), - errmsg("must be superuser to create procedural language"))); - - /* * Translate the language name and check that this language doesn't * already exist */ --- 69,74 ---- *************** *** 97,102 **** --- 97,124 ---- (errmsg("using pg_pltemplate information instead of CREATE LANGUAGE parameters"))); /* + * Check permission + */ + if (pltemplate->tmpltrusted && pltemplate->tmpldbaallowed) + { + if (!pg_database_ownercheck(MyDatabaseId, GetUserId())) + aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_DATABASE, + get_database_name(MyDatabaseId)); + } + else if (!superuser()) + { + if (!pltemplate->tmpltrusted) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be superuser to create untrusted procedural language"))); + else + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be superuser to create procedural language \"%s\"", languageName), + errhint("Column pg_pltemplate.tmpldbaallowed has been set to false for this language."))); + } + + /* * Find or create the handler function, which we force to be in the * pg_catalog schema. If already present, it must have the correct * return type. *************** *** 171,177 **** valOid = InvalidOid; /* ok, create it */ ! create_proc_lang(languageName, handlerOid, valOid, pltemplate->tmpltrusted); } else --- 193,199 ---- valOid = InvalidOid; /* ok, create it */ ! create_proc_lang(languageName, find_desired_language_owner(pltemplate), handlerOid, valOid, pltemplate->tmpltrusted); } else *************** *** 189,194 **** --- 211,224 ---- errhint("The supported languages are listed in the pg_pltemplate system catalog."))); /* + * Check permission + */ + if (!superuser()) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be superuser to create custom procedural language"))); + + /* * Lookup the PL handler function and check that it is of the expected * return type */ *************** *** 227,233 **** valOid = InvalidOid; /* ok, create it */ ! create_proc_lang(languageName, handlerOid, valOid, stmt->pltrusted); } } --- 257,263 ---- valOid = InvalidOid; /* ok, create it */ ! create_proc_lang(languageName, BOOTSTRAP_SUPERUSERID, handlerOid, valOid, stmt->pltrusted); } } *************** *** 236,242 **** */ static void create_proc_lang(const char *languageName, ! Oid handlerOid, Oid valOid, bool trusted) { Relationrel; TupleDesctupDesc; --- 266,272 ---- */ static void create_proc_lang(const char *languageName, ! Oid languageOwner, Oid handlerOid, Oid valOid, bool trusted) { Relationrel; TupleDesctupDesc; *************** *** 258,263 **** --- 288,294 ---- namestrcpy(&langname, languageName); values[Anum_pg_language_lanname - 1] = NameGetDatum(&langname); + values[Anum_pg_language_lanowner - 1] = ObjectIdGetDatum(languageOwner); values[Anum_pg_language_lanispl - 1] = BoolGetDatum(true); values[Anum_pg_language_lanpltrusted - 1] = BoolGetDatum(trusted); values[Anum_pg_language_lanplcallfoid - 1] = ObjectIdGetDatum(handlerOid); *************** *** 277,282 **** --- 308,319 ---- myself.objectId = HeapTupleGetOid(tup); myself.objectSubId = 0; + /* dependency on owner of language */ + referenced.classId = AuthIdRelationId; + referenced.objectId = languageOwner; + referenced.objectSubId = 0; + recordSharedDependencyOn(&myself, &referenced, SHARED_DEPENDENCY_OWNER); + /* dependency on the PL handler function */ referenced.classId = ProcedureRelationId; referenced.objectId = handlerOid; *************** *** 295,300 **** --- 332,371 ---- heap_close(rel, RowExclusiveLock); } + static Oid get_current_datdba() + { + /* find datdba for current db */ + HeapTupletuple; + Oiddba; + + tuple = SearchSysCache(DATABASEOID, + ObjectIdGetDatum(MyDatabaseId), + 0, 0, 0); + if (!HeapTupleIsValid(tuple)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_DATABASE), + errmsg("database with OID %u does not exist", MyDatabaseId))); + + dba = ((Form_pg_database) GETSTRUCT(tuple))->datdba; + + ReleaseSysCache(tuple); + return dba; + } + + + static Oid find_desired_language_owner (PLTemplate *pltemplate) + { + if (pltemplate->tmpltrusted && pltemplate->tmpldbaallowed) + { + return get_current_datdba(); + } + else + { + /* current behaviour */ + return BOOTSTRAP_SUPERUSERID; + } + } + /* * Look to see if we have template information for the given language name. */ *************** *** 325,330 **** --- 396,402 ---- result = (PLTemplate *) palloc0(sizeof(PLTemplate)); result->tmpltrusted = tmpl->tmpltrusted; + result->tmpldbaallowed = tmpl->tmpldbaallowed; /* Remaining fields are variable-width so we need heap_getattr */ datum = heap_getattr(tup, Anum_pg_pltemplate_tmplhandler, *************** *** 382,395 **** ObjectAddress object; /* - * Check permission - */ - if (!superuser()) - ereport(ERROR, - (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), - errmsg("must be superuser to drop procedural language"))); - - /* * Translate the language name, check that the language exists */ languageName = case_translate_language_name(stmt->plname); --- 454,459 ---- *************** *** 411,416 **** --- 475,487 ---- return; } + /* + * Check permission + */ + if (!pg_language_ownercheck(HeapTupleGetOid(langTup), GetUserId())) + aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_LANGUAGE, + languageName); + object.classId = LanguageRelationId; object.objectId = HeapTupleGetOid(langTup); object.objectSubId = 0; *************** *** 478,488 **** (errcode(ERRCODE_DUPLICATE_OBJECT), errmsg("language \"%s\" already exists", newname))); ! /* must be superuser, since we do not have owners for PLs */ ! if (!superuser()) ! ereport(ERROR, ! (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), ! errmsg("must be superuser to rename procedural language"))); /* rename */ namestrcpy(&(((Form_pg_language) GETSTRUCT(tup))->lanname), newname); --- 549,558 ---- (errcode(ERRCODE_DUPLICATE_OBJECT), errmsg("language \"%s\" already exists", newname))); ! /* must be owner of PL */ ! if (!pg_language_ownercheck(HeapTupleGetOid(tup), GetUserId())) ! aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_LANGUAGE, ! oldname); /* rename */ namestrcpy(&(((Form_pg_language) GETSTRUCT(tup))->lanname), newname); *************** *** 492,494 **** --- 562,670 ---- heap_close(rel, NoLock); heap_freetuple(tup); } + + /* + * Change language owner + */ + void + AlterLanguageOwner(const char *name, Oid newOwnerId) + { + HeapTupletup; + Relationrel; + + /* Translate name for consistency with CREATE */ + name = case_translate_language_name(name); + + rel = heap_open(LanguageRelationId, RowExclusiveLock); + + tup = SearchSysCache(LANGNAME, + CStringGetDatum(name), + 0, 0, 0); + + if (!HeapTupleIsValid(tup)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("language \"%s\" does not exist", name))); + + AlterLanguageOwner_internal(tup, rel, newOwnerId); + + ReleaseSysCache(tup); + heap_close(rel, RowExclusiveLock); + } + + static void + AlterLanguageOwner_internal(HeapTuple tup, Relation rel, Oid newOwnerId) + { + Form_pg_language lanForm; + + Assert(tup->t_tableOid == LanguageRelationId); + Assert(RelationGetRelid(rel) == LanguageRelationId); + + lanForm = (Form_pg_language) GETSTRUCT(tup); + + /* + * If the new owner is the same as the existing owner, consider the + * command to have succeeded. This is for dump restoration purposes. + */ + if (lanForm->lanowner != newOwnerId) + { + Datumrepl_val[Natts_pg_language]; + charrepl_null[Natts_pg_language]; + charrepl_repl[Natts_pg_language]; + Acl *newAcl; + DatumaclDatum; + boolisNull; + HeapTuplenewtuple; + AclResultaclresult; + + /* Otherwise, must be owner of the existing object */ + if (!pg_language_ownercheck(HeapTupleGetOid(tup), GetUserId())) + aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_LANGUAGE, + NameStr(lanForm->lanname)); + + /* Must be able to become new owner */ + check_is_member_of_role(GetUserId(), newOwnerId); + + /* + * must have rights to create this language + */ + if (!has_privs_of_role(newOwnerId, find_desired_language_owner (find_language_template (NameStr(lanForm->lanname))))) + { + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("new user must be allowed to create this language"))); + } + + memset(repl_null, ' ', sizeof(repl_null)); + memset(repl_repl, ' ', sizeof(repl_repl)); + + repl_repl[Anum_pg_language_lanowner - 1] = 'r'; + repl_val[Anum_pg_language_lanowner - 1] = ObjectIdGetDatum(newOwnerId); + + /* + * Determine the modified ACL for the new owner. This is only + * necessary when the ACL is non-null. + */ + aclDatum = SysCacheGetAttr(LANGNAME, tup, + Anum_pg_language_lanacl, + &isNull); + if (!isNull) + { + newAcl = aclnewowner(DatumGetAclP(aclDatum), + lanForm->lanowner, newOwnerId); + repl_repl[Anum_pg_language_lanacl - 1] = 'r'; + repl_val[Anum_pg_language_lanacl - 1] = PointerGetDatum(newAcl); + } + + newtuple = heap_modifytuple(tup, RelationGetDescr(rel), repl_val, repl_null, repl_repl); + + simple_heap_update(rel, &newtuple->t_self, newtuple); + CatalogUpdateIndexes(rel, newtuple); + + heap_freetuple(newtuple); + + /* Update owner dependency reference */ + changeDependencyOnOwner(LanguageRelationId, HeapTupleGetOid(tup), + newOwnerId); + } + } Index: src/backend/parser/gram.y =================================================================== RCS file: /data/local/jeremyd/postgres/cvsuproot/pgsql/src/backend/parser/gram.y,v retrieving revision 2.577 diff -c -r2.577 gram.y *** src/backend/parser/gram.y25 Jan 2007 11:53:51 -00002.577 --- src/backend/parser/gram.y25 Jan 2007 23:55:10 -0000 *************** *** 4596,4601 **** --- 4596,4609 ---- n->newowner = $7; $$ = (Node *)n; } + | ALTER LANGUAGE name OWNER TO RoleId + { + AlterOwnerStmt *n = makeNode(AlterOwnerStmt); + n->objectType = OBJECT_LANGUAGE; + n->object = list_make1($3); + n->newowner = $6; + $$ = (Node *)n; + } | ALTER OPERATOR any_operator '(' oper_argtypes ')' OWNER TO RoleId { AlterOwnerStmt *n = makeNode(AlterOwnerStmt); Index: src/backend/tcop/utility.c =================================================================== RCS file: /data/local/jeremyd/postgres/cvsuproot/pgsql/src/backend/tcop/utility.c,v retrieving revision 1.271 diff -c -r1.271 utility.c *** src/backend/tcop/utility.c23 Jan 2007 05:07:18 -00001.271 --- src/backend/tcop/utility.c26 Jan 2007 00:53:24 -0000 *************** *** 1530,1535 **** --- 1530,1538 ---- case OBJECT_FUNCTION: tag = "ALTER FUNCTION"; break; + case OBJECT_LANGUAGE: + tag = "ALTER LANGUAGE"; + break; case OBJECT_OPERATOR: tag = "ALTER OPERATOR"; break; Index: src/bin/psql/tab-complete.c =================================================================== RCS file: /data/local/jeremyd/postgres/cvsuproot/pgsql/src/bin/psql/tab-complete.c,v retrieving revision 1.157 diff -c -r1.157 tab-complete.c *** src/bin/psql/tab-complete.c5 Jan 2007 22:19:49 -00001.157 |