Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create configuration for locking the Submission for a flow after it is submitted #437

Merged
merged 21 commits into from
Nov 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
8990b1c
Avoid configuration property namespacing issues
spokenbird Nov 6, 2023
1a88e3a
Consolidate DisabedFlowPropertyConfiguration to FormFlowConfiguration…
spokenbird Nov 8, 2023
8d15387
Create test for locked submission in a given flow
spokenbird Nov 8, 2023
ca1ce8f
WIP - Beginning implementation
spokenbird Nov 8, 2023
19d38f4
WIP - Implement for POST and add default message on redirect
spokenbird Nov 14, 2023
a032a5d
WIP Adding more tests and implementations to make them pass
spokenbird Nov 15, 2023
76b31a9
WIP More tests and Build Time error handling!
spokenbird Nov 16, 2023
eae5e3d
Add edge case tests
spokenbird Nov 17, 2023
47e41d2
Refactory FlowsConfigurationFactory
spokenbird Nov 17, 2023
ac51af3
Validate first screen when session continuity interceptor is enabled
spokenbird Nov 18, 2023
63abc56
Add tests for flows configuration vactory flow validations
spokenbird Nov 18, 2023
d3e9e42
Fix method access modifiers
spokenbird Nov 18, 2023
f1cc8c0
Add documentation of submission locking to the README
spokenbird Nov 18, 2023
29549df
Fix failing tests
spokenbird Nov 18, 2023
11b5e6d
Merge branch 'main' into configure-submission-locking
spokenbird Nov 18, 2023
fdfd1a2
Merge branch 'main' into configure-submission-locking
spokenbird Nov 20, 2023
2fd4375
Merge branch 'main' into configure-submission-locking
spokenbird Nov 27, 2023
c73b38c
Cleanup from code walk through
spokenbird Nov 28, 2023
436bae0
Revert shouldRedirectDueToLockedSubmission signature change to fix fa…
spokenbird Nov 28, 2023
be63eb7
Merge branch 'main' into configure-submission-locking
spokenbird Nov 29, 2023
70cc1d8
Merge branch 'main' into configure-submission-locking
spokenbird Nov 29, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

user can can submit should read user can submit

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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this say: @param flowName The name of the flow? It feels self explanatory but I think we should still explain what it is.

* @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
Loading