<para>
The <structname>pg_available_extensions</structname> view lists the
- extensions that are available for installation. This view can only
- be read by superusers. See also the
+ extensions that are available for installation.
+ See also the
<link linkend="catalog-pg-extension"><structname>pg_extension</structname></link>
catalog, which shows the extensions currently installed.
</para>
<para>
The <structname>pg_available_extension_versions</structname> view lists the
- specific extension versions that are available for installation. This view
- can only be read by superusers. See also the <link
+ specific extension versions that are available for installation.
+ See also the <link
linkend="catalog-pg-extension"><structname>pg_extension</structname></link>
catalog, which shows the extensions currently installed.
</para>
installed</entry>
</row>
+ <row>
+ <entry><structfield>superuser</structfield></entry>
+ <entry><type>bool</type></entry>
+ <entry>True if only superusers are allowed to install this extension</entry>
+ </row>
+
<row>
<entry><structfield>relocatable</structfield></entry>
<entry><type>bool</type></entry>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><varname>superuser</varname> (<type>boolean</type>)</term>
+ <listitem>
+ <para>
+ If this parameter is <literal>true</> (which is the default),
+ only superusers can create the extension or update it to a new
+ version. If it is set to <literal>false</>, just the privileges
+ required to execute the commands in the installation or update script
+ are required.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><varname>relocatable</varname> (<type>boolean</type>)</term>
<listitem>
</para>
<para>
- Only superusers can execute <command>ALTER EXTENSION</command>.
+ You must own the extension to use <command>ALTER EXTENSION</command>.
+ The <literal>ADD</>/<literal>DROP</> forms require ownership of the
+ added/dropped object as well.
</para>
</refsect1>
<refsynopsisdiv>
<synopsis>
-CREATE EXTENSION <replaceable class="parameter">extension_name</replaceable>
+CREATE EXTENSION [ IF NOT EXISTS ] <replaceable class="parameter">extension_name</replaceable>
[ WITH ] [ SCHEMA <replaceable class="parameter">schema</replaceable> ]
[ VERSION <replaceable class="parameter">version</replaceable> ]
[ FROM <replaceable class="parameter">old_version</replaceable> ]
</para>
<para>
- Only superusers can execute <command>CREATE EXTENSION</command>.
+ Loading an extension requires the same privileges that would be
+ required to create its component objects. For most extensions this
+ means superuser or database owner privileges are needed.
+ The user who runs <command>CREATE EXTENSION</command> becomes the
+ owner of the extension for purposes of later privilege checks, as well
+ as the owner of any objects created by the extension's script.
</para>
</refsect1>
<title>Parameters</title>
<variablelist>
+ <varlistentry>
+ <term><literal>IF NOT EXISTS</></term>
+ <listitem>
+ <para>
+ Do not throw an error if an extension with the same name already
+ exists. A notice is issued in this case. Note that there is no
+ guarantee that the existing extension is anything like the one that
+ would have been created.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable class="parameter">extension_name</replaceable></term>
<listitem>
</para>
<para>
- An extension can only be dropped by a superuser.
+ You must own the extension to use <command>DROP EXTENSION</command>.
</para>
</refsect1>
#include "catalog/pg_conversion.h"
#include "catalog/pg_database.h"
#include "catalog/pg_default_acl.h"
+#include "catalog/pg_extension.h"
#include "catalog/pg_foreign_data_wrapper.h"
#include "catalog/pg_foreign_server.h"
#include "catalog/pg_language.h"
gettext_noop("permission denied for foreign server %s"),
/* ACL_KIND_FOREIGN_TABLE */
gettext_noop("permission denied for foreign table %s"),
+ /* ACL_KIND_EXTENSION */
+ gettext_noop("permission denied for extension %s"),
};
static const char *const not_owner_msg[MAX_ACL_KIND] =
gettext_noop("must be owner of foreign server %s"),
/* ACL_KIND_FOREIGN_TABLE */
gettext_noop("must be owner of foreign table %s"),
+ /* ACL_KIND_EXTENSION */
+ gettext_noop("must be owner of extension %s"),
};
return has_privs_of_role(roleid, ownerId);
}
+/*
+ * Ownership check for an extension (specified by OID).
+ */
+bool
+pg_extension_ownercheck(Oid ext_oid, Oid roleid)
+{
+ Relation pg_extension;
+ ScanKeyData entry[1];
+ SysScanDesc scan;
+ HeapTuple tuple;
+ Oid ownerId;
+
+ /* Superusers bypass all permission checking. */
+ if (superuser_arg(roleid))
+ return true;
+
+ /* There's no syscache for pg_extension, so do it the hard way */
+ pg_extension = heap_open(ExtensionRelationId, AccessShareLock);
+
+ ScanKeyInit(&entry[0],
+ ObjectIdAttributeNumber,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(ext_oid));
+
+ scan = systable_beginscan(pg_extension,
+ ExtensionOidIndexId, true,
+ SnapshotNow, 1, entry);
+
+ tuple = systable_getnext(scan);
+ if (!HeapTupleIsValid(tuple))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("extension with OID %u does not exist", ext_oid)));
+
+ ownerId = ((Form_pg_extension) GETSTRUCT(tuple))->extowner;
+
+ systable_endscan(scan);
+ heap_close(pg_extension, AccessShareLock);
+
+ return has_privs_of_role(roleid, ownerId);
+}
+
/*
* Fetch pg_default_acl entry for given role, namespace and object type
* (object type must be given in pg_default_acl's encoding).
#include "commands/proclang.h"
#include "commands/tablespace.h"
#include "commands/trigger.h"
+#include "libpq/be-fsstubs.h"
+#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "parser/parse_func.h"
#include "parser/parse_oper.h"
List *objargs);
static bool object_exists(ObjectAddress address);
+
/*
* Translate an object name and arguments (as passed by the parser) to an
* ObjectAddress.
heap_close(rel, AccessShareLock);
return found;
}
+
+
+/*
+ * Check ownership of an object previously identified by get_object_address.
+ */
+void
+check_object_ownership(Oid roleid, ObjectType objtype, ObjectAddress address,
+ List *objname, List *objargs, Relation relation)
+{
+ switch (objtype)
+ {
+ case OBJECT_INDEX:
+ case OBJECT_SEQUENCE:
+ case OBJECT_TABLE:
+ case OBJECT_VIEW:
+ case OBJECT_FOREIGN_TABLE:
+ case OBJECT_COLUMN:
+ case OBJECT_RULE:
+ case OBJECT_TRIGGER:
+ case OBJECT_CONSTRAINT:
+ if (!pg_class_ownercheck(RelationGetRelid(relation), roleid))
+ aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS,
+ RelationGetRelationName(relation));
+ break;
+ case OBJECT_DATABASE:
+ if (!pg_database_ownercheck(address.objectId, roleid))
+ aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_DATABASE,
+ NameListToString(objname));
+ break;
+ case OBJECT_TYPE:
+ case OBJECT_DOMAIN:
+ case OBJECT_ATTRIBUTE:
+ if (!pg_type_ownercheck(address.objectId, roleid))
+ aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TYPE,
+ format_type_be(address.objectId));
+ break;
+ case OBJECT_AGGREGATE:
+ case OBJECT_FUNCTION:
+ if (!pg_proc_ownercheck(address.objectId, roleid))
+ aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_PROC,
+ NameListToString(objname));
+ break;
+ case OBJECT_OPERATOR:
+ if (!pg_oper_ownercheck(address.objectId, roleid))
+ aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_OPER,
+ NameListToString(objname));
+ break;
+ case OBJECT_SCHEMA:
+ if (!pg_namespace_ownercheck(address.objectId, roleid))
+ aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_NAMESPACE,
+ NameListToString(objname));
+ break;
+ case OBJECT_COLLATION:
+ if (!pg_collation_ownercheck(address.objectId, roleid))
+ aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_COLLATION,
+ NameListToString(objname));
+ break;
+ case OBJECT_CONVERSION:
+ if (!pg_conversion_ownercheck(address.objectId, roleid))
+ aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CONVERSION,
+ NameListToString(objname));
+ break;
+ case OBJECT_EXTENSION:
+ if (!pg_extension_ownercheck(address.objectId, roleid))
+ aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_EXTENSION,
+ NameListToString(objname));
+ break;
+ case OBJECT_FOREIGN_SERVER:
+ if (!pg_foreign_server_ownercheck(address.objectId, roleid))
+ aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_FOREIGN_SERVER,
+ NameListToString(objname));
+ break;
+ case OBJECT_LANGUAGE:
+ if (!pg_language_ownercheck(address.objectId, roleid))
+ aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_LANGUAGE,
+ NameListToString(objname));
+ break;
+ case OBJECT_OPCLASS:
+ if (!pg_opclass_ownercheck(address.objectId, roleid))
+ aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_OPCLASS,
+ NameListToString(objname));
+ break;
+ case OBJECT_OPFAMILY:
+ if (!pg_opfamily_ownercheck(address.objectId, roleid))
+ aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_OPFAMILY,
+ NameListToString(objname));
+ break;
+ case OBJECT_LARGEOBJECT:
+ if (!lo_compat_privileges &&
+ !pg_largeobject_ownercheck(address.objectId, roleid))
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be owner of large object %u",
+ address.objectId)));
+ break;
+ case OBJECT_CAST:
+ {
+ /* We can only check permissions on the source/target types */
+ TypeName *sourcetype = (TypeName *) linitial(objname);
+ TypeName *targettype = (TypeName *) linitial(objargs);
+ Oid sourcetypeid = typenameTypeId(NULL, sourcetype);
+ Oid targettypeid = typenameTypeId(NULL, targettype);
+
+ if (!pg_type_ownercheck(sourcetypeid, roleid)
+ && !pg_type_ownercheck(targettypeid, roleid))
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be owner of type %s or type %s",
+ format_type_be(sourcetypeid),
+ format_type_be(targettypeid))));
+ }
+ break;
+ case OBJECT_TABLESPACE:
+ if (!pg_tablespace_ownercheck(address.objectId, roleid))
+ aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TABLESPACE,
+ NameListToString(objname));
+ break;
+ case OBJECT_ROLE:
+ if (!has_privs_of_role(roleid, address.objectId))
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be member of role \"%s\"",
+ NameListToString(objname))));
+ break;
+ case OBJECT_TSDICTIONARY:
+ if (!pg_ts_dict_ownercheck(address.objectId, roleid))
+ aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TSDICTIONARY,
+ NameListToString(objname));
+ break;
+ case OBJECT_TSCONFIGURATION:
+ if (!pg_ts_config_ownercheck(address.objectId, roleid))
+ aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TSCONFIGURATION,
+ NameListToString(objname));
+ break;
+ case OBJECT_FDW:
+ case OBJECT_TSPARSER:
+ case OBJECT_TSTEMPLATE:
+ /* We treat these object types as being owned by superusers */
+ if (!superuser_arg(roleid))
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be superuser")));
+ break;
+ default:
+ elog(ERROR, "unrecognized object type: %d",
+ (int) objtype);
+ }
+}
CREATE VIEW pg_available_extension_versions AS
SELECT E.name, E.version, (X.extname IS NOT NULL) AS installed,
- E.relocatable, E.schema, E.requires, E.comment
+ E.superuser, E.relocatable, E.schema, E.requires, E.comment
FROM pg_available_extension_versions() AS E
LEFT JOIN pg_extension AS X
ON E.name = X.extname AND E.version = X.extversion;
#include "catalog/pg_shdescription.h"
#include "commands/comment.h"
#include "commands/dbcommands.h"
-#include "libpq/be-fsstubs.h"
#include "miscadmin.h"
-#include "parser/parse_func.h"
-#include "parser/parse_type.h"
-#include "utils/acl.h"
#include "utils/builtins.h"
#include "utils/fmgroids.h"
-#include "utils/rel.h"
#include "utils/tqual.h"
-/*
- * For most object types, the permissions-checking logic is simple enough
- * that it makes sense to just include it in CommentObject(). However, a few
- * object types require something more complex; for those, we define helper
- * functions.
- */
-static void CheckAttributeComment(Relation relation);
-static void CheckCastComment(List *qualname, List *arguments);
-
/*
* CommentObject --
}
/*
- * Translate the parser representation which identifies this object into
- * an ObjectAddress. get_object_address() will throw an error if the
+ * Translate the parser representation that identifies this object into
+ * an ObjectAddress. get_object_address() will throw an error if the
* object does not exist, and will also acquire a lock on the target
* to guard against concurrent DROP operations.
*/
address = get_object_address(stmt->objtype, stmt->objname, stmt->objargs,
&relation, ShareUpdateExclusiveLock);
- /* Privilege and integrity checks. */
+ /* Require ownership of the target object. */
+ check_object_ownership(GetUserId(), stmt->objtype, address,
+ stmt->objname, stmt->objargs, relation);
+
+ /* Perform other integrity checks as needed. */
switch (stmt->objtype)
{
- case OBJECT_INDEX:
- case OBJECT_SEQUENCE:
- case OBJECT_TABLE:
- case OBJECT_VIEW:
- case OBJECT_FOREIGN_TABLE:
- if (!pg_class_ownercheck(RelationGetRelid(relation), GetUserId()))
- aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS,
- RelationGetRelationName(relation));
- break;
case OBJECT_COLUMN:
- CheckAttributeComment(relation);
- break;
- case OBJECT_DATABASE:
- if (!pg_database_ownercheck(address.objectId, GetUserId()))
- aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_DATABASE,
- strVal(linitial(stmt->objname)));
- break;
- case OBJECT_TYPE:
- case OBJECT_DOMAIN:
- if (!pg_type_ownercheck(address.objectId, GetUserId()))
- aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TYPE,
- format_type_be(address.objectId));
- break;
- case OBJECT_AGGREGATE:
- case OBJECT_FUNCTION:
- if (!pg_proc_ownercheck(address.objectId, GetUserId()))
- aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_PROC,
- NameListToString(stmt->objname));
- break;
- case OBJECT_OPERATOR:
- if (!pg_oper_ownercheck(address.objectId, GetUserId()))
- aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_OPER,
- NameListToString(stmt->objname));
- break;
- case OBJECT_RULE:
- case OBJECT_TRIGGER:
- case OBJECT_CONSTRAINT:
- if (!pg_class_ownercheck(RelationGetRelid(relation), GetUserId()))
- aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS,
- RelationGetRelationName(relation));
- break;
- case OBJECT_SCHEMA:
- if (!pg_namespace_ownercheck(address.objectId, GetUserId()))
- aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_NAMESPACE,
- strVal(linitial(stmt->objname)));
- break;
- case OBJECT_COLLATION:
- if (!pg_collation_ownercheck(address.objectId, GetUserId()))
- aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_COLLATION,
- NameListToString(stmt->objname));
- break;
- case OBJECT_CONVERSION:
- if (!pg_conversion_ownercheck(address.objectId, GetUserId()))
- aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CONVERSION,
- NameListToString(stmt->objname));
- break;
- case OBJECT_LANGUAGE:
- if (!superuser())
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser to comment on procedural language")));
- break;
- case OBJECT_EXTENSION:
- if (!superuser())
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser to comment on extension")));
- break;
- case OBJECT_OPCLASS:
- if (!pg_opclass_ownercheck(address.objectId, GetUserId()))
- aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_OPCLASS,
- NameListToString(stmt->objname));
- break;
- case OBJECT_OPFAMILY:
- if (!pg_opfamily_ownercheck(address.objectId, GetUserId()))
- aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_OPFAMILY,
- NameListToString(stmt->objname));
- break;
- case OBJECT_LARGEOBJECT:
- if (!lo_compat_privileges &&
- !pg_largeobject_ownercheck(address.objectId, GetUserId()))
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be owner of large object %u",
- address.objectId)));
- break;
- case OBJECT_CAST:
- CheckCastComment(stmt->objname, stmt->objargs);
- break;
- case OBJECT_TABLESPACE:
- if (!pg_tablespace_ownercheck(address.objectId, GetUserId()))
- aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TABLESPACE,
- strVal(linitial(stmt->objname)));
- break;
- case OBJECT_ROLE:
- if (!has_privs_of_role(GetUserId(), address.objectId))
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be member of role \"%s\" to comment upon it",
- strVal(linitial(stmt->objname)))));
- break;
- case OBJECT_TSPARSER:
- if (!superuser())
+ /*
+ * Allow comments only on columns of tables, views, composite
+ * types, and foreign tables (which are the only relkinds for
+ * which pg_dump will dump per-column comments). In particular we
+ * wish to disallow comments on index columns, because the naming
+ * of an index's columns may change across PG versions, so dumping
+ * per-column comments could create reload failures.
+ */
+ if (relation->rd_rel->relkind != RELKIND_RELATION &&
+ relation->rd_rel->relkind != RELKIND_VIEW &&
+ relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
+ relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser to comment on text search parser")));
- break;
- case OBJECT_TSDICTIONARY:
- if (!pg_ts_dict_ownercheck(address.objectId, GetUserId()))
- aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TSDICTIONARY,
- NameListToString(stmt->objname));
- break;
- case OBJECT_TSTEMPLATE:
- if (!superuser())
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser to comment on text search template")));
- break;
- case OBJECT_TSCONFIGURATION:
- if (!pg_ts_config_ownercheck(address.objectId, GetUserId()))
- aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TSCONFIGURATION,
- NameListToString(stmt->objname));
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("\"%s\" is not a table, view, composite type, or foreign table",
+ RelationGetRelationName(relation))));
break;
default:
- elog(ERROR, "unrecognized object type: %d",
- (int) stmt->objtype);
+ break;
}
/*
return comment;
}
-
-/*
- * Check whether the user is allowed to comment on an attribute of the
- * specified relation.
- */
-static void
-CheckAttributeComment(Relation relation)
-{
- if (!pg_class_ownercheck(RelationGetRelid(relation), GetUserId()))
- aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS,
- RelationGetRelationName(relation));
-
- /*
- * Allow comments only on columns of tables, views, composite types, and
- * foreign tables (which are the only relkinds for which pg_dump will dump
- * per-column comments). In particular we wish to disallow comments on
- * index columns, because the naming of an index's columns may change
- * across PG versions, so dumping per-column comments could create reload
- * failures.
- */
- if (relation->rd_rel->relkind != RELKIND_RELATION &&
- relation->rd_rel->relkind != RELKIND_VIEW &&
- relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
- relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
- ereport(ERROR,
- (errcode(ERRCODE_WRONG_OBJECT_TYPE),
- errmsg("\"%s\" is not a table, view, composite type, or foreign table",
- RelationGetRelationName(relation))));
-}
-
-/*
- * Check whether the user is allowed to comment on the specified cast.
- */
-static void
-CheckCastComment(List *qualname, List *arguments)
-{
- TypeName *sourcetype;
- TypeName *targettype;
- Oid sourcetypeid;
- Oid targettypeid;
-
- Assert(list_length(qualname) == 1);
- sourcetype = (TypeName *) linitial(qualname);
- Assert(IsA(sourcetype, TypeName));
- Assert(list_length(arguments) == 1);
- targettype = (TypeName *) linitial(arguments);
- Assert(IsA(targettype, TypeName));
-
- sourcetypeid = typenameTypeId(NULL, sourcetype);
- targettypeid = typenameTypeId(NULL, targettype);
-
- /* Permission check */
- if (!pg_type_ownercheck(sourcetypeid, GetUserId())
- && !pg_type_ownercheck(targettypeid, GetUserId()))
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be owner of type %s or type %s",
- format_type_be(sourcetypeid),
- format_type_be(targettypeid))));
-}
char *comment; /* comment, if any */
char *schema; /* target schema (allowed if !relocatable) */
bool relocatable; /* is ALTER EXTENSION SET SCHEMA supported? */
+ bool superuser; /* must be superuser to install? */
int encoding; /* encoding of the script file, or -1 */
List *requires; /* names of prerequisite extensions */
} ExtensionControlFile;
errmsg("parameter \"%s\" requires a Boolean value",
item->name)));
}
+ else if (strcmp(item->name, "superuser") == 0)
+ {
+ if (!parse_bool(item->value, &control->superuser))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("parameter \"%s\" requires a Boolean value",
+ item->name)));
+ }
else if (strcmp(item->name, "encoding") == 0)
{
control->encoding = pg_valid_server_encoding(item->value);
control = (ExtensionControlFile *) palloc0(sizeof(ExtensionControlFile));
control->name = pstrdup(extname);
control->relocatable = false;
+ control->superuser = true;
control->encoding = -1;
/*
StringInfoData pathbuf;
ListCell *lc;
+ /*
+ * Enforce superuser-ness if appropriate. We postpone this check until
+ * here so that the flag is correctly associated with the right script(s)
+ * if it's set in secondary control files.
+ */
+ if (control->superuser && !superuser())
+ {
+ if (from_version == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied to create extension \"%s\"",
+ control->name),
+ errhint("Must be superuser to create this extension.")));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied to update extension \"%s\"",
+ control->name),
+ errhint("Must be superuser to update this extension.")));
+ }
+
filename = get_extension_script_filename(control, from_version, version);
/*
List *requiredExtensions;
List *requiredSchemas;
Oid extensionOid;
+ AclResult aclresult;
ListCell *lc;
- /* Must be super user */
- if (!superuser())
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("permission denied to create extension \"%s\"",
- stmt->extname),
- errhint("Must be superuser to create an extension.")));
-
- /*
- * We use global variables to track the extension being created, so we
- * can create only one extension at the same time.
- */
- if (creating_extension)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("nested CREATE EXTENSION is not supported")));
-
/* Check extension name validity before any filesystem access */
check_valid_extension_name(stmt->extname);
/*
* Check for duplicate extension name. The unique index on
* pg_extension.extname would catch this anyway, and serves as a backstop
- * in case of race conditions; but this is a friendlier error message.
+ * in case of race conditions; but this is a friendlier error message,
+ * and besides we need a check to support IF NOT EXISTS.
*/
if (get_extension_oid(stmt->extname, true) != InvalidOid)
+ {
+ if (stmt->if_not_exists)
+ {
+ ereport(NOTICE,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("extension \"%s\" already exists, skipping",
+ stmt->extname)));
+ return;
+ }
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("extension \"%s\" already exists",
+ stmt->extname)));
+ }
+
+ /*
+ * We use global variables to track the extension being created, so we
+ * can create only one extension at the same time.
+ */
+ if (creating_extension)
ereport(ERROR,
- (errcode(ERRCODE_DUPLICATE_OBJECT),
- errmsg("extension \"%s\" already exists", stmt->extname)));
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("nested CREATE EXTENSION is not supported")));
/*
* Read the primary control file. Note we assume that it does not contain
}
/*
- * If we didn't already know user is superuser, we would probably want
- * to do pg_namespace_aclcheck(schemaOid, extowner, ACL_CREATE) here.
+ * Check we have creation rights in target namespace. Although strictly
+ * speaking the extension itself isn't in the schema, it will almost
+ * certainly want to create objects therein, so let's just check now.
*/
+ aclresult = pg_namespace_aclcheck(schemaOid, extowner, ACL_CREATE);
+ if (aclresult != ACLCHECK_OK)
+ aclcheck_error(aclresult, ACL_KIND_NAMESPACE, schemaName);
/*
* Look up the prerequisite extensions, and build lists of their OIDs
continue;
}
- /*
- * Permission check. For now, insist on superuser-ness; later we
- * might want to relax that to being owner of the extension.
- */
- if (!superuser())
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("permission denied to drop extension \"%s\"",
- extensionName),
- errhint("Must be superuser to drop an extension.")));
+ /* Permission check: must own extension */
+ if (!pg_extension_ownercheck(extensionId, GetUserId()))
+ aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_EXTENSION,
+ extensionName);
object.classId = ExtensionRelationId;
object.objectId = extensionId;
DIR *dir;
struct dirent *de;
- if (!superuser())
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- (errmsg("must be superuser to list available extensions"))));
-
/* check to see if caller supports us returning a tuplestore */
if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
ereport(ERROR,
DIR *dir;
struct dirent *de;
- if (!superuser())
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- (errmsg("must be superuser to list available extensions"))));
-
/* check to see if caller supports us returning a tuplestore */
if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
ereport(ERROR,
{
ExtensionControlFile *control;
char *vername;
- Datum values[6];
- bool nulls[6];
+ Datum values[7];
+ bool nulls[7];
/* must be a .sql file ... */
if (!is_extension_script_filename(de->d_name))
CStringGetDatum(control->name));
/* version */
values[1] = CStringGetTextDatum(vername);
+ /* superuser */
+ values[2] = BoolGetDatum(control->superuser);
/* relocatable */
- values[2] = BoolGetDatum(control->relocatable);
+ values[3] = BoolGetDatum(control->relocatable);
/* schema */
if (control->schema == NULL)
- nulls[3] = true;
+ nulls[4] = true;
else
- values[3] = DirectFunctionCall1(namein,
+ values[4] = DirectFunctionCall1(namein,
CStringGetDatum(control->schema));
/* requires */
if (control->requires == NIL)
- nulls[4] = true;
+ nulls[5] = true;
else
{
Datum *datums;
a = construct_array(datums, ndatums,
NAMEOID,
NAMEDATALEN, false, 'c');
- values[4] = PointerGetDatum(a);
+ values[5] = PointerGetDatum(a);
}
/* comment */
if (control->comment == NULL)
- nulls[5] = true;
+ nulls[6] = true;
else
- values[5] = CStringGetTextDatum(control->comment);
+ values[6] = CStringGetTextDatum(control->comment);
tuplestore_putvalues(tupstore, tupdesc, values, nulls);
}
ExtensionControlFile *control;
ListCell *lc1;
- if (!superuser())
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- (errmsg("must be superuser to list extension update paths"))));
-
/* Check extension name validity before any filesystem access */
check_valid_extension_name(NameStr(*extname));
Oid extensionOid;
Oid nspOid;
Oid oldNspOid = InvalidOid;
+ AclResult aclresult;
Relation extRel;
ScanKeyData key[2];
SysScanDesc extScan;
nspOid = LookupCreationNamespace(newschema);
- /* this might later become an ownership test */
- if (!superuser())
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- (errmsg("must be superuser to use ALTER EXTENSION"))));
+ /*
+ * Permission check: must own extension. Note that we don't bother to
+ * check ownership of the individual member objects ...
+ */
+ if (!pg_extension_ownercheck(extensionOid, GetUserId()))
+ aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_EXTENSION,
+ extensionName);
+
+ /* Permission check: must have creation rights in target namespace */
+ aclresult = pg_namespace_aclcheck(nspOid, GetUserId(), ACL_CREATE);
+ if (aclresult != ACLCHECK_OK)
+ aclcheck_error(aclresult, ACL_KIND_NAMESPACE, newschema);
/* Locate the pg_extension tuple */
extRel = heap_open(ExtensionRelationId, RowExclusiveLock);
bool isnull;
ListCell *lc;
- /*
- * For now, insist on superuser privilege. Later we might want to
- * relax this to ownership of the extension.
- */
- if (!superuser())
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- (errmsg("must be superuser to use ALTER EXTENSION"))));
-
/*
* We use global variables to track the extension being created, so we
* can create/update only one extension at the same time.
heap_close(extRel, AccessShareLock);
+ /* Permission check: must own extension */
+ if (!pg_extension_ownercheck(extensionOid, GetUserId()))
+ aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_EXTENSION,
+ stmt->extname);
+
/*
* Read the primary control file. Note we assume that it does not contain
* any non-ASCII data, so there is no need to worry about encoding at this
Relation relation;
Oid oldExtension;
- /*
- * For now, insist on superuser privilege. Later we might want to
- * relax this to ownership of the target object and the extension.
- */
- if (!superuser())
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- (errmsg("must be superuser to use ALTER EXTENSION"))));
-
- /* Do this next to fail on nonexistent extension */
extension.classId = ExtensionRelationId;
extension.objectId = get_extension_oid(stmt->extname, false);
extension.objectSubId = 0;
+ /* Permission check: must own extension */
+ if (!pg_extension_ownercheck(extension.objectId, GetUserId()))
+ aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_EXTENSION,
+ stmt->extname);
+
/*
* Translate the parser representation that identifies the object into
* an ObjectAddress. get_object_address() will throw an error if the
object = get_object_address(stmt->objtype, stmt->objname, stmt->objargs,
&relation, ShareUpdateExclusiveLock);
+ /* Permission check: must own target object, too */
+ check_object_ownership(GetUserId(), stmt->objtype, object,
+ stmt->objname, stmt->objargs, relation);
+
/*
* Check existing extension membership.
*/
CreateExtensionStmt *newnode = makeNode(CreateExtensionStmt);
COPY_STRING_FIELD(extname);
+ COPY_SCALAR_FIELD(if_not_exists);
COPY_NODE_FIELD(options);
return newnode;
_equalCreateExtensionStmt(CreateExtensionStmt *a, CreateExtensionStmt *b)
{
COMPARE_STRING_FIELD(extname);
+ COMPARE_SCALAR_FIELD(if_not_exists);
COMPARE_NODE_FIELD(options);
return true;
{
CreateExtensionStmt *n = makeNode(CreateExtensionStmt);
n->extname = $3;
+ n->if_not_exists = false;
n->options = $5;
$$ = (Node *) n;
}
+ | CREATE EXTENSION IF_P NOT EXISTS name opt_with create_extension_opt_list
+ {
+ CreateExtensionStmt *n = makeNode(CreateExtensionStmt);
+ n->extname = $6;
+ n->if_not_exists = true;
+ n->options = $8;
+ $$ = (Node *) n;
+ }
;
create_extension_opt_list:
#include "access/attnum.h"
#include "access/sysattr.h"
+#include "access/transam.h"
#include "catalog/pg_cast.h"
#include "catalog/pg_class.h"
#include "catalog/pg_default_acl.h"
if (!extinfo->dobj.dump || dataOnly)
return;
+ /*
+ * In a regular dump, we use IF NOT EXISTS so that there isn't a problem
+ * if the extension already exists in the target database; this is
+ * essential for installed-by-default extensions such as plpgsql.
+ *
+ * In binary-upgrade mode, that doesn't work well, so instead we skip
+ * extensions with OIDs less than FirstNormalObjectId; those were
+ * presumably installed by initdb, and we assume they'll exist in the
+ * target installation too.
+ */
+ if (binary_upgrade && extinfo->dobj.catId.oid < (Oid) FirstNormalObjectId)
+ return;
+
q = createPQExpBuffer();
delq = createPQExpBuffer();
labelq = createPQExpBuffer();
if (!binary_upgrade)
{
- appendPQExpBuffer(q, "CREATE EXTENSION %s WITH SCHEMA %s;\n",
+ appendPQExpBuffer(q, "CREATE EXTENSION IF NOT EXISTS %s WITH SCHEMA %s;\n",
qextname, fmtId(extinfo->namespace));
}
else
*/
/* yyyymmddN */
-#define CATALOG_VERSION_NO 201103032
+#define CATALOG_VERSION_NO 201103041
#endif
int32 objectSubId; /* Subitem within object (eg column), or 0 */
} ObjectAddress;
-ObjectAddress get_object_address(ObjectType objtype, List *objname,
+extern ObjectAddress get_object_address(ObjectType objtype, List *objname,
List *objargs, Relation *relp, LOCKMODE lockmode);
+extern void check_object_ownership(Oid roleid,
+ ObjectType objtype, ObjectAddress address,
+ List *objname, List *objargs, Relation relation);
+
#endif /* PARSE_OBJECT_H */
/* Extensions */
DATA(insert OID = 3082 ( pg_available_extensions PGNSP PGUID 12 10 100 0 f f f t t s 0 0 2249 "" "{19,25,25}" "{o,o,o}" "{name,default_version,comment}" _null_ pg_available_extensions _null_ _null_ _null_ ));
DESCR("list available extensions");
-DATA(insert OID = 3083 ( pg_available_extension_versions PGNSP PGUID 12 10 100 0 f f f t t s 0 0 2249 "" "{19,25,16,19,1003,25}" "{o,o,o,o,o,o}" "{name,version,relocatable,schema,requires,comment}" _null_ pg_available_extension_versions _null_ _null_ _null_ ));
+DATA(insert OID = 3083 ( pg_available_extension_versions PGNSP PGUID 12 10 100 0 f f f t t s 0 0 2249 "" "{19,25,16,16,19,1003,25}" "{o,o,o,o,o,o,o}" "{name,version,superuser,relocatable,schema,requires,comment}" _null_ pg_available_extension_versions _null_ _null_ _null_ ));
DESCR("list available extension versions");
DATA(insert OID = 3084 ( pg_extension_update_paths PGNSP PGUID 12 10 100 0 f f f t t s 1 0 2249 "19" "{19,25,25,25}" "{i,o,o,o}" "{name,source,target,path}" _null_ pg_extension_update_paths _null_ _null_ _null_ ));
DESCR("list an extension's version update paths");
{
NodeTag type;
char *extname;
+ bool if_not_exists; /* just do nothing if it already exists? */
List *options; /* List of DefElem nodes */
} CreateExtensionStmt;
ACL_KIND_FDW, /* pg_foreign_data_wrapper */
ACL_KIND_FOREIGN_SERVER, /* pg_foreign_server */
ACL_KIND_FOREIGN_TABLE, /* pg_foreign_table */
+ ACL_KIND_EXTENSION, /* pg_extension */
MAX_ACL_KIND /* MUST BE LAST */
} AclObjectKind;
extern bool pg_ts_dict_ownercheck(Oid dict_oid, Oid roleid);
extern bool pg_ts_config_ownercheck(Oid cfg_oid, Oid roleid);
extern bool pg_foreign_server_ownercheck(Oid srv_oid, Oid roleid);
+extern bool pg_extension_ownercheck(Oid ext_oid, Oid roleid);
#endif /* ACL_H */
viewname | definition
---------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
iexit | SELECT ih.name, ih.thepath, interpt_pp(ih.thepath, r.thepath) AS exit FROM ihighway ih, ramp r WHERE (ih.thepath ## r.thepath);
- pg_available_extension_versions | SELECT e.name, e.version, (x.extname IS NOT NULL) AS installed, e.relocatable, e.schema, e.requires, e.comment FROM (pg_available_extension_versions() e(name, version, relocatable, schema, requires, comment) LEFT JOIN pg_extension x ON (((e.name = x.extname) AND (e.version = x.extversion))));
+ pg_available_extension_versions | SELECT e.name, e.version, (x.extname IS NOT NULL) AS installed, e.superuser, e.relocatable, e.schema, e.requires, e.comment FROM (pg_available_extension_versions() e(name, version, superuser, relocatable, schema, requires, comment) LEFT JOIN pg_extension x ON (((e.name = x.extname) AND (e.version = x.extversion))));
pg_available_extensions | SELECT e.name, e.default_version, x.extversion AS installed_version, e.comment FROM (pg_available_extensions() e(name, default_version, comment) LEFT JOIN pg_extension x ON ((e.name = x.extname)));
pg_cursors | SELECT c.name, c.statement, c.is_holdable, c.is_binary, c.is_scrollable, c.creation_time FROM pg_cursor() c(name, statement, is_holdable, is_binary, is_scrollable, creation_time);
pg_group | SELECT pg_authid.rolname AS groname, pg_authid.oid AS grosysid, ARRAY(SELECT pg_auth_members.member FROM pg_auth_members WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist FROM pg_authid WHERE (NOT pg_authid.rolcanlogin);