Repository: edgedb/edgedb

Issue:
__subject__ does not work inside insert access policies
- EdgeDB Version: 2.7+5b16920
- EdgeDB CLI Version: 2.2.6+af56767
- OS Version: Ubuntu 22.04

Steps to Reproduce:

1. Declare a `User` object type, with no properties or links.
1. Declare a computed `global current_user` that resolves to a `User` object instance.
2. On the `User` type, create an `allow all` access policy such that `__subject__ ?= global current_user`.

This will fail with `there is no range var for ns~1@@(default::Redacted) value in <pg.SelectStmt at 0xDEADBEEF>`.

Schema:

```
using future nonrecursive_access_policies;

module default {
    global viewer_id -> uuid;
    global viewer := (select User filter .id = global viewer_id);
    
    type User {
        required property name -> str;

        access policy allow_self
            allow all
            using (__subject__ ?= global viewer);
    }
}
```



Discussion:
I've reproduced this when doing `insert User { name := "foo" }` using this schema. Have you managed to get it with non insert queries?

This is certainly a bug, but this policy will also never be true for inserts. (Even if you do have an explicitly specified `id` and set `viewer_id` to it, `global viewer` won't resolve to it because it didn't already exist).
You're right, I had simplified the schema a bit. The actual condition was `not exists global viewer or __subject__ ?= global viewer` and the test involved a double insert with no `global viewer_id` followed by a counting select with the `id` of one of the users assigned to `global viewer_id`.

After splitting the access policy into two (one for inserts and another for everything else), the test passed. But should it not have worked for the initial inserts given that `not exists global viewer` will evaluate to `true`?

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
@@ -708,10 +708,11 @@ def dynamic_get_path(
         if iterator is not None:
             pathctx.put_path_bond(select, iterator.path_id)
 
-    pathctx._put_path_output_var(
-        select, ir_stmt.subject.path_id, aspect='identity',
-        var=pgast.ColumnRef(name=['id']), env=ctx.env,
-    )
+    for aspect in ('value', 'identity'):
+        pathctx._put_path_output_var(
+            select, ir_stmt.subject.path_id, aspect=aspect,
+            var=pgast.ColumnRef(name=['id']), env=ctx.env,
+        )
 
     # Put the select that builds the tuples to insert into its own CTE.
     # We do this for two reasons:


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
@@ -529,6 +529,24 @@ async def test_edgeql_policies_11(self):
             };
             ''')
 
+    async def test_edgeql_policies_12(self):
+        await self.con.query('''
+            create global cur_user_obj := (
+                select User filter .name = global cur_user);
+            alter type User {
+                create access policy allow_self_obj
+                   allow all
+                   using (__subject__ ?= global cur_user_obj);
+            }
+        ''')
+
+        async with self.assertRaisesRegexTx(
+                edgedb.InvalidValueError,
+                r"access policy violation on insert of default::User"):
+            await self.con.query('''
+                insert User { name := 'whatever' }
+            ''')
+
     async def test_edgeql_policies_order_01(self):
         await self.con.execute('''
             insert CurOnly { name := "!" }