/* * Copyright 2016-2017 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.webmvc.jpa; import static org.hamcrest.Matchers.*; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; import org.junit.Test; import org.springframework.context.annotation.Bean; import org.springframework.data.rest.core.config.RepositoryRestConfiguration; import org.springframework.data.rest.tests.AbstractWebIntegrationTests; import org.springframework.data.rest.webmvc.BasePathAwareController; import org.springframework.data.rest.webmvc.RepositoryRestController; import org.springframework.data.rest.webmvc.config.RepositoryRestConfigurer; import org.springframework.data.rest.webmvc.config.RepositoryRestConfigurerAdapter; import org.springframework.hateoas.Link; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.test.context.ContextConfiguration; import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; /** * Web integration tests specific to Cross-origin resource sharing. * * @author Mark Paluch * @soundtrack 2 Unlimited - No Limit */ @ContextConfiguration public class CorsIntegrationTests extends AbstractWebIntegrationTests { static class CorsConfig extends JpaRepositoryConfig { @Bean RepositoryRestConfigurer repositoryRestConfigurer() { return new RepositoryRestConfigurerAdapter() { @Override public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) { config.getCorsRegistry().addMapping("/books/**") // .allowedMethods("GET", "PUT", "POST") // .allowedOrigins("http://far.far.away"); } }; } } @Test // DATAREST-573 public void appliesSelectiveDefaultCorsConfiguration() throws Exception { Link findItems = client.discoverUnique("items"); // Preflight request mvc.perform(options(findItems.expand().getHref()).header(HttpHeaders.ORIGIN, "http://far.far.away") .header(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "POST")) // .andExpect(status().isOk()) // .andExpect(header().string(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, "http://far.far.away")) // .andExpect( header().string(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, "GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS,TRACE")); } @Test // DATAREST-573 public void appliesGlobalCorsConfiguration() throws Exception { Link findBooks = client.discoverUnique("books"); // Preflight request mvc.perform(options(findBooks.expand().getHref()).header(HttpHeaders.ORIGIN, "http://far.far.away") .header(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET")) // .andExpect(status().isOk()) // .andExpect(header().string(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, "http://far.far.away")) // .andExpect(header().string(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, "GET,PUT,POST")); // CORS request mvc.perform(get(findBooks.expand().getHref()).header(HttpHeaders.ORIGIN, "http://far.far.away")) // .andExpect(status().isOk()) // .andExpect(header().string(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, "http://far.far.away")); } /** * @see BooksXmlController */ @Test // DATAREST-573 public void appliesCorsConfigurationOnCustomControllers() throws Exception { // Preflight request mvc.perform(options("/books/xml/1234") // .header(HttpHeaders.ORIGIN, "http://far.far.away") // .header(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET")) // .andExpect(status().isOk()) // .andExpect(header().longValue(HttpHeaders.ACCESS_CONTROL_MAX_AGE, 77123)) // .andExpect(header().string(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, "http://far.far.away")) // // See https://jira.spring.io/browse/SPR-14792 .andExpect(header().string(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, containsString("GET,PUT,POST"))); // CORS request mvc.perform(get("/books/xml/1234") // .header(HttpHeaders.ORIGIN, "http://far.far.away") // .accept(MediaType.APPLICATION_XML)) // .andExpect(status().isOk()) // .andExpect(header().string(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, "http://far.far.away")); } /** * @see BooksPdfController */ @Test // DATAREST-573 public void appliesCorsConfigurationOnCustomControllerMethod() throws Exception { // Preflight request mvc.perform(options("/books/pdf/1234").header(HttpHeaders.ORIGIN, "http://far.far.away") .header(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET")) // .andExpect(status().isOk()) // .andExpect(header().longValue(HttpHeaders.ACCESS_CONTROL_MAX_AGE, 4711)) // .andExpect(header().string(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, "http://far.far.away")) // // See https://jira.spring.io/browse/SPR-14792 .andExpect(header().string(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, containsString("GET,PUT,POST"))); } @Test // DATAREST-573 public void appliesCorsConfigurationOnRepository() throws Exception { Link authorsLink = client.discoverUnique("authors"); // Preflight request mvc.perform(options(authorsLink.expand().getHref()).header(HttpHeaders.ORIGIN, "http://not.so.far.away") .header(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET")) // .andExpect(status().isOk()) // .andExpect(header().longValue(HttpHeaders.ACCESS_CONTROL_MAX_AGE, 1234)) // .andExpect(header().string(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, "http://not.so.far.away")) // .andExpect(header().string(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true")) // .andExpect(header().string(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, "GET,PATCH")); } @Test // DATAREST-573 public void appliesCorsConfigurationOnRepositoryToCustomControllers() throws Exception { // Preflight request mvc.perform(options("/authors/pdf/1234").header(HttpHeaders.ORIGIN, "http://not.so.far.away") .header(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET")) // .andExpect(status().isOk()) // .andExpect(header().longValue(HttpHeaders.ACCESS_CONTROL_MAX_AGE, 1234)) // .andExpect(header().string(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, "http://not.so.far.away")) // .andExpect(header().string(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true")) // .andExpect(header().string(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, "GET,PATCH")); } @RepositoryRestController static class AuthorsPdfController { @RequestMapping(method = RequestMethod.GET, path = "/authors/pdf/1234", produces = MediaType.APPLICATION_PDF_VALUE) void authorToPdf() {} } @RepositoryRestController static class BooksPdfController { @RequestMapping(method = RequestMethod.GET, path = "/books/pdf/1234", produces = MediaType.APPLICATION_PDF_VALUE) @CrossOrigin(maxAge = 4711) void bookToPdf() {} } @BasePathAwareController static class BooksXmlController { @GetMapping(value = "/books/xml/{id}", produces = MediaType.APPLICATION_XML_VALUE) @CrossOrigin(maxAge = 77123) void bookToXml(@PathVariable String id) {} } }