summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJon Marius Venstad <jonmv@users.noreply.github.com>2019-04-02 20:06:22 +0200
committerGitHub <noreply@github.com>2019-04-02 20:06:22 +0200
commit4a2506315ac3a4f1c2fc89b7f3ffeb9cef3af7c2 (patch)
tree2bc168c1ac37372aae063f09e690e66f17f4f47d
parent6ad65a6ce0b7e56cde45a61db3dfb6314d9e8caf (diff)
parent1111eab23ae0d796496550b466009818f9f73fbc (diff)
Merge pull request #8990 from vespa-engine/jvenstad/consistent-normalising
Jvenstad/consistent normalising
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/http/flags/FlagsHandler.java6
-rw-r--r--container-core/abi-spec.json4
-rw-r--r--container-core/src/main/java/com/yahoo/restapi/Path.java39
-rw-r--r--container-core/src/test/java/com/yahoo/restapi/PathTest.java34
-rw-r--r--container-search-gui/src/main/java/com/yahoo/search/query/gui/GUIHandler.java3
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java10
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/athenz/AthenzApiHandler.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/controller/ControllerApiHandler.java8
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/cost/CostApiHandler.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/deployment/BadgeApiHandler.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/deployment/DeploymentApiHandler.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/AthenzRoleResolver.java1
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/ControllerAuthorizationFilter.java4
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/os/OsApiHandler.java8
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/statuspage/StatusPageProxyHandler.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/user/UserApiHandler.java8
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/zone/v1/ZoneApiHandler.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/zone/v2/ZoneApiHandler.java4
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/role/PathGroup.java10
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/role/Policy.java5
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/role/Role.java5
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/role/RoleMembership.java5
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ApplicationRequestToDiscFilterRequestWrapper.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/role/RoleMembershipTest.java56
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/NodesApiHandler.java4
25 files changed, 138 insertions, 90 deletions
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/http/flags/FlagsHandler.java b/configserver/src/main/java/com/yahoo/vespa/config/server/http/flags/FlagsHandler.java
index 6b07448ee04..00f3d457d3d 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/http/flags/FlagsHandler.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/http/flags/FlagsHandler.java
@@ -36,7 +36,7 @@ public class FlagsHandler extends HttpHandler {
@Override
protected HttpResponse handleGET(HttpRequest request) {
- Path path = new Path(request.getUri().getPath());
+ Path path = new Path(request.getUri());
if (path.matches("/flags/v1")) return new V1Response(flagsV1Uri(request), "data", "defined");
if (path.matches("/flags/v1/data")) return getFlagDataList(request);
if (path.matches("/flags/v1/data/{flagId}")) return getFlagData(findFlagId(request, path));
@@ -47,14 +47,14 @@ public class FlagsHandler extends HttpHandler {
@Override
protected HttpResponse handlePUT(HttpRequest request) {
- Path path = new Path(request.getUri().getPath());
+ Path path = new Path(request.getUri());
if (path.matches("/flags/v1/data/{flagId}")) return putFlagData(request, findFlagId(request, path));
throw new NotFoundException("Nothing at path '" + path + "'");
}
@Override
protected HttpResponse handleDELETE(HttpRequest request) {
- Path path = new Path(request.getUri().getPath());
+ Path path = new Path(request.getUri());
if (path.matches("/flags/v1/data/{flagId}")) return deleteFlagData(findFlagId(request, path));
throw new NotFoundException("Nothing at path '" + path + "'");
}
diff --git a/container-core/abi-spec.json b/container-core/abi-spec.json
index b21a496cd01..81af58b6681 100644
--- a/container-core/abi-spec.json
+++ b/container-core/abi-spec.json
@@ -863,6 +863,8 @@
"methods": [
"public void <init>(java.lang.String)",
"public void <init>(java.lang.String, java.lang.String)",
+ "public void <init>(java.net.URI)",
+ "public void <init>(java.net.URI, java.lang.String)",
"public boolean matches(java.lang.String)",
"public java.lang.String get(java.lang.String)",
"public java.lang.String getRest()",
@@ -871,4 +873,4 @@
],
"fields": []
}
-} \ No newline at end of file
+}
diff --git a/container-core/src/main/java/com/yahoo/restapi/Path.java b/container-core/src/main/java/com/yahoo/restapi/Path.java
index 764fa64f954..fe65245fd15 100644
--- a/container-core/src/main/java/com/yahoo/restapi/Path.java
+++ b/container-core/src/main/java/com/yahoo/restapi/Path.java
@@ -1,17 +1,22 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.restapi;
+import java.net.URI;
+import java.net.URLDecoder;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
+import java.util.stream.Stream;
/**
* A path which is able to match strings containing bracketed placeholders and return the
- * values given at the placeholders.
+ * values given at the placeholders. The path is split on '/', and each part is then URL decoded.
*
- * E.g a path /a/1/bar/fuz
- * will match /a/{foo}/bar/{b}
- * and return foo=1 and b=fuz
+ * E.g a path /a/1/bar/fuz/baz/%62%2f
+ * will match /a/{foo}/bar/{b}/baz/{c}
+ * and return foo=1, b=fuz, and c=c/
*
* Only full path elements may be placeholders, i.e /a{bar} is not interpreted as one.
*
@@ -21,7 +26,7 @@ import java.util.Map;
* Note that for convenience in common use this has state which changes as a side effect of each matches
* invocation. It is therefore for single thread use.
*
- * A optional prefix can be used to match the path spec against an alternative path. This
+ * An optional prefix can be used to match the path spec against an alternative path. This
* is used when you have alternative paths mapped to the same resource.
*
* @author bratseth
@@ -37,18 +42,36 @@ public class Path {
private final Map<String, String> values = new HashMap<>();
private String rest = "";
+ /**
+ * @deprecated use {@link #Path(URI)} for correct handling of URL encoded paths.
+ */
+ @Deprecated
public Path(String path) {
- this.optionalPrefix = "";
- this.pathString = path;
- this.elements = path.split("/");
+ this(path, "");
}
+ /**
+ * @deprecated use {@link #Path(URI, String)} for correct handling of URL encoded paths.
+ */
+ @Deprecated
public Path(String path, String optionalPrefix) {
this.optionalPrefix = optionalPrefix;
this.pathString = path;
this.elements = path.split("/");
}
+ public Path(URI uri) {
+ this(uri, "");
+ }
+
+ public Path(URI uri, String optionalPrefix) {
+ this.optionalPrefix = optionalPrefix;
+ this.pathString = uri.getRawPath();
+ this.elements = Stream.of(this.pathString.split("/"))
+ .map(part -> URLDecoder.decode(part, StandardCharsets.UTF_8))
+ .toArray(String[]::new);
+ }
+
private boolean matchesInner(String pathSpec) {
values.clear();
String[] specElements = pathSpec.split("/");
diff --git a/container-core/src/test/java/com/yahoo/restapi/PathTest.java b/container-core/src/test/java/com/yahoo/restapi/PathTest.java
index 886b3ba9c87..8d5a9bd6591 100644
--- a/container-core/src/test/java/com/yahoo/restapi/PathTest.java
+++ b/container-core/src/test/java/com/yahoo/restapi/PathTest.java
@@ -3,6 +3,8 @@ package com.yahoo.restapi;
import org.junit.Test;
+import java.net.URI;
+
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertEquals;
@@ -15,7 +17,7 @@ public class PathTest {
@Test
public void testWithPrefix() {
// Test that a path with a prefix matches spec without the prefix
- Path path = new Path("/ball/a/1/bar/fuz", "/ball");
+ Path path = new Path(URI.create("/ball/a/1/bar/fuz"), "/ball");
assertTrue(path.matches("/a/{foo}/bar/{b}"));
assertEquals("1", path.get("foo"));
assertEquals("fuz", path.get("b"));
@@ -27,11 +29,11 @@ public class PathTest {
@Test
public void testPath() {
- assertFalse(new Path("").matches("/a/{foo}/bar/{b}"));
- assertFalse(new Path("///").matches("/a/{foo}/bar/{b}"));
- assertFalse(new Path("///foo").matches("/a/{foo}/bar/{b}"));
- assertFalse(new Path("///bar/").matches("/a/{foo}/bar/{b}"));
- Path path = new Path("/a/1/bar/fuz");
+ assertFalse(new Path(URI.create("")).matches("/a/{foo}/bar/{b}"));
+ assertFalse(new Path(URI.create("///")).matches("/a/{foo}/bar/{b}"));
+ assertFalse(new Path(URI.create("///foo")).matches("/a/{foo}/bar/{b}"));
+ assertFalse(new Path(URI.create("///bar/")).matches("/a/{foo}/bar/{b}"));
+ Path path = new Path(URI.create("/a/1/bar/fuz"));
assertTrue(path.matches("/a/{foo}/bar/{b}"));
assertEquals("1", path.get("foo"));
assertEquals("fuz", path.get("b"));
@@ -40,7 +42,7 @@ public class PathTest {
@Test
public void testPathWithRest() {
{
- Path path = new Path("/a/1/bar/fuz/");
+ Path path = new Path(URI.create("/a/1/bar/fuz/"));
assertTrue(path.matches("/a/{foo}/bar/{b}/{*}"));
assertEquals("1", path.get("foo"));
assertEquals("fuz", path.get("b"));
@@ -48,7 +50,7 @@ public class PathTest {
}
{
- Path path = new Path("/a/1/bar/fuz/kanoo");
+ Path path = new Path(URI.create("/a/1/bar/fuz/kanoo"));
assertTrue(path.matches("/a/{foo}/bar/{b}/{*}"));
assertEquals("1", path.get("foo"));
assertEquals("fuz", path.get("b"));
@@ -56,7 +58,7 @@ public class PathTest {
}
{
- Path path = new Path("/a/1/bar/fuz/kanoo/trips");
+ Path path = new Path(URI.create("/a/1/bar/fuz/kanoo/trips"));
assertTrue(path.matches("/a/{foo}/bar/{b}/{*}"));
assertEquals("1", path.get("foo"));
assertEquals("fuz", path.get("b"));
@@ -64,7 +66,7 @@ public class PathTest {
}
{
- Path path = new Path("/a/1/bar/fuz/kanoo/trips/");
+ Path path = new Path(URI.create("/a/1/bar/fuz/kanoo/trips/"));
assertTrue(path.matches("/a/{foo}/bar/{b}/{*}"));
assertEquals("1", path.get("foo"));
assertEquals("fuz", path.get("b"));
@@ -72,4 +74,16 @@ public class PathTest {
}
}
+ @Test
+ public void testUrlEncodedPath() {
+ assertTrue(new Path(URI.create("/a/%62/c")).matches("/a/b/c"));
+ assertTrue(new Path(URI.create("/a/%2e%2e/c")).matches("/a/../c"));
+ assertFalse(new Path(URI.create("/a/b%2fc")).matches("/a/b/c"));
+
+ Path path = new Path(URI.create("/%61/%2f/%63"));
+ assertTrue(path.matches("/a/{slash}/{c}"));
+ assertEquals("/", path.get("slash"));
+ assertEquals("c", path.get("c"));
+ }
+
}
diff --git a/container-search-gui/src/main/java/com/yahoo/search/query/gui/GUIHandler.java b/container-search-gui/src/main/java/com/yahoo/search/query/gui/GUIHandler.java
index 1f47762477f..bafe1dbd43f 100644
--- a/container-search-gui/src/main/java/com/yahoo/search/query/gui/GUIHandler.java
+++ b/container-search-gui/src/main/java/com/yahoo/search/query/gui/GUIHandler.java
@@ -9,6 +9,7 @@ import com.yahoo.container.jdisc.HttpResponse;
import com.yahoo.container.jdisc.LoggingRequestHandler;
import com.yahoo.prelude.IndexModel;
import com.yahoo.prelude.querytransform.RecallSearcher;
+import com.yahoo.restapi.Path;
import com.yahoo.search.Query;
import com.yahoo.search.query.Model;
import com.yahoo.search.query.Presentation;
@@ -69,7 +70,7 @@ public class GUIHandler extends LoggingRequestHandler {
}
private HttpResponse handleGET(HttpRequest request) {
- com.yahoo.restapi.Path path = new com.yahoo.restapi.Path(request.getUri().getPath());
+ Path path = new Path(request.getUri());
if (path.matches("/querybuilder/")) {
return new FileResponse("_includes/index.html", null, null);
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java
index 402f91f1a14..fb27247c48a 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java
@@ -164,7 +164,7 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
}
private HttpResponse handleGET(HttpRequest request) {
- Path path = new Path(request.getUri().getPath(), OPTIONAL_PREFIX);
+ Path path = new Path(request.getUri(), OPTIONAL_PREFIX);
if (path.matches("/application/v4/")) return root(request);
if (path.matches("/application/v4/user")) return authenticatedUser(request);
if (path.matches("/application/v4/tenant")) return tenants(request);
@@ -187,7 +187,7 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
}
private HttpResponse handlePUT(HttpRequest request) {
- Path path = new Path(request.getUri().getPath(), OPTIONAL_PREFIX);
+ Path path = new Path(request.getUri(), OPTIONAL_PREFIX);
if (path.matches("/application/v4/user")) return createUser(request);
if (path.matches("/application/v4/tenant/{tenant}")) return updateTenant(path.get("tenant"), request);
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/environment/{environment}/region/{region}/instance/{instance}/global-rotation/override"))
@@ -196,7 +196,7 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
}
private HttpResponse handlePOST(HttpRequest request) {
- Path path = new Path(request.getUri().getPath(), OPTIONAL_PREFIX);
+ Path path = new Path(request.getUri(), OPTIONAL_PREFIX);
if (path.matches("/application/v4/tenant/{tenant}")) return createTenant(path.get("tenant"), request);
if (path.matches("/application/v4/tenant/{tenant}/application/{application}")) return createApplication(path.get("tenant"), path.get("application"), request);
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/promote")) return promoteApplication(path.get("tenant"), path.get("application"), request);
@@ -215,14 +215,14 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
}
private HttpResponse handlePATCH(HttpRequest request) {
- Path path = new Path(request.getUri().getPath(), OPTIONAL_PREFIX);
+ Path path = new Path(request.getUri(), OPTIONAL_PREFIX);
if (path.matches("/application/v4/tenant/{tenant}/application/{application}"))
return setMajorVersion(path.get("tenant"), path.get("application"), request);
return ErrorResponse.notFoundError("Nothing at " + path);
}
private HttpResponse handleDELETE(HttpRequest request) {
- Path path = new Path(request.getUri().getPath(), OPTIONAL_PREFIX);
+ Path path = new Path(request.getUri(), OPTIONAL_PREFIX);
if (path.matches("/application/v4/tenant/{tenant}")) return deleteTenant(path.get("tenant"), request);
if (path.matches("/application/v4/tenant/{tenant}/application/{application}")) return deleteApplication(path.get("tenant"), path.get("application"), request);
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/deploying")) return cancelDeploy(path.get("tenant"), path.get("application"), "all");
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/athenz/AthenzApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/athenz/AthenzApiHandler.java
index 44164281411..e048c963641 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/athenz/AthenzApiHandler.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/athenz/AthenzApiHandler.java
@@ -60,7 +60,7 @@ public class AthenzApiHandler extends LoggingRequestHandler {
}
private HttpResponse get(HttpRequest request) {
- Path path = new Path(request.getUri().getPath());
+ Path path = new Path(request.getUri());
if (path.matches("/athenz/v1")) return root(request);
if (path.matches("/athenz/v1/domains")) return domainList(request);
if (path.matches("/athenz/v1/properties")) return properties();
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/controller/ControllerApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/controller/ControllerApiHandler.java
index fbbd8724a8c..a59e0e9130f 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/controller/ControllerApiHandler.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/controller/ControllerApiHandler.java
@@ -65,7 +65,7 @@ public class ControllerApiHandler extends AuditLoggingRequestHandler {
}
private HttpResponse get(HttpRequest request) {
- Path path = new Path(request.getUri().getPath());
+ Path path = new Path(request.getUri());
if (path.matches("/controller/v1/")) return root(request);
if (path.matches("/controller/v1/auditlog/")) return new AuditLogResponse(controller.auditLogger().readLog());
if (path.matches("/controller/v1/maintenance/")) return new JobsResponse(maintenance.jobControl());
@@ -74,21 +74,21 @@ public class ControllerApiHandler extends AuditLoggingRequestHandler {
}
private HttpResponse post(HttpRequest request) {
- Path path = new Path(request.getUri().getPath());
+ Path path = new Path(request.getUri());
if (path.matches("/controller/v1/maintenance/inactive/{jobName}")) return setActive(path.get("jobName"), false);
if (path.matches("/controller/v1/jobs/upgrader/confidence/{version}")) return overrideConfidence(request, path.get("version"));
return notFound(path);
}
private HttpResponse delete(HttpRequest request) {
- Path path = new Path(request.getUri().getPath());
+ Path path = new Path(request.getUri());
if (path.matches("/controller/v1/maintenance/inactive/{jobName}")) return setActive(path.get("jobName"), true);
if (path.matches("/controller/v1/jobs/upgrader/confidence/{version}")) return removeConfidenceOverride(path.get("version"));
return notFound(path);
}
private HttpResponse patch(HttpRequest request) {
- Path path = new Path(request.getUri().getPath());
+ Path path = new Path(request.getUri());
if (path.matches("/controller/v1/jobs/upgrader")) return configureUpgrader(request);
return notFound(path);
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/cost/CostApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/cost/CostApiHandler.java
index a82d2f22e74..bae790a49ad 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/cost/CostApiHandler.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/cost/CostApiHandler.java
@@ -35,7 +35,7 @@ public class CostApiHandler extends LoggingRequestHandler {
return ErrorResponse.methodNotAllowed("Method '" + request.getMethod() + "' is not supported");
}
- Path path = new Path(request.getUri().getPath());
+ Path path = new Path(request.getUri());
if (path.matches("/cost/v1/csv")) {
Optional<String> cloudProperty = Optional.ofNullable(request.getProperty("cloud"));
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/deployment/BadgeApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/deployment/BadgeApiHandler.java
index 35fa812851a..0c5cfc539f1 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/deployment/BadgeApiHandler.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/deployment/BadgeApiHandler.java
@@ -54,7 +54,7 @@ public class BadgeApiHandler extends LoggingRequestHandler {
}
private HttpResponse get(HttpRequest request) {
- Path path = new Path(request.getUri().getPath());
+ Path path = new Path(request.getUri());
if (path.matches("/badge/v1/{tenant}/{application}/{instance}")) return badge(path.get("tenant"), path.get("application"), path.get("instance"));
if (path.matches("/badge/v1/{tenant}/{application}/{instance}/{jobName}")) return badge(path.get("tenant"), path.get("application"), path.get("instance"), path.get("jobName"), request.getProperty("historyLength"));
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/deployment/DeploymentApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/deployment/DeploymentApiHandler.java
index 8a5013a9c16..978b7e4397d 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/deployment/DeploymentApiHandler.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/deployment/DeploymentApiHandler.java
@@ -63,7 +63,7 @@ public class DeploymentApiHandler extends LoggingRequestHandler {
}
private HttpResponse handleGET(HttpRequest request) {
- Path path = new Path(request.getUri().getPath());
+ Path path = new Path(request.getUri());
if (path.matches("/deployment/v1/")) return root(request);
return ErrorResponse.notFoundError("Nothing at " + path);
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/AthenzRoleResolver.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/AthenzRoleResolver.java
index 21c9875bb8b..a1dfdbeb245 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/AthenzRoleResolver.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/AthenzRoleResolver.java
@@ -78,6 +78,7 @@ public class AthenzRoleResolver implements RoleMembership.Resolver {
if ( ! (principal instanceof AthenzPrincipal))
throw new IllegalStateException("Expected an AthenzPrincipal to be set on the request.");
+ @SuppressWarnings("deprecation") // TODO: Use URI when refactoring this.
Path path = new Path(uriPath.orElseThrow(() -> new IllegalArgumentException("This resolver needs the request path.")));
path.matches("/application/v4/tenant/{tenant}/{*}");
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/ControllerAuthorizationFilter.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/ControllerAuthorizationFilter.java
index 22747ab8510..dfcc5f732f8 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/ControllerAuthorizationFilter.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/ControllerAuthorizationFilter.java
@@ -59,11 +59,11 @@ public class ControllerAuthorizationFilter extends CorsRequestFilterBase {
Action action = Action.from(HttpRequest.Method.valueOf(request.getMethod()));
// Avoid expensive lookups when request is always legal.
- if (RoleMembership.everyoneIn(controller.system()).allows(action, request.getRequestURI()))
+ if (RoleMembership.everyoneIn(controller.system()).allows(action, request.getUri()))
return Optional.empty();
RoleMembership roles = this.roleResolver.membership(principal, Optional.of(request.getRequestURI()));
- if (roles.allows(action, request.getRequestURI()))
+ if (roles.allows(action, request.getUri()))
return Optional.empty();
}
catch (Exception e) {
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/os/OsApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/os/OsApiHandler.java
index 3c0f515aa8a..73bbcb1c383 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/os/OsApiHandler.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/os/OsApiHandler.java
@@ -66,19 +66,19 @@ public class OsApiHandler extends AuditLoggingRequestHandler {
}
private HttpResponse patch(HttpRequest request) {
- Path path = new Path(request.getUri().getPath());
+ Path path = new Path(request.getUri());
if (path.matches("/os/v1/")) return new SlimeJsonResponse(setOsVersion(request));
return ErrorResponse.notFoundError("Nothing at " + path);
}
private HttpResponse get(HttpRequest request) {
- Path path = new Path(request.getUri().getPath());
+ Path path = new Path(request.getUri());
if (path.matches("/os/v1/")) return new SlimeJsonResponse(osVersions());
return ErrorResponse.notFoundError("Nothing at " + path);
}
private HttpResponse post(HttpRequest request) {
- Path path = new Path(request.getUri().getPath());
+ Path path = new Path(request.getUri());
if (path.matches("/os/v1/firmware/")) return requestFirmwareCheckResponse(path);
if (path.matches("/os/v1/firmware/{environment}/")) return requestFirmwareCheckResponse(path);
if (path.matches("/os/v1/firmware/{environment}/{region}/")) return requestFirmwareCheckResponse(path);
@@ -86,7 +86,7 @@ public class OsApiHandler extends AuditLoggingRequestHandler {
}
private HttpResponse delete(HttpRequest request) {
- Path path = new Path(request.getUri().getPath());
+ Path path = new Path(request.getUri());
if (path.matches("/os/v1/firmware/")) return cancelFirmwareCheckResponse(path);
if (path.matches("/os/v1/firmware/{environment}/")) return cancelFirmwareCheckResponse(path);
if (path.matches("/os/v1/firmware/{environment}/{region}/")) return cancelFirmwareCheckResponse(path);
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/statuspage/StatusPageProxyHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/statuspage/StatusPageProxyHandler.java
index 9021de366cb..1aa883359ee 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/statuspage/StatusPageProxyHandler.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/statuspage/StatusPageProxyHandler.java
@@ -54,7 +54,7 @@ public class StatusPageProxyHandler extends LoggingRequestHandler {
}
private HttpResponse handleGET(HttpRequest request) {
- Path path = new Path(request.getUri().getPath());
+ Path path = new Path(request.getUri());
if (!path.matches("/statuspage/v1/{page}")) {
return ErrorResponse.notFoundError("Nothing at " + path);
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/user/UserApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/user/UserApiHandler.java
index f7693968b43..18b124778d5 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/user/UserApiHandler.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/user/UserApiHandler.java
@@ -48,7 +48,7 @@ public class UserApiHandler extends LoggingRequestHandler {
}
private HttpResponse handleGET(HttpRequest request) {
- Path path = new Path(request.getUri().getPath());
+ Path path = new Path(request.getUri());
return ErrorResponse.notFoundError(String.format("No '%s' handler at '%s'", request.getMethod(),
@@ -56,7 +56,7 @@ public class UserApiHandler extends LoggingRequestHandler {
}
private HttpResponse handlePUT(HttpRequest request) {
- Path path = new Path(request.getUri().getPath());
+ Path path = new Path(request.getUri());
return ErrorResponse.notFoundError(String.format("No '%s' handler at '%s'", request.getMethod(),
@@ -64,7 +64,7 @@ public class UserApiHandler extends LoggingRequestHandler {
}
private HttpResponse handlePOST(HttpRequest request) {
- Path path = new Path(request.getUri().getPath());
+ Path path = new Path(request.getUri());
return ErrorResponse.notFoundError(String.format("No '%s' handler at '%s'", request.getMethod(),
@@ -72,7 +72,7 @@ public class UserApiHandler extends LoggingRequestHandler {
}
private HttpResponse handleDELETE(HttpRequest request) {
- Path path = new Path(request.getUri().getPath());
+ Path path = new Path(request.getUri());
return ErrorResponse.notFoundError(String.format("No '%s' handler at '%s'", request.getMethod(),
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/zone/v1/ZoneApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/zone/v1/ZoneApiHandler.java
index 6f014e5661f..94a2004197a 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/zone/v1/ZoneApiHandler.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/zone/v1/ZoneApiHandler.java
@@ -54,7 +54,7 @@ public class ZoneApiHandler extends LoggingRequestHandler {
}
private HttpResponse get(HttpRequest request) {
- Path path = new Path(request.getUri().getPath());
+ Path path = new Path(request.getUri());
if (path.matches("/zone/v1")) {
return root(request);
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/zone/v2/ZoneApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/zone/v2/ZoneApiHandler.java
index 377a57fbb91..65f0abb16c8 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/zone/v2/ZoneApiHandler.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/zone/v2/ZoneApiHandler.java
@@ -66,7 +66,7 @@ public class ZoneApiHandler extends AuditLoggingRequestHandler {
}
private HttpResponse get(HttpRequest request) {
- Path path = new Path(request.getUri().getPath());
+ Path path = new Path(request.getUri());
if (path.matches("/zone/v2")) {
return root(request);
}
@@ -74,7 +74,7 @@ public class ZoneApiHandler extends AuditLoggingRequestHandler {
}
private HttpResponse proxy(HttpRequest request) {
- Path path = new Path(request.getUri().getPath());
+ Path path = new Path(request.getUri());
if ( ! path.matches("/zone/v2/{environment}/{region}/{*}")) {
return notFound(path);
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/role/PathGroup.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/role/PathGroup.java
index e3d81e04591..fdcd70fc0d1 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/role/PathGroup.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/role/PathGroup.java
@@ -3,6 +3,7 @@ package com.yahoo.vespa.hosted.controller.role;
import com.yahoo.restapi.Path;
+import java.net.URI;
import java.util.EnumSet;
import java.util.List;
import java.util.Optional;
@@ -127,8 +128,9 @@ public enum PathGroup {
}
/** Returns path if it matches any spec in this group, with match groups set by the match. */
- private Optional<Path> get(String path) {
- Path matcher = new Path(path);
+ @SuppressWarnings("deprecation")
+ private Optional<Path> get(URI uri) {
+ Path matcher = new Path(uri); // TODO Get URI down here.
for (String spec : pathSpecs) // Iterate to be sure the Path's state is that of the match.
if (matcher.matches(spec)) return Optional.of(matcher);
return Optional.empty();
@@ -140,8 +142,8 @@ public enum PathGroup {
}
/** Returns whether this group matches path in given context */
- public boolean matches(String path, Context context) {
- return get(path).map(p -> {
+ public boolean matches(URI uri, Context context) {
+ return get(uri).map(p -> {
boolean match = true;
String tenant = p.get(Matcher.tenant.name);
if (tenant != null && context.tenant().isPresent()) {
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/role/Policy.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/role/Policy.java
index 85702ac1b89..6ae68f598f0 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/role/Policy.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/role/Policy.java
@@ -5,6 +5,7 @@ import com.yahoo.config.provision.ApplicationName;
import com.yahoo.config.provision.SystemName;
import com.yahoo.config.provision.TenantName;
+import java.net.URI;
import java.util.Set;
/**
@@ -114,11 +115,11 @@ public enum Policy {
}
/** Returns whether action is allowed on path in given context */
- public boolean evaluate(Action action, String path, Context context) {
+ public boolean evaluate(Action action, URI uri, Context context) {
return privileges.stream().anyMatch(privilege -> privilege.actions().contains(action) &&
privilege.systems().contains(context.system()) &&
privilege.pathGroups().stream()
- .anyMatch(pg -> pg.matches(path, context)));
+ .anyMatch(pg -> pg.matches(uri, context)));
}
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/role/Role.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/role/Role.java
index cae143a92a2..d82e4063391 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/role/Role.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/role/Role.java
@@ -1,6 +1,7 @@
// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.role;
+import java.net.URI;
import java.util.EnumSet;
import java.util.Set;
@@ -85,8 +86,8 @@ public enum Role {
* Returns whether this role is allowed to perform action in given role context. Action is allowed if at least one
* policy evaluates to true.
*/
- public boolean allows(Action action, String path, Context context) {
- return policies.stream().anyMatch(policy -> policy.evaluate(action, path, context));
+ public boolean allows(Action action, URI uri, Context context) {
+ return policies.stream().anyMatch(policy -> policy.evaluate(action, uri, context));
}
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/role/RoleMembership.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/role/RoleMembership.java
index 2ee59d90a63..09e66528913 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/role/RoleMembership.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/role/RoleMembership.java
@@ -5,6 +5,7 @@ import com.yahoo.config.provision.ApplicationName;
import com.yahoo.config.provision.SystemName;
import com.yahoo.config.provision.TenantName;
+import java.net.URI;
import java.security.Principal;
import java.util.Collections;
import java.util.HashMap;
@@ -39,11 +40,11 @@ public class RoleMembership {
public static Builder in(SystemName system) { return new BuilderWithRole(system); }
/** Returns whether any role in this allows action to take place in path */
- public boolean allows(Action action, String path) {
+ public boolean allows(Action action, URI uri) {
return roles.entrySet().stream().anyMatch(kv -> {
Role role = kv.getKey();
Set<Context> contexts = kv.getValue();
- return contexts.stream().anyMatch(context -> role.allows(action, path, context));
+ return contexts.stream().anyMatch(context -> role.allows(action, uri, context));
});
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ApplicationRequestToDiscFilterRequestWrapper.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ApplicationRequestToDiscFilterRequestWrapper.java
index f2dc22c9c60..7cc00f1ad52 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ApplicationRequestToDiscFilterRequestWrapper.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ApplicationRequestToDiscFilterRequestWrapper.java
@@ -48,7 +48,7 @@ public class ApplicationRequestToDiscFilterRequestWrapper extends DiscFilterRequ
@Override
public URI getUri() {
- return URI.create(request.getUri());
+ return URI.create(request.getUri()).normalize(); // Consistent with what JDisc does.
}
@Override
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/role/RoleMembershipTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/role/RoleMembershipTest.java
index 972ddefb1a5..1da5d3764f6 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/role/RoleMembershipTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/role/RoleMembershipTest.java
@@ -6,6 +6,8 @@ import com.yahoo.config.provision.SystemName;
import com.yahoo.config.provision.TenantName;
import org.junit.Test;
+import java.net.URI;
+
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@@ -21,11 +23,11 @@ public class RoleMembershipTest {
.build();
// Operator actions
- assertFalse(roles.allows(Action.create, "/not/explicitly/defined"));
- assertTrue(roles.allows(Action.create, "/controller/v1/foo"));
- assertTrue(roles.allows(Action.update, "/os/v1/bar"));
- assertTrue(roles.allows(Action.update, "/application/v4/tenant/t1/application/a1"));
- assertTrue(roles.allows(Action.update, "/application/v4/tenant/t2/application/a2"));
+ assertFalse(roles.allows(Action.create, URI.create("/not/explicitly/defined")));
+ assertTrue(roles.allows(Action.create, URI.create("/controller/v1/foo")));
+ assertTrue(roles.allows(Action.update, URI.create("/os/v1/bar")));
+ assertTrue(roles.allows(Action.update, URI.create("/application/v4/tenant/t1/application/a1")));
+ assertTrue(roles.allows(Action.update, URI.create("/application/v4/tenant/t2/application/a2")));
}
@Test
@@ -33,35 +35,35 @@ public class RoleMembershipTest {
RoleMembership roles = RoleMembership.in(SystemName.main)
.add(Role.athenzTenantAdmin).limitedTo(TenantName.from("t1"), ApplicationName.from("a1"))
.build();
- assertFalse(roles.allows(Action.create, "/not/explicitly/defined"));
- assertFalse("Deny access to operator API", roles.allows(Action.create, "/controller/v1/foo"));
- assertFalse("Deny access to other tenant and app", roles.allows(Action.update, "/application/v4/tenant/t2/application/a2"));
- assertFalse("Deny access to other app", roles.allows(Action.update, "/application/v4/tenant/t1/application/a2"));
- assertTrue(roles.allows(Action.update, "/application/v4/tenant/t1/application/a1"));
+ assertFalse(roles.allows(Action.create, URI.create("/not/explicitly/defined")));
+ assertFalse("Deny access to operator API", roles.allows(Action.create, URI.create("/controller/v1/foo")));
+ assertFalse("Deny access to other tenant and app", roles.allows(Action.update, URI.create("/application/v4/tenant/t2/application/a2")));
+ assertFalse("Deny access to other app", roles.allows(Action.update, URI.create("/application/v4/tenant/t1/application/a2")));
+ assertTrue(roles.allows(Action.update, URI.create("/application/v4/tenant/t1/application/a1")));
RoleMembership multiContext = RoleMembership.in(SystemName.main)
.add(Role.athenzTenantAdmin).limitedTo(TenantName.from("t1"), ApplicationName.from("a1"))
.add(Role.athenzTenantAdmin).limitedTo(TenantName.from("t2"), ApplicationName.from("a2"))
.build();
- assertFalse("Deny access to other tenant and app", multiContext.allows(Action.update, "/application/v4/tenant/t3/application/a3"));
- assertTrue(multiContext.allows(Action.update, "/application/v4/tenant/t2/application/a2"));
- assertTrue(multiContext.allows(Action.update, "/application/v4/tenant/t1/application/a1"));
+ assertFalse("Deny access to other tenant and app", multiContext.allows(Action.update, URI.create("/application/v4/tenant/t3/application/a3")));
+ assertTrue(multiContext.allows(Action.update, URI.create("/application/v4/tenant/t2/application/a2")));
+ assertTrue(multiContext.allows(Action.update, URI.create("/application/v4/tenant/t1/application/a1")));
RoleMembership publicSystem = RoleMembership.in(SystemName.vaas)
.add(Role.athenzTenantAdmin).limitedTo(TenantName.from("t1"), ApplicationName.from("a1"))
.build();
- assertFalse(publicSystem.allows(Action.read, "/controller/v1/foo"));
- assertTrue(multiContext.allows(Action.update, "/application/v4/tenant/t1/application/a1"));
+ assertFalse(publicSystem.allows(Action.read, URI.create("/controller/v1/foo")));
+ assertTrue(multiContext.allows(Action.update, URI.create("/application/v4/tenant/t1/application/a1")));
}
@Test
public void build_service_membership() {
RoleMembership roles = RoleMembership.in(SystemName.main)
.add(Role.tenantPipeline).build();
- assertFalse(roles.allows(Action.create, "/not/explicitly/defined"));
- assertFalse(roles.allows(Action.update, "/application/v4/tenant/t1/application/a1"));
- assertTrue(roles.allows(Action.create, "/application/v4/tenant/t1/application/a1/jobreport"));
- assertFalse("No global read access", roles.allows(Action.read, "/controller/v1/foo"));
+ assertFalse(roles.allows(Action.create, URI.create("/not/explicitly/defined")));
+ assertFalse(roles.allows(Action.update, URI.create("/application/v4/tenant/t1/application/a1")));
+ assertTrue(roles.allows(Action.create, URI.create("/application/v4/tenant/t1/application/a1/jobreport")));
+ assertFalse("No global read access", roles.allows(Action.read, URI.create("/controller/v1/foo")));
}
@Test
@@ -71,14 +73,14 @@ public class RoleMembershipTest {
.add(Role.tenantPipeline)
.add(Role.everyone)
.build();
- assertFalse(roles.allows(Action.create, "/not/explicitly/defined"));
- assertFalse(roles.allows(Action.create, "/controller/v1/foo"));
- assertTrue(roles.allows(Action.create, "/application/v4/tenant/t1/application/a1/jobreport"));
- assertTrue(roles.allows(Action.update, "/application/v4/tenant/t1/application/a1"));
- assertTrue("Global read access", roles.allows(Action.read, "/controller/v1/foo"));
- assertTrue("Dashboard read access", roles.allows(Action.read, "/"));
- assertTrue("Dashboard read access", roles.allows(Action.read, "/d/nodes"));
- assertTrue("Dashboard read access", roles.allows(Action.read, "/statuspage/v1/incidents"));
+ assertFalse(roles.allows(Action.create, URI.create("/not/explicitly/defined")));
+ assertFalse(roles.allows(Action.create, URI.create("/controller/v1/foo")));
+ assertTrue(roles.allows(Action.create, URI.create("/application/v4/tenant/t1/application/a1/jobreport")));
+ assertTrue(roles.allows(Action.update, URI.create("/application/v4/tenant/t1/application/a1")));
+ assertTrue("Global read access", roles.allows(Action.read, URI.create("/controller/v1/foo")));
+ assertTrue("Dashboard read access", roles.allows(Action.read, URI.create("/")));
+ assertTrue("Dashboard read access", roles.allows(Action.read, URI.create("/d/nodes")));
+ assertTrue("Dashboard read access", roles.allows(Action.read, URI.create("/statuspage/v1/incidents")));
}
}
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/NodesApiHandler.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/NodesApiHandler.java
index 9c3e83b9f5a..58125304d6f 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/NodesApiHandler.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/NodesApiHandler.java
@@ -155,7 +155,7 @@ public class NodesApiHandler extends LoggingRequestHandler {
}
private HttpResponse handlePOST(HttpRequest request) {
- Path path = new Path(request.getUri().getPath());
+ Path path = new Path(request.getUri());
if (path.matches("/nodes/v2/command/restart")) {
int restartCount = nodeRepository.restart(toNodeFilter(request)).size();
return new MessageResponse("Scheduled restart of " + restartCount + " matching nodes");
@@ -177,7 +177,7 @@ public class NodesApiHandler extends LoggingRequestHandler {
}
private HttpResponse handleDELETE(HttpRequest request) {
- Path path = new Path(request.getUri().getPath());
+ Path path = new Path(request.getUri());
if (path.matches("/nodes/v2/node/{hostname}")) {
String hostname = path.get("hostname");
List<Node> removedNodes = nodeRepository.removeRecursively(hostname);