diff --git a/compendium_v2/db/auth_model.py b/compendium_v2/db/auth_model.py index b105a8b1947c754fea227cd70a922d4100bf81e0..50be7e3e009ef40c6762738a72b9049cf8923496 100644 --- a/compendium_v2/db/auth_model.py +++ b/compendium_v2/db/auth_model.py @@ -75,6 +75,10 @@ class User(UserMixin, db.Model): def is_admin(self): return self.roles == ROLES.admin + @property + def is_observer(self): + return self.roles == ROLES.observer + @property def nren(self): if len(self.nrens) == 0: diff --git a/compendium_v2/routes/response.py b/compendium_v2/routes/response.py index 60b7fa9b6bde972d6006e65dec4b58dc3758bc42..56d99fea6a373fa2a279b942274b9240d879a1a8 100644 --- a/compendium_v2/routes/response.py +++ b/compendium_v2/routes/response.py @@ -77,16 +77,29 @@ class SurveyMode(str, Enum): Edit = "edit" -def check_access_nren(user: User, nren: str) -> bool: +def check_access_nren_read(user: User, nren: str) -> bool: if user.is_anonymous: return False if user.is_admin: return True + if user.is_observer: + return True if nren == user.nren: return True return False +def check_access_nren_write(user: User, nren: str) -> bool: + if not check_access_nren_read(current_user, nren): + return False + if user.is_observer: + # observers can't edit their own nrens either! + return False + # admins can edit all nrens + # users can edit their own nrens + return True + + @routes.route('/try/<int:year>', methods=['GET']) @common.require_accepts_json @admin_required @@ -206,7 +219,7 @@ def load_survey(year, nren_name) -> Any: if not survey: return {'message': 'Survey not found'}, 404 - if not check_access_nren(current_user, nren): + if not check_access_nren_read(current_user, nren): return {'message': 'You do not have permissions to access this survey.'}, 403 response = db.session.scalar( @@ -215,6 +228,8 @@ def load_survey(year, nren_name) -> Any: data, page, verification_status, locked_by = get_response_data(response, year, nren.id) + edit_allowed = current_user.is_admin or ( + survey.status == SurveyStatus.open and check_access_nren_write(current_user, nren)) return { "model": survey.survey, "locked_by": locked_by, @@ -223,7 +238,7 @@ def load_survey(year, nren_name) -> Any: "verification_status": verification_status, "mode": SurveyMode.Display, "status": response.status.value if response else RESPONSE_NOT_STARTED, - "edit_allowed": current_user.is_admin or survey.status == SurveyStatus.open + "edit_allowed": edit_allowed } @@ -249,7 +264,7 @@ def lock_survey(year, nren_name) -> Any: if not survey: return {'message': 'Survey not found'}, 404 - if not check_access_nren(current_user, nren): + if not check_access_nren_write(current_user, nren): return {'message': 'You do not have permissions to access this survey.'}, 403 if survey.status != SurveyStatus.open and not current_user.is_admin: @@ -321,7 +336,7 @@ def save_survey(year, nren_name) -> Any: if survey is None: return {'message': 'Survey not found'}, 404 - if not check_access_nren(current_user, nren): + if not check_access_nren_write(current_user, nren): return {'message': 'You do not have permission to edit this survey.'}, 403 if survey.status != SurveyStatus.open and not current_user.is_admin: @@ -384,7 +399,7 @@ def unlock_survey(year, nren_name) -> Any: if survey is None: return {'message': 'Survey not found'}, 404 - if not check_access_nren(current_user, nren): + if not check_access_nren_write(current_user, nren): return {'message': 'You do not have permission to edit this survey.'}, 403 response = db.session.scalar( diff --git a/compendium_v2/routes/survey.py b/compendium_v2/routes/survey.py index 041e60f9c91ffc992da03a3e5e3bf3ce2ffffa60..0a0717673c5f554002f33719c896762c1b7ff4d8 100644 --- a/compendium_v2/routes/survey.py +++ b/compendium_v2/routes/survey.py @@ -2,6 +2,7 @@ import logging from typing import Any, TypedDict, List, Dict from flask import Blueprint +from flask_login import login_required, current_user from sqlalchemy import delete, select from sqlalchemy.orm import joinedload, load_only @@ -47,7 +48,7 @@ LIST_SURVEYS_RESPONSE_SCHEMA = { @routes.route('/list', methods=['GET']) @common.require_accepts_json -@admin_required +@login_required def list_surveys() -> Any: """ retrieve a list of surveys and responses, including their status @@ -58,6 +59,10 @@ def list_surveys() -> Any: compendium_v2.routes.survey.LIST_SURVEYS_RESPONSE_SCHEMA """ + + if not (current_user.is_admin or current_user.is_observer): + return {'message': 'Insufficient privileges to access this resource'}, 403 + surveys = db.session.scalars( select(Survey).options( load_only(Survey.year, Survey.status), @@ -74,21 +79,26 @@ def list_surveys() -> Any: status: str responses: List[Dict[str, str]] - entries: List[SurveyDict] = [ - { - "year": entry.year, - "status": entry.status.value, - "responses": [ - { - "nren": r.nren.name, - "status": r.status.value, - "lock_description": r.lock_description - } - for r in sorted(entry.responses, key=response_key) - ] + entries: List[SurveyDict] = [] + + def _get_response(response: SurveyResponse) -> Dict[str, str]: + res = { + "nren": response.nren.name, + "status": response.status.value, + "lock_description": response.lock_description } - for entry in surveys - ] + if current_user.is_observer: + res["lock_description"] = response.lock_description + return res + + for entry in surveys: + # only include lock description if the user is an admin + entries.append( + { + "year": entry.year, + "status": entry.status.value, + "responses": [_get_response(r) for r in sorted(entry.responses, key=response_key)] + }) # add in nrens without a response if the survey is open nren_names = set([name for name in db.session.scalars(select(NREN.name))])