Skip to content
Merged
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
51 changes: 35 additions & 16 deletions hasura/functions/tournaments/team/can_manage_team.sql
Original file line number Diff line number Diff line change
@@ -1,32 +1,51 @@
CREATE OR REPLACE FUNCTION public.can_manage_tournament_team(tournament_team public.tournament_teams, hasura_session json) RETURNS BOOLEAN
LANGUAGE plpgsql STABLE
AS $$
DECLARE
_user_steam_id bigint;
BEGIN

IF hasura_session ->> 'x-hasura-role' = 'admin' OR hasura_session ->> 'x-hasura-role' = 'administrator' OR hasura_session ->> 'x-hasura-role' = 'tournament_organizer' THEN
RETURN true;
END IF;

IF tournament_team.team_id IS NOT NULL THEN
RETURN EXISTS (
SELECT 1 FROM tournament_team_roster
WHERE
tournament_id = tournament_team.tournament_id
AND player_steam_id = (hasura_session ->> 'x-hasura-user-id')::bigint
AND role IN ('Admin')
);
_user_steam_id := (hasura_session ->> 'x-hasura-user-id')::bigint;

IF _user_steam_id IS NULL THEN
RETURN false;
END IF;

IF tournament_team.owner_steam_id = (hasura_session ->> 'x-hasura-user-id')::bigint THEN
IF tournament_team.owner_steam_id = _user_steam_id THEN
RETURN true;
END IF;

RETURN EXISTS (
SELECT 1 FROM tournament_team_roster
WHERE
tournament_id = tournament_team.tournament_id
AND player_steam_id = (hasura_session ->> 'x-hasura-user-id')::bigint
-- Scoped to this tournament_team, never the tournament: a roster row only
-- makes you an Admin of the team it belongs to.
IF EXISTS (
SELECT 1 FROM tournament_team_roster
WHERE
tournament_team_id = tournament_team.id
AND player_steam_id = _user_steam_id
AND role IN ('Admin')
);
) THEN
RETURN true;
END IF;

IF tournament_team.team_id IS NOT NULL THEN
RETURN EXISTS (
SELECT 1 FROM teams
WHERE
id = tournament_team.team_id
AND (owner_steam_id = _user_steam_id OR captain_steam_id = _user_steam_id)
) OR EXISTS (
SELECT 1 FROM team_roster
WHERE
team_id = tournament_team.team_id
AND player_steam_id = _user_steam_id
AND role IN ('Admin')
);
END IF;

RETURN false;
END;
$$;
$$;
Original file line number Diff line number Diff line change
Expand Up @@ -191,26 +191,33 @@ delete_permissions:
- role: user
permission:
filter:
_or:
- team:
roster:
_and:
- role:
_eq: Admin
- player_steam_id:
_and:
- _or:
- team:
roster:
_and:
- role:
_eq: Admin
- player_steam_id:
_eq: X-Hasura-User-Id
- team:
owner_steam_id:
_eq: X-Hasura-User-Id
- team:
captain_steam_id:
_eq: X-Hasura-User-Id
- _and:
- team_id:
_is_null: true
- owner_steam_id:
_eq: X-Hasura-User-Id
- _and:
- team_id:
_is_null: true
- owner_steam_id:
_eq: X-Hasura-User-Id
- tournament:
is_organizer:
_eq: true
- tournament:
is_organizer:
_eq: true
- tournament:
status:
_in:
- Setup
- RegistrationOpen
- RegistrationClosed
_nin:
- Cancelled
- CancelledMinTeams
- Finished
comment: ""
56 changes: 56 additions & 0 deletions test/permissions.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ import { PostgresService } from "./../src/postgres/postgres.service";
import { Fixtures } from "./utils/fixtures";
import {
bootMigratedDb,
runAsUser,
seedRegionWithServer,
SqlTestDb,
} from "./utils/sql-test-db";
import { TournamentFixtures } from "./utils/tournament-fixtures";

// Exercises the Hasura permission functions — the layer that decides what a
// session may do. These run as plain SELECTs with an explicit session JSON,
Expand Down Expand Up @@ -330,4 +332,58 @@ describe("permission functions (SQL-driven)", () => {
).toBe(true);
});
});

describe("can_manage_tournament_team", () => {
const canManage = (tournamentTeamId: string, steamId: string) =>
boolFn(
"can_manage_tournament_team",
"tournament_teams",
tournamentTeamId,
"id",
session(steamId),
);

// A free-agent entry: no team_id, so the joiner's own roster row is
// promoted to Admin by tbi_tournament_team_roster.
const joinAsFreeAgent = async (tournamentId: string) => {
const joiner = await fx.player();
const id = await runAsUser(postgres, joiner, "user", async (query) => {
const [row] = (await query(
`INSERT INTO tournament_teams (tournament_id, name, owner_steam_id)
VALUES ($1, $2, $3) RETURNING id`,
[tournamentId, fx.nextName("solo"), joiner],
)) as Array<{ id: string }>;
await query(
`INSERT INTO tournament_team_roster (tournament_team_id, tournament_id, player_steam_id)
VALUES ($1, $2, $3)`,
[row.id, tournamentId, joiner],
);
return row.id;
});
return { id, joiner };
};

it("scopes management to the joiner's own team", async () => {
const tf = new TournamentFixtures(postgres, fx);
const tournament = await tf.createTournament([
{ type: "SingleElimination", order: 1, minTeams: 4, maxTeams: 8 },
]);
await tf.setStatus(tournament.id, tournament.organizer, "RegistrationOpen");

const team = await fx.team(1);
const registered = await tf.registerTeam(tournament.id, team);
const solo = await joinAsFreeAgent(tournament.id);

expect(await canManage(solo.id, solo.joiner)).toBe(true);
expect(await canManage(registered, team.owner)).toBe(true);

// Being Admin of one entry in the tournament must not carry over.
expect(await canManage(registered, solo.joiner)).toBe(false);
expect(await canManage(solo.id, team.owner)).toBe(false);

const outsider = await fx.player();
expect(await canManage(registered, outsider)).toBe(false);
expect(await canManage(solo.id, outsider)).toBe(false);
});
});
});
Loading