Jetty 10 7918 root pathspec (#7920)

Fix #7918 Root path spec
Handle root pathspec in PathMappings.asPathSpec
Introduce protected asPathSpec to allow for extensibility

Signed-off-by: Greg Wilkins <gregw@webtide.com>
This commit is contained in:
Greg Wilkins 2022-04-26 19:04:17 +02:00 committed by GitHub
parent efd9f26024
commit e12d5d58b6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 70 additions and 12 deletions

View File

@ -18,7 +18,6 @@ import java.util.ArrayList;
import java.util.Comparator; import java.util.Comparator;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.TreeSet; import java.util.TreeSet;
import java.util.function.Predicate; import java.util.function.Predicate;
@ -199,23 +198,21 @@ public class PathMappings<E> implements Iterable<MappedResource<E>>, Dumpable
public static PathSpec asPathSpec(String pathSpecString) public static PathSpec asPathSpec(String pathSpecString)
{ {
if ((pathSpecString == null) || (pathSpecString.length() < 1)) if (pathSpecString == null)
{
throw new RuntimeException("Path Spec String must start with '^', '/', or '*.': got [" + pathSpecString + "]"); throw new RuntimeException("Path Spec String must start with '^', '/', or '*.': got [" + pathSpecString + "]");
}
if (pathSpecString.length() == 0)
return new ServletPathSpec("");
return pathSpecString.charAt(0) == '^' ? new RegexPathSpec(pathSpecString) : new ServletPathSpec(pathSpecString); return pathSpecString.charAt(0) == '^' ? new RegexPathSpec(pathSpecString) : new ServletPathSpec(pathSpecString);
} }
public E get(PathSpec spec) public E get(PathSpec spec)
{ {
Optional<E> optionalResource = _mappings.stream() return _mappings.stream()
.filter(mappedResource -> mappedResource.getPathSpec().equals(spec)) .filter(mappedResource -> mappedResource.getPathSpec().equals(spec))
.map(mappedResource -> mappedResource.getResource()) .map(MappedResource::getResource)
.findFirst(); .findFirst().orElse(null);
if (!optionalResource.isPresent())
return null;
return optionalResource.get();
} }
public boolean put(String pathSpecString, E resource) public boolean put(String pathSpecString, E resource)

View File

@ -20,6 +20,7 @@ import org.junit.jupiter.params.provider.ValueSource;
import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.notNullValue; import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.nullValue; import static org.hamcrest.Matchers.nullValue;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
@ -456,4 +457,18 @@ public class PathMappingsTest
assertThat(p.remove(new ServletPathSpec("/a/b/c")), is(true)); assertThat(p.remove(new ServletPathSpec("/a/b/c")), is(true));
assertThat(p.remove(new ServletPathSpec("/a/b/c")), is(false)); assertThat(p.remove(new ServletPathSpec("/a/b/c")), is(false));
} }
@Test
public void testAsPathSpec()
{
assertThat(PathMappings.asPathSpec(""), instanceOf(ServletPathSpec.class));
assertThat(PathMappings.asPathSpec("/"), instanceOf(ServletPathSpec.class));
assertThat(PathMappings.asPathSpec("/*"), instanceOf(ServletPathSpec.class));
assertThat(PathMappings.asPathSpec("/foo/*"), instanceOf(ServletPathSpec.class));
assertThat(PathMappings.asPathSpec("*.jsp"), instanceOf(ServletPathSpec.class));
assertThat(PathMappings.asPathSpec("^$"), instanceOf(RegexPathSpec.class));
assertThat(PathMappings.asPathSpec("^.*"), instanceOf(RegexPathSpec.class));
assertThat(PathMappings.asPathSpec("^/"), instanceOf(RegexPathSpec.class));
}
} }

View File

@ -35,6 +35,7 @@ import javax.servlet.annotation.ServletSecurity.TransportGuarantee;
import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.pathmap.MappedResource; import org.eclipse.jetty.http.pathmap.MappedResource;
import org.eclipse.jetty.http.pathmap.PathMappings; import org.eclipse.jetty.http.pathmap.PathMappings;
import org.eclipse.jetty.http.pathmap.PathSpec;
import org.eclipse.jetty.server.HttpConfiguration; import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Response; import org.eclipse.jetty.server.Response;
@ -422,7 +423,7 @@ public class ConstraintSecurityHandler extends SecurityHandler implements Constr
*/ */
protected void processConstraintMapping(ConstraintMapping mapping) protected void processConstraintMapping(ConstraintMapping mapping)
{ {
Map<String, RoleInfo> mappings = _constraintRoles.get(PathMappings.asPathSpec(mapping.getPathSpec())); Map<String, RoleInfo> mappings = _constraintRoles.get(asPathSpec(mapping));
if (mappings == null) if (mappings == null)
{ {
mappings = new HashMap<>(); mappings = new HashMap<>();
@ -467,6 +468,13 @@ public class ConstraintSecurityHandler extends SecurityHandler implements Constr
} }
} }
protected PathSpec asPathSpec(ConstraintMapping mapping)
{
// As currently written, this allows regex patterns to be used.
// This may not be supported by default in future releases.
return PathMappings.asPathSpec(mapping.getPathSpec());
}
/** /**
* Constraints that name method omissions are dealt with differently. * Constraints that name method omissions are dealt with differently.
* We create an entry in the mappings with key "&lt;method&gt;.omission". This entry * We create an entry in the mappings with key "&lt;method&gt;.omission". This entry

View File

@ -1869,6 +1869,44 @@ public class ConstraintTest
assertThat(response, startsWith("HTTP/1.1 403 ")); assertThat(response, startsWith("HTTP/1.1 403 "));
} }
@Test
public void testDefaultConstraint() throws Exception
{
_security.setAuthenticator(new BasicAuthenticator());
ConstraintMapping forbidDefault = new ConstraintMapping();
forbidDefault.setPathSpec("/");
forbidDefault.setConstraint(_forbidConstraint);
_security.addConstraintMapping(forbidDefault);
ConstraintMapping allowRoot = new ConstraintMapping();
allowRoot.setPathSpec("");
allowRoot.setConstraint(_relaxConstraint);
_security.addConstraintMapping(allowRoot);
_server.start();
String response;
response = _connector.getResponse("GET /ctx/ HTTP/1.0\r\n\r\n");
assertThat(response, startsWith("HTTP/1.1 200 OK"));
response = _connector.getResponse("GET /ctx/anything HTTP/1.0\r\n\r\n");
assertThat(response, startsWith("HTTP/1.1 403 Forbidden"));
response = _connector.getResponse("GET /ctx/noauth/info HTTP/1.0\r\n\r\n");
assertThat(response, startsWith("HTTP/1.1 403 Forbidden"));
response = _connector.getResponse("GET /ctx/forbid/info HTTP/1.0\r\n\r\n");
assertThat(response, startsWith("HTTP/1.1 403 Forbidden"));
response = _connector.getResponse("GET /ctx/auth/info HTTP/1.0\r\n\r\n");
assertThat(response, startsWith("HTTP/1.1 401 Unauthorized"));
assertThat(response, containsString("WWW-Authenticate: basic realm=\"TestRealm\""));
response = _connector.getResponse("GET /ctx/admin/relax/info HTTP/1.0\r\n\r\n");
assertThat(response, startsWith("HTTP/1.1 200 OK"));
}
private static String authBase64(String authorization) private static String authBase64(String authorization)
{ {
byte[] raw = authorization.getBytes(ISO_8859_1); byte[] raw = authorization.getBytes(ISO_8859_1);