From 2c4e3d3fcd5e662b2bb3a807808b30b54bc0029c Mon Sep 17 00:00:00 2001 From: Martin Mares <mj@ucw.cz> Date: Tue, 28 Sep 2021 20:19:15 +0200 Subject: [PATCH] =?UTF-8?q?Omezen=C3=AD=20pr=C3=A1v=20=C5=A1koln=C3=ADch?= =?UTF-8?q?=20garant=C5=AF=20k=20u=C5=BEivatel=C5=AFm?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Školní garant smí pracovat jen s uživateli ze své školy (studujícími na dané škole nebo účastnícími se příslušného školního kola). --- mo/rights.py | 81 +++++++++++++++++++++++++++++---- mo/web/org_users.py | 19 +++++++- mo/web/templates/org_users.html | 3 ++ 3 files changed, 93 insertions(+), 10 deletions(-) diff --git a/mo/rights.py b/mo/rights.py index c8d549a3..b0e8878a 100644 --- a/mo/rights.py +++ b/mo/rights.py @@ -2,9 +2,12 @@ from enum import Enum, auto from dataclasses import dataclass +from sqlalchemy import or_ +from sqlalchemy.orm.query import Query from typing import Set, List, Dict, Tuple, Optional import mo +import mo.config as config import mo.db as db @@ -23,7 +26,10 @@ class Right(Enum): edit_points = auto() # Přidělovat body ve stavu "grading" view_statement = auto() # Prohlížet zadání, pokud je dostupné pro dozor add_users = auto() - edit_users = auto() + view_all_users = auto() # Prohlížet všechny uživatele + view_school_users = auto() # Prohlížet uživatele ze své školy (jen garant_skola) + edit_all_users = auto() # Editovat všechny účastníky + edit_school_users = auto() # Editovat uživatele ze své školy (jen garant_skola) add_orgs = auto() edit_orgs = auto() @@ -57,7 +63,8 @@ roles: List[Role] = [ Right.upload_submits, Right.edit_points, Right.add_users, - Right.edit_users, + Right.view_all_users, + Right.edit_all_users, Right.add_orgs, Right.edit_orgs, }, @@ -75,7 +82,8 @@ roles: List[Role] = [ Right.edit_points, Right.view_statement, Right.add_users, - Right.edit_users, + Right.view_all_users, + Right.edit_all_users, Right.add_orgs, Right.edit_orgs, }, @@ -93,7 +101,8 @@ roles: List[Role] = [ Right.edit_points, Right.view_statement, Right.add_users, - Right.edit_users, + Right.view_all_users, + Right.edit_all_users, Right.add_orgs, Right.edit_orgs, }, @@ -101,8 +110,6 @@ roles: List[Role] = [ 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, @@ -112,7 +119,8 @@ roles: List[Role] = [ Right.edit_points, Right.view_statement, Right.add_users, - Right.edit_users, + Right.view_school_users, + Right.edit_school_users, Right.add_orgs, Right.edit_orgs, }, @@ -191,12 +199,69 @@ class Rights: # Práva na práci s uživateli + def can_view_user(self, user: db.User) -> bool: + if user.is_admin or user.is_org: + return True + elif self.have_right(Right.view_all_users): + return True + elif self.have_right(Right.view_school_users): + schools = self.get_user_schools(Right.view_school_users) + if schools: + q = db.get_session().query(db.User).filter_by(user_id=user.user_id) + q = self.restrict_user_query(q, schools) + if q.first(): + return True + return False + else: + return False + def can_edit_user(self, user: db.User) -> bool: 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) + elif self.have_right(Right.edit_all_users): + return True + elif self.have_right(Right.edit_school_users): + schools = self.get_user_schools(Right.edit_school_users) + if schools: + q = db.get_session().query(db.User).filter_by(user_id=user.user_id) + q = self.restrict_user_query(q, schools) + if q.first(): + return True + return False + else: + return False + + def get_user_schools(self, right: Right) -> Set[db.Place]: + """Vrátí seznam škol, kde má organizátor právo spravovat uživatele.""" + places: Set[db.Place] = set() + for role in self.user_roles: + r = roles_by_type[role.role] + if right in r.rights: + places.add(role.place) + return places + + def restrict_user_query(self, q: Query, schools: Set[db.Place]) -> Query: + """Přidá k dotazu na hledání uživatelů podmínku na školy z dané množiny.""" + sess = db.get_session() + school_ids = {s.place_id for s in schools} + q = q.filter(or_( + db.User.user_id.in_( + sess.query(db.Participant.user_id) + .filter(db.Participant.school.in_(school_ids)) + .filter(db.Participant.year >= config.CURRENT_YEAR - 1) + ), + db.User.user_id.in_( + sess.query(db.Participation.user_id) + .select_from(db.Participation) + .join(db.Contest) + .join(db.Round) + .filter(or_(db.Contest.place_id.in_(school_ids), db.Participation.place_id.in_(school_ids))) + .filter(db.Round.year == config.CURRENT_YEAR) + ) + )) + return q class RoundRights(Rights): diff --git a/mo/web/org_users.py b/mo/web/org_users.py index 11d56053..0f8b055a 100644 --- a/mo/web/org_users.py +++ b/mo/web/org_users.py @@ -52,6 +52,11 @@ def org_users(): sess = db.get_session() rr = g.gatekeeper.rights_generic() + if rr.have_right(Right.view_all_users): + schools = None + else: + schools = rr.get_user_schools(Right.view_school_users) + q = sess.query(db.User).filter_by(is_admin=False, is_org=False).options( subqueryload(db.User.participants).joinedload(db.Participant.school_place) ) @@ -108,6 +113,9 @@ def org_users(): if participation_filter_apply: q = q.filter(db.User.user_id.in_(participation_filter)) + if schools is not None: + q = rr.restrict_user_query(q, schools) + # print(str(q)) (count, q) = filter.apply_limits(q, pagesize=50) users = q.all() @@ -115,8 +123,9 @@ def org_users(): return render_template( 'org_users.html', users=users, count=count, filter=filter, - can_edit=rr.have_right(Right.edit_users), + can_edit=rr.have_right(Right.edit_all_users) or rr.have_right(Right.edit_school_users), can_add=rr.have_right(Right.add_users), + is_restricted=(schools is not None), ) @@ -265,6 +274,8 @@ def org_org(id: int): raise werkzeug.exceptions.NotFound() rr = g.gatekeeper.rights_generic() + if not rr.can_view_user(user): + raise werkzeug.exceptions.Forbidden() can_assign_rights = rr.have_right(Right.assign_rights) resend_invite_form: Optional[ResendInviteForm] = None @@ -348,6 +359,10 @@ def org_user(id: int): return redirect(url_for('org_org', id=id)) rr = g.gatekeeper.rights_generic() + can_edit = rr.can_edit_user(user) + can_view = can_edit or rr.can_view_user(user) # Zkratka, abychom se vyhnuli drahému dotazu + if not can_view: + raise werkzeug.exceptions.Forbidden() resend_invite_form: Optional[ResendInviteForm] = None if user.last_login_at is None and rr.can_edit_user(user): @@ -369,7 +384,7 @@ def org_user(id: int): ) return render_template( - 'org_user.html', user=user, can_edit=rr.can_edit_user(user), + 'org_user.html', user=user, can_edit=can_edit, can_incarnate=g.user.is_admin, participants=participants, participations=participations, resend_invite_form=resend_invite_form, diff --git a/mo/web/templates/org_users.html b/mo/web/templates/org_users.html index c8c16ddf..b55111cf 100644 --- a/mo/web/templates/org_users.html +++ b/mo/web/templates/org_users.html @@ -60,6 +60,9 @@ {% else %} <b>Nebyly nalezeny žádné záznamy soutěžících.</b> {% endif %} + {% if is_restricted %} + Pozor, vidíte pouze účastníky ze svých škol. + {% endif %} <input type="hidden" name="offset" value="{{filter.offset.data}}"> <input type="hidden" name="limit" value="{{filter.limit.data}}"> </form> -- GitLab