DATAREST-774 - Separated integration tests from core project to avoid classpath overlap.

Extracted store specific tests into separate test modules to prevent classpath overlap between projects. Those tests are now executed in an "it" build profile to prevent the tests being packaged for distribution on release.

Use Map-based repositories and mapping contexts for test in the Core and WebMvc module.

Slightly changed the configuration API for lookup types on RepositoryRestConfiguration.

Related ticket: DATAREST-776.
This commit is contained in:
Oliver Gierke
2016-02-25 17:18:58 +01:00
parent 897bc88d69
commit 892409da2c
189 changed files with 1884 additions and 1506 deletions

View File

@@ -0,0 +1,32 @@
/*
* Copyright 2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.rest.tests.security;
import lombok.Value;
import java.util.UUID;
import org.springframework.data.annotation.Id;
/**
* @author Oliver Gierke
*/
@Value
public class Order {
@Id UUID id = UUID.randomUUID();
Person customer;
}

View File

@@ -0,0 +1,32 @@
/*
* Copyright 2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.rest.tests.security;
import lombok.Value;
import java.util.UUID;
import org.springframework.data.annotation.Id;
/**
* @author Oliver Gierke
*/
@Value
public class Person {
@Id UUID id = UUID.randomUUID();
String firstname, lastname;
}

View File

@@ -0,0 +1,47 @@
/*
* Copyright 2013-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.rest.tests.security;
import java.util.UUID;
import org.springframework.data.repository.CrudRepository;
import org.springframework.security.access.prepost.PreAuthorize;
/**
* @author Oliver Gierke
* @author Greg Turnquist
*/
// tag::code[]
@PreAuthorize("hasRole('ROLE_USER')") // <1>
public interface PreAuthorizedOrderRepository extends CrudRepository<Order, UUID> {
@PreAuthorize("hasRole('ROLE_ADMIN')") // <2>
@Override
void delete(UUID aLong);
@PreAuthorize("hasRole('ROLE_ADMIN')")
@Override
void delete(Order order);
@PreAuthorize("hasRole('ROLE_ADMIN')")
@Override
void delete(Iterable<? extends Order> orders);
@PreAuthorize("hasRole('ROLE_ADMIN')")
@Override
void deleteAll();
}
// end::code[]

View File

@@ -0,0 +1,45 @@
/*
* Copyright 2014-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.rest.tests.security;
import java.util.UUID;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
import org.springframework.security.access.annotation.Secured;
// tag::code[]
@Secured("ROLE_USER") // <1>
@RepositoryRestResource(collectionResourceRel = "people", path = "people")
public interface SecuredPersonRepository extends CrudRepository<Person, UUID> {
@Secured("ROLE_ADMIN") // <2>
@Override
void delete(UUID aLong);
@Secured("ROLE_ADMIN")
@Override
void delete(Person person);
@Secured("ROLE_ADMIN")
@Override
void delete(Iterable<? extends Person> persons);
@Secured("ROLE_ADMIN")
@Override
void deleteAll();
}
// end::code[]

View File

@@ -0,0 +1,53 @@
/*
* Copyright 2014-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.rest.tests.security;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
// tag::code[]
@Configuration // <1>
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true) // <2>
public class SecurityConfiguration extends WebSecurityConfigurerAdapter { // <3>
// end::code[]
@Autowired
public void configureAuth(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("user").password("user").roles("USER").and()
.withUser("admin").password("admin").roles("USER", "ADMIN");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.
authorizeRequests()
.antMatchers(HttpMethod.GET, "/").permitAll() // Ignore security at the root URI.
.anyRequest().authenticated()
.and()
.httpBasic()
.and()
.csrf().disable(); // Disable CSRF since it's not critical for the scope of testing.
}
}

View File

@@ -0,0 +1,282 @@
/*
* Copyright 2014-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.rest.tests.security;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.*;
import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.map.repository.config.EnableMapRepositories;
import org.springframework.data.rest.tests.AbstractWebIntegrationTests;
import org.springframework.data.rest.tests.TestMvcClient;
import org.springframework.data.rest.webmvc.config.RepositoryRestMvcConfiguration;
import org.springframework.http.HttpStatus;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.context.WebApplicationContext;
/**
* Test Spring Data REST in the context of being locked down by Spring Security. Uses MockMvc to simulate HTTP-based
* interactions. Testing is possible on the repository level, but that doesn't align with the mission of Spring Data
* REST.
*
* @author Greg Turnquist
* @author Rob Winch
*/
@RunWith(SpringJUnit4ClassRunner.class)
@Transactional
@ContextConfiguration(classes = { SecurityIntegrationTests.Config.class, SecurityConfiguration.class,
RepositoryRestMvcConfiguration.class })
public class SecurityIntegrationTests extends AbstractWebIntegrationTests {
@Autowired WebApplicationContext context;
@Autowired MethodSecurityInterceptor methodSecurityInterceptor;
@Autowired SecuredPersonRepository personRepository;
@Autowired PreAuthorizedOrderRepository orderRepository;
@Configuration
@EnableMapRepositories
static class Config {}
@Before
@Override
public void setUp() {
super.setUp();
SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken("user", "user",
AuthorityUtils.createAuthorityList("ROLE_USER", "ROLE_ADMIN")));
personRepository.deleteAll();
orderRepository.deleteAll();
Person frodo = personRepository.save(new Person("Frodo", "Baggins"));
orderRepository.save(new Order(frodo));
SecurityContextHolder.clearContext();
}
/**
* Override the configuration used in {@link AbstractWebIntegrationTests} by plugging in Spring Security's
* {@link TestSecurityContextHolderPostProcessor} via apply(springSecurity()).
*/
@Override
protected void setupMockMvc() {
this.mvc = MockMvcBuilders.webAppContextSetup(context).//
defaultRequest(get("/").//
accept(TestMvcClient.DEFAULT_MEDIA_TYPE))
.//
apply(springSecurity()).//
build();
}
/**
* @see DATAREST-327
*/
@Test
public void deletePersonAccessDeniedForNoCredentials() throws Exception {
// Getting the collection is not tested here. This is to get the URI that will later be tested for DELETE
final String people = client.discoverUnique("people").expand().getHref();
MockHttpServletResponse response = mvc
.perform(get(people).//
with(user("user").roles("USER")))
.//
andReturn().getResponse();
String href = assertHasJsonPathValue("$._embedded.people[0]._links.self.href", response);
// Clear any side effects of logging in to get the URI from security.
SecurityContextHolder.clearContext();
mvc.perform(delete(href)).andExpect(status().isUnauthorized());
}
/**
* @see DATAREST-327
*/
@Test
public void deletePersonAccessDeniedForUsers() throws Exception {
MockHttpServletResponse response = mvc
.perform(get(client.discoverUnique("people").expand().getHref()).//
with(user("user").roles("USER")))
.//
andReturn().getResponse();
String href = assertHasJsonPathValue("$._embedded.people[0]._links.self.href", response);
// Clear any side effects of logging in to get the URI from security.
SecurityContextHolder.clearContext();
mvc.perform(delete(href).with(user("user").roles("USER"))).//
andExpect(status().isForbidden());
}
/**
* @see DATAREST-327
*/
@Test
public void deletePersonAccessGrantedForAdmins() throws Exception {
MockHttpServletResponse response = mvc
.perform(get(client.discoverUnique("people").expand().getHref()).//
with(user("user").roles("USER", "ADMIN")))
.//
andReturn().getResponse();
String href = assertHasJsonPathValue("$._embedded.people[0]._links.self.href", response);
// Clear any side effects of logging in to get the URI from security.
SecurityContextHolder.clearContext();
mvc.perform(delete(href).with(user("user").roles("USER", "ADMIN")))
.andExpect(status().is(HttpStatus.NO_CONTENT.value()));
}
/**
* @see DATAREST-327
*/
@Test
public void findAllPeopleAccessDeniedForNoCredentials() throws Throwable {
mvc.perform(get(client.discoverUnique("people").expand().getHref())).//
andExpect(status().isUnauthorized());
}
/**
* @see DATAREST-327
*/
@Test
public void findAllPeopleAccessGrantedForUsers() throws Throwable {
mvc.perform(get(client.discoverUnique("people").expand().getHref()).//
with(user("user").roles("USER"))).//
andExpect(status().isOk());
}
/**
* @see DATAREST-327
*/
@Test
public void findAllPeopleAccessGrantedForAdmins() throws Throwable {
mvc.perform(get(client.discoverUnique("people").expand().getHref()).//
with(user("user").roles("USER", "ADMIN"))).//
andExpect(status().isOk());
}
/**
* @see DATAREST-327
*/
@Test
public void deleteOrderAccessDeniedForNoCredentials() throws Exception {
// Getting the collection is not tested here. This is to get the URI that will later be tested for DELETE
MockHttpServletResponse response = mvc
.perform(get(client.discoverUnique("orders").expand().getHref()).//
with(user("user").roles("USER")))
.//
andReturn().getResponse();
String href = assertHasJsonPathValue("$._embedded.orders[0]._links.self.href", response);
// Clear any side effects of logging in to get the URI from security.
SecurityContextHolder.clearContext();
mvc.perform(delete(href)).andExpect(status().isUnauthorized());
}
/**
* @see DATAREST-327
*/
@Test
public void deleteOrderAccessDeniedForUsers() throws Exception {
MockHttpServletResponse response = mvc
.perform(get(client.discoverUnique("orders").expand().getHref()).//
with(user("user").roles("USER")))
.//
andReturn().getResponse();
String href = assertHasJsonPathValue("$._embedded.orders[0]._links.self.href", response);
mvc.perform(delete(href).with(user("user").roles("USER"))).//
andExpect(status().isForbidden());
}
/**
* @see DATAREST-327
*/
@Test
public void deleteOrderAccessGrantedForAdmins() throws Exception {
MockHttpServletResponse response = mvc
.perform(get(client.discoverUnique("orders").expand().getHref()).//
with(user("user").roles("USER")))
.//
andReturn().getResponse();
String href = assertHasJsonPathValue("$._embedded.orders[0]._links.self.href", response);
// Clear any side effects of logging in to get the URI from security.
SecurityContextHolder.clearContext();
mvc.perform(delete(href).with(user("user").roles("USER", "ADMIN")))
.andExpect(status().is(HttpStatus.NO_CONTENT.value()));
}
/**
* @see DATAREST-327
*/
@Test
public void findAllOrdersAccessDeniedForNoCredentials() throws Throwable {
mvc.perform(get(client.discoverUnique("orders").expand().getHref())).//
andExpect(status().isUnauthorized());
}
/**
* @see DATAREST-327
*/
@Test
public void findAllOrdersAccessGrantedForUsers() throws Throwable {
mvc.perform(get(client.discoverUnique("orders").expand().getHref()).//
with(user("user").roles("USER"))).//
andExpect(status().isOk());
}
/**
* @see DATAREST-327
*/
@Test
public void findAllOrdersAccessGrantedForAdmins() throws Throwable {
mvc.perform(get(client.discoverUnique("orders").expand().getHref()).//
with(user("user").roles("USER", "ADMIN"))).//
andExpect(status().isOk());
}
}