From 0ce2bb0bf70ed14cea156c94717da76970dddc61 Mon Sep 17 00:00:00 2001 From: baranowb Date: Fri, 25 Nov 2022 11:35:36 +0100 Subject: [PATCH] [UNDERTOW-1737] add failure-reason to AccessControlListHandler --- core/src/main/java/io/undertow/Handlers.java | 12 +++++ .../handlers/AccessControlListHandler.java | 48 +++++++++++++++++-- ...AgentAccessControlHandlerUnitTestCase.java | 5 ++ 3 files changed, 61 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/io/undertow/Handlers.java b/core/src/main/java/io/undertow/Handlers.java index ef601e8499..98e60dec82 100644 --- a/core/src/main/java/io/undertow/Handlers.java +++ b/core/src/main/java/io/undertow/Handlers.java @@ -331,6 +331,18 @@ public static final AccessControlListHandler acl(final HttpHandler next, boolean return new AccessControlListHandler(next, attribute).setDefaultAllow(defaultAllow); } + /** + * Returns a new handler that can allow or deny access to a resource based an at attribute of the exchange + * + * @param next The next handler in the chain + * @param defaultAllow Determine if a non-matching user agent will be allowed by default + * @param denyResponseCode response code that will be sent back from 400 range + * @return A new user agent access control handler + */ + public static final AccessControlListHandler acl(final HttpHandler next, boolean defaultAllow, ExchangeAttribute attribute, final int denyResponseCode) { + return new AccessControlListHandler(next, attribute, denyResponseCode).setDefaultAllow(defaultAllow); + } + /** * A handler that automatically handles HTTP 100-continue responses, by sending a continue * response when the first attempt is made to read from the request channel. diff --git a/core/src/main/java/io/undertow/server/handlers/AccessControlListHandler.java b/core/src/main/java/io/undertow/server/handlers/AccessControlListHandler.java index 002829ac65..7665f384de 100644 --- a/core/src/main/java/io/undertow/server/handlers/AccessControlListHandler.java +++ b/core/src/main/java/io/undertow/server/handlers/AccessControlListHandler.java @@ -49,15 +49,42 @@ public class AccessControlListHandler implements HttpHandler { private volatile boolean defaultAllow = false; private final ExchangeAttribute attribute; private final List acl = new CopyOnWriteArrayList<>(); + private final int denyResponseCode; public AccessControlListHandler(final HttpHandler next, ExchangeAttribute attribute) { this.next = next; this.attribute = attribute; + this.denyResponseCode = StatusCodes.FORBIDDEN; } public AccessControlListHandler(ExchangeAttribute attribute) { this.attribute = attribute; this.next = ResponseCodeHandler.HANDLE_404; + this.denyResponseCode = StatusCodes.FORBIDDEN; + } + + public AccessControlListHandler(final HttpHandler next, ExchangeAttribute attribute, final int denyResponseCode) { + this.next = next; + this.attribute = attribute; + if(denyResponseCode == -1) { + this.denyResponseCode = StatusCodes.FORBIDDEN; + } else { + assert denyResponseCode > 399; + assert denyResponseCode < 500; + this.denyResponseCode = denyResponseCode; + } + } + + public AccessControlListHandler(ExchangeAttribute attribute, final int denyResponseCode) { + this.attribute = attribute; + this.next = ResponseCodeHandler.HANDLE_404; + if(denyResponseCode == -1) { + this.denyResponseCode = StatusCodes.FORBIDDEN; + } else { + assert denyResponseCode > 399; + assert denyResponseCode < 500; + this.denyResponseCode = denyResponseCode; + } } @Override @@ -66,7 +93,7 @@ public void handleRequest(final HttpServerExchange exchange) throws Exception { if (isAllowed(attribute)) { next.handleRequest(exchange); } else { - exchange.setStatusCode(StatusCodes.FORBIDDEN); + exchange.setStatusCode(this.denyResponseCode); exchange.endExchange(); } } @@ -182,6 +209,7 @@ public Map> parameters() { params.put("acl", String[].class); params.put("default-allow", boolean.class); params.put("attribute", ExchangeAttribute.class); + params.put("failure-status", int.class); return params; } @@ -219,7 +247,12 @@ public HandlerWrapper build(Map config) { throw UndertowMessages.MESSAGES.invalidAclRule(rule); } } - return new Wrapper(peerMatches, defaultAllow == null ? false : defaultAllow, attribute); + Integer failureStatus = (Integer) config.get("failure-status"); + if(failureStatus == null) { + return new Wrapper(peerMatches, defaultAllow == null ? false : defaultAllow, attribute); + } else { + return new Wrapper(peerMatches, defaultAllow == null ? false : defaultAllow, attribute, failureStatus.intValue()); + } } } @@ -229,18 +262,25 @@ private static class Wrapper implements HandlerWrapper { private final List peerMatches; private final boolean defaultAllow; private final ExchangeAttribute attribute; - + private final int denyResponseCode; private Wrapper(List peerMatches, boolean defaultAllow, ExchangeAttribute attribute) { this.peerMatches = peerMatches; this.defaultAllow = defaultAllow; this.attribute = attribute; + this.denyResponseCode = -1; } + private Wrapper(List peerMatches, boolean defaultAllow, ExchangeAttribute attribute, final int denyResponseCode) { + this.peerMatches = peerMatches; + this.defaultAllow = defaultAllow; + this.attribute = attribute; + this.denyResponseCode = denyResponseCode; + } @Override public HttpHandler wrap(HttpHandler handler) { - AccessControlListHandler res = new AccessControlListHandler(handler, attribute); + AccessControlListHandler res = new AccessControlListHandler(handler, attribute, denyResponseCode); for(AclMatch match: peerMatches) { if(match.deny) { res.addDeny(match.pattern.pattern()); diff --git a/core/src/test/java/io/undertow/server/handlers/UserAgentAccessControlHandlerUnitTestCase.java b/core/src/test/java/io/undertow/server/handlers/UserAgentAccessControlHandlerUnitTestCase.java index 7646b820ac..c1a8649d42 100644 --- a/core/src/test/java/io/undertow/server/handlers/UserAgentAccessControlHandlerUnitTestCase.java +++ b/core/src/test/java/io/undertow/server/handlers/UserAgentAccessControlHandlerUnitTestCase.java @@ -17,7 +17,12 @@ */ package io.undertow.server.handlers; +import static org.easymock.EasyMock.*; + +import io.undertow.server.HttpServerExchange; import io.undertow.testutils.category.UnitTest; +import io.undertow.util.HeaderMap; + import org.junit.Test; import org.junit.experimental.categories.Category;