From b722bbce2655e2f12f1fa89dbd36e57c6d8f8a31 Mon Sep 17 00:00:00 2001 From: Phil Dominguez <142051477+phildominguez-gsa@users.noreply.github.com> Date: Mon, 13 Jan 2025 12:31:44 -0500 Subject: [PATCH] Adding @verify_status decorator to unlock endpoints (#4612) * Adding verify_status decorator to unlock endpoints * Import cleanup --- backend/audit/verify_status.py | 44 +++++++++++++++++++ .../audit/views/unlock_after_certification.py | 5 +++ backend/audit/views/views.py | 37 +--------------- 3 files changed, 51 insertions(+), 35 deletions(-) create mode 100644 backend/audit/verify_status.py diff --git a/backend/audit/verify_status.py b/backend/audit/verify_status.py new file mode 100644 index 0000000000..56fc3af95c --- /dev/null +++ b/backend/audit/verify_status.py @@ -0,0 +1,44 @@ +import logging + +from django.shortcuts import redirect +from django.core.exceptions import PermissionDenied + +from audit.models import SingleAuditChecklist + + +logging.basicConfig( + format="%(asctime)s %(levelname)-8s %(module)s:%(lineno)d %(message)s" +) +logger = logging.getLogger(__name__) + + +def verify_status(status): + """ + Decorator to be applied to view request methods (i.e. get, post) to verify + that the submission is in the correct state before allowing the user to + proceed. An incorrect status usually happens via direct URL access. If the + given status does not match the submission's, it will redirect them back to + the submission progress page. + """ + + def decorator_verify_status(request_method): + def verify(view, request, *args, **kwargs): + report_id = kwargs["report_id"] + + try: + sac = SingleAuditChecklist.objects.get(report_id=report_id) + except SingleAuditChecklist.DoesNotExist: + raise PermissionDenied("You do not have access to this audit.") + + # Return to checklist, the Audit is not in the correct state. + if sac.submission_status != status: + logger.warning( + f"Expected submission status {status} but it's currently {sac.submission_status}" + ) + return redirect(f"/audit/submission-progress/{sac.report_id}") + else: + return request_method(view, request, *args, **kwargs) + + return verify + + return decorator_verify_status diff --git a/backend/audit/views/unlock_after_certification.py b/backend/audit/views/unlock_after_certification.py index 004849985d..74b6a578ff 100644 --- a/backend/audit/views/unlock_after_certification.py +++ b/backend/audit/views/unlock_after_certification.py @@ -4,6 +4,7 @@ from django.shortcuts import render, redirect from django.views import generic from django.urls import reverse + from audit.forms import UnlockAfterCertificationForm from audit.mixins import ( SingleAuditChecklistAccessRequiredMixin, @@ -13,6 +14,8 @@ ) from audit.models.models import STATUS from audit.models.viewflow import sac_transition +from audit.verify_status import verify_status + logger = logging.getLogger(__name__) @@ -25,6 +28,7 @@ class UnlockAfterCertificationView( READY_FOR_CERTIFICATION. """ + @verify_status(STATUS.READY_FOR_CERTIFICATION) def get(self, request, *args, **kwargs): report_id = kwargs["report_id"] @@ -51,6 +55,7 @@ def get(self, request, *args, **kwargs): except SingleAuditChecklist.DoesNotExist: raise PermissionDenied("You do not have access to this audit.") + @verify_status(STATUS.READY_FOR_CERTIFICATION) def post(self, request, *args, **kwargs): report_id = kwargs["report_id"] diff --git a/backend/audit/views/views.py b/backend/audit/views/views.py index c5a87acf72..d28e18da38 100644 --- a/backend/audit/views/views.py +++ b/backend/audit/views/views.py @@ -14,8 +14,6 @@ from django.http import JsonResponse from audit.fixtures.excel import FORM_SECTIONS, UNKNOWN_WORKBOOK - - from audit.forms import ( AuditorCertificationStep1Form, AuditorCertificationStep2Form, @@ -38,11 +36,12 @@ from audit.models.models import STATUS from audit.models.viewflow import sac_transition from audit.intakelib.exceptions import ExcelExtractionError +from audit.utils import FORM_SECTION_HANDLERS from audit.validators import ( validate_auditee_certification_json, validate_auditor_certification_json, ) -from audit.utils import FORM_SECTION_HANDLERS +from audit.verify_status import verify_status from dissemination.remove_workbook_artifacts import remove_workbook_artifacts from dissemination.file_downloads import get_download_url, get_filename @@ -61,38 +60,6 @@ def _friendly_status(status): return dict(SingleAuditChecklist.STATUS_CHOICES)[status] -def verify_status(status): - """ - Decorator to be applied to view request methods (i.e. get, post) to verify - that the submission is in the correct state before allowing the user to - proceed. An incorrect status usually happens via direct URL access. If the - given status does not match the submission's, it will redirect them back to - the submission progress page. - """ - - def decorator_verify_status(request_method): - def verify(view, request, *args, **kwargs): - report_id = kwargs["report_id"] - - try: - sac = SingleAuditChecklist.objects.get(report_id=report_id) - except SingleAuditChecklist.DoesNotExist: - raise PermissionDenied("You do not have access to this audit.") - - # Return to checklist, the Audit is not in the correct state. - if sac.submission_status != status: - logger.warning( - f"Expected submission status {status} but it's currently {sac.submission_status}" - ) - return redirect(f"/audit/submission-progress/{sac.report_id}") - else: - return request_method(view, request, *args, **kwargs) - - return verify - - return decorator_verify_status - - class MySubmissions(LoginRequiredMixin, generic.View): redirect_field_name = "Home"