/*
* Copyright 2002-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.web.reactive.result.method.annotation;
import java.util.Properties;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.core.env.PropertiesPropertySource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.reactive.config.EnableWebFlux;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
/**
* Integration tests with {@code @CrossOrigin} and {@code @RequestMapping}
* annotated handler methods.
*
* @author Sebastien Deleuze
* @author Rossen Stoyanchev
*/
public class CrossOriginAnnotationIntegrationTests extends AbstractRequestMappingIntegrationTests {
private HttpHeaders headers;
@Before
public void setup() throws Exception {
super.setup();
this.headers = new HttpHeaders();
this.headers.setOrigin("http://site1.com");
}
@Override
protected ApplicationContext initApplicationContext() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(WebConfig.class);
Properties props = new Properties();
props.setProperty("myOrigin", "http://site1.com");
context.getEnvironment().getPropertySources().addFirst(new PropertiesPropertySource("ps", props));
context.register(PropertySourcesPlaceholderConfigurer.class);
context.refresh();
return context;
}
@Override
protected RestTemplate initRestTemplate() {
// JDK default HTTP client blacklist headers like Origin
return new RestTemplate(new HttpComponentsClientHttpRequestFactory());
}
@Test
public void actualGetRequestWithoutAnnotation() throws Exception {
ResponseEntity<String> entity = performGet("/no", this.headers, String.class);
assertEquals(HttpStatus.OK, entity.getStatusCode());
assertNull(entity.getHeaders().getAccessControlAllowOrigin());
assertEquals("no", entity.getBody());
}
@Test
public void actualPostRequestWithoutAnnotation() throws Exception {
ResponseEntity<String> entity = performPost("/no", this.headers, null, String.class);
assertEquals(HttpStatus.OK, entity.getStatusCode());
assertNull(entity.getHeaders().getAccessControlAllowOrigin());
assertEquals("no-post", entity.getBody());
}
@Test
public void actualRequestWithDefaultAnnotation() throws Exception {
ResponseEntity<String> entity = performGet("/default", this.headers, String.class);
assertEquals(HttpStatus.OK, entity.getStatusCode());
assertEquals("http://site1.com", entity.getHeaders().getAccessControlAllowOrigin());
assertEquals(true, entity.getHeaders().getAccessControlAllowCredentials());
assertEquals("default", entity.getBody());
}
@Test
public void preflightRequestWithDefaultAnnotation() throws Exception {
this.headers.add(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET");
ResponseEntity<Void> entity = performOptions("/default", this.headers, Void.class);
assertEquals(HttpStatus.OK, entity.getStatusCode());
assertEquals("http://site1.com", entity.getHeaders().getAccessControlAllowOrigin());
assertEquals(1800, entity.getHeaders().getAccessControlMaxAge());
assertEquals(true, entity.getHeaders().getAccessControlAllowCredentials());
}
@Test
public void actualRequestWithDefaultAnnotationAndNoOrigin() throws Exception {
HttpHeaders headers = new HttpHeaders();
ResponseEntity<String> entity = performGet("/default", headers, String.class);
assertEquals(HttpStatus.OK, entity.getStatusCode());
assertNull(entity.getHeaders().getAccessControlAllowOrigin());
assertEquals("default", entity.getBody());
}
@Test
public void actualRequestWithCustomizedAnnotation() throws Exception {
ResponseEntity<String> entity = performGet("/customized", this.headers, String.class);
assertEquals(HttpStatus.OK, entity.getStatusCode());
assertEquals("http://site1.com", entity.getHeaders().getAccessControlAllowOrigin());
assertEquals(false, entity.getHeaders().getAccessControlAllowCredentials());
assertEquals(-1, entity.getHeaders().getAccessControlMaxAge());
assertEquals("customized", entity.getBody());
}
@Test
public void preflightRequestWithCustomizedAnnotation() throws Exception {
this.headers.add(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET");
this.headers.add(HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS, "header1, header2");
ResponseEntity<String> entity = performOptions("/customized", this.headers, String.class);
assertEquals(HttpStatus.OK, entity.getStatusCode());
assertEquals("http://site1.com", entity.getHeaders().getAccessControlAllowOrigin());
assertArrayEquals(new HttpMethod[] {HttpMethod.GET},
entity.getHeaders().getAccessControlAllowMethods().toArray());
assertArrayEquals(new String[] {"header1", "header2"},
entity.getHeaders().getAccessControlAllowHeaders().toArray());
assertArrayEquals(new String[] {"header3", "header4"},
entity.getHeaders().getAccessControlExposeHeaders().toArray());
assertEquals(false, entity.getHeaders().getAccessControlAllowCredentials());
assertEquals(123, entity.getHeaders().getAccessControlMaxAge());
}
@Test
public void customOriginDefinedViaValueAttribute() throws Exception {
ResponseEntity<String> entity = performGet("/origin-value-attribute", this.headers, String.class);
assertEquals(HttpStatus.OK, entity.getStatusCode());
assertEquals("http://site1.com", entity.getHeaders().getAccessControlAllowOrigin());
assertEquals("value-attribute", entity.getBody());
}
@Test
public void customOriginDefinedViaPlaceholder() throws Exception {
ResponseEntity<String> entity = performGet("/origin-placeholder", this.headers, String.class);
assertEquals(HttpStatus.OK, entity.getStatusCode());
assertEquals("http://site1.com", entity.getHeaders().getAccessControlAllowOrigin());
assertEquals("placeholder", entity.getBody());
}
@Test
public void classLevel() throws Exception {
ResponseEntity<String> entity = performGet("/foo", this.headers, String.class);
assertEquals(HttpStatus.OK, entity.getStatusCode());
assertEquals("*", entity.getHeaders().getAccessControlAllowOrigin());
assertEquals(false, entity.getHeaders().getAccessControlAllowCredentials());
assertEquals("foo", entity.getBody());
entity = performGet("/bar", this.headers, String.class);
assertEquals(HttpStatus.OK, entity.getStatusCode());
assertEquals("*", entity.getHeaders().getAccessControlAllowOrigin());
assertEquals(false, entity.getHeaders().getAccessControlAllowCredentials());
assertEquals("bar", entity.getBody());
entity = performGet("/baz", this.headers, String.class);
assertEquals(HttpStatus.OK, entity.getStatusCode());
assertEquals("http://site1.com", entity.getHeaders().getAccessControlAllowOrigin());
assertEquals(true, entity.getHeaders().getAccessControlAllowCredentials());
assertEquals("baz", entity.getBody());
}
@Test
public void ambiguousHeaderPreflightRequest() throws Exception {
this.headers.add(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET");
this.headers.add(HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS, "header1");
ResponseEntity<String> entity = performOptions("/ambiguous-header", this.headers, String.class);
assertEquals(HttpStatus.OK, entity.getStatusCode());
assertEquals("http://site1.com", entity.getHeaders().getAccessControlAllowOrigin());
assertArrayEquals(new HttpMethod[] {HttpMethod.GET},
entity.getHeaders().getAccessControlAllowMethods().toArray());
assertArrayEquals(new String[] {"header1"},
entity.getHeaders().getAccessControlAllowHeaders().toArray());
assertEquals(true, entity.getHeaders().getAccessControlAllowCredentials());
}
@Test
public void ambiguousProducesPreflightRequest() throws Exception {
this.headers.add(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET");
ResponseEntity<String> entity = performOptions("/ambiguous-produces", this.headers, String.class);
assertEquals(HttpStatus.OK, entity.getStatusCode());
assertEquals("http://site1.com", entity.getHeaders().getAccessControlAllowOrigin());
assertArrayEquals(new HttpMethod[] {HttpMethod.GET},
entity.getHeaders().getAccessControlAllowMethods().toArray());
assertEquals(true, entity.getHeaders().getAccessControlAllowCredentials());
}
@Configuration
@EnableWebFlux
@ComponentScan(resourcePattern = "**/CrossOriginAnnotationIntegrationTests*")
@SuppressWarnings({"unused", "WeakerAccess"})
static class WebConfig {
}
@RestController @SuppressWarnings("unused")
private static class MethodLevelController {
@GetMapping("/no")
public String noAnnotation() {
return "no";
}
@PostMapping("/no")
public String noAnnotationPost() {
return "no-post";
}
@CrossOrigin
@GetMapping("/default")
public String defaultAnnotation() {
return "default";
}
@CrossOrigin
@GetMapping(path = "/default", params = "q")
public void defaultAnnotationWithParams() {
}
@CrossOrigin
@GetMapping(path = "/ambiguous-header", headers = "header1=a")
public void ambigousHeader1a() {
}
@CrossOrigin
@GetMapping(path = "/ambiguous-header", headers = "header1=b")
public void ambigousHeader1b() {
}
@CrossOrigin
@GetMapping(path = "/ambiguous-produces", produces = "application/xml")
public String ambigousProducesXml() {
return "<a></a>";
}
@CrossOrigin
@GetMapping(path = "/ambiguous-produces", produces = "application/json")
public String ambigousProducesJson() {
return "{}";
}
@CrossOrigin(
origins = { "http://site1.com", "http://site2.com" },
allowedHeaders = { "header1", "header2" },
exposedHeaders = { "header3", "header4" },
methods = RequestMethod.GET,
maxAge = 123,
allowCredentials = "false")
@RequestMapping(path = "/customized", method = { RequestMethod.GET, RequestMethod.POST })
public String customized() {
return "customized";
}
@CrossOrigin("http://site1.com")
@GetMapping("/origin-value-attribute")
public String customOriginDefinedViaValueAttribute() {
return "value-attribute";
}
@CrossOrigin("${myOrigin}")
@GetMapping("/origin-placeholder")
public String customOriginDefinedViaPlaceholder() {
return "placeholder";
}
}
@RestController
@CrossOrigin(allowCredentials = "false")
@SuppressWarnings("unused")
private static class ClassLevelController {
@GetMapping("/foo")
public String foo() {
return "foo";
}
@CrossOrigin
@GetMapping("/bar")
public String bar() {
return "bar";
}
@CrossOrigin(allowCredentials = "true")
@GetMapping("/baz")
public String baz() {
return "baz";
}
}
}