Skip to content

Add ReadOnlyDatabaseRole model - #260

Draft
kiwitcms-bot wants to merge 1 commit into
masterfrom
read-only-database-role-model
Draft

kiwitcms-bot wants to merge 1 commit into
masterfrom
read-only-database-role-model

Conversation

@kiwitcms-bot

Copy link
Copy Markdown
Member

What

Adds a new ReadOnlyDatabaseRole model (standalone; the integration that creates the role for tenants comes in a follow-up PR).

Fields: name, created_at, valid_until, password.

ReadOnlyDatabaseRole.create_for_schema(schema_name, valid_until):

  • name = ro_for_<schema_name> (READ_ONLY_ROLE_PREFIX is a class attribute)
  • password = random UUID
  • created_at = now
  • valid_until = passed in by the caller

It stores the model record and creates an actual PostgreSQL role via raw SQL, escaping identifiers and values with psycopg's sql.Identifier / sql.Literal (no string interpolation), so user supplied values can't be used for SQL injection.

The role is created with LOGIN + ENCRYPTED PASSWORD + VALID UNTIL, gets USAGE on the tenant schema, SELECT on all tables in it, and ALTER DEFAULT PRIVILEGES ... GRANT SELECT ON TABLES so tables created later are covered too. No write privileges are granted.

All statements are sent in a single cursor.execute() call (PostgreSQL runs a multi-statement simple query in one implicit transaction), and the model row + DB role are created inside one transaction.atomic() block, so a failure leaves neither a stale row nor a partially-granted role. update_valid_until() updates the model and the DB role in one transaction.

Tests

tests/test_read_only_database_role.py: the record is stored, the actual DB role exists with rolvaliduntil, and has_table_privilege() reports SELECT but not INSERT/UPDATE/DELETE; plus update_valid_until().

black, flake8 and pylint (10.00/10) are clean; makemigrations --check reports no pending changes.

Keeps track of a read-only DB role for an existing tenant schema.
The role has SELECT privileges on all tables in the tenant schema
and is valid for as long as the customer's subscription renews.

The actual DB role is created via raw SQL where identifiers and
values are escaped via psycopg's Identifier/Literal wrappers to
prevent SQL injection. All statements are sent in a single
cursor.execute() call and the model row + DB role are created inside
one transaction.atomic() block so a failure leaves nothing behind.
@atodorov
atodorov marked this pull request as draft October 6, 2026 16:37
@kiwitcms-bot
kiwitcms-bot force-pushed the read-only-database-role-model branch from 37782c8 to 7a681ff Compare October 6, 2026 16:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants