Nobody has started this yet — be first.
Business impact
This product's entire pitch to tenant owners is: three roles, and only the owner can touch billing (start/change/cancel a subscription). That is not an implementation detail, it is a promise made to every customer who adds an admin to their workspace -- "admin" means "can run the workspace day-to-day," not "can spend the company's money." Right now that promise is broken: any tenant admin can hit POST /api/billing/checkout-session and start a paid subscription the owner never approved. A disgruntled or compromised admin account (a far more common attack surface than the owner's own credentials, since there are more of them and they are handed out more casually) can put a tenant on a paid plan without the person who actually owns the invoice ever being asked.
Problem
ROLE_PERMISSIONS[ROLE_ADMIN] includes PERM_MANAGE_BILLING, when the README and the product's own permission table say manage_billing should be owner-only.
Current behavior
role_has_permission("admin", "manage_billing") returns True because PERM_MANAGE_BILLING is present in the admin role's permission set in ROLE_PERMISSIONS, letting an admin session reach POST /api/billing/checkout-session without a 403.
Expected behavior
role_has_permission("admin", "manage_billing") is False. Only owner has manage_billing.
Steps to reproduce
Why this matters
ROLE_PERMISSIONS is deliberately the single place that decides who can do what -- edit the table, do not grep the codebase for scattered checks. That design is only as trustworthy as the table itself: a single wrong entry here silently grants a real capability to a role that should not have it, with no compiler or type system able to catch it, because a set containing an extra string is completely valid Python.
Suggested approach
Compare ROLE_PERMISSIONS[ROLE_ADMIN] against the permission table documented in README.md's RBAC section, or against the project's own stated rule: admin manages users/settings, no billing.
Acceptance criteria
Verification
pytest practice_tickets/tests/test_ticket_03_admin_billing_permission.py -v
Hints (0/2)
Try it without hints first — the reading is the exercise.
Working on this ticket
Work on a branch named for the ticket — that's what you'll submit.
Branch off your fork
Sign in to create it automatically, or do it yourself:
$git checkout -b fix/admin-billing-permissionFix it and commit
Meet every acceptance criterion, and add a test that would have caught this.
Push the branch
$git push -u origin fix/admin-billing-permissionSubmit it below
Paste your fork URL and the branch name, with a short write-up of the root cause.
Questions
Ask about anything unclear in the ticket — the maintainer and anyone who has solved it can answer. Please don't post full solutions.
Sign in to ask a question or reply.
Sign inacmeglobexmake seed
*.localhost subdomains resolve to 127.0.0.1 out of the box in curl and every modern browser -- no /etc/hosts editing needed:
curl -s -X POST http://acme.localhost:5000/api/auth/login \
-H "Content-Type: application/json" \
--cookie-jar /tmp/acme.cookies --cookie /tmp/acme.cookies \
-d '{"email": "owner@acme.test", "password": "owner-acme"}'
curl -s http://acme.localhost:5000/api/tenant/settings --cookie /tmp/acme.cookies
For tooling that cannot fake subdomains (some HTTP clients, CI), send X-Tenant-Slug instead -- honored only when TRUST_TENANT_HEADER=true (the Docker dev setup enables it; production does not):
curl -s http://localhost:5000/api/tenant/settings -H "X-Tenant-Slug: acme"
You do not strictly need Docker to work the tickets, but -- unlike a project that can fake its backing services -- this one genuinely needs a real Postgres and Redis, because the whole point is proving isolation against the real database, not a mock. Without Docker: cp .env.example .env, point DATABASE_URL/ADMIN_DATABASE_URL/TEST_DATABASE_URL/TEST_ADMIN_DATABASE_URL/REDIS_URL at a local Postgres/Redis (docker/init-db.sql documents the exact roles/databases the app expects -- notably the saas_admin/BYPASSRLS role migrations need and the plain saas role every request uses), then:
python3 -m venv .venv && .venv/bin/pip install -r requirements-dev.txt
.venv/bin/flask db upgrade
.venv/bin/pytest practice_tickets/tests/test_ticket_07_webhook_signature_raw_body.py -v # a single ticket
./practice_tickets/run_tickets.sh # all 15, clean pass/fail summary
./practice_tickets/run_tickets.sh 07 12 13 # just a few, by number
All 15 tests fail at the start; as you fix each ticket in app/ (or, for TICKET-12, in migrations/versions/0002_row_level_security.py, plus re-applying it), its test -- and only its test -- should flip to passing. make test runs the project's own main suite; several of its tests (RBAC, tenant-spoofing, and billing-webhook tests among them) fail at the start for the same underlying reasons as specific tickets -- fixing those tickets fixes those failures too, not a separate task.
Level 1
Fix a bug
Read existing behaviour, correct it.