Skip to content
Snippets Groups Projects
Commit 5c8d5174 authored by Martin Mareš's avatar Martin Mareš
Browse files

Rights: Práva k soutěžím a kolům

Původně třída Rights měla metody testující práva k zadané soutěži/kolu.
To se ukázalo být nepraktickým, tak definuji třídy RoundRights a
ContestRights, které si pamatují odkaz na konkrétní kolo/soutěž,
a vše vyhodnocují relativně k němu.
parent 09ee9c8b
No related branches found
No related tags found
1 merge request!49Stavy soutěže
......@@ -126,6 +126,7 @@ roles: List[Role] = [
Right.view_submits,
Right.upload_feedback,
Right.edit_points,
Right.view_statement,
},
),
]
......@@ -158,7 +159,7 @@ class Rights:
role_set = set(ur.role for ur in self.user_roles)
return sorted(role_set, key=lambda r: role_order_by_type[r])
# Helper methods for concrete rights
# Práva na práci s podřízenými místy
def can_edit_place(self, place: db.Place) -> bool:
if self.have_right(Right.edit_region):
......@@ -176,6 +177,8 @@ class Rights:
return True
return False
# Práva na práci s uživateli
def can_edit_user(self, user: db.User) -> bool:
if user.is_admin:
return self.user.is_admin # only admins can edit admins
......@@ -183,36 +186,90 @@ class Rights:
return self.have_right(Right.edit_orgs)
return self.have_right(Right.edit_users)
def can_upload_solutions(self, round: db.Round) -> bool:
return (self.have_right(Right.upload_submits)
or self.have_right(Right.upload_solutions) and round.state == db.RoundState.running)
def can_upload_feedback(self, round: db.Round) -> bool:
return (self.have_right(Right.upload_submits)
or self.have_right(Right.upload_feedback) and round.state == db.RoundState.grading)
def can_edit_points(self, round: db.Round) -> bool:
return (self.have_right(Right.edit_points) and round.state == db.RoundState.grading
or self.have_right(Right.manage_contest))
# Interní rozhodovaní o dostupnosti zadání
def can_view_statement(self, round: db.Round) -> bool:
def _check_view_statement(self, round: db.Round):
if round.tasks_file is None:
return False
if self.have_right(Right.manage_round):
# Správce kola může vždy všechno
return True
# Pokud už soutěž skončila, přístup k zadání má každý org.
# XXX: Rozhodujeme podle stavu kola, nikoliv soutěže!
if round.state in [db.RoundState.grading, db.RoundState.closed]:
return True
# Od stanoveného času vidí zadání orgové s právem view_statement.
if (self.have_right(Right.view_statement)
and round.state == db.RoundState.running
and round.state != db.RoundState.preparing
and round.pr_tasks_start is not None
and mo.now >= round.pr_tasks_start):
# Pokud soutěž běží, může mít právo přístupu dozor
return True
if round.state in [db.RoundState.grading, db.RoundState.closed]:
# Pokud už soutěž skončila, přístup k zadání má každý org
return True
# Ve zbylých případech jsme konzervativní a zadání neukazujeme
return False
class RoundRights(Rights):
"""Práva ke kolu."""
round: db.Round
def __repr__(self):
ros = " ".join([r.role.name for r in self.user_roles])
ris = " ".join([r.name for r in self.rights])
return f"RoundRights(uid={self.user.user_id} is_admin={self.user.is_admin} round=#{self.round.round_id} roles=<{ros}> rights=<{ris}>)"
# Metody offer_* testují, zda se má v UI nabízet příslušná funkce.
# Skutečnou kontrolu práv dělá až implementace funkce podle stavu soutěže.
def _is_active(self) -> bool:
return self.round.state not in [db.RoundState.preparing, db.RoundState.closed]
def offer_upload_solutions(self) -> bool:
return (self.have_right(Right.upload_submits)
or (self.have_right(Right.upload_solutions) and self._is_active()))
def offer_upload_feedback(self) -> bool:
return (self.have_right(Right.upload_submits)
or (self.have_right(Right.upload_feedback) and self._is_active()))
def offer_edit_points(self) -> bool:
return (self.have_right(Right.manage_contest)
or (self.have_right(Right.edit_points) and self._is_active()))
def can_view_statement(self) -> bool:
return self._check_view_statement(self.round)
class ContestRights(Rights):
"""Práva k soutěži."""
contest: db.Contest
def __repr__(self):
ros = " ".join([r.role.name for r in self.user_roles])
ris = " ".join([r.name for r in self.rights])
return f"ContestRights(uid={self.user.user_id} is_admin={self.user.is_admin} contest=#{self.contest.contest_id} roles=<{ros}> rights=<{ris}>)"
def can_upload_solutions(self) -> bool:
return (self.have_right(Right.upload_submits)
or self.have_right(Right.upload_solutions) and self.contest.get_state() == db.RoundState.running)
def can_upload_feedback(self) -> bool:
return (self.have_right(Right.upload_submits)
or self.have_right(Right.upload_feedback) and self.contest.get_state() == db.RoundState.grading)
def can_edit_points(self) -> bool:
return (self.have_right(Right.edit_points) and self.contest.get_state() == db.RoundState.grading
or self.have_right(Right.manage_contest))
def can_view_statement(self) -> bool:
return self._check_view_statement(self.contest.round)
class Gatekeeper:
"""Dveřník rozhoduje, jaká práva má uživatel k danému objektu.
Typicky existuje jen v jedné instanci, navázaná na aktuální request
......@@ -239,18 +296,20 @@ class Gatekeeper:
def rights_for(
self, place: Optional[db.Place] = None, year: Optional[int] = None,
cat: Optional[str] = None, seq: Optional[int] = None,
min_role: Optional[db.RoleType] = None):
min_role: Optional[db.RoleType] = None,
to_rights: Optional[Rights] = None) -> Rights:
"""Posbírá role a práva, která se vztahují k danému místu (možno i tranzitivně) a soutěži.
Pokud place=None, omezení role na místo se nebere v úvahu.
Pokud year==None, vyhovují role s libovolným ročníkem; pokud year=0, vyhovují jen ty s neuvedeným ročníkem.
Podobně cat a seq.
Pokud min_role!=None, tak se uvažují jen role, které jsou v hierarchii alespoň na úrovni min_role."""
Pokud min_role!=None, tak se uvažují jen role, které jsou v hierarchii alespoň na úrovni min_role.
Pokud to_rights!=None, tak vyplní existující objekt typu Rights."""
cache_key = (place.place_id if place is not None else None, year, cat, seq, min_role)
if cache_key in self.rights_cache:
return self.rights_cache[cache_key]
rights = Rights()
rights = to_rights or Rights()
rights.user = self.user
rights.user_roles = []
rights.rights = set()
......@@ -281,15 +340,33 @@ class Gatekeeper:
"""Posbírá role a práva, ale ignoruje omezení rolí na místa a soutěže. Hodí se pro práva k editaci uživatelů apod."""
return self.rights_for()
def rights_for_round(self, round: db.Round, any_place: bool):
def rights_for_round(self, round: db.Round, any_place: bool) -> RoundRights:
if any_place:
place = None
else:
place = db.get_root_place()
return self.rights_for(place=place, year=round.year, cat=round.category, seq=round.seq)
rights = RoundRights()
rights.round = round
self.rights_for(
place=place,
year=round.year,
cat=round.category,
seq=round.seq,
to_rights=rights,
)
return rights
def rights_for_contest(self, contest: db.Contest, site: Optional[db.Place] = None):
return self.rights_for(place=site or contest.place, year=contest.round.year, cat=contest.round.category, seq=contest.round.seq)
def rights_for_contest(self, contest: db.Contest, site: Optional[db.Place] = None) -> ContestRights:
rights = ContestRights()
rights.contest = contest
self.rights_for(
place=site or contest.place,
year=contest.round.year,
cat=contest.round.category,
seq=contest.round.seq,
to_rights=rights,
)
return rights
def can_set_role(self, role: db.UserRole):
# We will use get_for but we need to slightly modify its behavior. Can
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment