Commit c5f3802a authored by Jean-Baptiste Nizet's avatar Jean-Baptiste Nizet
Browse files

feat: add security on harvest and actuator endpoints

Also:
- configure actuator for more information
- use Import instead of SpringBootTest to add config classes to tests
parent 4ca92206
......@@ -83,14 +83,15 @@ environment variable for example).
The files must have the extension `.json`, and must be stored in that directory (not in a sub-directory).
Once the files are ready and the server is started, the harvest is triggered by sending a POST request
to the endpoint `/api/harvests`, without any request body.
This endpoint, as well as the actuator endpoints, is only accessible to an authenticated user. The user (`rare`) and its password (`f01a7031fc17`) are configured in the application.yml file (and can thus be overridden using environment variables for example).
Example with the `http` command ([HTTPie](https://httpie.org/)):
http POST http://localhost:8080/api/harvests
http --auth rare:f01a7031fc17 POST http://localhost:8080/api/harvests
Example with the `curl` command:
curl -i -X POST http://localhost:8080/api/harvests
curl -i -X POST -u rare:f01a7031fc17 http://localhost:8080/api/harvests
The harvest job is executed asynchronously, and a response is immediately sent back, with the URL allowing
to get the result of the job. For example:
......@@ -102,11 +103,11 @@ to get the result of the job. For example:
To get the result of the job, you can then send a GET request to the returned URL:
http GET http://localhost:8080/api/harvests/abb5784d-3006-48fb-b5db-d3ff9583e8b9
http --auth rare:f01a7031fc17 GET http://localhost:8080/api/harvests/abb5784d-3006-48fb-b5db-d3ff9583e8b9
or
curl http://localhost:8080/api/harvests/abb5784d-3006-48fb-b5db-d3ff9583e8b9
curl -u rare:f01a7031fc17 http://localhost:8080/api/harvests/abb5784d-3006-48fb-b5db-d3ff9583e8b9
`http` has the advantage of nicely formetting the returned JSON.
......
......@@ -72,6 +72,8 @@ tasks {
dependencies {
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.springframework.boot:spring-boot-starter-actuator")
implementation("org.springframework.boot:spring-boot-starter-security")
implementation("org.springframework.boot:spring-boot-starter-webflux")
implementation("org.springframework.data:spring-data-elasticsearch:3.1.0.M3")
implementation("org.elasticsearch:elasticsearch:6.3.1")
implementation("org.elasticsearch.client:transport:6.3.1")
......
package fr.inra.urgi.rare.config;
import org.springframework.boot.actuate.autoconfigure.security.servlet.EndpointRequest;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
/**
* Security configuration. It makes sure that harvest endpoints and the actuator endpoints are only accessible to
* authenticated users
* @author JB Nizet
*/
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/api/harvests", "/api/harvests/**").authenticated()
.requestMatchers(EndpointRequest.toAnyEndpoint()).authenticated() // EndpointRequest.toAnyEndpoint()
// only targets the **actuator**
// endpoints.
.antMatchers("/**").permitAll()
.and()
.httpBasic()
.and()
.csrf().disable()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
}
......@@ -3,6 +3,11 @@ rare:
elasticsearch-prefix: ''
spring:
security:
user:
name: rare
password: f01a7031fc17
data:
elasticsearch:
cluster:
......@@ -10,6 +15,15 @@ spring:
host: 127.0.0.1
port: 9300
management:
endpoint:
health:
show-details: 'always'
endpoints:
web:
exposure:
include: '*'
server:
compression:
enabled: true
......
......@@ -10,13 +10,13 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.json.JsonTest;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Import;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.junit.jupiter.SpringExtension;
@ExtendWith(SpringExtension.class)
@TestPropertySource("/test.properties")
@SpringBootTest(classes = ElasticSearchConfig.class)
@Import(ElasticSearchConfig.class)
@JsonTest
class GeneticResourceDaoTest {
......
......@@ -9,7 +9,7 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.json.JsonTest;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Import;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.junit.jupiter.SpringExtension;
......@@ -19,7 +19,7 @@ import org.springframework.test.context.junit.jupiter.SpringExtension;
*/
@ExtendWith(SpringExtension.class)
@TestPropertySource("/test.properties")
@SpringBootTest(classes = ElasticSearchConfig.class)
@Import(ElasticSearchConfig.class)
@JsonTest
class HarvestResultDaoTest {
@Autowired
......
......@@ -13,7 +13,7 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.json.JsonTest;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Import;
import org.springframework.test.context.junit.jupiter.SpringExtension;
/**
......@@ -21,7 +21,7 @@ import org.springframework.test.context.junit.jupiter.SpringExtension;
* @author JB Nizet
*/
@JsonTest
@SpringBootTest(classes = HarvestConfig.class)
@Import(HarvestConfig.class)
@ExtendWith(SpringExtension.class)
class GeneticResourceTest {
......
package fr.inra.urgi.rare.harvest;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import fr.inra.urgi.rare.config.SecurityConfig;
import fr.inra.urgi.rare.dao.HarvestResultDao;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Import;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.web.servlet.MockMvc;
/**
* Tests that security is active for {@link HarvesterController}
* @author JB Nizet
*/
@ExtendWith(SpringExtension.class)
@Import(SecurityConfig.class)
@WebMvcTest(controllers = HarvesterController.class)
class HarvesterControllerSecurityTest {
@MockBean
private AsyncHarvester mockAsyncHarvester;
@MockBean
private HarvestResultDao mockHarvestResultDao;
@Autowired
private MockMvc mockMvc;
@Test
void shouldBeUnauthorizedWhenGetting() throws Exception {
HarvestResult harvestResult = HarvestResult.builder().build();
mockMvc.perform(get("/api/harvests/{id}", harvestResult.getId()))
.andExpect(status().isUnauthorized());
}
@Test
void shouldBeUnauthorizedWhenCreating() throws Exception {
mockMvc.perform(post("/api/harvests"))
.andExpect(status().isUnauthorized());
}
}
......@@ -24,7 +24,7 @@ import org.springframework.test.web.servlet.MockMvc;
* @author JB Nizet
*/
@ExtendWith(SpringExtension.class)
@WebMvcTest(controllers = HarvesterController.class)
@WebMvcTest(controllers = HarvesterController.class, secure = false)
class HarvesterControllerTest {
@MockBean
private AsyncHarvester mockAsyncHarvester;
......
......@@ -35,7 +35,7 @@ import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.json.JsonTest;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Import;
import org.springframework.test.context.junit.jupiter.SpringExtension;
/**
......@@ -45,7 +45,7 @@ import org.springframework.test.context.junit.jupiter.SpringExtension;
@TestInstance(Lifecycle.PER_CLASS)
@ExtendWith({MockitoExtension.class, TempDirectory.class, SpringExtension.class})
@JsonTest
@SpringBootTest(classes = HarvestConfig.class)
@Import(HarvestConfig.class)
class HarvesterTest {
@Mock
......
......@@ -8,6 +8,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
import java.util.List;
import fr.inra.urgi.rare.config.SecurityConfig;
import fr.inra.urgi.rare.dao.GeneticResourceDao;
import fr.inra.urgi.rare.domain.GeneticResource;
import fr.inra.urgi.rare.domain.GeneticResourceBuilder;
......@@ -17,6 +18,7 @@ import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Import;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.elasticsearch.core.query.SearchQuery;
......@@ -27,6 +29,7 @@ import org.springframework.test.web.servlet.MockMvc;
* MVC tests for {@link SearchController}
*/
@ExtendWith(SpringExtension.class)
@Import(SecurityConfig.class)
@WebMvcTest(controllers = SearchController.class)
class SearchControllerTest {
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment