Skip to content
Snippets Groups Projects
Select Git revision
  • 31139484b19235ed3ca7418ec5e79da84b06fbc5
  • devel default
  • master
  • fo
  • jirka/typing
  • fo-base
  • mj/submit-images
  • jk/issue-96
  • jk/issue-196
  • honza/add-contestant
  • honza/mr7
  • honza/mrf
  • honza/mrd
  • honza/mra
  • honza/mr6
  • honza/submit-images
  • honza/kolo-vs-soutez
  • jh-stress-test-wip
  • shorten-schools
19 results

api.py

Blame
  • rights.py 8.78 KiB
    # Přístupová práva
    
    from enum import Enum, auto
    from dataclasses import dataclass
    from typing import Set, List, Dict, Tuple, Optional
    
    import mo.db as db
    
    
    class Right(Enum):
        assign_rights = auto()
        edit_region = auto()
        edit_place = auto()
        manage_round = auto()
        manage_contest = auto()
        upload_solutions = auto()       # Odevzdávat za účastníka ve stavu "running"
        upload_feedback = auto()        # Nahrávat opravené řešení ve stavu "grading"
        view_submits = auto()           # Prohlížet si řešení a opravy
        upload_submits = auto()         # Nahrávat řešení a opravy nezávisle na stavu soutěže
        edit_points = auto()
        add_users = auto()
        edit_users = auto()
        add_orgs = auto()
        edit_orgs = auto()
    
    
    @dataclass
    class Role:
        role: db.RoleType
        name: str
        rights: Set[Right]
    
        def __init__(self, role: db.RoleType, rights: Set[Right]):
            self.role = role
            self.name = role.friendly_name()
            self.rights = rights
    
    
    # Order in this list represents hierarchy for assign_rights right
    # (garant could assign role of garant or garant_kraj, but garant_kraj cannot
    # assign role garant).
    roles: List[Role] = [
        Role(
            role=db.RoleType.garant,
            rights={
                Right.assign_rights,
                Right.edit_place,
                Right.manage_round,
                Right.manage_contest,
                Right.view_submits,
                Right.upload_submits,
                Right.edit_points,
                Right.add_users,
                Right.edit_users,
                Right.add_orgs,
                Right.edit_orgs,
            },
        ),
        Role(
            role=db.RoleType.garant_kraj,
            rights={
                Right.assign_rights,
                Right.edit_place,
                Right.manage_contest,
                Right.view_submits,
                Right.upload_submits,
                Right.edit_points,
                Right.add_users,
                Right.edit_users,
                Right.add_orgs,
                Right.edit_orgs,
            },
        ),
        Role(
            role=db.RoleType.garant_okres,
            rights={
                Right.assign_rights,
                Right.edit_place,
                Right.manage_contest,
                Right.view_submits,
                Right.upload_submits,
                Right.edit_points,
                Right.add_users,
                Right.edit_users,
                Right.add_orgs,
                Right.edit_orgs,
            },
        ),
        Role(
            role=db.RoleType.garant_skola,
            rights={
                # FIXME: Až se pořádně rozjedou školní kola, asi chceme školním správcům omezit
                # práva na editaci uživatelů. Viz issue #66.
                Right.assign_rights,
                Right.edit_place,
                Right.manage_contest,
                Right.view_submits,
                Right.upload_submits,
                Right.edit_points,
                Right.add_users,
                Right.edit_users,
                Right.add_orgs,
                Right.edit_orgs,
            },
        ),
        Role(
            role=db.RoleType.dozor,
            rights={
                Right.view_submits,
                Right.upload_solutions,
            },
        ),
        Role(
            role=db.RoleType.opravovatel,
            rights={
                Right.view_submits,
                Right.upload_feedback,
                Right.edit_points,
            },
        ),
    ]
    
    
    roles_by_type: Dict[db.RoleType, Role] = {r.role: r for r in roles}
    role_order_by_type: Dict[db.RoleType, int] = {r.role: i for (i, r) in enumerate(roles)}
    
    
    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_add_place_child(self, place: db.Place):
            if self.have_right(Right.edit_region):
                return True
            elif place.level >= 2 and self.have_right(Right.edit_place):
                # Can add cities 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]
        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
            self.roles = user.roles
            assert user.is_org or user.is_admin
            self.parent_cache = {}
            self.rights_cache = {}
    
        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 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):
            """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."""
    
            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)
                        and (role.year is None or year is None or role.year == year)
                        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])):
                    rights.roles.append(role)
                    r = roles_by_type[role.role]
                    rights.rights |= r.rights
    
            # XXX: This might be faster...
            if place is None:
                for role in self.roles:
                    try_role(role, None)
            else:
                for at in self.get_parents(place):
                    for role in self.roles:
                        try_role(role, at)
    
            self.rights_cache[cache_key] = rights
            return rights
    
        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 rights_for_round(self, round: db.Round):
            return self.rights_for(place=None, year=round.year, cat=round.category, seq=round.seq)
    
        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 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).
            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 rights.have_right(Right.assign_rights)