Skip to content

Commit

Permalink
Merge branch 'master' into ian-UID2-1974-back-fill-site-created
Browse files Browse the repository at this point in the history
  • Loading branch information
Ian-Nara committed Oct 5, 2023
2 parents 21e7258 + e640f8a commit 4d30915
Show file tree
Hide file tree
Showing 23 changed files with 545 additions and 69 deletions.
14 changes: 13 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,16 @@ When running locally, GitHub OAuth 2.0 is disabled and users are logged in as *t
`is_auth_disabled` flag. The user has all the rights available. To change the user rights, make changes to
`src/main/resources/localstack/s3/admins/admins.json` and `docker-compose restart`.

If you want to test with GitHub OAuth 2.0, you will need to create an OAuth application on GitHub with `http://localhost:8089/oauth2-callback` as the callback URL, then generate a client ID/secret. Once generated, set the `is_auth_disabled` flag to `false`, and copy the client ID/secret into `github_client_id` and `github_client_secret`.
If you want to test with GitHub OAuth 2.0, you will need to create an OAuth application on GitHub with `http://localhost:8089/oauth2-callback` as the callback URL, then generate a client ID/secret. Once generated, set the `is_auth_disabled` flag to `false`, and copy the client ID/secret into `github_client_id` and `github_client_secret`.

## V2 API

The v2 API is based on individual route provider classes. Each class should provide exactly one endpoint and must implement IRouteProvider or IBlockingRouteProvider.

### IRouteProvider

**Caution:** When implementing an API endpoint, you need to decide whether you should have a blocking or a non-blocking handler. Non-blocking handlers are suitable for most read-only operations, while most write operations should be done on a blocking handler. If you are calling into a service with a `synchronized` block, you **must** use a blocking handler. You can make your handler blocking by implementing the `IBlockingRouteProvider` interface *instead of* the `IRouteProvider` interface.

IRouteProvider requires a `getHandler` method, which should return a valid handler function - see `GetClientSideKeypairsBySite.java`. This method *must* be annotated with the Path, Method, and Roles annotations.

The route handler will automatically be wrapped by the Auth middleware based on the roles specified in the Roles annotation.
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
<!-- check micrometer.version vertx-micrometer-metrics consumes before bumping up -->
<micrometer.version>1.1.0</micrometer.version>
<junit-jupiter.version>5.7.0</junit-jupiter.version>
<uid2-shared.version>5.9.6-62621be878</uid2-shared.version>
<uid2-shared.version>5.11.0-d71d3c960e</uid2-shared.version>
<image.version>${project.version}</image.version>
</properties>
<repositories>
Expand Down
9 changes: 7 additions & 2 deletions src/main/java/com/uid2/admin/Main.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import com.uid2.admin.vertx.AdminVerticle;
import com.uid2.admin.vertx.JsonUtil;
import com.uid2.admin.vertx.WriteLock;
import com.uid2.admin.vertx.api.V2RouterModule;
import com.uid2.admin.vertx.service.*;
import com.uid2.shared.Const;
import com.uid2.shared.Utils;
Expand Down Expand Up @@ -52,6 +53,7 @@
import io.vertx.core.http.HttpServerOptions;
import io.vertx.core.http.impl.HttpUtils;
import io.vertx.core.json.JsonObject;
import lombok.val;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.vertx.micrometer.Label;
Expand Down Expand Up @@ -233,6 +235,7 @@ public void run() {
JobDispatcher jobDispatcher = new JobDispatcher("job-dispatcher", 1000 * 60, 3, clock);
jobDispatcher.start();

val clientSideKeypairService = new ClientSideKeypairService(config, auth, writeLock, clientSideKeypairStoreWriter, clientSideKeypairProvider, siteProvider, keysetManager, keypairGenerator, clock);

IService[] services = {
new AdminKeyService(config, auth, writeLock, adminUserStoreWriter, adminUserProvider, keyGenerator, keyHasher, clientKeyStoreWriter, encryptionKeyStoreWriter, keyAclStoreWriter),
Expand All @@ -241,7 +244,7 @@ public void run() {
encryptionKeyService,
new KeyAclService(auth, writeLock, keyAclStoreWriter, keyAclProvider, siteProvider, encryptionKeyService),
new SharingService(auth, writeLock, adminKeysetProvider, keysetManager, siteProvider, enableKeysets),
new ClientSideKeypairService(config, auth, writeLock, clientSideKeypairStoreWriter, clientSideKeypairProvider, siteProvider, keysetManager, keypairGenerator, clock),
clientSideKeypairService,
new ServiceService(auth, writeLock, serviceStoreWriter, serviceProvider, siteProvider),
new ServiceLinkService(auth, writeLock, serviceLinkStoreWriter, serviceLinkProvider, serviceProvider, siteProvider),
new OperatorKeyService(config, auth, writeLock, operatorKeyStoreWriter, operatorKeyProvider, siteProvider, keyGenerator, keyHasher),
Expand All @@ -257,7 +260,9 @@ public void run() {
"admins", 10000, adminUserProvider);
vertx.deployVerticle(rotatingAdminUserStoreVerticle);

AdminVerticle adminVerticle = new AdminVerticle(config, authFactory, adminUserProvider, services);
val v2RouterModule = new V2RouterModule(clientSideKeypairService, auth);

AdminVerticle adminVerticle = new AdminVerticle(config, authFactory, adminUserProvider, services, v2RouterModule.getRouter());
vertx.deployVerticle(adminVerticle);

CloudPath keysetMetadataPath = new CloudPath(config.getString("keysets_metadata_path"));
Expand Down
3 changes: 2 additions & 1 deletion src/main/java/com/uid2/admin/secret/IKeypairManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@
import com.uid2.shared.model.ClientSideKeypair;

public interface IKeypairManager {
ClientSideKeypair createAndSaveSiteKeypair(int siteId, String contact, boolean disabled) throws Exception;
ClientSideKeypair createAndSaveSiteKeypair(int siteId, String contact, boolean disabled, String name) throws Exception;
Iterable<ClientSideKeypair> getKeypairsBySite(int siteId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ private static JsonObject toJson(ClientSideKeypair keypair, boolean includePriva
jo.put("contact", keypair.getContact());
jo.put("created", keypair.getCreated().getEpochSecond());
jo.put("disabled", keypair.isDisabled());
jo.put("name", keypair.getName());
return jo;
}

Expand Down
11 changes: 10 additions & 1 deletion src/main/java/com/uid2/admin/vertx/AdminVerticle.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@

import com.fasterxml.jackson.databind.ObjectWriter;
import com.uid2.admin.auth.*;
import com.uid2.admin.vertx.api.V2Router;
import com.uid2.admin.vertx.service.IService;
import com.uid2.shared.Const;
import com.uid2.shared.Utils;
import io.vertx.core.AbstractVerticle;
import io.vertx.core.Promise;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;
import lombok.val;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.vertx.ext.auth.authentication.TokenCredentials;
Expand All @@ -29,16 +31,19 @@ public class AdminVerticle extends AbstractVerticle {
private final AuthFactory authFactory;
private final IAdminUserProvider adminUserProvider;
private final IService[] services;
private final V2Router v2Router;
private final ObjectWriter jsonWriter = JsonUtil.createJsonWriter();

public AdminVerticle(JsonObject config,
AuthFactory authFactory,
IAdminUserProvider adminUserProvider,
IService[] services) {
IService[] services,
V2Router v2Router) {
this.config = config;
this.authFactory = authFactory;
this.adminUserProvider = adminUserProvider;
this.services = services;
this.v2Router = v2Router;
}

public void start(Promise<Void> startPromise) {
Expand Down Expand Up @@ -78,6 +83,10 @@ private Router createRoutesSetup() {
service.setupRoutes(router);
}

if (v2Router != null) {
v2Router.setupSubRouter(vertx, router);
}

return router;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.uid2.admin.vertx.api;

/*
*Important*
If you implement this interface, your route will be registered as a blocking handler. Use IRouteProvider
instead if you want to provide a non-blocking handler. See `readme.md` for more information.
*/
public interface IBlockingRouteProvider extends IRouteProvider {
}
16 changes: 16 additions & 0 deletions src/main/java/com/uid2/admin/vertx/api/IRouteProvider.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.uid2.admin.vertx.api;

import io.vertx.core.Handler;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.RoutingContext;

/*
*Important*
If you implement this interface, your route will be registered as a non-blocking handler. Use IBlockingRouteProvider
instead if you want to provide a blocking handler. See `readme.md` for more information.
*/
public interface IRouteProvider {
Handler<RoutingContext> getHandler();
}


18 changes: 18 additions & 0 deletions src/main/java/com/uid2/admin/vertx/api/UrlParameterProviders.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.uid2.admin.vertx.api;

import io.vertx.core.Handler;
import io.vertx.ext.web.RoutingContext;
import lombok.val;

public class UrlParameterProviders {
@FunctionalInterface
public interface ISiteIdRouteHandler {
void handle(RoutingContext rc, int siteId);
}
public static Handler<RoutingContext> provideSiteId(ISiteIdRouteHandler handler) {
return (RoutingContext rc) -> {
val siteId = Integer.parseInt(rc.pathParam("siteId"));
handler.handle(rc, siteId);
};
}
}
53 changes: 53 additions & 0 deletions src/main/java/com/uid2/admin/vertx/api/V2Router.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package com.uid2.admin.vertx.api;

import com.uid2.admin.vertx.api.annotations.Method;
import com.uid2.admin.vertx.api.annotations.Path;
import com.uid2.admin.vertx.api.annotations.Roles;
import com.uid2.shared.middleware.AuthMiddleware;
import io.vertx.core.Vertx;
import io.vertx.ext.web.Router;
import lombok.val;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Arrays;
import java.util.Set;

public class V2Router {
private static final Logger LOGGER = LoggerFactory.getLogger(V2Router.class);
private final IRouteProvider[] routeProviders;
private final AuthMiddleware auth;

public V2Router(IRouteProvider[] routeProviders, AuthMiddleware auth) {
this.routeProviders = routeProviders;
this.auth = auth;
}

public void setupSubRouter(Vertx verticle, Router parentRouter) {
val v2router = Router.router(verticle);

for (IRouteProvider provider : routeProviders) {
LOGGER.info("Configuring v2 router with " + provider.getClass());
java.lang.reflect.Method handler = null;
try {
handler = provider.getClass().getMethod("getHandler");
val path = handler.getAnnotation(Path.class).value();
val method = handler.getAnnotation(Method.class).value().vertxMethod;
val roles = handler.getAnnotation(Roles.class).value();
val authWrappedHandler = auth.handle(provider.getHandler(), roles);
if (Arrays.stream(provider.getClass().getInterfaces()).anyMatch(iface -> iface == IBlockingRouteProvider.class)) {
LOGGER.info("Using blocking handler for " + provider.getClass().getName());
v2router.route(method, path).blockingHandler(authWrappedHandler);
}
else {
LOGGER.info("Using non-blocking handler for " + provider.getClass().getName());
v2router.route(method, path).handler(authWrappedHandler);
}
} catch (NoSuchMethodException e) {
LOGGER.error("Could not find handle method for API handler: " + provider.getClass().getName());
}
}

parentRouter.route("/api/v2/*").subRouter(v2router);
}
}
29 changes: 29 additions & 0 deletions src/main/java/com/uid2/admin/vertx/api/V2RouterModule.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.uid2.admin.vertx.api;

import com.uid2.admin.secret.IKeypairManager;
import com.uid2.admin.vertx.api.cstg.GetClientSideKeypairsBySite;
import com.uid2.shared.middleware.AuthMiddleware;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class V2RouterModule {
private static final Logger LOGGER = LoggerFactory.getLogger(V2RouterModule.class);

private final IKeypairManager keypairManager;
private final AuthMiddleware authMiddleware;

public V2RouterModule(IKeypairManager keypairManager, AuthMiddleware authMiddleware) {
this.keypairManager = keypairManager;
this.authMiddleware = authMiddleware;
}

protected IRouteProvider[] getRouteProviders() {
return new IRouteProvider[] {
new GetClientSideKeypairsBySite(keypairManager)
};
}

public V2Router getRouter() {
return new V2Router(getRouteProviders(), authMiddleware);
}
}
12 changes: 12 additions & 0 deletions src/main/java/com/uid2/admin/vertx/api/annotations/ApiMethod.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.uid2.admin.vertx.api.annotations;

import io.vertx.core.http.HttpMethod;

public enum ApiMethod {
GET(HttpMethod.GET);
public final HttpMethod vertxMethod;

ApiMethod(HttpMethod vertxMethod) {
this.vertxMethod = vertxMethod;
}
}
14 changes: 14 additions & 0 deletions src/main/java/com/uid2/admin/vertx/api/annotations/Method.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.uid2.admin.vertx.api.annotations;

import io.vertx.core.http.HttpMethod;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Method {
ApiMethod value();
}
12 changes: 12 additions & 0 deletions src/main/java/com/uid2/admin/vertx/api/annotations/Path.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.uid2.admin.vertx.api.annotations;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Path {
String value();
}
14 changes: 14 additions & 0 deletions src/main/java/com/uid2/admin/vertx/api/annotations/Roles.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.uid2.admin.vertx.api.annotations;

import com.uid2.shared.auth.Role;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Roles {
Role[] value();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.uid2.admin.vertx.api.cstg;

import com.uid2.shared.model.ClientSideKeypair;
import lombok.AllArgsConstructor;

import java.time.Instant;

@AllArgsConstructor
public class ClientSideKeypairResponse {
public final int siteId;
public final String subscriptionId;
public final String publicKey;
public final Instant created;
public final boolean disabled;

static ClientSideKeypairResponse fromClientSiteKeypair(ClientSideKeypair keypair) {
return new ClientSideKeypairResponse(keypair.getSiteId(), keypair.getSubscriptionId(), keypair.encodePublicKeyToString(), keypair.getCreated(), keypair.isDisabled());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package com.uid2.admin.vertx.api.cstg;

import com.google.common.collect.Streams;
import com.uid2.admin.secret.IKeypairManager;
import com.uid2.admin.vertx.ResponseUtil;
import com.uid2.admin.vertx.api.IRouteProvider;
import com.uid2.admin.vertx.api.UrlParameterProviders;
import com.uid2.admin.vertx.api.annotations.ApiMethod;
import com.uid2.admin.vertx.api.annotations.Method;
import com.uid2.admin.vertx.api.annotations.Path;
import com.uid2.admin.vertx.api.annotations.Roles;
import com.uid2.shared.auth.Role;
import io.vertx.core.Handler;
import io.vertx.ext.web.RoutingContext;
import lombok.val;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class GetClientSideKeypairsBySite implements IRouteProvider {
private static final Logger LOGGER = LoggerFactory.getLogger(GetClientSideKeypairsBySite.class);

private final IKeypairManager keypairManager;

public GetClientSideKeypairsBySite(IKeypairManager keypairManager) {
this.keypairManager = keypairManager;
}

@Path("/sites/:siteId/client-side-keypairs")
@Method(ApiMethod.GET)
@Roles({Role.ADMINISTRATOR, Role.SHARING_PORTAL})
public Handler<RoutingContext> getHandler() {
return UrlParameterProviders.provideSiteId(this::handleGetClientSideKeys);
}

public void handleGetClientSideKeys(RoutingContext rc, int siteId) {
val keypairs = keypairManager.getKeypairsBySite(siteId);
if (keypairs != null) {
val result = Streams.stream(keypairs)
.map(kp -> ClientSideKeypairResponse.fromClientSiteKeypair(kp))
.toArray(ClientSideKeypairResponse[]::new);
rc.json(result);
}
else {
ResponseUtil.error(rc, 404, "No keypairs available for site ID: " + siteId);
}
}
}
Loading

0 comments on commit 4d30915

Please sign in to comment.