parent
							
								
									3aebf11700
								
							
						
					
					
						commit
						fca8bf6088
					
				|  | @ -0,0 +1,155 @@ | ||||||
|  | /* | ||||||
|  |  * | ||||||
|  |  *  * 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.web.server.util.matcher; | ||||||
|  | 
 | ||||||
|  | import java.util.Arrays; | ||||||
|  | import java.util.Collection; | ||||||
|  | import java.util.Collections; | ||||||
|  | import java.util.List; | ||||||
|  | import java.util.Set; | ||||||
|  | 
 | ||||||
|  | import org.apache.commons.logging.Log; | ||||||
|  | import org.apache.commons.logging.LogFactory; | ||||||
|  | import reactor.core.publisher.Mono; | ||||||
|  | 
 | ||||||
|  | import org.springframework.http.MediaType; | ||||||
|  | import org.springframework.util.Assert; | ||||||
|  | import org.springframework.web.accept.ContentNegotiationStrategy; | ||||||
|  | import org.springframework.web.reactive.accept.HeaderContentTypeResolver; | ||||||
|  | import org.springframework.web.reactive.accept.RequestedContentTypeResolver; | ||||||
|  | import org.springframework.web.server.NotAcceptableStatusException; | ||||||
|  | import org.springframework.web.server.ServerWebExchange; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * @author Rob Winch | ||||||
|  |  * @since 5.0 | ||||||
|  |  */ | ||||||
|  | public class MediaTypeServerWebExchangeMatcher implements ServerWebExchangeMatcher { | ||||||
|  | 	private final Log logger = LogFactory.getLog(getClass()); | ||||||
|  | 	private RequestedContentTypeResolver requestedContentTypeResolver = new HeaderContentTypeResolver(); | ||||||
|  | 
 | ||||||
|  | 	private final Collection<MediaType> matchingMediaTypes; | ||||||
|  | 	private boolean useEquals; | ||||||
|  | 	private Set<MediaType> ignoredMediaTypes = Collections.emptySet(); | ||||||
|  | 
 | ||||||
|  | 	public MediaTypeServerWebExchangeMatcher(MediaType... matchingMediaTypes) { | ||||||
|  | 		Assert.notEmpty(matchingMediaTypes, "matchingMediaTypes cannot be null"); | ||||||
|  | 		Assert.noNullElements(matchingMediaTypes, "matchingMediaTypes cannot contain null"); | ||||||
|  | 		this.matchingMediaTypes = Arrays.asList(matchingMediaTypes); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	public MediaTypeServerWebExchangeMatcher(Collection<MediaType> matchingMediaTypes) { | ||||||
|  | 		Assert.notEmpty(matchingMediaTypes, "matchingMediaTypes cannot be null"); | ||||||
|  | 		Assert.isTrue(!matchingMediaTypes.contains(null), () -> "matchingMediaTypes cannot contain null. Got " + matchingMediaTypes); | ||||||
|  | 		this.matchingMediaTypes = matchingMediaTypes; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	@Override | ||||||
|  | 	public Mono<MatchResult> matches(ServerWebExchange exchange) { | ||||||
|  | 		List<MediaType> httpRequestMediaTypes; | ||||||
|  | 		try { | ||||||
|  | 			httpRequestMediaTypes = this.requestedContentTypeResolver.resolveMediaTypes(exchange); | ||||||
|  | 		} | ||||||
|  | 		catch (NotAcceptableStatusException e) { | ||||||
|  | 			this.logger.debug("Failed to parse MediaTypes, returning false", e); | ||||||
|  | 			return MatchResult.notMatch(); | ||||||
|  | 		} | ||||||
|  | 		if (this.logger.isDebugEnabled()) { | ||||||
|  | 			this.logger.debug("httpRequestMediaTypes=" + httpRequestMediaTypes); | ||||||
|  | 		} | ||||||
|  | 		for (MediaType httpRequestMediaType : httpRequestMediaTypes) { | ||||||
|  | 			if (this.logger.isDebugEnabled()) { | ||||||
|  | 				this.logger.debug("Processing " + httpRequestMediaType); | ||||||
|  | 			} | ||||||
|  | 			if (shouldIgnore(httpRequestMediaType)) { | ||||||
|  | 				this.logger.debug("Ignoring"); | ||||||
|  | 				continue; | ||||||
|  | 			} | ||||||
|  | 			if (this.useEquals) { | ||||||
|  | 				boolean isEqualTo = this.matchingMediaTypes | ||||||
|  | 					.contains(httpRequestMediaType); | ||||||
|  | 				this.logger.debug("isEqualTo " + isEqualTo); | ||||||
|  | 				return isEqualTo ? MatchResult.match() : MatchResult.notMatch(); | ||||||
|  | 			} | ||||||
|  | 			for (MediaType matchingMediaType : this.matchingMediaTypes) { | ||||||
|  | 				boolean isCompatibleWith = matchingMediaType | ||||||
|  | 					.isCompatibleWith(httpRequestMediaType); | ||||||
|  | 				if (this.logger.isDebugEnabled()) { | ||||||
|  | 					this.logger.debug(matchingMediaType + " .isCompatibleWith " | ||||||
|  | 						+ httpRequestMediaType + " = " + isCompatibleWith); | ||||||
|  | 				} | ||||||
|  | 				if (isCompatibleWith) { | ||||||
|  | 					return MatchResult.match(); | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		this.logger.debug("Did not match any media types"); | ||||||
|  | 		return MatchResult.notMatch(); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 	private boolean shouldIgnore(MediaType httpRequestMediaType) { | ||||||
|  | 		for (MediaType ignoredMediaType : this.ignoredMediaTypes) { | ||||||
|  | 			if (httpRequestMediaType.includes(ignoredMediaType)) { | ||||||
|  | 				return true; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		return false; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	/** | ||||||
|  | 	 * If set to true, matches on exact {@link MediaType}, else uses | ||||||
|  | 	 * {@link MediaType#isCompatibleWith(MediaType)}. | ||||||
|  | 	 * | ||||||
|  | 	 * @param useEquals specify if equals comparison should be used. | ||||||
|  | 	 */ | ||||||
|  | 	public void setUseEquals(boolean useEquals) { | ||||||
|  | 		this.useEquals = useEquals; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	/** | ||||||
|  | 	 * Sets the {@link RequestedContentTypeResolver} to be used | ||||||
|  | 	 * @param requestedContentTypeResolver the resolver to use. Default is {@link HeaderContentTypeResolver} | ||||||
|  | 	 */ | ||||||
|  | 	public void setRequestedContentTypeResolver( | ||||||
|  | 		RequestedContentTypeResolver requestedContentTypeResolver) { | ||||||
|  | 		Assert.notNull(requestedContentTypeResolver, "requestedContentTypeResolver cannot be null"); | ||||||
|  | 		this.requestedContentTypeResolver = requestedContentTypeResolver; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	/** | ||||||
|  | 	 * Set the {@link MediaType} to ignore from the {@link ContentNegotiationStrategy}. | ||||||
|  | 	 * This is useful if for example, you want to match on | ||||||
|  | 	 * {@link MediaType#APPLICATION_JSON} but want to ignore {@link MediaType#ALL}. | ||||||
|  | 	 * | ||||||
|  | 	 * @param ignoredMediaTypes the {@link MediaType}'s to ignore from the | ||||||
|  | 	 * {@link ContentNegotiationStrategy} | ||||||
|  | 	 */ | ||||||
|  | 	public void setIgnoredMediaTypes(Set<MediaType> ignoredMediaTypes) { | ||||||
|  | 		this.ignoredMediaTypes = ignoredMediaTypes; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	@Override | ||||||
|  | 	public String toString() { | ||||||
|  | 		return "MediaTypeRequestMatcher [requestedContentTypeResolver=" | ||||||
|  | 			+ this.requestedContentTypeResolver + ", matchingMediaTypes=" | ||||||
|  | 			+ this.matchingMediaTypes + ", useEquals=" + this.useEquals | ||||||
|  | 			+ ", ignoredMediaTypes=" + this.ignoredMediaTypes + "]"; | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | @ -0,0 +1,138 @@ | ||||||
|  | /* | ||||||
|  |  * | ||||||
|  |  *  * 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.web.server.util.matcher; | ||||||
|  | 
 | ||||||
|  | import java.util.Arrays; | ||||||
|  | import java.util.Collections; | ||||||
|  | import java.util.List; | ||||||
|  | 
 | ||||||
|  | import org.junit.Test; | ||||||
|  | import org.junit.runner.RunWith; | ||||||
|  | import org.mockito.Mock; | ||||||
|  | import org.mockito.runners.MockitoJUnitRunner; | ||||||
|  | 
 | ||||||
|  | import org.springframework.http.MediaType; | ||||||
|  | import org.springframework.mock.http.server.reactive.MockServerHttpRequest; | ||||||
|  | import org.springframework.web.reactive.accept.RequestedContentTypeResolver; | ||||||
|  | import org.springframework.web.server.ServerWebExchange; | ||||||
|  | 
 | ||||||
|  | import static org.assertj.core.api.Assertions.assertThat; | ||||||
|  | import static org.mockito.Matchers.any; | ||||||
|  | import static org.mockito.Mockito.when; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * @author Rob Winch | ||||||
|  |  * @since 5.0 | ||||||
|  |  */ | ||||||
|  | @RunWith(MockitoJUnitRunner.class) | ||||||
|  | public class MediaTypeServerWebExchangeMatcherTests { | ||||||
|  | 	@Mock | ||||||
|  | 	private RequestedContentTypeResolver resolver; | ||||||
|  | 
 | ||||||
|  | 	private MediaTypeServerWebExchangeMatcher matcher; | ||||||
|  | 
 | ||||||
|  | 	@Test(expected = IllegalArgumentException.class) | ||||||
|  | 	public void constructorMediaTypeArrayWhenNullThenThrowsIllegalArgumentException() { | ||||||
|  | 		MediaType[] types = null; | ||||||
|  | 		new MediaTypeServerWebExchangeMatcher(types); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	@Test(expected = IllegalArgumentException.class) | ||||||
|  | 	public void constructorMediaTypeArrayWhenContainsNullThenThrowsIllegalArgumentException() { | ||||||
|  | 		MediaType[] types = { null }; | ||||||
|  | 		new MediaTypeServerWebExchangeMatcher(types); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	@Test(expected = IllegalArgumentException.class) | ||||||
|  | 	public void constructorMediaTypeListWhenNullThenThrowsIllegalArgumentException() { | ||||||
|  | 		List<MediaType> types = null; | ||||||
|  | 		new MediaTypeServerWebExchangeMatcher(types); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	@Test(expected = IllegalArgumentException.class) | ||||||
|  | 	public void constructorMediaTypeListWhenContainsNullThenThrowsIllegalArgumentException() { | ||||||
|  | 		List<MediaType> types = Collections.singletonList(null); | ||||||
|  | 		new MediaTypeServerWebExchangeMatcher(types); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	@Test | ||||||
|  | 	public void matchWhenDefaultResolverAndAcceptEqualThenMatch() { | ||||||
|  | 		MediaType acceptType = MediaType.TEXT_HTML; | ||||||
|  | 		MediaTypeServerWebExchangeMatcher matcher = new MediaTypeServerWebExchangeMatcher(acceptType); | ||||||
|  | 
 | ||||||
|  | 		assertThat(matcher.matches(exchange(acceptType)).block().isMatch()).isTrue(); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	@Test | ||||||
|  | 	public void matchWhenDefaultResolverAndAcceptEqualAndIgnoreThenMatch() { | ||||||
|  | 		MediaType acceptType = MediaType.TEXT_HTML; | ||||||
|  | 		MediaTypeServerWebExchangeMatcher matcher = new MediaTypeServerWebExchangeMatcher(acceptType); | ||||||
|  | 		matcher.setIgnoredMediaTypes(Collections.singleton(MediaType.ALL)); | ||||||
|  | 
 | ||||||
|  | 		assertThat(matcher.matches(exchange(acceptType)).block().isMatch()).isTrue(); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	@Test | ||||||
|  | 	public void matchWhenDefaultResolverAndAcceptEqualAndIgnoreThenNotMatch() { | ||||||
|  | 		MediaType acceptType = MediaType.TEXT_HTML; | ||||||
|  | 		MediaTypeServerWebExchangeMatcher matcher = new MediaTypeServerWebExchangeMatcher(acceptType); | ||||||
|  | 		matcher.setIgnoredMediaTypes(Collections.singleton(MediaType.ALL)); | ||||||
|  | 
 | ||||||
|  | 		assertThat(matcher.matches(exchange(MediaType.ALL)).block().isMatch()).isFalse(); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	@Test | ||||||
|  | 	public void matchWhenDefaultResolverAndAcceptImpliedThenMatch() { | ||||||
|  | 		MediaTypeServerWebExchangeMatcher matcher = new MediaTypeServerWebExchangeMatcher(MediaType.parseMediaTypes("text/*")); | ||||||
|  | 
 | ||||||
|  | 		assertThat(matcher.matches(exchange(MediaType.TEXT_HTML)).block().isMatch()).isTrue(); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	@Test | ||||||
|  | 	public void matchWhenDefaultResolverAndAcceptImpliedAndUseEqualsThenNotMatch() { | ||||||
|  | 		MediaTypeServerWebExchangeMatcher matcher = new MediaTypeServerWebExchangeMatcher(MediaType.ALL); | ||||||
|  | 		matcher.setUseEquals(true); | ||||||
|  | 
 | ||||||
|  | 		assertThat(matcher.matches(exchange(MediaType.TEXT_HTML)).block().isMatch()).isFalse(); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	@Test | ||||||
|  | 	public void matchWhenCustomResolverAndAcceptEqualThenMatch() { | ||||||
|  | 		MediaType acceptType = MediaType.TEXT_HTML; | ||||||
|  | 		when(this.resolver.resolveMediaTypes(any())).thenReturn(Arrays.asList(acceptType)); | ||||||
|  | 		MediaTypeServerWebExchangeMatcher matcher = new MediaTypeServerWebExchangeMatcher(acceptType); | ||||||
|  | 		matcher.setRequestedContentTypeResolver(this.resolver); | ||||||
|  | 
 | ||||||
|  | 		assertThat(matcher.matches(exchange(acceptType)).block().isMatch()).isTrue(); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	@Test | ||||||
|  | 	public void matchWhenCustomResolverAndDifferentAcceptThenNotMatch() { | ||||||
|  | 		MediaType acceptType = MediaType.TEXT_HTML; | ||||||
|  | 		when(this.resolver.resolveMediaTypes(any())).thenReturn(Arrays.asList(acceptType)); | ||||||
|  | 		MediaTypeServerWebExchangeMatcher matcher = new MediaTypeServerWebExchangeMatcher(MediaType.APPLICATION_JSON); | ||||||
|  | 		matcher.setRequestedContentTypeResolver(this.resolver); | ||||||
|  | 
 | ||||||
|  | 		assertThat(matcher.matches(exchange(acceptType)).block().isMatch()).isFalse(); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	private static ServerWebExchange exchange(MediaType... accept) { | ||||||
|  | 		return MockServerHttpRequest.get("/").accept(accept).toExchange(); | ||||||
|  | 	} | ||||||
|  | } | ||||||
		Loading…
	
		Reference in New Issue