Skip to content

Commit

Permalink
Create configuration for locking the Submission for a flow after it i…
Browse files Browse the repository at this point in the history
…s submitted (#437)

Adds the ability to lock a flow down such that it redirects the user to a configured page when they attempt to access a page or POST to a page within a flow after their application has been submitted.

* Consolidate DisabedFlowPropertyConfiguration to FormFlowConfigurationProperties
* Create test for locked submission in a given flow
* Add tests for flows configuration factory flow validations
* Refactor FlowsConfigurationFactory
* Validate first screen when session continuity interceptor is enabled
* Add documentation of submission locking to the README

---------

Co-authored-by: Cypress Borg <[email protected]>
  • Loading branch information
spokenbird and Cypress Borg authored Nov 30, 2023
1 parent a7099fc commit 722d670
Show file tree
Hide file tree
Showing 28 changed files with 1,010 additions and 240 deletions.
49 changes: 49 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2644,6 +2644,55 @@ form-flow:
staticRedirectPage: '/docUploadDisabled'
```

### Locking the Submission for a flow once it has been submitted

The form flow library provides a configuration mechanism to lock a Submission object once it has been submitted.
This will prevent users from being able to edit a Submission object once it has been submitted. If
configured, the user will be redirected to a configured screen if they attempt to GET or POST a page
within a locked flow.

Note that this does not actually lock the Submission object, but rather prevents the user from accessing
any endpoints that would update the Submission object.

When enabled, the form flow library will check if a Submission object has been submitted by looking
for a non-null `submittedAt` value. If the `submittedAt` value is not null, and a user attempts
to access a screen within the flow that is not part of a list of allowed pages, the library will redirect
the user to the configured screen. See below for more on the list of allowed pages.

To enable submission locking for a given flow, add the following to your `application.yaml` file:

```yaml
form-flow:
lock-after-submitted:
- flow: ubi
redirect: success
- flow: docUpload
redirect: docUploadSuccess
```

If you enable submission locking for a flow, you **must** provide a list of allowed pages that can be
accessed after given flows Submission object has been submitted. This list of allowed pages is configured
within your `flows-config.yaml` files `landmarks` section. See below for an example of how to configure
the list of allowed pages for a given flow.

```yaml
landmarks:
afterSubmitPages:
- nextSteps
- success
```

Note that most of the time you will only want to include static pages in the list of allowed pages.
This is because any data that is submitted after the submission object has been submitted will not
be included in the PDF that is generated or in any other processes that are triggered upon submitting.

An example of a time you might include a non-static page in the list of allowed pages is if you have
a page that is used to collect feedback from the user after they have submitted their application. In
this scenario you can include the feedback page in the list of allowed pages so that the user can
can submit their feedback after they have submitted their application. The data would be saved
in the Submission object, but would not be included in the PDF or any other processes that are triggered
upon submitting.

#### Design System

We are moving towards using a [custom theme](https://codeforamerica.github.io/uswds/dist/) of
Expand Down
15 changes: 12 additions & 3 deletions src/main/java/formflow/library/FileController.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import com.lowagie.text.exceptions.BadPasswordException;
import com.lowagie.text.pdf.PdfReader;
import formflow.library.config.FlowConfiguration;
import formflow.library.config.FormFlowConfigurationProperties;
import formflow.library.data.Submission;
import formflow.library.data.SubmissionRepositoryService;
import formflow.library.data.UserFile;
Expand Down Expand Up @@ -55,7 +56,6 @@ public class FileController extends FormFlowController {
private final CloudFileRepository cloudFileRepository;
private final Boolean blockIfClammitUnreachable;
private final FileVirusScanner fileVirusScanner;
private final MessageSource messageSource;
private final FileValidationService fileValidationService;
private final String SESSION_USERFILES_KEY = "userFiles";
private final Integer maxFiles;
Expand All @@ -74,13 +74,14 @@ public FileController(
FileVirusScanner fileVirusScanner,
SubmissionRepositoryService submissionRepositoryService,
List<FlowConfiguration> flowConfigurations,
FormFlowConfigurationProperties formFlowConfigurationProperties,
MessageSource messageSource,
FileValidationService fileValidationService,
@Value("${form-flow.uploads.max-files:20}") Integer maxFiles,
@Value("${form-flow.uploads.virus-scanning.block-if-unreachable:false}") boolean blockIfClammitUnreachable) {
super(submissionRepositoryService, userFileRepositoryService, flowConfigurations);
super(submissionRepositoryService, userFileRepositoryService, flowConfigurations, formFlowConfigurationProperties,
messageSource);
this.cloudFileRepository = cloudFileRepository;
this.messageSource = messageSource;
this.fileValidationService = fileValidationService;
this.maxFiles = maxFiles;
this.fileVirusScanner = fileVirusScanner;
Expand All @@ -105,6 +106,7 @@ public ResponseEntity<?> upload(
@RequestParam("flow") String flow,
@RequestParam("inputName") String inputName,
@RequestParam("thumbDataURL") String thumbDataUrl,
@RequestParam("screen") String screen,
HttpSession httpSession,
HttpServletRequest request,
Locale locale
Expand All @@ -117,6 +119,13 @@ public ResponseEntity<?> upload(
}

Submission submission = findOrCreateSubmission(httpSession, flow);

if (shouldRedirectDueToLockedSubmission(screen, submission,flow)) {
log.info("The Submission for flow {} is locked. Cannot upload file.", flow);
String message = messageSource.getMessage("upload-documents.locked-submission", null, locale);
return new ResponseEntity<>(message, HttpStatus.BAD_REQUEST);
}

UUID userFileId = UUID.randomUUID();
if (submission.getId() == null) {
submission.setFlow(flow);
Expand Down
31 changes: 30 additions & 1 deletion src/main/java/formflow/library/FormFlowController.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package formflow.library;

import formflow.library.config.FlowConfiguration;
import formflow.library.config.FormFlowConfigurationProperties;
import formflow.library.data.Submission;
import formflow.library.data.SubmissionRepositoryService;
import formflow.library.data.UserFileRepositoryService;
Expand All @@ -11,6 +12,7 @@
import java.util.Map;
import java.util.HashMap;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.MessageSource;
import org.springframework.http.HttpStatus;
import org.springframework.web.server.ResponseStatusException;

Expand All @@ -22,14 +24,21 @@ public abstract class FormFlowController {
protected final UserFileRepositoryService userFileRepositoryService;

protected final List<FlowConfiguration> flowConfigurations;

protected final FormFlowConfigurationProperties formFlowConfigurationProperties;

protected final MessageSource messageSource;

public static final String SUBMISSION_MAP_NAME = "submissionMap";

FormFlowController(SubmissionRepositoryService submissionRepositoryService, UserFileRepositoryService userFileRepositoryService,
List<FlowConfiguration> flowConfigurations) {
List<FlowConfiguration> flowConfigurations, FormFlowConfigurationProperties formFlowConfigurationProperties,
MessageSource messageSource) {
this.submissionRepositoryService = submissionRepositoryService;
this.userFileRepositoryService = userFileRepositoryService;
this.flowConfigurations = flowConfigurations;
this.formFlowConfigurationProperties = formFlowConfigurationProperties;
this.messageSource = messageSource;
}

protected Submission saveToRepository(Submission submission) {
Expand Down Expand Up @@ -167,4 +176,24 @@ protected void setSubmissionInSession(HttpSession session, Submission submission
submissionMap.put(flow, id);
session.setAttribute(SUBMISSION_MAP_NAME, submissionMap);
}

/**
* Determines if the user should be redirected due to a locked submission.
*
* @param screen The current screen name.
* @param submission The submission object.
* @param flowName
* @return true if the user should be redirected, false otherwise.
*/
public boolean shouldRedirectDueToLockedSubmission(String screen, Submission submission, String flowName) {
FlowConfiguration flowConfig = getFlowConfigurationByName(flowName);
boolean submissionIsLocked = this.formFlowConfigurationProperties.isSubmissionLockedForFlow(flowName);

if (submissionIsLocked) {
boolean isSubmissionSubmitted = submission.getSubmittedAt() != null;
boolean isCurrentScreenAfterSubmit = flowConfig.getLandmarks().getAfterSubmitPages().contains(screen);
return isSubmissionSubmitted && !isCurrentScreenAfterSubmit;
}
return false;
}
}
9 changes: 5 additions & 4 deletions src/main/java/formflow/library/PdfController.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package formflow.library;

import formflow.library.config.FlowConfiguration;
import formflow.library.config.FormFlowConfigurationProperties;
import formflow.library.data.Submission;
import formflow.library.data.SubmissionRepositoryService;
import formflow.library.data.UserFileRepositoryService;
Expand Down Expand Up @@ -31,15 +32,15 @@
@RequestMapping("/download")
public class PdfController extends FormFlowController {

private final MessageSource messageSource;
private final PdfService pdfService;

public PdfController(MessageSource messageSource, PdfService pdfService,
SubmissionRepositoryService submissionRepositoryService,
UserFileRepositoryService userFileRepositoryService,
List<FlowConfiguration> flowConfigurations) {
super(submissionRepositoryService, userFileRepositoryService, flowConfigurations);
this.messageSource = messageSource;
List<FlowConfiguration> flowConfigurations,
FormFlowConfigurationProperties formFlowConfigurationProperties) {
super(submissionRepositoryService, userFileRepositoryService, flowConfigurations, formFlowConfigurationProperties,
messageSource);
this.pdfService = pdfService;
}

Expand Down
Loading

0 comments on commit 722d670

Please sign in to comment.