Skip to content
Snippets Groups Projects

Reforma vyhodnocování práv

Closed Martin Mareš requested to merge mj/rights into master
1 unresolved thread
1 file
+ 76
55
Compare changes
  • Side-by-side
  • Inline
+ 76
55
@@ -2,7 +2,7 @@
from enum import Enum, auto
from dataclasses import dataclass
from typing import Set, List, Dict, Optional
from typing import Set, List, Dict, Tuple, Optional
import mo.db as db
@@ -126,27 +126,65 @@ role_order_by_type: Dict[db.RoleType, int] = {r.role: i for (i, r) in enumerate(
class Rights:
"""Třída, která popisuje množinu práv k nějakému objektu."""
user: db.User
roles: List[db.UserRole]
rights: Set[Right]
def __repr__(self):
ros = " ".join([r.role.name for r in self.roles])
ris = " ".join([r.name for r in self.rights])
return f"Rights(uid={self.user.user_id} is_admin={self.user.is_admin} roles=<{ros}> rights=<{ris}>)"
def have_right(self, right: Right):
if self.user.is_admin:
return True
assert self.rights is not None
return right in self.rights
# Helper methods for concrete rights
def can_edit_place(self, place: db.Place):
if self.have_right(Right.edit_region):
return True
elif place.level >= 3 and self.have_right(Right.edit_place):
# level >= 3 ... city and lower
return True
return False
def can_edit_user(self, user: db.User):
if user.is_admin:
return self.user.is_admin # only admins can edit admins
elif user.is_org:
return self.have_right(Right.edit_orgs)
return self.have_right(Right.edit_users)
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
nebo právě zpracovávaný job."""
user: db.User
roles: List[db.UserRole]
current_place: Optional[db.Place]
current_parents: Optional[List[db.Place]]
current_roles: List[db.UserRole]
current_rights: Set[Right]
parent_cache: Dict[int, List[db.Place]]
rights_cache: Dict[Tuple[Optional[int], Optional[int], Optional[str], Optional[int], Optional[db.RoleType]], Rights]
def __init__(self, user: db.User):
self.user = user
assert user.is_org or user.is_admin
self.roles = user.roles
self.current_place = None
self.current_parents = None
self.current_roles = []
self.current_rights = set()
assert user.is_org or user.is_admin
self.parent_cache = {}
self.rights_cache = {}
def __repr__(self):
rs = " ".join([r.role.name for r in self.roles])
return f"Rights(uid={self.user.user_id} roles=<{rs}>)"
def get_parents(self, place: db.Place) -> List[db.Place]:
pid = place.place_id
if pid not in self.parent_cache:
self.parent_cache[pid] = db.get_place_parents(place)
return self.parent_cache[pid]
def get_for(
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):
@@ -156,9 +194,14 @@ class Rights:
Podobně cat a seq.
Pokud min_role!=None, tak se uvažují jen role, které jsou v hierarchii alespoň na úrovni min_role."""
self.current_place = place
self.current_roles = []
self.current_rights = set()
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.user = self.user
rights.roles = []
rights.rights = set()
def try_role(role: db.UserRole, at: Optional[db.Place]):
if ((at is None or role.place_id == at.place_id)
@@ -166,66 +209,44 @@ class Rights:
and (role.category is None or cat is None or role.category == cat or (role.category == 'Z' and cat.startswith('Z')))
and (role.seq is None or seq is None or role.seq == seq)
and (min_role is None or role_order_by_type[min_role] >= role_order_by_type[role.role])):
self.current_roles.append(role)
rights.roles.append(role)
r = roles_by_type[role.role]
self.current_rights |= r.rights
rights.rights |= r.rights
# XXX: This might be faster...
if place is None:
self.current_parents = []
for role in self.roles:
try_role(role, None)
else:
self.current_parents = db.get_place_parents(place)
for at in self.current_parents:
for at in self.get_parents(place):
for role in self.roles:
try_role(role, at)
def get_generic(self):
"""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.get_for()
def get_for_round(self, round: db.Round):
return self.get_for(place=None, year=round.year, cat=round.category, seq=round.seq)
self.rights_cache[cache_key] = rights
return rights
def get_for_contest(self, contest: db.Contest):
return self.get_for(place=contest.place, year=contest.round.year, cat=contest.round.category, seq=contest.round.seq)
def rights_generic(self):
"""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 get_for_contest_site(self, contest: db.Contest, place: db.Place):
return self.get_for(place=place, year=contest.round.year, cat=contest.round.category, seq=contest.round.seq)
def rights_for_round(self, round: db.Round):
return self.rights_for(place=None, year=round.year, cat=round.category, seq=round.seq)
def have_right(self, right: Right):
if self.user.is_admin:
return True
assert self.current_rights is not None
return right in self.current_rights
# Helper methods for concrete rights
def rights_for_contest(self, contest: db.Contest):
return self.rights_for(place=contest.place, year=contest.round.year, cat=contest.round.category, seq=contest.round.seq)
def can_edit_place(self, place: db.Place):
if self.user.is_admin or self.have_right(Right.edit_region):
return True
elif place.level >= 3 and self.have_right(Right.edit_place):
# level >= 3 ... city and lower
return True
return False
def can_edit_user(self, user: db.User):
if user.is_admin:
return self.user.is_admin # only admins can edit admins
elif user.is_org:
return self.have_right(Right.edit_orgs)
return self.have_right(Right.edit_users)
def rights_for_contest_site(self, contest: db.Contest, place: db.Place):
return self.rights_for(place=place, year=contest.round.year, cat=contest.round.category, seq=contest.round.seq)
def can_set_role(self, role: db.UserRole):
# We will use get_for but we need to slightly modify its behavior. Can
# set role with * year only if current user has role with * year, thus we
# need to pass year=0 for * year (cat and seq similarly).
self.get_for(
rights = self.rights_for(
place=role.place,
year=role.year or 0,
cat=role.category or '',
seq=role.seq or 0,
min_role=role.role
)
return self.have_right(Right.assign_rights)
return rights.have_right(Right.assign_rights)
Loading