Repository: edgedb/edgedb

Issue:
Custom access control policy fails for Insert operations (RBAC Implementation)
<!-- Please search existing issues to avoid creating duplicates. -->

<!--
For the EdgeDB Version: run `edgedb query 'select sys::get_version_as_str()'` from your project directory (or run `select sys::get_version_as_str();` in the EdgeDB interactive shell).
For the EdgeDB CLI Version: Run `edgedb --version` from anywhere
-->
Posting this on behalf of spost on Discord as he's OOO currently. 

[Discord thread](https://discord.com/channels/841451783728529451/1055624003508776970/1055624008655183872) for context 

- EdgeDB Version: 2.8+afca054
- EdgeDB CLI Version: EdgeDB CLI 2.2.6+b869667
- OS Version: Pop!_OS 22.04 LTS x86_64

Steps to Reproduce:
Attached is a mostly-empty edgedb project with the RBAC schema, an Example type that extends RBAC::Controlled, and a setup.edgeql file

1.  Use attached zip and run `edgedb query -f setup.edgeql`
2. Try to add an Example that belongs to that organization while using that role


[edgedb-rbac-repro.zip](https://github.com/edgedb/edgedb/files/10320540/edgedb-rbac-repro.zip)



Discussion:
I've checkout the RBAC repro project and I must say it's impressive!

First, I think that in `setup.edgeql`, you have a minor mistake:

```diff
# Set our role_ids
 set global RBAC::role_ids := <str>(
 	select RBAC::DbRole
 	filter .name = "Organization Admin" and .organization.name = "Example Organization"
-	limit 1).organization.id;
+	limit 1).id;
```
... but this is not the crux of the problem.

---

I've debugged it down to this condition:
```
# ... within a policy when condition
    # A permission covers this scope, or all scopes (*)
    equal_or_star(
        permissions.scope,
        pluck_scope_annotation(__subject__.__type__)
    )
```
`pluck_scope_annotation` returns an empty set even though `__subject__.__type__` should refer to `default::Example`. I've checked that this does return the value of scope annotation:
```
select RBAC::pluck_scope_annotation(
  (select schema::ObjectType filter .name = 'default::Example' limit 1)
)
```

Which means that this is a bug or an undocumented limitation. I'll ask @msullivan about it.
Cheers, thanks DJ, and good catch on the repro @aljazerzen.  I'm back in the office now, so I'm free to answer any questions about this.
I think I can reproduce this with
```
create type T {
    create access policy ok allow all;
    create access policy asdf deny insert using (.__type__.name ?!= 'default::T');
};
```

Resolving patch:
diff --git a/edb/pgsql/compiler/dml.py b/edb/pgsql/compiler/dml.py
--- a/edb/pgsql/compiler/dml.py
+++ b/edb/pgsql/compiler/dml.py
@@ -569,14 +569,12 @@ def process_insert_body(
     if typeref.material_type is not None:
         typeref = typeref.material_type
 
+    type_val = pgast.TypeCast(
+        arg=pgast.StringConstant(val=str(typeref.id)),
+        type_name=pgast.TypeName(name=('uuid',)),
+    )
     values.append(
-        pgast.ResTarget(
-            name='__type__',
-            val=pgast.TypeCast(
-                arg=pgast.StringConstant(val=str(typeref.id)),
-                type_name=pgast.TypeName(name=('uuid',))
-            ),
-        )
+        pgast.ResTarget(name='__type__', val=type_val)
     )
 
     # Handle an UNLESS CONFLICT if we need it
@@ -616,6 +614,8 @@ def process_insert_body(
 
     # Use a dynamic rvar to return values out of the select purely
     # based on material rptr, as if it was a base relation.
+    # This is to make it easy for access policies to operate on the result
+    # of the INSERT.
     def dynamic_get_path(
         rel: pgast.Query, path_id: irast.PathId, *,
         flavor: str,
@@ -627,6 +627,8 @@ def dynamic_get_path(
             return None
         if ret := ptr_map.get(rptr.real_material_ptr):
             return ret
+        if rptr.real_material_ptr.shortname.name == '__type__':
+            return type_val
         # Properties that aren't specified are {}
         return pgast.NullConstant()
 


Test patch:
diff --git a/tests/test_edgeql_policies.py b/tests/test_edgeql_policies.py
--- a/tests/test_edgeql_policies.py
+++ b/tests/test_edgeql_policies.py
@@ -1002,6 +1002,23 @@ async def test_edgeql_policies_recursive_01(self):
             [1],
         )
 
+    async def test_edgeql_policies_insert_type(self):
+        await self.con.execute('''
+            create type T {
+                create access policy ok allow all;
+                create access policy asdf deny insert using (
+                    .__type__.name ?!= 'default::T')
+            };
+            create type S extending T;
+        ''')
+
+        await self.con.execute('insert T')
+        async with self.assertRaisesRegexTx(
+            edgedb.InvalidValueError,
+            'access policy violation',
+        ):
+            await self.con.execute('insert S')
+
     async def test_edgeql_policies_internal_shape_01(self):
         await self.con.execute('''
             alter type Issue {