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