parent
							
								
									45bac0fd2c
								
							
						
					
					
						commit
						a0a0a32bda
					
				| 
						 | 
					@ -36,6 +36,9 @@ dependencies {
 | 
				
			||||||
	testCompile 'ch.qos.logback:logback-classic'
 | 
						testCompile 'ch.qos.logback:logback-classic'
 | 
				
			||||||
	testCompile 'javax.annotation:jsr250-api:1.0'
 | 
						testCompile 'javax.annotation:jsr250-api:1.0'
 | 
				
			||||||
	testCompile 'ldapsdk:ldapsdk:4.1'
 | 
						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.codehaus.groovy:groovy-all'
 | 
				
			||||||
	testCompile 'org.eclipse.persistence:javax.persistence'
 | 
						testCompile 'org.eclipse.persistence:javax.persistence'
 | 
				
			||||||
	testCompile 'org.hibernate:hibernate-entitymanager'
 | 
						testCompile 'org.hibernate:hibernate-entitymanager'
 | 
				
			||||||
| 
						 | 
					@ -43,6 +46,13 @@ dependencies {
 | 
				
			||||||
	testCompile ('org.openid4java:openid4java-nodeps') {
 | 
						testCompile ('org.openid4java:openid4java-nodeps') {
 | 
				
			||||||
		exclude group: 'com.google.code.guice', module: 'guice'
 | 
							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.slf4j:jcl-over-slf4j'
 | 
				
			||||||
	testCompile 'org.springframework.ldap:spring-ldap-core'
 | 
						testCompile 'org.springframework.ldap:spring-ldap-core'
 | 
				
			||||||
	testCompile 'org.springframework:spring-expression'
 | 
						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-module-junit4:1.6.2'
 | 
				
			||||||
		dependency 'org.powermock:powermock-reflect:1.6.2'
 | 
							dependency 'org.powermock:powermock-reflect:1.6.2'
 | 
				
			||||||
		dependency 'org.python:jython:2.5.0'
 | 
							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-core:1.0-groovy-2.4'
 | 
				
			||||||
		dependency 'org.spockframework:spock-spring: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;
 | 
					package org.springframework.security.test.web.reactive.server;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import org.springframework.http.HttpStatus;
 | 
					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;
 | 
				
			||||||
import org.springframework.test.web.reactive.server.WebTestClient.Builder;
 | 
					import org.springframework.test.web.reactive.server.WebTestClient.Builder;
 | 
				
			||||||
import org.springframework.web.bind.annotation.RequestMapping;
 | 
					import org.springframework.web.bind.annotation.RequestMapping;
 | 
				
			||||||
import org.springframework.web.bind.annotation.ResponseStatus;
 | 
					import org.springframework.web.bind.annotation.ResponseStatus;
 | 
				
			||||||
import org.springframework.web.bind.annotation.RestController;
 | 
					import org.springframework.web.bind.annotation.RestController;
 | 
				
			||||||
import org.springframework.web.server.WebFilter;
 | 
					import org.springframework.web.server.WebFilter;
 | 
				
			||||||
 | 
					import reactor.core.publisher.Flux;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Provides a convenient mechanism for running {@link WebTestClient} against
 | 
					 * Provides a convenient mechanism for running {@link WebTestClient} against
 | 
				
			||||||
| 
						 | 
					@ -39,6 +42,10 @@ public class WebTestClientBuilder {
 | 
				
			||||||
		return WebTestClient.bindToController(new Http200RestController()).webFilter(webFilters).configureClient();
 | 
							return WebTestClient.bindToController(new Http200RestController()).webFilter(webFilters).configureClient();
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public static Builder bindToWebFilters(SecurityWebFilterChain securityWebFilterChain) {
 | 
				
			||||||
 | 
							return bindToWebFilters(WebFilterChainFilter.fromSecurityWebFilterChains(securityWebFilterChain));
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@RestController
 | 
						@RestController
 | 
				
			||||||
	static class Http200RestController {
 | 
						static class Http200RestController {
 | 
				
			||||||
		@RequestMapping("/**")
 | 
							@RequestMapping("/**")
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue