Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions alembic/versions/adb481b7c60b_add_calibration_superseded_column.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
"""add_calibration_superseded_column

Revision ID: adb481b7c60b
Revises: 398067c53257
Create Date: 2026-06-01 16:45:35.507837

"""
from alembic import op
import sqlalchemy as sa

# revision identifiers, used by Alembic.
revision = 'adb481b7c60b'
down_revision = 'a7f3c2e9b104'
branch_labels = None
depends_on = None


def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('score_calibrations', sa.Column('replaces_id', sa.Integer(), nullable=True))
op.create_index(op.f('ix_score_calibrations_replaces_id'), 'score_calibrations', ['replaces_id'], unique=False)
op.create_foreign_key(None, 'score_calibrations', 'score_calibrations', ['replaces_id'], ['id'])
# ### end Alembic commands ###


def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_constraint(None, 'score_calibrations', type_='foreignkey')
op.drop_index(op.f('ix_score_calibrations_replaces_id'), table_name='score_calibrations')
op.drop_column('score_calibrations', 'replaces_id')
# ### end Alembic commands ###
1 change: 1 addition & 0 deletions src/mavedb/lib/permissions/actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@ class Action(Enum):
ADD_BADGE = "add_badge"
CHANGE_RANK = "change_rank"
ADD_CALIBRATION = "add_calibration"
SUPERSEDE_CALIBRATION = "supersede_calibration"
48 changes: 48 additions & 0 deletions src/mavedb/lib/permissions/score_calibration.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ def has_permission(user_data: Optional[UserData], entity: ScoreCalibration, acti
Action.DELETE: _handle_delete_action,
Action.PUBLISH: _handle_publish_action,
Action.CHANGE_RANK: _handle_change_rank_action,
Action.SUPERSEDE_CALIBRATION: _handle_supersede_action
}

if action not in handlers:
Expand Down Expand Up @@ -293,3 +294,50 @@ def _handle_change_rank_action(

user_may_view_private = user_is_owner or (entity.investigator_provided and user_is_contributor_to_score_set)
return deny_action_for_entity(entity, private, user_data, user_may_view_private, "score calibration")


def _handle_supersede_action(
user_data: Optional[UserData],
entity: ScoreCalibration,
user_is_owner: bool,
user_is_contributor_to_score_set: bool,
private: bool,
active_roles: list[UserRole],
) -> PermissionResponse:
"""
Handle SUPERSEDE action permission check for ScoreCalibration entities.

Only public score calibrations are allowed to be superseded.
Only superseding the calibration from the same score set.
Admin, owner, or investigator-provided plus a contributor to the score set can be the users who have permissions to
supersede a calibration.

Args:
user_data: The user's authentication data.
entity: The ScoreCalibration entity being accessed.
user_is_owner: Whether the user created the ScoreCalibration.
user_is_contributor_to_score_set: Whether the user is a contributor to the associated ScoreSet.
private: Whether the ScoreCalibration is private.
active_roles: List of the user's active roles.

Returns:
PermissionResponse: Permission result with appropriate HTTP status.
"""
## Allow read access under the following conditions:
# Only public score calibrations are allowed to be superseded.
if private:
return PermissionResponse(False)
# Owners of the ScoreCalibration may supersede it.
if user_is_owner:
return PermissionResponse(True)
# System admins may supersede any ScoreCalibration.
if roles_permitted(active_roles, [UserRole.admin]):
return PermissionResponse(True)
# If the calibration is investigator provided, contributors to the ScoreCalibration may supersede it.
if entity.investigator_provided and user_is_contributor_to_score_set:
return PermissionResponse(True)
# Only non superseded calibration can be superseded.
if entity.superseding_calibration:
return PermissionResponse(False)

return deny_action_for_entity(entity, private, user_data, False, "score calibration")
Loading
Loading