# User and role management, `SHOW TRANSACTIONS`, `:sysinfo` ## Problem - Neo4j's user and role commands on the `system` database (`SHOW USERS`, `CREATE USER`, `GRANT ROLE`, ...) are rejected by `_run_system` (`NotSystemDatabaseCommand`). `dbms.showCurrentUser` always reports the role `admin` (`boltage/backend.py:247`). - `SHOW TRANSACTIONS` / `TERMINATE TRANSACTIONS` are not emulated. - neo4j-browser's `:sysinfo` frame shows nothing useful, because the queries behind it are not emulated. Authentication already maps Neo4j users to PostgreSQL roles one to one, so the natural model is that Neo4j users and roles **are** PostgreSQL roles. ## Part 1: users and roles ### Mapping | Neo4j | PostgreSQL | | --- | --- | | user | role with `LOGIN` | | role | role without `LOGIN` | | user has role | role membership (`GRANT role TO user`) | | suspended user | `NOLOGIN` on a role that has a password (or a marker, see below) | | `admin` role | superuser, or membership in a configurable role | | home database | role setting `boltage.home_database` (`ALTER ROLE ... SET boltage.home_database = 'g'`, a custom GUC) | ### Commands | Cypher (on `system`) | SQL | | --- | --- | | `SHOW USERS` | `pg_roles` where `rolcanlogin` (or suspended), with memberships from `pg_auth_members` | | `SHOW CURRENT USER` | same, for `current_user` | | `CREATE USER u [IF NOT EXISTS] SET [PLAINTEXT] PASSWORD 'p' [CHANGE [NOT] REQUIRED] [SET STATUS ...] [SET HOME DATABASE g]` | `CREATE ROLE u LOGIN PASSWORD 'p'`, plus `ALTER ROLE ... SET` | | `ALTER USER u SET PASSWORD 'p'`, `SET STATUS SUSPENDED/ACTIVE`, `SET HOME DATABASE` | `ALTER ROLE` | | `ALTER CURRENT USER SET PASSWORD FROM 'old' TO 'new'` | check `old` by opening a short connection with it, then `ALTER ROLE current_user PASSWORD 'new'` | | `DROP USER u [IF EXISTS]` | `DROP ROLE u` | | `SHOW ROLES [WITH USERS]`, `CREATE ROLE r`, `DROP ROLE r`, `RENAME ROLE/USER` | `pg_roles`, `CREATE/DROP ROLE`, `ALTER ROLE ... RENAME TO` | | `GRANT ROLE r TO u`, `REVOKE ROLE r FROM u` | `GRANT r TO u`, `REVOKE r FROM u` | Steps: 1. Parse the commands with regexes in `_run_system`, like `CREATE DATABASE`; the grammar is small and fixed. Run them only on the `system` database. 2. Build SQL with `psycopg.sql.Identifier` for names. Passwords cannot be bind parameters in `CREATE ROLE`; use `sql.Literal`, and make sure the debug log at `boltage/backend.py` (`log.debug("SQL: ...")`) and `_sql` never log statements containing passwords (add a `secret=True` flag to `_sql` that redacts). 3. `CHANGE REQUIRED`: PostgreSQL has no equivalent. Accept `CHANGE NOT REQUIRED` and reject `CHANGE REQUIRED`, or store a marker in a role setting (`boltage.password_change_required`) and enforce it at login by allowing only `ALTER CURRENT USER SET PASSWORD` (Neo4j's behaviour, signalled with `Neo.ClientError.Security.CredentialsExpired`). Start with rejecting. 4. Suspension: `NOLOGIN` makes a suspended user indistinguishable from a role. Store `boltage.suspended = 'true'` as a role setting next to `NOLOGIN` so `SHOW USERS` still lists the user. 5. Use the home database at login: after `connect()`, read `current_setting('boltage.home_database', true)` and use it instead of `BOLTAGE_DEFAULT_GRAPH` in `resolve_db` and `SHOW HOME DATABASE`. 6. `dbms.showCurrentUser` and `SHOW CURRENT USER` report the real roles; report `admin` only for superusers / members of the admin role, so neo4j-browser does not offer admin actions that will fail. 7. Errors: map PostgreSQL's `42501` (insufficient privilege) on these commands to `Neo.ClientError.Security.Forbidden`, and duplicate/missing roles to Neo4j's `Neo.ClientError.General.InvalidArguments` messages. ### Privileges (`GRANT ... ON GRAPH`) Neo4j's fine-grained privileges (`TRAVERSE`, `READ {prop}`, per label) do not map onto PostgreSQL. A coarse subset does, per graph schema: | Cypher | SQL | | --- | --- | | `GRANT ACCESS ON DATABASE g TO r` | `GRANT USAGE ON SCHEMA g TO r` | | `GRANT MATCH {*} ON GRAPH g TO r` | `GRANT SELECT ON ALL TABLES IN SCHEMA g TO r` (+ `ALTER DEFAULT PRIVILEGES` for new labels) | | `GRANT WRITE ON GRAPH g TO r` | `INSERT, UPDATE, DELETE` on all tables and `USAGE` on sequences in the schema | | `GRANT MATCH {*} ON GRAPH g NODES L TO r` | `SELECT` on `g."L"` only | Anything finer (property-level, `TRAVERSE` without `READ`, `DENY`) is rejected with `FeatureNotSupported`. Do this after the user commands, and only if wanted; the README's trigger section already explains granting SQL privileges directly. ### neo4j-browser The browser's `:server user list` / `:server user add` frames send these commands. Capture the exact queries (run boltage with `BOLTAGE_LOG_LEVEL=DEBUG`, or read the failure log lines) and make sure the shapes they use are covered. ## Part 2: `SHOW TRANSACTIONS` and `TERMINATE TRANSACTIONS` ### Approach 1. Make PostgreSQL backends identifiable: set `application_name` to `boltage ` (`boltage/backend.py:126`), and remember each session's backend pid (`SELECT pg_backend_pid()` at connect). 2. Keep a process-wide registry in boltage of live sessions: connection id, user, client address (`peername`), driver `user_agent` from `HELLO`, current graph, transaction start time, and the current **Cypher** text (not the generated SQL). 3. `SHOW TRANSACTIONS [ids] [YIELD ...]` joins the registry with `pg_stat_activity` (by pid) for `state`, `xact_start`, `query_start`, wait events, and lists: `database`, `transactionId`, `currentQueryId`, `connectionId`, `clientAddress`, `username`, `currentQuery`, `startTime`, `status`, `elapsedTime`. Transaction ids look like Neo4j's: `-transaction-`, with `n` from a per-process counter incremented at each `BEGIN` / auto-commit query. 4. Visibility: show only the user's own transactions unless they are a superuser or member of `pg_read_all_stats`, matching what `pg_stat_activity` allows. 5. `TERMINATE TRANSACTIONS 'id'`: `pg_cancel_backend(pid)` on the session's backend, which aborts the running statement and its transaction. Mark the Bolt session so its next message fails with `Neo.TransientError.Transaction.Terminated`. Only allowed for one's own transactions or with `pg_signal_backend`. 6. Several boltage processes against one database (for example behind a load balancer) each know only their own registry. Fall back to `pg_stat_activity` alone for backends with `application_name` starting with `boltage` that are not in the local registry, extracting the Cypher text from between the dollar quotes of the `cypher()` call. Also emulate the older `CALL dbms.listTransactions()` and `CALL dbms.listQueries()` with the same data if neo4j-browser or cypher-shell still send them. ## Part 3: `:sysinfo` 1. **Find out what the frame queries.** neo4j-browser's `:sysinfo` frame uses different queries depending on the server version it detects (boltage reports `5.26.0`): typically `SHOW DATABASES` plus `CALL dbms.queryJmx(...)` for store sizes, id usage, page cache and transaction counts. Capture them from the failure log, or read `SysInfoFrame` in the neo4j-browser version pinned by `NEO4J_BROWSER_REF`. 2. **Emulate `dbms.queryJmx`** for the beans the frame reads, from PostgreSQL: | Sysinfo field | Source | | --- | --- | | store sizes | `pg_total_relation_size` summed over the graph schema's tables; `pg_database_size` for the total | | node / relationship id counts | row counts or `reltuples` of `_ag_label_vertex` / `_ag_label_edge` (already used by `_counts`) | | page cache hits / faults / hit ratio | `pg_statio_user_tables` (`heap_blks_hit`, `heap_blks_read`) for the schema | | transactions (open, committed, rolled back, peak) | `pg_stat_database` (`xact_commit`, `xact_rollback`) and `pg_stat_activity` | | version / edition | the existing `dbms.components` values, plus PostgreSQL and AGE versions | 3. Fields that have no PostgreSQL equivalent return empty values rather than errors, so the frame still renders. ## Tests Smoke test, as the superuser of the test instance: create a user with a password, log in as that user, grant and revoke a role, `SHOW USERS` / `SHOW ROLES`, change the user's own password, drop the user. `SHOW TRANSACTIONS` from one session sees a transaction open in another, and `TERMINATE TRANSACTIONS` makes the other fail. Manual check of `:sysinfo` and `:server user list` in neo4j-browser. ## README changes Replace the bullet with a "Users and roles" section explaining that they are PostgreSQL roles, the supported subset of privileges, and what `:sysinfo` shows.