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
2 changes: 1 addition & 1 deletion backend/bracket/routes/courts.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ async def create_court(
database,
Court,
courts.select().where(
courts.c.id == last_record_id and courts.c.tournament_id == tournament_id
(courts.c.id == last_record_id) & (courts.c.tournament_id == tournament_id)
),
)
)
Expand Down
30 changes: 21 additions & 9 deletions backend/bracket/routes/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from bracket.models.db.team import FullTeamWithPlayers, Team
from bracket.models.db.tournament import Tournament, TournamentStatus
from bracket.models.db.util import RoundWithMatches, StageItemWithRounds, StageWithStageItems
from bracket.schema import matches, rounds, teams
from bracket.schema import matches, rounds, stage_items, stages, teams
from bracket.sql.rounds import get_round_by_id
from bracket.sql.stage_items import get_stage_item
from bracket.sql.stages import get_full_tournament_details
Expand All @@ -21,7 +21,13 @@ async def round_dependency(tournament_id: TournamentId, round_id: RoundId) -> Ro
round_ = await fetch_one_parsed(
database,
Round,
rounds.select().where(rounds.c.id == round_id and matches.c.tournament_id == tournament_id),
rounds.select()
.select_from(
rounds.join(stage_items, rounds.c.stage_item_id == stage_items.c.id).join(
stages, stage_items.c.stage_id == stages.c.id
)
)
.where((rounds.c.id == round_id) & (stages.c.tournament_id == tournament_id)),
)

if round_ is None:
Expand All @@ -40,17 +46,17 @@ async def round_with_matches_dependency(


async def stage_dependency(tournament_id: TournamentId, stage_id: StageId) -> StageWithStageItems:
stages = await get_full_tournament_details(
stages_result = await get_full_tournament_details(
tournament_id, no_draft_rounds=False, stage_id=stage_id
)

if len(stages) < 1:
if len(stages_result) < 1:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Could not find stage with id {stage_id}",
)

return stages[0]
return stages_result[0]


async def stage_item_dependency(
Expand All @@ -63,9 +69,13 @@ async def match_dependency(tournament_id: TournamentId, match_id: MatchId) -> Ma
match = await fetch_one_parsed(
database,
Match,
matches.select().where(
matches.c.id == match_id and matches.c.tournament_id == tournament_id
),
matches.select()
.select_from(
matches.join(rounds, matches.c.round_id == rounds.c.id)
.join(stage_items, rounds.c.stage_item_id == stage_items.c.id)
.join(stages, stage_items.c.stage_id == stages.c.id)
)
.where((matches.c.id == match_id) & (stages.c.tournament_id == tournament_id)),
)

if match is None:
Expand All @@ -81,7 +91,9 @@ async def team_dependency(tournament_id: TournamentId, team_id: TeamId) -> Team:
team = await fetch_one_parsed(
database,
Team,
teams.select().where(teams.c.id == team_id and teams.c.tournament_id == tournament_id),
teams.select().where(
(teams.c.id == team_id) & (teams.c.tournament_id == tournament_id)
),
)

if team is None:
Expand Down
36 changes: 33 additions & 3 deletions backend/tests/integration_tests/api/teams_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,19 @@
from bracket.models.db.team import Team
from bracket.schema import players, teams
from bracket.utils.db import fetch_one_parsed_certain
from bracket.utils.dummy_records import DUMMY_MOCK_TIME, DUMMY_TEAM1
from bracket.utils.dummy_records import DUMMY_MOCK_TIME, DUMMY_TEAM1, DUMMY_TOURNAMENT
from bracket.utils.http import HTTPMethod
from tests.integration_tests.api.shared import SUCCESS_RESPONSE, send_tournament_request
from tests.integration_tests.api.shared import (
SUCCESS_RESPONSE,
send_auth_request,
send_tournament_request,
)
from tests.integration_tests.models import AuthContext
from tests.integration_tests.sql import assert_row_count_and_clear, inserted_team
from tests.integration_tests.sql import (
assert_row_count_and_clear,
inserted_team,
inserted_tournament,
)


@pytest.mark.asyncio(loop_scope="session")
Expand Down Expand Up @@ -153,3 +161,25 @@ async def test_team_upload_and_remove_logo(
assert not await aiofiles.os.path.exists(
f"static/team-logos/{response['data']['logo_path']}"
)


@pytest.mark.asyncio(loop_scope="session")
async def test_cross_tournament_team_access_denied(
startup_and_shutdown_uvicorn_server: None, auth_context: AuthContext
) -> None:
"""Regression test: a team from tournament B cannot be accessed via tournament A's URL."""
async with inserted_tournament(
DUMMY_TOURNAMENT.model_copy(
update={"club_id": auth_context.club.id, "dashboard_endpoint": None}
)
) as other_tournament:
async with inserted_team(
DUMMY_TEAM1.model_copy(update={"tournament_id": other_tournament.id})
) as other_team:
response = await send_auth_request(
HTTPMethod.PUT,
f"tournaments/{auth_context.tournament.id}/teams/{other_team.id}",
auth_context,
json={"name": "Hacked", "active": True, "player_ids": []},
)
assert response.get("detail") == f"Could not find team with id {other_team.id}"