Files
shopdb-flask/migrations/versions/7d41_drop_guest_tools_secrets.py
cproudlock 1a8f426e93 Make revoking final, keep password rules in one place, drop a grant nobody read
Three credential and authz findings, all the same shape: a second path to a
decision, with weaker rules than the path built for it.

PUT /api/apitokens/<id> {"isactive": true} un-revoked a token, and the ORIGINAL
secret worked again - so "revoked" meant "paused until somebody flips it back",
for a credential whose secret is assumed to be out by the act of revoking it.
Revocation is one way now. Deactivating through the same route still works;
bringing one back does not, and the refusal says to mint a new token.

PUT /api/users/<self> accepted a `password` field with no current-password
proof and no length rule, while /api/auth/change-password requires both. When
two routes reach the same state the weaker one is the real rule, so the stronger
one was decoration. Changing your own password through the users route is
refused and points at the right endpoint. An ADMIN may still set one - that is
how a temporary password is issued - with the same 8-character floor, and
mustchangepassword is now set, because a password somebody else chose is
temporary by definition.

tools.secrets was declared guest-grantable, so the role grid offered it and
stored it, and the secure-send route never consulted the guest role at all: it
decides on securesend_allow_guests, which fails closed and carries the rate
limit. Ticking the box changed nothing in either direction, so the screen and
the behaviour disagreed. The declaration is gone; tools.view stays, because it
really does gate the nav entry.

Removing it needed care: update_role refuses the WHOLE request when a submitted
permission is not grantable, and the grid resubmits what the role already holds,
so a site whose guest role held tools.secrets could no longer save that role at
all - the deadlock fixed in 723a4ff, re-created from the other end. Migration
7d41 revokes the grant from the guest role, and nothing is lost because it
granted nothing.

Eight tests. The first two verified by planting: remove either guard and three
fail.

Scope: product
2026-09-03 16:03:47 -04:00

57 lines
2.0 KiB
Python

"""Revoke a guest grant that never did anything, before it locks the role.
tools.secrets was declared guest-grantable, so the role grid offered it and
stored it - and the secure-send route never consulted the guest ROLE at all. It
decides on the securesend_allow_guests setting, which fails closed and carries
the rate limit. A site that ticked the box got no change in behaviour, in either
direction.
The plugin no longer declares it grantable. That alone would repeat the deadlock
fixed in 723a4ff: update_role refuses the WHOLE request when a submitted
permission is not grantable, and the role grid resubmits what the role already
holds - so a site whose guest role holds tools.secrets could no longer save the
guest role at all, including to change something else entirely.
So the grant is removed here, from the one role that could hold it. Nothing is
lost: it granted nothing. Guest secret-link creation stays exactly where it was,
under securesend_allow_guests.
Only the GUEST role is touched. A signed-in role holding tools.secrets is
unaffected - that permission works normally for an account.
Revision ID: 7d41_drop_guest_tools_secrets
Revises: 7d40_machine_maintenance_dates
"""
from alembic import op
import sqlalchemy as sa
revision = '7d41_drop_guest_tools_secrets'
down_revision = '7d40_machine_maintenance_dates'
branch_labels = None
depends_on = None
def upgrade():
bind = op.get_bind()
inspector = sa.inspect(bind)
tables = set(inspector.get_table_names())
if not {'roles', 'permissions', 'rolepermissions'} <= tables:
return
bind.execute(sa.text(
'DELETE FROM rolepermissions WHERE roleid IN '
'(SELECT roleid FROM roles WHERE rolename = :guest) '
'AND permissionid IN '
'(SELECT permissionid FROM permissions WHERE name = :permission)'),
{'guest': 'guest', 'permission': 'tools.secrets'})
def downgrade():
"""Deliberately a no-op.
Re-granting a permission that nothing reads would restore the confusion
rather than any capability.
"""