summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorØyvind Grønnesby <oyving@verizonmedia.com>2021-07-16 10:23:12 +0200
committerGitHub <noreply@github.com>2021-07-16 10:23:12 +0200
commit236c15593258ec5e0d495332edc64468df269fc4 (patch)
tree97920ab02f74f18cda048c8d6914cc1137295f99
parent73d916d2164c9654366cff7e96f5a5687bd23f59 (diff)
parentf449a4f2d1e477afcc30050f6f72f2cb996f26ae (diff)
Merge pull request #18620 from vespa-engine/bjorncs/restapi
Bjorncs/restapi
-rw-r--r--container-core/src/main/java/com/yahoo/restapi/RestApi.java3
-rw-r--r--container-core/src/main/java/com/yahoo/restapi/RestApiException.java6
-rw-r--r--container-core/src/main/java/com/yahoo/restapi/RestApiImpl.java7
-rw-r--r--container-core/src/main/java/com/yahoo/restapi/RestApiMappers.java12
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/athenz/AthenzApiHandler.java92
5 files changed, 54 insertions, 66 deletions
diff --git a/container-core/src/main/java/com/yahoo/restapi/RestApi.java b/container-core/src/main/java/com/yahoo/restapi/RestApi.java
index 2ef14679553..d15a94bea30 100644
--- a/container-core/src/main/java/com/yahoo/restapi/RestApi.java
+++ b/container-core/src/main/java/com/yahoo/restapi/RestApi.java
@@ -8,6 +8,7 @@ import com.yahoo.container.jdisc.HttpResponse;
import com.yahoo.container.jdisc.RequestHandlerSpec;
import java.io.InputStream;
+import java.security.Principal;
import java.util.List;
import java.util.Optional;
import java.util.OptionalDouble;
@@ -129,6 +130,8 @@ public interface RestApi {
ObjectMapper jacksonJsonMapper();
UriBuilder uriBuilder();
AclMapping.Action aclAction();
+ Optional<Principal> userPrincipal();
+ Principal userPrincipalOrThrow();
interface Parameters {
Optional<String> getString(String name);
diff --git a/container-core/src/main/java/com/yahoo/restapi/RestApiException.java b/container-core/src/main/java/com/yahoo/restapi/RestApiException.java
index 68e46a3a9b8..747cf1cedeb 100644
--- a/container-core/src/main/java/com/yahoo/restapi/RestApiException.java
+++ b/container-core/src/main/java/com/yahoo/restapi/RestApiException.java
@@ -78,4 +78,10 @@ public class RestApiException extends RuntimeException {
public Conflict(String message) { this(message, null); }
public Conflict(String message, Throwable cause) { super(ErrorResponse::conflict, message, cause); }
}
+
+ public static class Unauthorized extends RestApiException {
+ public Unauthorized() { this("Unauthorized", null); }
+ public Unauthorized(String message) { this(message, null); }
+ public Unauthorized(String message, Throwable cause) { super(ErrorResponse::unauthorized, message, cause); }
+ }
}
diff --git a/container-core/src/main/java/com/yahoo/restapi/RestApiImpl.java b/container-core/src/main/java/com/yahoo/restapi/RestApiImpl.java
index 646177e60db..fda08342ead 100644
--- a/container-core/src/main/java/com/yahoo/restapi/RestApiImpl.java
+++ b/container-core/src/main/java/com/yahoo/restapi/RestApiImpl.java
@@ -14,6 +14,7 @@ import com.yahoo.restapi.RestApiMappers.ResponseMapperHolder;
import java.io.InputStream;
import java.net.URI;
+import java.security.Principal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -405,6 +406,12 @@ class RestApiImpl implements RestApi {
: new UriBuilder(uri.getScheme() + "://" + uri.getHost());
}
@Override public AclMapping.Action aclAction() { return aclAction; }
+ @Override public Optional<Principal> userPrincipal() {
+ return Optional.ofNullable(request.getJDiscRequest().getUserPrincipal());
+ }
+ @Override public Principal userPrincipalOrThrow() {
+ return userPrincipal().orElseThrow(RestApiException.Unauthorized::new);
+ }
private class PathParametersImpl implements RestApi.RequestContext.PathParameters {
@Override
diff --git a/container-core/src/main/java/com/yahoo/restapi/RestApiMappers.java b/container-core/src/main/java/com/yahoo/restapi/RestApiMappers.java
index 36d98421e6a..87574e6af9a 100644
--- a/container-core/src/main/java/com/yahoo/restapi/RestApiMappers.java
+++ b/container-core/src/main/java/com/yahoo/restapi/RestApiMappers.java
@@ -29,7 +29,7 @@ public class RestApiMappers {
private static final Logger log = Logger.getLogger(RestApiMappers.class.getName());
- public static List<RequestMapperHolder<?>> DEFAULT_REQUEST_MAPPERS = List.of(
+ static List<RequestMapperHolder<?>> DEFAULT_REQUEST_MAPPERS = List.of(
new RequestMapperHolder<>(Slime.class, RestApiMappers::toSlime),
new RequestMapperHolder<>(JsonNode.class, ctx -> toJsonNode(ctx, ctx.jacksonJsonMapper())),
new RequestMapperHolder<>(String.class, RestApiMappers::toString),
@@ -37,14 +37,14 @@ public class RestApiMappers {
new RequestMapperHolder<>(InputStream .class, RestApiMappers::toInputStream),
new RequestMapperHolder<>(Void.class, ctx -> Optional.empty()));
- public static List<ResponseMapperHolder<?>> DEFAULT_RESPONSE_MAPPERS = List.of(
+ static List<ResponseMapperHolder<?>> DEFAULT_RESPONSE_MAPPERS = List.of(
new ResponseMapperHolder<>(HttpResponse.class, (context, entity) -> entity),
new ResponseMapperHolder<>(String.class, (context, entity) -> new MessageResponse(entity)),
new ResponseMapperHolder<>(Slime.class, (context, entity) -> new SlimeJsonResponse(entity)),
new ResponseMapperHolder<>(JsonNode.class,
(context, entity) -> new JacksonJsonResponse<>(200, entity, context.jacksonJsonMapper(), true)));
- public static List<ExceptionMapperHolder<?>> DEFAULT_EXCEPTION_MAPPERS = List.of(
+ static List<ExceptionMapperHolder<?>> DEFAULT_EXCEPTION_MAPPERS = List.of(
new ExceptionMapperHolder<>(RestApiException.class, (context, exception) -> exception.response()));
private RestApiMappers() {}
@@ -75,7 +75,7 @@ public class RestApiMappers {
}
}
- public static class RequestMapperHolder<ENTITY> {
+ static class RequestMapperHolder<ENTITY> {
final Class<ENTITY> type;
final RestApi.RequestMapper<ENTITY> mapper;
@@ -85,7 +85,7 @@ public class RestApiMappers {
}
}
- public static class ResponseMapperHolder<ENTITY> {
+ static class ResponseMapperHolder<ENTITY> {
final Class<ENTITY> type;
final RestApi.ResponseMapper<ENTITY> mapper;
@@ -99,7 +99,7 @@ public class RestApiMappers {
}
}
- public static class ExceptionMapperHolder<EXCEPTION extends RuntimeException> {
+ static class ExceptionMapperHolder<EXCEPTION extends RuntimeException> {
final Class<EXCEPTION> type;
final RestApi.ExceptionMapper<EXCEPTION> mapper;
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 9ac94d0208c..621ce9189e9 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
@@ -1,19 +1,14 @@
// 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.restapi.athenz;
+import com.google.inject.Inject;
import com.yahoo.config.provision.SystemName;
-import com.yahoo.container.jdisc.HttpRequest;
import com.yahoo.container.jdisc.HttpResponse;
-import com.yahoo.container.jdisc.LoggingRequestHandler;
-import com.yahoo.jdisc.http.HttpRequest.Method;
-import com.yahoo.restapi.ErrorResponse;
-import com.yahoo.restapi.MessageResponse;
-import com.yahoo.restapi.Path;
import com.yahoo.restapi.ResourceResponse;
-import com.yahoo.restapi.SlimeJsonResponse;
+import com.yahoo.restapi.RestApi;
+import com.yahoo.restapi.RestApiRequestHandler;
import com.yahoo.slime.Cursor;
import com.yahoo.slime.Slime;
-import com.yahoo.text.Text;
import com.yahoo.vespa.athenz.api.AthenzDomain;
import com.yahoo.vespa.athenz.api.AthenzPrincipal;
import com.yahoo.vespa.athenz.api.AthenzUser;
@@ -22,20 +17,19 @@ import com.yahoo.vespa.hosted.controller.api.identifiers.Property;
import com.yahoo.vespa.hosted.controller.api.identifiers.PropertyId;
import com.yahoo.vespa.hosted.controller.api.integration.entity.EntityService;
import com.yahoo.vespa.hosted.controller.athenz.impl.AthenzFacade;
-import com.yahoo.yolean.Exceptions;
import java.util.Map;
-import java.util.Optional;
-import java.util.logging.Level;
import java.util.logging.Logger;
+import static com.yahoo.restapi.RestApi.route;
+
/**
* This API proxies requests to an Athenz server.
*
* @author jonmv
*/
@SuppressWarnings("unused") // Handler
-public class AthenzApiHandler extends LoggingRequestHandler {
+public class AthenzApiHandler extends RestApiRequestHandler<AthenzApiHandler> {
private final static Logger log = Logger.getLogger(AthenzApiHandler.class.getName());
@@ -43,55 +37,32 @@ public class AthenzApiHandler extends LoggingRequestHandler {
private final AthenzDomain sandboxDomain;
private final EntityService properties;
+ @Inject
public AthenzApiHandler(Context parentCtx, AthenzFacade athenz, Controller controller) {
- super(parentCtx);
+ super(parentCtx, AthenzApiHandler::createRestApi);
this.athenz = athenz;
this.sandboxDomain = new AthenzDomain(sandboxDomainIn(controller.system()));
this.properties = controller.serviceRegistry().entityService();
}
- @Override
- public HttpResponse handle(HttpRequest request) {
- Method method = request.getMethod();
- try {
- switch (method) {
- case GET: return get(request);
- case POST: return post(request);
- default: return ErrorResponse.methodNotAllowed("Method '" + method + "' is unsupported");
- }
- }
- catch (IllegalArgumentException|IllegalStateException e) {
- return ErrorResponse.badRequest(Exceptions.toMessageString(e));
- }
- catch (RuntimeException e) {
- log.log(Level.WARNING, "Unexpected error handling '" + request.getUri() + "'", e);
- return ErrorResponse.internalServerError(Exceptions.toMessageString(e));
- }
- }
-
- private HttpResponse get(HttpRequest request) {
- 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();
-
- return ErrorResponse.notFoundError(Text.format("No '%s' handler at '%s'", request.getMethod(),
- request.getUri().getPath()));
+ private static RestApi createRestApi(AthenzApiHandler self) {
+ return RestApi.builder()
+ .addRoute(route("/athenz/v1")
+ .get(self::root))
+ .addRoute(route("/athenz/v1/domains")
+ .get(self::domainList))
+ .addRoute(route("/athenz/v1/properties")
+ .get(self::properties))
+ .addRoute(route("/athenz/v1/user")
+ .post(self::signup))
+ .build();
}
- private HttpResponse post(HttpRequest request) {
- Path path = new Path(request.getUri());
- if (path.matches("/athenz/v1/user")) return signup(request);
- return ErrorResponse.notFoundError(Text.format("No '%s' handler at '%s'", request.getMethod(),
- request.getUri().getPath()));
+ private HttpResponse root(RestApi.RequestContext ctx) {
+ return new ResourceResponse(ctx.request(), "domains", "properties");
}
- private HttpResponse root(HttpRequest request) {
- return new ResourceResponse(request, "domains", "properties");
- }
-
-
- private HttpResponse properties() {
+ private Slime properties(RestApi.RequestContext ctx) {
Slime slime = new Slime();
Cursor response = slime.setObject();
Cursor array = response.setArray("properties");
@@ -100,26 +71,27 @@ public class AthenzApiHandler extends LoggingRequestHandler {
propertyObject.setString("propertyid", entry.getKey().id());
propertyObject.setString("property", entry.getValue().id());
}
- return new SlimeJsonResponse(slime);
+ return slime;
}
- private HttpResponse domainList(HttpRequest request) {
+ private Slime domainList(RestApi.RequestContext ctx) {
Slime slime = new Slime();
Cursor array = slime.setObject().setArray("data");
- for (AthenzDomain athenzDomain : athenz.getDomainList(request.getProperty("prefix")))
+ for (AthenzDomain athenzDomain : athenz.getDomainList(ctx.queryParameters().getString("prefix").orElse(null)))
array.addString(athenzDomain.getName());
- return new SlimeJsonResponse(slime);
+ return slime;
}
- private HttpResponse signup(HttpRequest request) {
- AthenzUser user = athenzUser(request);
+ private String signup(RestApi.RequestContext ctx) {
+ AthenzUser user = athenzUser(ctx);
athenz.addTenantAdmin(sandboxDomain, user);
- return new MessageResponse("User '" + user.getName() + "' added to admin role of '" + sandboxDomain.getName() + "'");
+ return "User '" + user.getName() + "' added to admin role of '" + sandboxDomain.getName() + "'";
}
- private static AthenzUser athenzUser(HttpRequest request) {
- return Optional.ofNullable(request.getJDiscRequest().getUserPrincipal()).filter(AthenzPrincipal.class::isInstance)
+ private static AthenzUser athenzUser(RestApi.RequestContext ctx) {
+ return ctx.userPrincipal()
+ .filter(AthenzPrincipal.class::isInstance)
.map(AthenzPrincipal.class::cast)
.map(AthenzPrincipal::getIdentity)
.filter(AthenzUser.class::isInstance)