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

Omezení práv školních garantů k uživatelům

Š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).
parent 8a7c18a7
Branches
No related tags found
No related merge requests found
...@@ -2,9 +2,12 @@ ...@@ -2,9 +2,12 @@
from enum import Enum, auto from enum import Enum, auto
from dataclasses import dataclass from dataclasses import dataclass
from sqlalchemy import or_
from sqlalchemy.orm.query import Query
from typing import Set, List, Dict, Tuple, Optional from typing import Set, List, Dict, Tuple, Optional
import mo import mo
import mo.config as config
import mo.db as db import mo.db as db
...@@ -23,7 +26,10 @@ class Right(Enum): ...@@ -23,7 +26,10 @@ class Right(Enum):
edit_points = auto() # Přidělovat body ve stavu "grading" edit_points = auto() # Přidělovat body ve stavu "grading"
view_statement = auto() # Prohlížet zadání, pokud je dostupné pro dozor view_statement = auto() # Prohlížet zadání, pokud je dostupné pro dozor
add_users = auto() 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() add_orgs = auto()
edit_orgs = auto() edit_orgs = auto()
...@@ -57,7 +63,8 @@ roles: List[Role] = [ ...@@ -57,7 +63,8 @@ roles: List[Role] = [
Right.upload_submits, Right.upload_submits,
Right.edit_points, Right.edit_points,
Right.add_users, Right.add_users,
Right.edit_users, Right.view_all_users,
Right.edit_all_users,
Right.add_orgs, Right.add_orgs,
Right.edit_orgs, Right.edit_orgs,
}, },
...@@ -75,7 +82,8 @@ roles: List[Role] = [ ...@@ -75,7 +82,8 @@ roles: List[Role] = [
Right.edit_points, Right.edit_points,
Right.view_statement, Right.view_statement,
Right.add_users, Right.add_users,
Right.edit_users, Right.view_all_users,
Right.edit_all_users,
Right.add_orgs, Right.add_orgs,
Right.edit_orgs, Right.edit_orgs,
}, },
...@@ -93,7 +101,8 @@ roles: List[Role] = [ ...@@ -93,7 +101,8 @@ roles: List[Role] = [
Right.edit_points, Right.edit_points,
Right.view_statement, Right.view_statement,
Right.add_users, Right.add_users,
Right.edit_users, Right.view_all_users,
Right.edit_all_users,
Right.add_orgs, Right.add_orgs,
Right.edit_orgs, Right.edit_orgs,
}, },
...@@ -101,8 +110,6 @@ roles: List[Role] = [ ...@@ -101,8 +110,6 @@ roles: List[Role] = [
Role( Role(
role=db.RoleType.garant_skola, role=db.RoleType.garant_skola,
rights={ 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.assign_rights,
Right.edit_place, Right.edit_place,
Right.manage_contest, Right.manage_contest,
...@@ -112,7 +119,8 @@ roles: List[Role] = [ ...@@ -112,7 +119,8 @@ roles: List[Role] = [
Right.edit_points, Right.edit_points,
Right.view_statement, Right.view_statement,
Right.add_users, Right.add_users,
Right.edit_users, Right.view_school_users,
Right.edit_school_users,
Right.add_orgs, Right.add_orgs,
Right.edit_orgs, Right.edit_orgs,
}, },
...@@ -191,12 +199,69 @@ class Rights: ...@@ -191,12 +199,69 @@ class Rights:
# Práva na práci s uživateli # 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: def can_edit_user(self, user: db.User) -> bool:
if user.is_admin: if user.is_admin:
return self.user.is_admin # only admins can edit admins return self.user.is_admin # only admins can edit admins
elif user.is_org: elif user.is_org:
return self.have_right(Right.edit_orgs) 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): class RoundRights(Rights):
......
...@@ -52,6 +52,11 @@ def org_users(): ...@@ -52,6 +52,11 @@ def org_users():
sess = db.get_session() sess = db.get_session()
rr = g.gatekeeper.rights_generic() 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( q = sess.query(db.User).filter_by(is_admin=False, is_org=False).options(
subqueryload(db.User.participants).joinedload(db.Participant.school_place) subqueryload(db.User.participants).joinedload(db.Participant.school_place)
) )
...@@ -108,6 +113,9 @@ def org_users(): ...@@ -108,6 +113,9 @@ def org_users():
if participation_filter_apply: if participation_filter_apply:
q = q.filter(db.User.user_id.in_(participation_filter)) 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)) # print(str(q))
(count, q) = filter.apply_limits(q, pagesize=50) (count, q) = filter.apply_limits(q, pagesize=50)
users = q.all() users = q.all()
...@@ -115,8 +123,9 @@ def org_users(): ...@@ -115,8 +123,9 @@ def org_users():
return render_template( return render_template(
'org_users.html', users=users, count=count, 'org_users.html', users=users, count=count,
filter=filter, 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), can_add=rr.have_right(Right.add_users),
is_restricted=(schools is not None),
) )
...@@ -265,6 +274,8 @@ def org_org(id: int): ...@@ -265,6 +274,8 @@ def org_org(id: int):
raise werkzeug.exceptions.NotFound() raise werkzeug.exceptions.NotFound()
rr = g.gatekeeper.rights_generic() 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) can_assign_rights = rr.have_right(Right.assign_rights)
resend_invite_form: Optional[ResendInviteForm] = None resend_invite_form: Optional[ResendInviteForm] = None
...@@ -348,6 +359,10 @@ def org_user(id: int): ...@@ -348,6 +359,10 @@ def org_user(id: int):
return redirect(url_for('org_org', id=id)) return redirect(url_for('org_org', id=id))
rr = g.gatekeeper.rights_generic() 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 resend_invite_form: Optional[ResendInviteForm] = None
if user.last_login_at is None and rr.can_edit_user(user): if user.last_login_at is None and rr.can_edit_user(user):
...@@ -369,7 +384,7 @@ def org_user(id: int): ...@@ -369,7 +384,7 @@ def org_user(id: int):
) )
return render_template( 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, can_incarnate=g.user.is_admin,
participants=participants, participations=participations, participants=participants, participations=participations,
resend_invite_form=resend_invite_form, resend_invite_form=resend_invite_form,
......
...@@ -60,6 +60,9 @@ ...@@ -60,6 +60,9 @@
{% else %} {% else %}
<b>Nebyly nalezeny žádné záznamy soutěžících.</b> <b>Nebyly nalezeny žádné záznamy soutěžících.</b>
{% endif %} {% 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="offset" value="{{filter.offset.data}}">
<input type="hidden" name="limit" value="{{filter.limit.data}}"> <input type="hidden" name="limit" value="{{filter.limit.data}}">
</form> </form>
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment