Skip to content

Latest commit

 

History

History
177 lines (133 loc) · 8.72 KB

DEV_GUIDE.md

File metadata and controls

177 lines (133 loc) · 8.72 KB

Formplayer Dev Resources

This document is aimed at developers starting out with Formplayer and particularly those who may not be familiar with Java or Spring. It lays out a set of resources that can be used as to learn about the technologies used.

Formplayer is a Spring Boot which takes an opinionated view of the Spring platform and third-party libraries, so that you can get started with minimum fuss. Most Spring Boot applications need very little Spring configuration.

Spring is a collection of tools and libraries that have been built to make developing Java applications easier and safer. At it's core is the Spring Framework which provides the technologies that support the rest of the tools: dependency injection, events, resources, i18n, validation, data binding, type conversion, SpEL (Spring Expressive Language), AOP (Aspect Oriented Programming)

Developing Formplayer will require some level of knowledge of at least the following core components:

Spring Boot ties a number of the components of Spring together in way to meets the needs of most projects. It does this by using conventions, but it does not stop you from overriding the conventions.

In Formpayer the main features we make use of are:

Security

All Formplayer endpoints other than "/serverup" are secured using Spring Security.

Spring security is part of the Spring framework that provides filters and classes for securing an application. The primary mechanism is a set of request filters that apply the security policy to requests.

The security policy is defined in the WebSecurityConfig class.

Formplayer uses two custom auth filters which are applied to each request. Each filter is responsible for separate authentication mechanisms and only one filter will be applied to each request.

Django session auth

The primary mode is to use the session token provided by Django. This is passed to Formplayer via the Django session cookie which is accessible to Formplayer since it is running under the same domain.

Formplayer reads the session ID out of the request along with the username and domain. It then makes a request to CommCare HQ for the user details which it validates against the current request.

CommCareSessionAuthFilter

  • generates a PreAuthenticatedAuthenticationToken containing:
    • credentials: session cookie value
    • principal: UserDomainPreAuthPrincipal containing username and domain of the request
  • Calls HQUserDetailsService.loadUserDetails(token) (via the AuthenticationManager)
    • this makes the call the HQ and returns an HqUserDetailsBean or raises an exception
  • If everything is successful the HqUserDetailsBean is placed into the security context.

HMAC auth

This mode of authenticating requests is used when requests are performed outside the scope of a user session. For example, when CommCare HQ makes requests to Formplayer as part of an SMS interaction.

The authentication for this mode is handled by the HmacAuthFilter which validates the HMAC in the X-MAC-DIGEST request header. If the HMAC is valid the filter constructs a HqUserDetailsBean from the request body or by fetching a session record from the DB. This user details bean is then placed in the security context. If it is not possible to construct the user details then an anonymous token is placed in the security context with the role "COMMCARE" to indicate that the HMAC auth passed but with no user details.

Testing

Formplayer has a lot of legacy tests which use mock controllers (anything inheriting from BaseTestClass) but we have begun the process to migrate test to a more standard Spring Boot architecture. The rest of this section will discuss the new approach.

WebMvcTest

Most tests are interacting with the REST controllers. In order to test these without the need to set up the full server and database we use the @WebMvcTest annotation. With this annotation we can autowire a MockMvc object into the test which can be used to make mock requests to the controller.

@WebMvcTest does not configure services or the data access layer so those need to be configured manually or mocked. In most cases we mock those except for tests that are testing those classes directly.

To make sure a specific controller is available for testing use the @Import annotation:

@WebMvcTest
@Import(UtilController.class)
class UtilControllerTests {
    @Autowired
    private MockMvc mockMvc;

    // ...
}

To provide the dependent services that the controllers require we use a set of configuration classes which create the bean required by the controllers:

@WebMvcTest
@Import(UtilController.class)
@ContextConfiguration(classes={TestContext.class, CacheConfiguration.class})
class UtilControllerTests {
    // ...
}

Some beans require further configuration. This is done using Junit5 extensions which hook into the test lifecycle and configure the service mocks:

These can be applied to tests using the @ExtendWith annotation at the test class level:

@ExtendWith(FormDefSessionServiceExtension.class)
class MyTests {
    // ...
}

Some extensions require configuration:

class MyTests {
    @RegisterExtension
    static RestoreFactoryExtension restoreFactoryExt = new RestoreFactoryExtension.builder()
            .withUser("user").withDomain("domain")
            .withRestorePath("custom/restore/file.xml")
            .build();
}

We also have some convenience annotations which apply a number of the extensions together:

Mock Requests

Making requests to the controllers can be done directly using the MockMvc class. Alternately we have created a set of request classes for common requests:

These can be used as follows:

NewFormRequest request = new NewFormRequest(mockMvc, webClientMock, "form.xml");
Response response = request.request();
response.andExpect(jsonPath("status").value("success"));
NewFormResponse responseBean = response.bean();

Additional utilities:

Glossary

  • JPA: Jakarta Persistence
  • Hibernate: ORM framework that implements the JPA spec
  • IoC: Inversion of Control programming principal