From 01f9f34cf140c0b59ff2bfbeb8037db0241ff614 Mon Sep 17 00:00:00 2001 From: Philip Dominguez <142051477+phildominguez-gsa@users.noreply.github.com> Date: Mon, 13 Jan 2025 09:32:24 -0500 Subject: [PATCH 1/2] Adding verify_status decorator to unlock endpoints --- backend/audit/verify_status.py | 44 +++++++++++++++++++ .../audit/views/unlock_after_certification.py | 5 +++ backend/audit/views/views.py | 34 +------------- 3 files changed, 50 insertions(+), 33 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..e702226468 100644 --- a/backend/audit/views/views.py +++ b/backend/audit/views/views.py @@ -15,7 +15,6 @@ from audit.fixtures.excel import FORM_SECTIONS, UNKNOWN_WORKBOOK - from audit.forms import ( AuditorCertificationStep1Form, AuditorCertificationStep2Form, @@ -42,6 +41,7 @@ validate_auditee_certification_json, validate_auditor_certification_json, ) +from audit.verify_status import verify_status from audit.utils import FORM_SECTION_HANDLERS from dissemination.remove_workbook_artifacts import remove_workbook_artifacts @@ -61,38 +61,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" From 5fc8e72566c02e371192cf2968613205f1ebbfc8 Mon Sep 17 00:00:00 2001 From: Philip Dominguez <142051477+phildominguez-gsa@users.noreply.github.com> Date: Mon, 13 Jan 2025 09:51:24 -0500 Subject: [PATCH 2/2] Import cleanup --- backend/audit/views/views.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/backend/audit/views/views.py b/backend/audit/views/views.py index e702226468..d28e18da38 100644 --- a/backend/audit/views/views.py +++ b/backend/audit/views/views.py @@ -14,7 +14,6 @@ from django.http import JsonResponse from audit.fixtures.excel import FORM_SECTIONS, UNKNOWN_WORKBOOK - from audit.forms import ( AuditorCertificationStep1Form, AuditorCertificationStep2Form, @@ -37,12 +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.verify_status import verify_status -from audit.utils import FORM_SECTION_HANDLERS from dissemination.remove_workbook_artifacts import remove_workbook_artifacts from dissemination.file_downloads import get_download_url, get_filename