parent
							
								
									45bac0fd2c
								
							
						
					
					
						commit
						a0a0a32bda
					
				| 
						 | 
				
			
			@ -36,6 +36,9 @@ dependencies {
 | 
			
		|||
	testCompile 'ch.qos.logback:logback-classic'
 | 
			
		||||
	testCompile 'javax.annotation:jsr250-api:1.0'
 | 
			
		||||
	testCompile 'ldapsdk:ldapsdk:4.1'
 | 
			
		||||
	testCompile('net.sourceforge.htmlunit:htmlunit') {
 | 
			
		||||
		exclude group: 'commons-logging', module: 'commons-logging'
 | 
			
		||||
	}
 | 
			
		||||
	testCompile 'org.codehaus.groovy:groovy-all'
 | 
			
		||||
	testCompile 'org.eclipse.persistence:javax.persistence'
 | 
			
		||||
	testCompile 'org.hibernate:hibernate-entitymanager'
 | 
			
		||||
| 
						 | 
				
			
			@ -43,6 +46,13 @@ dependencies {
 | 
			
		|||
	testCompile ('org.openid4java:openid4java-nodeps') {
 | 
			
		||||
		exclude group: 'com.google.code.guice', module: 'guice'
 | 
			
		||||
	}
 | 
			
		||||
	testCompile('org.seleniumhq.selenium:htmlunit-driver') {
 | 
			
		||||
		exclude group: 'commons-logging', module: 'commons-logging'
 | 
			
		||||
	}
 | 
			
		||||
	testCompile('org.seleniumhq.selenium:selenium-java') {
 | 
			
		||||
		exclude group: 'commons-logging', module: 'commons-logging'
 | 
			
		||||
		exclude group: 'io.netty', module: 'netty'
 | 
			
		||||
	}
 | 
			
		||||
	testCompile 'org.slf4j:jcl-over-slf4j'
 | 
			
		||||
	testCompile 'org.springframework.ldap:spring-ldap-core'
 | 
			
		||||
	testCompile 'org.springframework:spring-expression'
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,205 @@
 | 
			
		|||
/*
 | 
			
		||||
 * Copyright 2002-2017 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.security.htmlunit.server;
 | 
			
		||||
 | 
			
		||||
import java.net.URI;
 | 
			
		||||
import java.net.URL;
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.Collection;
 | 
			
		||||
import java.util.HashMap;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
import java.util.Set;
 | 
			
		||||
import java.util.StringTokenizer;
 | 
			
		||||
 | 
			
		||||
import com.gargoylesoftware.htmlunit.FormEncodingType;
 | 
			
		||||
import com.gargoylesoftware.htmlunit.WebClient;
 | 
			
		||||
import com.gargoylesoftware.htmlunit.WebRequest;
 | 
			
		||||
 | 
			
		||||
import com.gargoylesoftware.htmlunit.util.NameValuePair;
 | 
			
		||||
import org.springframework.http.HttpCookie;
 | 
			
		||||
import org.springframework.http.HttpMethod;
 | 
			
		||||
import org.springframework.http.MediaType;
 | 
			
		||||
import org.springframework.http.ResponseCookie;
 | 
			
		||||
import org.springframework.lang.Nullable;
 | 
			
		||||
import org.springframework.test.web.reactive.server.FluxExchangeResult;
 | 
			
		||||
import org.springframework.test.web.reactive.server.WebTestClient;
 | 
			
		||||
import org.springframework.util.Assert;
 | 
			
		||||
import org.springframework.util.LinkedMultiValueMap;
 | 
			
		||||
import org.springframework.util.MultiValueMap;
 | 
			
		||||
import org.springframework.web.reactive.function.BodyInserters;
 | 
			
		||||
import org.springframework.web.reactive.function.client.ClientRequest;
 | 
			
		||||
import org.springframework.web.reactive.function.client.ClientResponse;
 | 
			
		||||
import org.springframework.web.reactive.function.client.ExchangeFilterFunction;
 | 
			
		||||
import org.springframework.web.reactive.function.client.ExchangeFunction;
 | 
			
		||||
import reactor.core.publisher.Mono;
 | 
			
		||||
 | 
			
		||||
final class HtmlUnitWebTestClient {
 | 
			
		||||
 | 
			
		||||
	private final WebClient webClient;
 | 
			
		||||
 | 
			
		||||
	private final WebTestClient webTestClient;
 | 
			
		||||
 | 
			
		||||
	public HtmlUnitWebTestClient(WebClient webClient, WebTestClient webTestClient) {
 | 
			
		||||
		Assert.notNull(webClient, "WebClient must not be null");
 | 
			
		||||
		Assert.notNull(webTestClient, "WebTestClient must not be null");
 | 
			
		||||
		this.webClient = webClient;
 | 
			
		||||
		this.webTestClient = webTestClient.mutate()
 | 
			
		||||
			.filter(new FollowRedirects())
 | 
			
		||||
			.filter(new CookieManager())
 | 
			
		||||
			.build();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public FluxExchangeResult<String> getResponse(WebRequest webRequest) {
 | 
			
		||||
		WebTestClient.RequestBodySpec request = this.webTestClient
 | 
			
		||||
				.method(httpMethod(webRequest))
 | 
			
		||||
				.uri(uri(webRequest));
 | 
			
		||||
		contentType(request, webRequest);
 | 
			
		||||
		cookies(request, webRequest);
 | 
			
		||||
		headers(request, webRequest);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		return content(request, webRequest).exchange().returnResult(String.class);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private WebTestClient.RequestHeadersSpec<?> content(WebTestClient.RequestBodySpec request, WebRequest webRequest) {
 | 
			
		||||
		String requestBody = webRequest.getRequestBody();
 | 
			
		||||
		if (requestBody == null) {
 | 
			
		||||
			List<NameValuePair> params = webRequest.getRequestParameters();
 | 
			
		||||
			if(params != null && !params.isEmpty()) {
 | 
			
		||||
				return request.body(BodyInserters.fromFormData(formData(params)));
 | 
			
		||||
			}
 | 
			
		||||
			return request;
 | 
			
		||||
		}
 | 
			
		||||
		return request.body(BodyInserters.fromObject(requestBody));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private MultiValueMap<String,String> formData(List<NameValuePair> params) {
 | 
			
		||||
		MultiValueMap<String,String> result = new LinkedMultiValueMap<>(params.size());
 | 
			
		||||
		params.forEach( pair -> result.add(pair.getName(), pair.getValue()));
 | 
			
		||||
		return result;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private void contentType(WebTestClient.RequestBodySpec request, WebRequest webRequest) {
 | 
			
		||||
		String contentType = header("Content-Type", webRequest);
 | 
			
		||||
		if (contentType == null) {
 | 
			
		||||
			FormEncodingType encodingType = webRequest.getEncodingType();
 | 
			
		||||
			if (encodingType != null) {
 | 
			
		||||
				contentType = encodingType.getName();
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		MediaType mediaType = contentType == null ? MediaType.ALL : MediaType.parseMediaType(contentType);
 | 
			
		||||
		request.contentType(mediaType);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private void cookies(WebTestClient.RequestBodySpec request, WebRequest webRequest) {
 | 
			
		||||
		String cookieHeaderValue = header("Cookie", webRequest);
 | 
			
		||||
		if (cookieHeaderValue != null) {
 | 
			
		||||
			StringTokenizer tokens = new StringTokenizer(cookieHeaderValue, "=;");
 | 
			
		||||
			while (tokens.hasMoreTokens()) {
 | 
			
		||||
				String cookieName = tokens.nextToken().trim();
 | 
			
		||||
				Assert.isTrue(tokens.hasMoreTokens(),
 | 
			
		||||
						() -> "Expected value for cookie name '" + cookieName +
 | 
			
		||||
								"': full cookie header was [" + cookieHeaderValue + "]");
 | 
			
		||||
				String cookieValue = tokens.nextToken().trim();
 | 
			
		||||
				request.cookie(cookieName, cookieValue);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		Set<com.gargoylesoftware.htmlunit.util.Cookie> managedCookies = this.webClient.getCookies(webRequest.getUrl());
 | 
			
		||||
		for (com.gargoylesoftware.htmlunit.util.Cookie cookie : managedCookies) {
 | 
			
		||||
			request.cookie(cookie.getName(), cookie.getValue());
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Nullable
 | 
			
		||||
	private String header(String headerName, WebRequest webRequest) {
 | 
			
		||||
		return webRequest.getAdditionalHeaders().get(headerName);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private void headers(WebTestClient.RequestBodySpec request, WebRequest webRequest) {
 | 
			
		||||
		webRequest.getAdditionalHeaders().forEach( (name,value) -> request.header(name, value));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private HttpMethod httpMethod(WebRequest webRequest) {
 | 
			
		||||
		String httpMethod = webRequest.getHttpMethod().name();
 | 
			
		||||
		return HttpMethod.valueOf(httpMethod);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private URI uri(WebRequest webRequest) {
 | 
			
		||||
		URL url = webRequest.getUrl();
 | 
			
		||||
		return URI.create(url.toExternalForm());
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	static class FollowRedirects implements ExchangeFilterFunction {
 | 
			
		||||
		@Override
 | 
			
		||||
		public Mono<ClientResponse> filter(ClientRequest request, ExchangeFunction next) {
 | 
			
		||||
			return next.exchange(request)
 | 
			
		||||
				.flatMap( response -> redirectIfNecessary(request, next, response));
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		private Mono<ClientResponse> redirectIfNecessary(ClientRequest request, ExchangeFunction next, ClientResponse response) {
 | 
			
		||||
			URI location = response.headers().asHttpHeaders().getLocation();
 | 
			
		||||
			if(location != null) {
 | 
			
		||||
				ClientRequest redirect = ClientRequest.method(HttpMethod.GET, URI.create("http://localhost" + location.toASCIIString()))
 | 
			
		||||
					.headers(headers -> headers.addAll(request.headers()))
 | 
			
		||||
					.cookies(cookies -> cookies.addAll(request.cookies()))
 | 
			
		||||
					.attributes(attributes -> attributes.putAll(request.attributes()))
 | 
			
		||||
					.build();
 | 
			
		||||
 | 
			
		||||
				return next.exchange(redirect).flatMap( r -> redirectIfNecessary(request, next, r));
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			return Mono.just(response);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	static class CookieManager implements ExchangeFilterFunction {
 | 
			
		||||
		private Map<String, ResponseCookie> cookies = new HashMap<>();
 | 
			
		||||
 | 
			
		||||
		@Override
 | 
			
		||||
		public Mono<ClientResponse> filter(ClientRequest request, ExchangeFunction next) {
 | 
			
		||||
			return next.exchange(withClientCookies(request))
 | 
			
		||||
				.doOnSuccess( response -> {
 | 
			
		||||
					response.cookies().values().forEach( cookies -> {
 | 
			
		||||
						cookies.forEach( cookie -> {
 | 
			
		||||
							if(cookie.getMaxAge().isZero()) {
 | 
			
		||||
								this.cookies.remove(cookie.getName());
 | 
			
		||||
							} else {
 | 
			
		||||
								this.cookies.put(cookie.getName(), cookie);
 | 
			
		||||
							}
 | 
			
		||||
						});
 | 
			
		||||
					});
 | 
			
		||||
				});
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		private ClientRequest withClientCookies(ClientRequest request) {
 | 
			
		||||
			return ClientRequest.from(request)
 | 
			
		||||
				.cookies( c -> {
 | 
			
		||||
					c.addAll(clientCookies());
 | 
			
		||||
				}).build();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		private MultiValueMap<String,String> clientCookies() {
 | 
			
		||||
			MultiValueMap<String,String> result = new LinkedMultiValueMap<>(this.cookies.size());
 | 
			
		||||
			this.cookies.values().forEach( cookie ->
 | 
			
		||||
				result.add(cookie.getName(), cookie.getValue())
 | 
			
		||||
			);
 | 
			
		||||
			return result;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,77 @@
 | 
			
		|||
/*
 | 
			
		||||
 * Copyright 2002-2017 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.security.htmlunit.server;
 | 
			
		||||
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
import com.gargoylesoftware.htmlunit.WebRequest;
 | 
			
		||||
import com.gargoylesoftware.htmlunit.WebResponse;
 | 
			
		||||
import com.gargoylesoftware.htmlunit.WebResponseData;
 | 
			
		||||
import com.gargoylesoftware.htmlunit.util.NameValuePair;
 | 
			
		||||
 | 
			
		||||
import org.springframework.http.HttpHeaders;
 | 
			
		||||
import org.springframework.http.HttpStatus;
 | 
			
		||||
import org.springframework.test.web.reactive.server.FluxExchangeResult;
 | 
			
		||||
import org.springframework.util.Assert;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @author Rob Winch
 | 
			
		||||
 * @since 5.0
 | 
			
		||||
 */
 | 
			
		||||
final class MockWebResponseBuilder {
 | 
			
		||||
	private final long startTime;
 | 
			
		||||
 | 
			
		||||
	private final WebRequest webRequest;
 | 
			
		||||
 | 
			
		||||
	private final FluxExchangeResult<String> exchangeResult;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	public MockWebResponseBuilder(long startTime, WebRequest webRequest, FluxExchangeResult<String> exchangeResult) {
 | 
			
		||||
		Assert.notNull(webRequest, "WebRequest must not be null");
 | 
			
		||||
		Assert.notNull(exchangeResult, "FluxExchangeResult must not be null");
 | 
			
		||||
		this.startTime = startTime;
 | 
			
		||||
		this.webRequest = webRequest;
 | 
			
		||||
		this.exchangeResult = exchangeResult;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	public WebResponse build() throws IOException {
 | 
			
		||||
		WebResponseData webResponseData = webResponseData();
 | 
			
		||||
		long endTime = System.currentTimeMillis();
 | 
			
		||||
		return new WebResponse(webResponseData, this.webRequest, endTime - this.startTime);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private WebResponseData webResponseData() throws IOException {
 | 
			
		||||
		List<NameValuePair> responseHeaders = responseHeaders();
 | 
			
		||||
		HttpStatus status = this.exchangeResult.getStatus();
 | 
			
		||||
		return new WebResponseData(this.exchangeResult.getResponseBodyContent(), status.value(), status.getReasonPhrase(), responseHeaders);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private List<NameValuePair> responseHeaders() {
 | 
			
		||||
		HttpHeaders responseHeaders = this.exchangeResult.getResponseHeaders();
 | 
			
		||||
		List<NameValuePair> result = new ArrayList<>(responseHeaders.size());
 | 
			
		||||
		responseHeaders.forEach( (headerName, headerValues) ->
 | 
			
		||||
			headerValues.forEach( headerValue ->
 | 
			
		||||
				result.add(new NameValuePair(headerName, headerValue))
 | 
			
		||||
			)
 | 
			
		||||
		);
 | 
			
		||||
		return result;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,48 @@
 | 
			
		|||
/*
 | 
			
		||||
 *
 | 
			
		||||
 *  * Copyright 2002-2017 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.security.htmlunit.server;
 | 
			
		||||
 | 
			
		||||
import com.gargoylesoftware.htmlunit.WebClient;
 | 
			
		||||
import org.openqa.selenium.WebDriver;
 | 
			
		||||
import org.springframework.test.web.reactive.server.WebTestClient;
 | 
			
		||||
import org.springframework.test.web.servlet.htmlunit.webdriver.WebConnectionHtmlUnitDriver;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @author Rob Winch
 | 
			
		||||
 * @since 5.0
 | 
			
		||||
 */
 | 
			
		||||
public class WebTestClientHtmlUnitDriverBuilder {
 | 
			
		||||
	private final WebTestClient webTestClient;
 | 
			
		||||
 | 
			
		||||
	private WebTestClientHtmlUnitDriverBuilder(WebTestClient webTestClient) {
 | 
			
		||||
		this.webTestClient = webTestClient;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public WebDriver build() {
 | 
			
		||||
		WebConnectionHtmlUnitDriver driver = new WebConnectionHtmlUnitDriver();
 | 
			
		||||
		WebClient webClient = driver.getWebClient();
 | 
			
		||||
		WebTestClientWebConnection connection = new WebTestClientWebConnection(this.webTestClient, webClient);
 | 
			
		||||
		driver.setWebConnection(connection);
 | 
			
		||||
		return driver;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public static WebTestClientHtmlUnitDriverBuilder webTestClientSetup(WebTestClient webTestClient) {
 | 
			
		||||
		return new WebTestClientHtmlUnitDriverBuilder(webTestClient);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,131 @@
 | 
			
		|||
/*
 | 
			
		||||
 *
 | 
			
		||||
 *  * Copyright 2002-2017 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.security.htmlunit.server;
 | 
			
		||||
 | 
			
		||||
import org.junit.Test;
 | 
			
		||||
import org.openqa.selenium.WebDriver;
 | 
			
		||||
import org.springframework.http.HttpStatus;
 | 
			
		||||
import org.springframework.http.MediaType;
 | 
			
		||||
import org.springframework.http.ResponseCookie;
 | 
			
		||||
import org.springframework.http.server.reactive.ServerHttpResponse;
 | 
			
		||||
import org.springframework.security.web.util.TextEscapeUtils;
 | 
			
		||||
import org.springframework.stereotype.Controller;
 | 
			
		||||
import org.springframework.test.web.reactive.server.WebTestClient;
 | 
			
		||||
import org.springframework.web.bind.annotation.CookieValue;
 | 
			
		||||
import org.springframework.web.bind.annotation.GetMapping;
 | 
			
		||||
import org.springframework.web.bind.annotation.PostMapping;
 | 
			
		||||
import org.springframework.web.bind.annotation.ResponseBody;
 | 
			
		||||
import reactor.core.publisher.Mono;
 | 
			
		||||
 | 
			
		||||
import java.net.URI;
 | 
			
		||||
import java.time.Duration;
 | 
			
		||||
 | 
			
		||||
import static org.assertj.core.api.Assertions.assertThat;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @author Rob Winch
 | 
			
		||||
 * @since 5.0
 | 
			
		||||
 */
 | 
			
		||||
public class WebTestClientHtmlUnitDriverBuilderTests {
 | 
			
		||||
 | 
			
		||||
	@Test
 | 
			
		||||
	public void helloWorld() {
 | 
			
		||||
		WebTestClient webTestClient = WebTestClient
 | 
			
		||||
			.bindToController(new HelloWorldController())
 | 
			
		||||
			.build();
 | 
			
		||||
		WebDriver driver = WebTestClientHtmlUnitDriverBuilder
 | 
			
		||||
			.webTestClientSetup(webTestClient).build();
 | 
			
		||||
 | 
			
		||||
		driver.get("http://localhost/");
 | 
			
		||||
 | 
			
		||||
		assertThat(driver.getPageSource()).contains("Hello World");
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * @author Rob Winch
 | 
			
		||||
	 * @since 5.0
 | 
			
		||||
	 */
 | 
			
		||||
	@Controller
 | 
			
		||||
	class HelloWorldController {
 | 
			
		||||
		@ResponseBody
 | 
			
		||||
		@GetMapping(produces = MediaType.TEXT_HTML_VALUE)
 | 
			
		||||
		public String index() {
 | 
			
		||||
			return "<html>\n"
 | 
			
		||||
				+ "<head>\n"
 | 
			
		||||
				+ "<title>Hello World</title>\n"
 | 
			
		||||
				+ "</head>\n"
 | 
			
		||||
				+ "<body>\n"
 | 
			
		||||
				+ "<h1>Hello World</h1>\n"
 | 
			
		||||
				+ "</body>\n"
 | 
			
		||||
				+ "</html>";
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Test
 | 
			
		||||
	public void cookies() {
 | 
			
		||||
		WebTestClient webTestClient = WebTestClient
 | 
			
		||||
			.bindToController(new CookieController())
 | 
			
		||||
			.build();
 | 
			
		||||
		WebDriver driver = WebTestClientHtmlUnitDriverBuilder
 | 
			
		||||
			.webTestClientSetup(webTestClient).build();
 | 
			
		||||
 | 
			
		||||
		driver.get("http://localhost/cookie");
 | 
			
		||||
 | 
			
		||||
		assertThat(driver.getPageSource()).contains("theCookie");
 | 
			
		||||
 | 
			
		||||
		driver.get("http://localhost/cookie/delete");
 | 
			
		||||
 | 
			
		||||
		assertThat(driver.getPageSource()).contains("null");
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Controller
 | 
			
		||||
	@ResponseBody
 | 
			
		||||
	class CookieController {
 | 
			
		||||
		@GetMapping(path = "/", produces = MediaType.TEXT_HTML_VALUE)
 | 
			
		||||
		public String view(@CookieValue(required = false) String cookieName) {
 | 
			
		||||
			return "<html>\n"
 | 
			
		||||
				+ "<head>\n"
 | 
			
		||||
				+ "<title>Hello World</title>\n"
 | 
			
		||||
				+ "</head>\n"
 | 
			
		||||
				+ "<body>\n"
 | 
			
		||||
				+ "<h1>" + TextEscapeUtils.escapeEntities(cookieName) + "</h1>\n"
 | 
			
		||||
				+ "</body>\n"
 | 
			
		||||
				+ "</html>";
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		@GetMapping("/cookie")
 | 
			
		||||
		public Mono<Void> setCookie(ServerHttpResponse response) {
 | 
			
		||||
			response.addCookie(ResponseCookie.from("cookieName", "theCookie").build());
 | 
			
		||||
			return redirect(response);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		private Mono<Void> redirect(ServerHttpResponse response) {
 | 
			
		||||
			response.setStatusCode(HttpStatus.MOVED_PERMANENTLY);
 | 
			
		||||
			response.getHeaders().setLocation(URI.create("/"));
 | 
			
		||||
			return response.setComplete();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		@GetMapping("/cookie/delete")
 | 
			
		||||
		public Mono<Void> deleteCookie(ServerHttpResponse response) {
 | 
			
		||||
			response.addCookie(
 | 
			
		||||
				ResponseCookie.from("cookieName", "").maxAge(Duration.ofSeconds(0)).build());
 | 
			
		||||
			return redirect(response);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,95 @@
 | 
			
		|||
/*
 | 
			
		||||
 *
 | 
			
		||||
 *  * Copyright 2002-2017 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.security.htmlunit.server;
 | 
			
		||||
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
 | 
			
		||||
import com.gargoylesoftware.htmlunit.WebClient;
 | 
			
		||||
import com.gargoylesoftware.htmlunit.WebConnection;
 | 
			
		||||
import com.gargoylesoftware.htmlunit.WebRequest;
 | 
			
		||||
import com.gargoylesoftware.htmlunit.WebResponse;
 | 
			
		||||
 | 
			
		||||
import org.springframework.lang.Nullable;
 | 
			
		||||
import org.springframework.test.web.reactive.server.FluxExchangeResult;
 | 
			
		||||
import org.springframework.test.web.reactive.server.WebTestClient;
 | 
			
		||||
import org.springframework.util.Assert;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @author Rob Winch
 | 
			
		||||
 * @since 5.0
 | 
			
		||||
 */
 | 
			
		||||
public class WebTestClientWebConnection implements WebConnection {
 | 
			
		||||
 | 
			
		||||
	private final WebTestClient webTestClient;
 | 
			
		||||
 | 
			
		||||
	private final String contextPath;
 | 
			
		||||
	private final HtmlUnitWebTestClient requestBuilder;
 | 
			
		||||
 | 
			
		||||
	private WebClient webClient;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	public WebTestClientWebConnection(WebTestClient webTestClient, WebClient webClient) {
 | 
			
		||||
		this(webTestClient, webClient, "");
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public WebTestClientWebConnection(WebTestClient webTestClient, WebClient webClient, String contextPath) {
 | 
			
		||||
		Assert.notNull(webTestClient, "MockMvc must not be null");
 | 
			
		||||
		Assert.notNull(webClient, "WebClient must not be null");
 | 
			
		||||
		validateContextPath(contextPath);
 | 
			
		||||
 | 
			
		||||
		this.webClient = webClient;
 | 
			
		||||
		this.webTestClient = webTestClient;
 | 
			
		||||
		this.contextPath = contextPath;
 | 
			
		||||
		this.requestBuilder = new HtmlUnitWebTestClient(this.webClient, this.webTestClient);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Validate the supplied {@code contextPath}.
 | 
			
		||||
	 * <p>If the value is not {@code null}, it must conform to
 | 
			
		||||
	 * {@link javax.servlet.http.HttpServletRequest#getContextPath()} which
 | 
			
		||||
	 * states that it can be an empty string and otherwise must start with
 | 
			
		||||
	 * a "/" character and not end with a "/" character.
 | 
			
		||||
	 * @param contextPath the path to validate
 | 
			
		||||
	 */
 | 
			
		||||
	static void validateContextPath(@Nullable String contextPath) {
 | 
			
		||||
		if (contextPath == null || "".equals(contextPath)) {
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
		Assert.isTrue(contextPath.startsWith("/"), () -> "contextPath '" + contextPath + "' must start with '/'.");
 | 
			
		||||
		Assert.isTrue(!contextPath.endsWith("/"), () -> "contextPath '" + contextPath + "' must not end with '/'.");
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	public void setWebClient(WebClient webClient) {
 | 
			
		||||
		Assert.notNull(webClient, "WebClient must not be null");
 | 
			
		||||
		this.webClient = webClient;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public WebResponse getResponse(WebRequest webRequest) throws IOException {
 | 
			
		||||
		long startTime = System.currentTimeMillis();
 | 
			
		||||
 | 
			
		||||
		FluxExchangeResult<String> exchangeResult = this.requestBuilder.getResponse(webRequest);
 | 
			
		||||
 | 
			
		||||
		return new MockWebResponseBuilder(startTime, webRequest, exchangeResult).build();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public void close() {}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -20,6 +20,7 @@ dependencyManagement {
 | 
			
		|||
		dependency 'org.powermock:powermock-module-junit4:1.6.2'
 | 
			
		||||
		dependency 'org.powermock:powermock-reflect:1.6.2'
 | 
			
		||||
		dependency 'org.python:jython:2.5.0'
 | 
			
		||||
		dependency 'org.seleniumhq.selenium:selenium-java:3.4.0'
 | 
			
		||||
		dependency 'org.spockframework:spock-core:1.0-groovy-2.4'
 | 
			
		||||
		dependency 'org.spockframework:spock-spring:1.0-groovy-2.4'
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -18,12 +18,15 @@
 | 
			
		|||
package org.springframework.security.test.web.reactive.server;
 | 
			
		||||
 | 
			
		||||
import org.springframework.http.HttpStatus;
 | 
			
		||||
import org.springframework.security.web.server.SecurityWebFilterChain;
 | 
			
		||||
import org.springframework.security.web.server.WebFilterChainFilter;
 | 
			
		||||
import org.springframework.test.web.reactive.server.WebTestClient;
 | 
			
		||||
import org.springframework.test.web.reactive.server.WebTestClient.Builder;
 | 
			
		||||
import org.springframework.web.bind.annotation.RequestMapping;
 | 
			
		||||
import org.springframework.web.bind.annotation.ResponseStatus;
 | 
			
		||||
import org.springframework.web.bind.annotation.RestController;
 | 
			
		||||
import org.springframework.web.server.WebFilter;
 | 
			
		||||
import reactor.core.publisher.Flux;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Provides a convenient mechanism for running {@link WebTestClient} against
 | 
			
		||||
| 
						 | 
				
			
			@ -39,6 +42,10 @@ public class WebTestClientBuilder {
 | 
			
		|||
		return WebTestClient.bindToController(new Http200RestController()).webFilter(webFilters).configureClient();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public static Builder bindToWebFilters(SecurityWebFilterChain securityWebFilterChain) {
 | 
			
		||||
		return bindToWebFilters(WebFilterChainFilter.fromSecurityWebFilterChains(securityWebFilterChain));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@RestController
 | 
			
		||||
	static class Http200RestController {
 | 
			
		||||
		@RequestMapping("/**")
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue