Skip to content

Commit

Permalink
Updated Readme
Browse files Browse the repository at this point in the history
#185865123

Co-authored-by: Ben Calegari <bcalegari@codeforamerica.org>
Co-authored-by: Alex Gonzalez <agonzalez@codeforamerica.org>
  • Loading branch information
3 people authored and bseeger committed Sep 25, 2023
1 parent a87fc60 commit 29ed4cf
Show file tree
Hide file tree
Showing 3 changed files with 122 additions and 16 deletions.
84 changes: 84 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ Table of Contents
* [Sending Email](#sending-email)
* [Mailgun](#mailgun)
* [Localization](#localization)
* [Interceptors](#interceptors)
* [DataRequiredInterceptor](#datarequiredinterceptor)
* [Configuration](#configuration)
* [Setup Application.yaml for DataRequiredIncerceptors](#setup-applicationyaml-for-datarequired-interceptor)
* [Setup Flows-Config](#setup-flows-config)
* [How to use](#how-to-use)
* [Configuration Details](#configuration-details)
* [Environment Variables](#environment-variables)
Expand Down Expand Up @@ -2182,6 +2187,85 @@ in [our JavaScript](https://github.com/codeforamerica/form-flow/blob/9a1c6f7a253

We send the whole url to Mixpanel, including any query parameters, such as `lang`.

## Interceptors

[Interceptors](https://www.baeldung.com/spring-mvc-handlerinterceptor#Interceptor) are used in
spring boot to intercept requests between the DispatcherServlet and our
Controllers. Form-flow library uses the `DataRequiredInterceptor` to prevent users from entering a
flow without a session.

### DataRequiredInterceptor

The `DataRequiredInterceptor` prevents clients from entering the middle of a flow without starting a
submission. If no session has been established or a `session` lacks the appropriate `submissionId`,
a client will be returned to the index page of the application.

`DataRequiredInterceptor` intercepts a users request to enter a flow. When
the `session-continuity-interceptor.enabled` flag is set to true, the `DataRequiredInterceptor` is
on. The `DataRequiredInterceptor` is the last interceptor run by any application using FFL.

#### Configuration

Configuring `DataRequiredInterceptor` requires two steps:

1. Set the application.yaml to enable or disable the interceptor (Note: the `DataRequiredINterceptor
is on by default)
2. Set the flow-config.yaml with the `landmarks.firstScreen` yaml field.

##### Setup application.yaml for DataRequired Interceptor

The `DataRequiredInterceptor` can be turned on or off by adding true or false to
the `session-continuity-interceptor.enabled` fields of the `application.yaml` file.

```yaml
form-flow:
session-continuity-interceptor:
enabled: true
```

Note: `DataRequiredInterceptor` is on by default. If you do not include
the `session-continuity-interceptor.enabled` field in your `application.yaml` file the application
will run the `DataRequiredInterceptor`.

<table>
<tr></tr>
<tr>
<td></td>
<td><b>Not Present</b></td>
<td><code>session-continuity-interceptor.enabled=true</code></td>
<td><code>session-continuity-interceptor.enabled=false</code></td>
</tr>
<tr>
<td>Interceptor State</td>
<td>on</td>
<td>on</td>
<td>off</td>
</tr>
<tr>
<td>Description</td>
<td>See <code>session-continuity-interceptor.enabled=true</code>.</td>
<td>Turns on the <code>DataRequiredInterceptor</code>. The <code>DataRequiredInterceptor</code> has the lowest priority. It will always be the last interceptor executed.</td>
<td>Turns off the <code>DataRequiredInterceptor</code>. This means that the flows-config will not be checked for landmark pages and users will be able to access any page in a flow.</td>
</tr>
</table>

##### Setup flows-config

`DataRequiredInterceptor` requires that `landmarks.firstScreen` is set to the first screen in the
flow. The `firstScreen` is the first screen to start a flow. An omitted or malformed landmark field
will throw an error. Please review an example of `flows-config.yaml` setup for landing pages below.

```yaml
name: ubi
flow:
howThisWorks:
nextScreens:
- name: languagePreference
#...
landmarks:
firstScreen: howThisWorks
```

# How to use

## Configuration Details
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,19 @@
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/***
* Adds Data Required interceptors to the Interceptor registry.
*/
@Configuration
@ConditionalOnProperty(name = "form-flow.session-continuity-interceptor.enabled", havingValue = "true")
public class DataHandlerConfiguration implements WebMvcConfigurer {

@Autowired
List<FlowConfiguration> flowConfigurations;

@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new DataRequiredInterceptor(flowConfigurations)).addPathPatterns(List.of(DataRequiredInterceptor.FLOW_PATH_FORMAT, DataRequiredInterceptor.NAVIGATION_FLOW_PATH_FORMAT));
registry.addInterceptor(new DataRequiredInterceptor(flowConfigurations))
.addPathPatterns(List.of(DataRequiredInterceptor.FLOW_PATH_FORMAT, DataRequiredInterceptor.NAVIGATION_FLOW_PATH_FORMAT));
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
package formflow.library.interceptors;

import formflow.library.config.FlowConfiguration;
import formflow.library.config.ScreenNavigationConfiguration;
import formflow.library.exceptions.LandmarkNotSetException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import lombok.extern.slf4j.Slf4j;
Expand All @@ -17,6 +15,9 @@
import org.springframework.util.AntPathMatcher;
import org.springframework.web.servlet.HandlerInterceptor;

/**
* This interceptor prevents users from jumping to random pages in a flow.
*/
@Component
@Slf4j
@ConditionalOnProperty(name = "form-flow.session-continuity-interceptor.enabled", havingValue = "true")
Expand All @@ -31,6 +32,14 @@ public DataRequiredInterceptor(List<FlowConfiguration> flowConfigurations) {
this.flowConfigurations = flowConfigurations;
}

/**
* @param request current HTTP request
* @param response current HTTP response
* @param handler chosen handler to execute, for type and/or instance evaluation
* @return Boolean True - allows the request to proceed to the ScreenController, False - stops the request from reaching the
* Screen Controller.
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request, @NotNull HttpServletResponse response, @NotNull Object handler)
throws Exception {
Expand All @@ -45,52 +54,61 @@ public boolean preHandle(HttpServletRequest request, @NotNull HttpServletRespons
.orElse(null);

if (flowConfiguration == null) {
// The flow doesn't exist, but we will pass the request through to the controller to handle it and return the appropriate error response.
return true;
}

boolean landmarkNotImplemented = flowConfiguration.getLandmarks() == null;

if (landmarkNotImplemented) {
throw new LandmarkNotSetException("You have enabled session continuity interception but have not created a landmark section in your applications flow configuration file.");
throw new LandmarkNotSetException(
"You have enabled session continuity interception but have not created a landmark section in your applications flow configuration file.");
}

boolean firstScreenNotSet = flowConfiguration.getLandmarks().getFirstScreen() == null;

if (firstScreenNotSet) {
throw new LandmarkNotSetException("Please make sure to set a firstScreen under your flow configuration files landmark section.");
throw new LandmarkNotSetException(
"Please make sure to set a firstScreen under your flow configuration files landmark section.");
}

String firstScreen = flowConfiguration.getLandmarks().getFirstScreen();
boolean firstScreenExists = flowConfiguration.getFlow().containsKey(firstScreen);

if (!firstScreenExists) {
throw new LandmarkNotSetException(String.format("Please make sure that you have correctly set the firstScreen under your flow configuration files landmark section. Your flow configuration file does not contain a screen with the name %s.", firstScreen));
throw new LandmarkNotSetException(String.format(
"Please make sure that you have correctly set the firstScreen under your flow configuration files landmark section. Your flow configuration file does not contain a screen with the name %s.",
firstScreen));
}

boolean onFirstScreen = screen.equals(firstScreen);
if (session == null && onFirstScreen) {
return true;
}
}

if (session == null) {
log.error("No active session found for request to {}. Redirecting to landing page.", request.getRequestURI());
response.sendRedirect(redirect_url);
return false;
}

if (session.getAttribute("id") == null) {
log.error("A submission ID was not found in the session for request to {}. Redirecting to landing page.", request.getRequestURI());
log.error("A submission ID was not found in the session for request to {}. Redirecting to landing page.",
request.getRequestURI());
response.sendRedirect(redirect_url);
return false;
}

return true;
} catch (IllegalStateException e) {
return true;
}
}


/**
* Sets the value of the interceptor to the highest integer value making it the last interceptor executed.
*
* @return Max Integer value.
*/
@Override
public int getOrder() {
// Max value ensures that this interceptor is executed last.
Expand Down

0 comments on commit 29ed4cf

Please sign in to comment.