Repository: edgedb/edgedb

Issue:
Disallow user modifications of extension modules
Oops

Important for #5988


Discussion:

Resolving patch:
diff --git a/edb/schema/delta.py b/edb/schema/delta.py
--- a/edb/schema/delta.py
+++ b/edb/schema/delta.py
@@ -2391,14 +2391,15 @@ def _validate_legal_command(
 
             if (
                 isinstance(self.classname, sn.QualName)
+                and (modname := self.classname.get_module_name())
                 and (
-                    (modname := self.classname.get_module_name())
+                    (modroot := sn.UnqualName(modname.name.partition('::')[0]))
                     in s_schema.STD_MODULES
                 )
             ):
                 raise errors.SchemaDefinitionError(
                     f'cannot {self._delta_action} {self.get_verbosename()}: '
-                    f'module {modname} is read-only',
+                    f'module {modroot} is read-only',
                     context=self.source_context)
 
     def get_verbosename(self, parent: Optional[str] = None) -> str:
diff --git a/edb/schema/modules.py b/edb/schema/modules.py
--- a/edb/schema/modules.py
+++ b/edb/schema/modules.py
@@ -66,8 +66,10 @@ def _validate_legal_command(
         super()._validate_legal_command(schema, context)
 
         last = str(self.classname)
+        first = last
         enclosing = None
         if '::' in str(self.classname):
+            first, _, _ = str(self.classname).partition('::')
             enclosing, _, last = str(self.classname).rpartition('::')
             if not schema.has_module(enclosing):
                 raise errors.UnknownModuleError(
@@ -79,18 +81,11 @@ def _validate_legal_command(
 
         if (
             not context.stdmode and not context.testmode
-            and (
-                (modname := self.classname) in s_schema.STD_MODULES
-                or (
-                    enclosing
-                    and (modname := sn.UnqualName(enclosing))
-                    in s_schema.STD_MODULES
-                )
-            )
+            and sn.UnqualName(first) in s_schema.STD_MODULES
         ):
             raise errors.SchemaDefinitionError(
                 f'cannot {self._delta_action} {self.get_verbosename()}: '
-                f'module {modname} is read-only',
+                f'module {first} is read-only',
                 context=self.source_context)
 
 


Test patch:
diff --git a/tests/test_edgeql_userddl.py b/tests/test_edgeql_userddl.py
--- a/tests/test_edgeql_userddl.py
+++ b/tests/test_edgeql_userddl.py
@@ -385,3 +385,35 @@ async def test_edgeql_userddl_28(self):
             await self.con.execute(r'''
             create type Foo extending cfg::ConfigObject;
             ''')
+
+    async def test_edgeql_userddl_29(self):
+        await self.con.execute('''
+            configure session set __internal_testmode := true;
+            create module ext::_test;
+            create type ext::_test::X;
+            configure session reset __internal_testmode;
+        ''')
+
+        async with self.assertRaisesRegexTx(
+                edgedb.SchemaDefinitionError, "module ext is read-only"):
+            await self.con.execute('''
+                create module ext::_test::foo;
+            ''')
+
+        async with self.assertRaisesRegexTx(
+                edgedb.SchemaDefinitionError, "module ext is read-only"):
+            await self.con.execute('''
+                create type ext::_test::foo;
+            ''')
+
+        async with self.assertRaisesRegexTx(
+                edgedb.SchemaDefinitionError, "module ext is read-only"):
+            await self.con.execute('''
+                alter type ext::_test::X { create property x -> str };
+            ''')
+
+        async with self.assertRaisesRegexTx(
+                edgedb.SchemaDefinitionError, "module ext is read-only"):
+            await self.con.execute('''
+                drop type ext::_test::X;
+            ''')