Ver Fonte

Delegating ServerAccessDeniedHandler by exchange

Fixes: gh-5747
Josh Cummings há 7 anos atrás
pai
commit
1c74706232

+ 123 - 0
web/src/main/java/org/springframework/security/web/server/authorization/ServerWebExchangeDelegatingServerAccessDeniedHandler.java

@@ -0,0 +1,123 @@
+/*
+ * Copyright 2002-2018 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.authorization;
+
+import java.util.Arrays;
+import java.util.List;
+
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+import org.springframework.http.HttpStatus;
+import org.springframework.security.access.AccessDeniedException;
+import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher;
+import org.springframework.util.Assert;
+import org.springframework.web.server.ServerWebExchange;
+
+/**
+ * A {@link ServerAccessDeniedHandler} which delegates to multiple {@link ServerAccessDeniedHandler}s based
+ * on a {@link ServerWebExchangeMatcher}
+ *
+ * @author Josh Cummings
+ * @since 5.1
+ */
+public class ServerWebExchangeDelegatingServerAccessDeniedHandler
+	implements ServerAccessDeniedHandler {
+
+	private final List<DelegateEntry> handlers;
+
+	private ServerAccessDeniedHandler defaultHandler = (exchange, e) -> {
+		exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN);
+		return exchange.getResponse().setComplete();
+	};
+
+	/**
+	 * Creates a new instance
+	 *
+	 * @param handlers a list of {@link ServerWebExchangeMatcher}/
+	 * {@link ServerAccessDeniedHandler} pairs that should be used. Each is considered
+	 * in the order they are specified and only the first {@link ServerAccessDeniedHandler}
+	 * is used. If none match, then the default {@link ServerAccessDeniedHandler}
+	 * is used.
+	 */
+	public ServerWebExchangeDelegatingServerAccessDeniedHandler(
+			DelegateEntry... handlers) {
+		this(Arrays.asList(handlers));
+	}
+
+	/**
+	 * Creates a new instance
+	 *
+	 * @param handlers a list of {@link ServerWebExchangeMatcher}/
+	 * {@link ServerAccessDeniedHandler} pairs that should be used. Each is considered
+	 * in the order they are specified and only the first {@link ServerAccessDeniedHandler}
+	 * is used. If none match, then the default {@link ServerAccessDeniedHandler}
+	 * is used.
+	 */
+	public ServerWebExchangeDelegatingServerAccessDeniedHandler(
+			List<DelegateEntry> handlers) {
+		Assert.notEmpty(handlers, "handlers cannot be null");
+		this.handlers = handlers;
+	}
+
+	@Override
+	public Mono<Void> handle(ServerWebExchange exchange,
+		AccessDeniedException denied) {
+		return Flux.fromIterable(this.handlers)
+				.filterWhen(entry -> isMatch(exchange, entry))
+				.next()
+				.map(DelegateEntry::getAccessDeniedHandler)
+				.defaultIfEmpty(this.defaultHandler)
+				.flatMap(handler -> handler.handle(exchange, denied));
+	}
+
+	/**
+	 * Use this {@link ServerAccessDeniedHandler} when no {@link ServerWebExchangeMatcher}
+	 * matches.
+	 *
+	 * @param accessDeniedHandler - the default {@link ServerAccessDeniedHandler} to use
+	 */
+	public void setDefaultAccessDeniedHandler(ServerAccessDeniedHandler accessDeniedHandler) {
+		Assert.notNull(accessDeniedHandler, "accessDeniedHandler cannot be null");
+		this.defaultHandler = accessDeniedHandler;
+	}
+
+	public static class DelegateEntry {
+		private final ServerWebExchangeMatcher matcher;
+		private final ServerAccessDeniedHandler accessDeniedHandler;
+
+		public DelegateEntry(ServerWebExchangeMatcher matcher,
+				ServerAccessDeniedHandler accessDeniedHandler) {
+			this.matcher = matcher;
+			this.accessDeniedHandler = accessDeniedHandler;
+		}
+
+		public ServerWebExchangeMatcher getMatcher() {
+			return this.matcher;
+		}
+
+		public ServerAccessDeniedHandler getAccessDeniedHandler() {
+			return this.accessDeniedHandler;
+		}
+	}
+
+	private Mono<Boolean> isMatch(ServerWebExchange exchange, DelegateEntry entry) {
+		ServerWebExchangeMatcher matcher = entry.getMatcher();
+		return matcher.matches(exchange)
+				.map(ServerWebExchangeMatcher.MatchResult::isMatch);
+	}
+}

+ 112 - 0
web/src/test/java/org/springframework/security/web/server/authorization/ServerWebExchangeDelegatingServerAccessDeniedHandlerTests.java

@@ -0,0 +1,112 @@
+/*
+ * Copyright 2002-2018 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.authorization;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.Before;
+import org.junit.Test;
+import reactor.core.publisher.Mono;
+
+import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher;
+import org.springframework.web.server.ServerWebExchange;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.springframework.security.web.server.authorization.ServerWebExchangeDelegatingServerAccessDeniedHandler.DelegateEntry;
+import static org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher.MatchResult.match;
+import static org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher.MatchResult.notMatch;
+
+public class ServerWebExchangeDelegatingServerAccessDeniedHandlerTests {
+	private ServerWebExchangeDelegatingServerAccessDeniedHandler delegator;
+	private List<ServerWebExchangeDelegatingServerAccessDeniedHandler.DelegateEntry> entries;
+	private ServerAccessDeniedHandler accessDeniedHandler;
+	private ServerWebExchange exchange;
+
+	@Before
+	public void setup() {
+		this.accessDeniedHandler = mock(ServerAccessDeniedHandler.class);
+		this.entries = new ArrayList<>();
+		this.exchange = mock(ServerWebExchange.class);
+	}
+
+	@Test
+	public void handleWhenNothingMatchesThenOnlyDefaultHandlerInvoked() {
+		ServerAccessDeniedHandler handler = mock(ServerAccessDeniedHandler.class);
+		ServerWebExchangeMatcher matcher = mock(ServerWebExchangeMatcher.class);
+		when(matcher.matches(this.exchange)).thenReturn(notMatch());
+		when(handler.handle(this.exchange, null)).thenReturn(Mono.empty());
+		when(this.accessDeniedHandler.handle(this.exchange, null)).thenReturn(Mono.empty());
+
+		this.entries.add(new DelegateEntry(matcher, handler));
+		this.delegator = new ServerWebExchangeDelegatingServerAccessDeniedHandler(this.entries);
+		this.delegator.setDefaultAccessDeniedHandler(this.accessDeniedHandler);
+
+		this.delegator.handle(this.exchange, null).block();
+
+		verify(this.accessDeniedHandler).handle(this.exchange, null);
+		verify(handler, never()).handle(this.exchange, null);
+	}
+
+	@Test
+	public void handleWhenFirstMatchesThenOnlyFirstInvoked() {
+		ServerAccessDeniedHandler firstHandler = mock(ServerAccessDeniedHandler.class);
+		ServerWebExchangeMatcher firstMatcher = mock(ServerWebExchangeMatcher.class);
+		ServerAccessDeniedHandler secondHandler = mock(ServerAccessDeniedHandler.class);
+		ServerWebExchangeMatcher secondMatcher = mock(ServerWebExchangeMatcher.class);
+		when(firstMatcher.matches(this.exchange)).thenReturn(match());
+		when(firstHandler.handle(this.exchange, null)).thenReturn(Mono.empty());
+		when(secondHandler.handle(this.exchange, null)).thenReturn(Mono.empty());
+
+		this.entries.add(new DelegateEntry(firstMatcher, firstHandler));
+		this.entries.add(new DelegateEntry(secondMatcher, secondHandler));
+		this.delegator = new ServerWebExchangeDelegatingServerAccessDeniedHandler(this.entries);
+		this.delegator.setDefaultAccessDeniedHandler(this.accessDeniedHandler);
+
+		this.delegator.handle(this.exchange, null).block();
+
+		verify(firstHandler).handle(this.exchange, null);
+		verify(secondHandler, never()).handle(this.exchange, null);
+		verify(this.accessDeniedHandler, never()).handle(this.exchange, null);
+		verify(secondMatcher, never()).matches(this.exchange);
+	}
+
+	@Test
+	public void handleWhenSecondMatchesThenOnlySecondInvoked() {
+		ServerAccessDeniedHandler firstHandler = mock(ServerAccessDeniedHandler.class);
+		ServerWebExchangeMatcher firstMatcher = mock(ServerWebExchangeMatcher.class);
+		ServerAccessDeniedHandler secondHandler = mock(ServerAccessDeniedHandler.class);
+		ServerWebExchangeMatcher secondMatcher = mock(ServerWebExchangeMatcher.class);
+		when(firstMatcher.matches(this.exchange)).thenReturn(notMatch());
+		when(secondMatcher.matches(this.exchange)).thenReturn(match());
+		when(firstHandler.handle(this.exchange, null)).thenReturn(Mono.empty());
+		when(secondHandler.handle(this.exchange, null)).thenReturn(Mono.empty());
+
+		this.entries.add(new DelegateEntry(firstMatcher, firstHandler));
+		this.entries.add(new DelegateEntry(secondMatcher, secondHandler));
+		this.delegator = new ServerWebExchangeDelegatingServerAccessDeniedHandler(this.entries);
+
+		this.delegator.handle(this.exchange, null).block();
+
+		verify(secondHandler).handle(this.exchange, null);
+		verify(firstHandler, never()).handle(this.exchange, null);
+		verify(this.accessDeniedHandler, never()).handle(this.exchange, null);
+	}
+}