nocfbot.changes.diff
text/x-patch
Filename: nocfbot.changes.diff
Type: text/x-patch
Part: 1
Patch
Same data as JSON:
GET /api/v1/attachments/:id/patch
the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes.
API reference →
Format: unified
| File | + | − |
|---|---|---|
| src/backend/commands/extension.c | 62 | 22 |
| src/test/modules/test_extensions/expected/test_extensions.out | 103 | 0 |
| src/test/modules/test_extensions/Makefile | 7 | 1 |
| src/test/modules/test_extensions/meson.build | 7 | 0 |
| src/test/modules/test_extensions/sql/test_extensions.sql | 48 | 0 |
| src/test/modules/test_extensions/test_ext_owned_schema--1.0--1.1.sql | 3 | 0 |
| src/test/modules/test_extensions/test_ext_owned_schema_nosuperuser--1.0--1.1.sql | 5 | 0 |
| src/test/modules/test_extensions/test_ext_owned_schema_nosuperuser--1.0.sql | 3 | 0 |
| src/test/modules/test_extensions/test_ext_owned_schema_nosuperuser.control | 5 | 0 |
| src/test/modules/test_extensions/test_ext_owned_schema_trusted--1.0--1.1.sql | 3 | 0 |
| src/test/modules/test_extensions/test_ext_owned_schema_trusted--1.0.sql | 3 | 0 |
| src/test/modules/test_extensions/test_ext_owned_schema_trusted.control | 5 | 0 |
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 77bc6a82987..03b3de357bd 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1243,6 +1243,44 @@ extension_is_trusted(ExtensionControlFile *control)
return false;
}
+/*
+ * Enforce superuser requirements for extension operations.
+ *
+ * Returns true if we should switch to superuser for trusted extensions.
+ * Throws an error if superuser is required but not available.
+ *
+ * This function should only be called after choosing the appropriate
+ * control file (including any secondary control files for updates).
+ */
+static bool
+check_extension_superuser_requirements(ExtensionControlFile *control,
+ const char *from_version)
+{
+ if (control->superuser && !superuser())
+ {
+ if (extension_is_trusted(control))
+ return true;
+ else if (from_version == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied to create extension \"%s\"",
+ control->name),
+ control->trusted
+ ? errhint("Must have CREATE privilege on current database to create this extension.")
+ : errhint("Must be superuser to create this extension.")));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied to update extension \"%s\"",
+ control->name),
+ control->trusted
+ ? errhint("Must have CREATE privilege on current database to update this extension.")
+ : errhint("Must be superuser to update this extension.")));
+ }
+
+ return false;
+}
+
/*
* Execute the appropriate script file for installing or updating the extension
*
@@ -1271,27 +1309,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
* here so that the control flags are correctly associated with the right
* script(s) if they happen to be set in secondary control files.
*/
- if (control->superuser && !superuser())
- {
- if (extension_is_trusted(control))
- switch_to_superuser = true;
- else if (from_version == NULL)
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("permission denied to create extension \"%s\"",
- control->name),
- control->trusted
- ? errhint("Must have CREATE privilege on current database to create this extension.")
- : errhint("Must be superuser to create this extension.")));
- else
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("permission denied to update extension \"%s\"",
- control->name),
- control->trusted
- ? errhint("Must have CREATE privilege on current database to update this extension.")
- : errhint("Must be superuser to update this extension.")));
- }
+ switch_to_superuser = check_extension_superuser_requirements(control, from_version);
filename = get_extension_script_filename(control, from_version, version);
@@ -1854,7 +1872,7 @@ CreateSchemaForExtension(char *schemaName)
}
/*
- * Create a owned schema with the given name, as part of CREATE EXTENSION, and
+ * Create an owned schema with the given name, as part of CREATE EXTENSION, and
* fails if the schema already exist.
*/
static Oid
@@ -2017,6 +2035,9 @@ CreateExtensionInternal(char *extensionName,
Oid extensionOid;
ObjectAddress address;
ListCell *lc;
+ bool switch_to_superuser = false;
+ Oid save_userid = 0;
+ int save_sec_context = 0;
/*
* Read the primary control file. Note we assume that it does not contain
@@ -2084,8 +2105,27 @@ CreateExtensionInternal(char *extensionName,
*/
control = read_extension_aux_control_file(pcontrol, versionName);
+ /*
+ * For trusted extensions with owned schemas, we need to create the schema
+ * as superuser to ensure proper ownership.
+ */
+ if (control->owned_schema)
+ {
+ switch_to_superuser = check_extension_superuser_requirements(control, NULL);
+ if (switch_to_superuser)
+ {
+ GetUserIdAndSecContext(&save_userid, &save_sec_context);
+ SetUserIdAndSecContext(BOOTSTRAP_SUPERUSERID,
+ save_sec_context | SECURITY_LOCAL_USERID_CHANGE);
+ }
+ }
+
schemaOid = GetOrCreateSchemaForExtension(&schemaName, control, cascade);
+ /* Restore authentication state after schema creation if needed */
+ if (switch_to_superuser)
+ SetUserIdAndSecContext(save_userid, save_sec_context);
+
/*
* Make note if a temporary namespace has been accessed in this
* transaction.
diff --git a/src/test/modules/test_extensions/Makefile b/src/test/modules/test_extensions/Makefile
index a6594c19d7e..0a7cf692214 100644
--- a/src/test/modules/test_extensions/Makefile
+++ b/src/test/modules/test_extensions/Makefile
@@ -10,7 +10,8 @@ EXTENSION = test_ext1 test_ext2 test_ext3 test_ext4 test_ext5 test_ext6 \
test_ext_evttrig \
test_ext_set_schema \
test_ext_req_schema1 test_ext_req_schema2 test_ext_req_schema3 \
- test_ext_owned_schema test_ext_owned_schema_relocatable
+ test_ext_owned_schema test_ext_owned_schema_nosuperuser \
+ test_ext_owned_schema_trusted test_ext_owned_schema_relocatable
DATA = test_ext1--1.0.sql test_ext2--1.0.sql test_ext3--1.0.sql \
test_ext4--1.0.sql test_ext5--1.0.sql test_ext6--1.0.sql \
@@ -28,6 +29,11 @@ DATA = test_ext1--1.0.sql test_ext2--1.0.sql test_ext3--1.0.sql \
test_ext_req_schema2--1.0.sql \
test_ext_req_schema3--1.0.sql \
test_ext_owned_schema--1.0.sql \
+ test_ext_owned_schema--1.0--1.1.sql \
+ test_ext_owned_schema_nosuperuser--1.0.sql \
+ test_ext_owned_schema_nosuperuser--1.0--1.1.sql \
+ test_ext_owned_schema_trusted--1.0.sql \
+ test_ext_owned_schema_trusted--1.0--1.1.sql \
test_ext_owned_schema_relocatable--1.0.sql
REGRESS = test_extensions test_extdepend
diff --git a/src/test/modules/test_extensions/expected/test_extensions.out b/src/test/modules/test_extensions/expected/test_extensions.out
index b82b3979a90..1820e76d746 100644
--- a/src/test/modules/test_extensions/expected/test_extensions.out
+++ b/src/test/modules/test_extensions/expected/test_extensions.out
@@ -690,7 +690,36 @@ Objects in extension "test_ext_owned_schema"
schema test_ext_owned_schema
(2 rows)
+-- Test ALTER EXTENSION UPDATE with owned schema
+ALTER EXTENSION test_ext_owned_schema UPDATE TO '1.1';
+\dx+ test_ext_owned_schema;
+Objects in extension "test_ext_owned_schema"
+ Object description
+-----------------------------------------
+ function test_ext_owned_schema.owned1()
+ function test_ext_owned_schema.owned2()
+ schema test_ext_owned_schema
+(3 rows)
+
+-- Verify that the owned schema is dropped together with the extension
DROP EXTENSION test_ext_owned_schema;
+SELECT COUNT(*) FROM pg_namespace WHERE nspname = 'test_ext_owned_schema';
+ count
+-------
+ 0
+(1 row)
+
+-- Test drop behavior with non-extension objects in the owned schema
+CREATE EXTENSION test_ext_owned_schema;
+CREATE FUNCTION test_ext_owned_schema.non_ext_func() RETURNS int LANGUAGE SQL AS $$ SELECT 1 $$;
+-- Fails because of the non-extension object
+DROP EXTENSION test_ext_owned_schema;
+ERROR: cannot drop extension test_ext_owned_schema because other objects depend on it
+DETAIL: function test_ext_owned_schema.non_ext_func() depends on schema test_ext_owned_schema
+HINT: Use DROP ... CASCADE to drop the dependent objects too.
+-- Succeeds and cascades to the non-extension object
+DROP EXTENSION test_ext_owned_schema CASCADE;
+NOTICE: drop cascades to function test_ext_owned_schema.non_ext_func()
CREATE SCHEMA already_existing;
-- Fails for an already existing schema to be provided
CREATE EXTENSION test_ext_owned_schema_relocatable SCHEMA already_existing;
@@ -721,3 +750,77 @@ Objects in extension "test_ext_owned_schema_relocatable"
(2 rows)
DROP EXTENSION test_ext_owned_schema_relocatable;
+-- Verify the schema was dropped with the extension
+DROP SCHEMA some_other_name;
+ERROR: schema "some_other_name" does not exist
+-- Test owned_schema + superuser=false extension
+CREATE USER test_ext_user;
+GRANT CREATE ON DATABASE regression_test_extensions TO test_ext_user;
+SET SESSION AUTHORIZATION test_ext_user;
+CREATE EXTENSION test_ext_owned_schema_nosuperuser;
+\dx+ test_ext_owned_schema_nosuperuser;
+Objects in extension "test_ext_owned_schema_nosuperuser"
+ Object description
+-------------------------------------------------
+ function test_owned_schema_nosuperuser.owned1()
+ schema test_owned_schema_nosuperuser
+(2 rows)
+
+-- Check that schema is owned by the creating user (not bootstrap superuser)
+SELECT n.nspname, n.nspowner = current_user::regrole as owned_by_current_user
+FROM pg_namespace n
+WHERE n.nspname = 'test_owned_schema_nosuperuser';
+ nspname | owned_by_current_user
+-------------------------------+-----------------------
+ test_owned_schema_nosuperuser | t
+(1 row)
+
+-- Upgrades should work for superuser=false extensions
+ALTER EXTENSION test_ext_owned_schema_nosuperuser UPDATE TO '1.1';
+\dx+ test_ext_owned_schema_nosuperuser;
+Objects in extension "test_ext_owned_schema_nosuperuser"
+ Object description
+-------------------------------------------------
+ function test_owned_schema_nosuperuser.owned1()
+ function test_owned_schema_nosuperuser.owned2()
+ schema test_owned_schema_nosuperuser
+(3 rows)
+
+DROP EXTENSION test_ext_owned_schema_nosuperuser;
+RESET SESSION AUTHORIZATION;
+-- Test owned_schema + trusted=true extension
+SET SESSION AUTHORIZATION test_ext_user;
+CREATE EXTENSION test_ext_owned_schema_trusted;
+\dx+ test_ext_owned_schema_trusted;
+Objects in extension "test_ext_owned_schema_trusted"
+ Object description
+---------------------------------------------
+ function test_owned_schema_trusted.owned1()
+ schema test_owned_schema_trusted
+(2 rows)
+
+-- Check that schema is owned by bootstrap superuser for trusted extension,
+-- even though the extension is owned by test_ext_user
+SELECT n.nspname, n.nspowner = 10 as owned_by_bootstrap_superuser
+FROM pg_namespace n
+WHERE n.nspname = 'test_owned_schema_trusted';
+ nspname | owned_by_bootstrap_superuser
+---------------------------+------------------------------
+ test_owned_schema_trusted | t
+(1 row)
+
+-- Updating trusted extensions should work normally
+ALTER EXTENSION test_ext_owned_schema_trusted UPDATE TO '1.1';
+\dx+ test_ext_owned_schema_trusted;
+Objects in extension "test_ext_owned_schema_trusted"
+ Object description
+---------------------------------------------
+ function test_owned_schema_trusted.owned1()
+ function test_owned_schema_trusted.owned2()
+ schema test_owned_schema_trusted
+(3 rows)
+
+DROP EXTENSION test_ext_owned_schema_trusted;
+RESET SESSION AUTHORIZATION;
+REVOKE CREATE ON DATABASE regression_test_extensions FROM test_ext_user;
+DROP USER test_ext_user;
diff --git a/src/test/modules/test_extensions/meson.build b/src/test/modules/test_extensions/meson.build
index 3e25c4466ec..019e0d2a243 100644
--- a/src/test/modules/test_extensions/meson.build
+++ b/src/test/modules/test_extensions/meson.build
@@ -45,7 +45,14 @@ test_install_data += files(
'test_ext_set_schema--1.0.sql',
'test_ext_set_schema.control',
'test_ext_owned_schema--1.0.sql',
+ 'test_ext_owned_schema--1.0--1.1.sql',
'test_ext_owned_schema.control',
+ 'test_ext_owned_schema_nosuperuser--1.0.sql',
+ 'test_ext_owned_schema_nosuperuser--1.0--1.1.sql',
+ 'test_ext_owned_schema_nosuperuser.control',
+ 'test_ext_owned_schema_trusted--1.0.sql',
+ 'test_ext_owned_schema_trusted--1.0--1.1.sql',
+ 'test_ext_owned_schema_trusted.control',
'test_ext_owned_schema_relocatable--1.0.sql',
'test_ext_owned_schema_relocatable.control',
)
diff --git a/src/test/modules/test_extensions/sql/test_extensions.sql b/src/test/modules/test_extensions/sql/test_extensions.sql
index a97866d00ea..700033fc13b 100644
--- a/src/test/modules/test_extensions/sql/test_extensions.sql
+++ b/src/test/modules/test_extensions/sql/test_extensions.sql
@@ -316,7 +316,20 @@ CREATE EXTENSION test_ext_owned_schema SCHEMA test_schema;
DROP SCHEMA test_ext_owned_schema;
CREATE EXTENSION test_ext_owned_schema;
\dx+ test_ext_owned_schema;
+-- Test ALTER EXTENSION UPDATE with owned schema
+ALTER EXTENSION test_ext_owned_schema UPDATE TO '1.1';
+\dx+ test_ext_owned_schema;
+-- Verify that the owned schema is dropped together with the extension
+DROP EXTENSION test_ext_owned_schema;
+SELECT COUNT(*) FROM pg_namespace WHERE nspname = 'test_ext_owned_schema';
+
+-- Test drop behavior with non-extension objects in the owned schema
+CREATE EXTENSION test_ext_owned_schema;
+CREATE FUNCTION test_ext_owned_schema.non_ext_func() RETURNS int LANGUAGE SQL AS $$ SELECT 1 $$;
+-- Fails because of the non-extension object
DROP EXTENSION test_ext_owned_schema;
+-- Succeeds and cascades to the non-extension object
+DROP EXTENSION test_ext_owned_schema CASCADE;
CREATE SCHEMA already_existing;
-- Fails for an already existing schema to be provided
@@ -330,3 +343,38 @@ ALTER EXTENSION test_ext_owned_schema_relocatable SET SCHEMA already_existing;
ALTER EXTENSION test_ext_owned_schema_relocatable SET SCHEMA some_other_name;
\dx+ test_ext_owned_schema_relocatable
DROP EXTENSION test_ext_owned_schema_relocatable;
+-- Verify the schema was dropped with the extension
+DROP SCHEMA some_other_name;
+
+-- Test owned_schema + superuser=false extension
+CREATE USER test_ext_user;
+GRANT CREATE ON DATABASE regression_test_extensions TO test_ext_user;
+SET SESSION AUTHORIZATION test_ext_user;
+CREATE EXTENSION test_ext_owned_schema_nosuperuser;
+\dx+ test_ext_owned_schema_nosuperuser;
+-- Check that schema is owned by the creating user (not bootstrap superuser)
+SELECT n.nspname, n.nspowner = current_user::regrole as owned_by_current_user
+FROM pg_namespace n
+WHERE n.nspname = 'test_owned_schema_nosuperuser';
+-- Upgrades should work for superuser=false extensions
+ALTER EXTENSION test_ext_owned_schema_nosuperuser UPDATE TO '1.1';
+\dx+ test_ext_owned_schema_nosuperuser;
+DROP EXTENSION test_ext_owned_schema_nosuperuser;
+RESET SESSION AUTHORIZATION;
+
+-- Test owned_schema + trusted=true extension
+SET SESSION AUTHORIZATION test_ext_user;
+CREATE EXTENSION test_ext_owned_schema_trusted;
+\dx+ test_ext_owned_schema_trusted;
+-- Check that schema is owned by bootstrap superuser for trusted extension,
+-- even though the extension is owned by test_ext_user
+SELECT n.nspname, n.nspowner = 10 as owned_by_bootstrap_superuser
+FROM pg_namespace n
+WHERE n.nspname = 'test_owned_schema_trusted';
+-- Updating trusted extensions should work normally
+ALTER EXTENSION test_ext_owned_schema_trusted UPDATE TO '1.1';
+\dx+ test_ext_owned_schema_trusted;
+DROP EXTENSION test_ext_owned_schema_trusted;
+RESET SESSION AUTHORIZATION;
+REVOKE CREATE ON DATABASE regression_test_extensions FROM test_ext_user;
+DROP USER test_ext_user;
diff --git a/src/test/modules/test_extensions/test_ext_owned_schema--1.0--1.1.sql b/src/test/modules/test_extensions/test_ext_owned_schema--1.0--1.1.sql
new file mode 100644
index 00000000000..a87be1ac967
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext_owned_schema--1.0--1.1.sql
@@ -0,0 +1,3 @@
+-- Test extension upgrade script
+CREATE FUNCTION owned2() RETURNS text
+ LANGUAGE SQL AS $$ SELECT 'owned2'::text $$;
diff --git a/src/test/modules/test_extensions/test_ext_owned_schema_nosuperuser--1.0--1.1.sql b/src/test/modules/test_extensions/test_ext_owned_schema_nosuperuser--1.0--1.1.sql
new file mode 100644
index 00000000000..e9ba788d751
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext_owned_schema_nosuperuser--1.0--1.1.sql
@@ -0,0 +1,5 @@
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "ALTER EXTENSION test_ext_owned_schema_nosuperuser UPDATE TO '1.1'" to load this file. \quit
+
+CREATE FUNCTION owned2() RETURNS text
+ LANGUAGE SQL AS $$ SELECT 'owned2'::text $$;
diff --git a/src/test/modules/test_extensions/test_ext_owned_schema_nosuperuser--1.0.sql b/src/test/modules/test_extensions/test_ext_owned_schema_nosuperuser--1.0.sql
new file mode 100644
index 00000000000..6c22c777519
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext_owned_schema_nosuperuser--1.0.sql
@@ -0,0 +1,3 @@
+-- Test extension with owned schema (superuser=false)
+CREATE FUNCTION owned1() RETURNS text
+ LANGUAGE SQL AS $$ SELECT 'owned1'::text $$;
diff --git a/src/test/modules/test_extensions/test_ext_owned_schema_nosuperuser.control b/src/test/modules/test_extensions/test_ext_owned_schema_nosuperuser.control
new file mode 100644
index 00000000000..caad0960996
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext_owned_schema_nosuperuser.control
@@ -0,0 +1,5 @@
+comment = 'Test extension with owned schema (superuser=false)'
+default_version = '1.0'
+superuser = false
+owned_schema = true
+schema = 'test_owned_schema_nosuperuser'
diff --git a/src/test/modules/test_extensions/test_ext_owned_schema_trusted--1.0--1.1.sql b/src/test/modules/test_extensions/test_ext_owned_schema_trusted--1.0--1.1.sql
new file mode 100644
index 00000000000..a87be1ac967
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext_owned_schema_trusted--1.0--1.1.sql
@@ -0,0 +1,3 @@
+-- Test extension upgrade script
+CREATE FUNCTION owned2() RETURNS text
+ LANGUAGE SQL AS $$ SELECT 'owned2'::text $$;
diff --git a/src/test/modules/test_extensions/test_ext_owned_schema_trusted--1.0.sql b/src/test/modules/test_extensions/test_ext_owned_schema_trusted--1.0.sql
new file mode 100644
index 00000000000..4e5410c184d
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext_owned_schema_trusted--1.0.sql
@@ -0,0 +1,3 @@
+-- Test extension with owned schema (trusted=true)
+CREATE FUNCTION owned1() RETURNS text
+ LANGUAGE SQL AS $$ SELECT 'owned1'::text $$;
diff --git a/src/test/modules/test_extensions/test_ext_owned_schema_trusted.control b/src/test/modules/test_extensions/test_ext_owned_schema_trusted.control
new file mode 100644
index 00000000000..277b3f1603b
--- /dev/null
+++ b/src/test/modules/test_extensions/test_ext_owned_schema_trusted.control
@@ -0,0 +1,5 @@
+comment = 'Test extension with owned schema (trusted=true)'
+default_version = '1.0'
+trusted = true
+owned_schema = true
+schema = 'test_owned_schema_trusted'