Skip to content

Commit

Permalink
Use Akka Extensions for CustomApiRoutesProvider and JwtAuthorizationS…
Browse files Browse the repository at this point in the history
…ubjectsProvider

In effort to make these functionalities more extensible

Signed-off-by: David Schwilk <david.schwilk@bosch.io>
  • Loading branch information
DerSchwilk committed Apr 21, 2022
1 parent 2967d5e commit afcbf50
Show file tree
Hide file tree
Showing 19 changed files with 275 additions and 154 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,32 +12,56 @@
*/
package org.eclipse.ditto.gateway.service.endpoints.routes;

import java.util.List;

import org.eclipse.ditto.base.model.headers.DittoHeaders;
import org.eclipse.ditto.base.model.json.JsonSchemaVersion;
import org.eclipse.ditto.gateway.service.util.config.DittoGatewayConfig;
import org.eclipse.ditto.gateway.service.util.config.GatewayConfig;
import org.eclipse.ditto.internal.utils.akka.AkkaClassLoader;
import org.eclipse.ditto.internal.utils.config.DefaultScopedConfig;

import akka.actor.AbstractExtensionId;
import akka.actor.ActorSystem;
import akka.actor.ExtendedActorSystem;
import akka.actor.Extension;
import akka.http.javadsl.server.Route;

/**
* Provider for custom routes.
* You can distinguish between routes for unauthorized access and authorized access.
*/
public interface CustomApiRoutesProvider {

/**
* Provides a custom route for unauthorized access.
*
* @param apiVersion the API version.
* @param correlationId the correlation ID.
* @return custom route for unauthorized access.
*/
Route unauthorized(JsonSchemaVersion apiVersion, CharSequence correlationId);

/**
* Provides a custom route for authorized access.
*
* @param headers headers of the request.
* @return custom route for authorized access.
*/
Route authorized(DittoHeaders headers);
public abstract class CustomApiRoutesProvider implements Extension {

private static final ExtensionId EXTENSION_ID = new ExtensionId();

protected final ActorSystem actorSystem;

protected CustomApiRoutesProvider(final ActorSystem actorSystem) {
this.actorSystem = actorSystem;
}

public abstract Route unauthorized(RouteBaseProperties routeBaseProperties, JsonSchemaVersion version, CharSequence correlationId);

public abstract Route authorized(RouteBaseProperties routeBaseProperties, DittoHeaders headers);

public static CustomApiRoutesProvider get(final ActorSystem actorSystem) {
return EXTENSION_ID.get(actorSystem);
}

private static final class ExtensionId extends AbstractExtensionId<CustomApiRoutesProvider> {

@Override
public CustomApiRoutesProvider createExtension(final ExtendedActorSystem system) {
final GatewayConfig gatewayConfig =
DittoGatewayConfig.of(DefaultScopedConfig.dittoScoped(
system.settings().config()));

return AkkaClassLoader.instantiate(system, CustomApiRoutesProvider.class,
gatewayConfig.getHttpConfig().getCustomApiRoutesProvider(),
List.of(ActorSystem.class),
List.of(system));
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -17,30 +17,28 @@
import org.eclipse.ditto.base.model.headers.DittoHeaders;
import org.eclipse.ditto.base.model.json.JsonSchemaVersion;

import akka.actor.ActorSystem;
import akka.http.javadsl.server.Directives;
import akka.http.javadsl.server.Route;

@Immutable
public final class NoopCustomApiRoutesProvider implements CustomApiRoutesProvider {
public final class NoopCustomApiRoutesProvider extends CustomApiRoutesProvider {

private static final NoopCustomApiRoutesProvider INSTANCE = new NoopCustomApiRoutesProvider();
private static final Route EMPTY_ROUTE = Directives.reject();

private NoopCustomApiRoutesProvider() {
super();
}

public static NoopCustomApiRoutesProvider getInstance() {
return INSTANCE;
public NoopCustomApiRoutesProvider(final ActorSystem actorSystem) {
super(actorSystem);
}

@Override
public Route unauthorized(final JsonSchemaVersion apiVersion, final CharSequence correlationId) {
public Route unauthorized(final RouteBaseProperties routeBaseProperties, final JsonSchemaVersion apiVersion,
final CharSequence correlationId) {

return EMPTY_ROUTE;
}

@Override
public Route authorized(final DittoHeaders dittoHeaders) {
public Route authorized(final RouteBaseProperties routeBaseProperties, final DittoHeaders dittoHeaders) {
return EMPTY_ROUTE;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ public final class RootRoute extends AllDirectives {
private final CloudEventsRoute cloudEventsRoute;

private final CustomApiRoutesProvider customApiRoutesProvider;
private final RouteBaseProperties routeBaseProperties;
private final GatewayAuthenticationDirective apiAuthenticationDirective;
private final GatewayAuthenticationDirective wsAuthenticationDirective;
private final CorsEnablingDirective corsDirective;
Expand Down Expand Up @@ -123,6 +124,7 @@ private RootRoute(final Builder builder) {
whoamiRoute = builder.whoamiRoute;
cloudEventsRoute = builder.cloudEventsRoute;
customApiRoutesProvider = builder.customApiRoutesProvider;
routeBaseProperties = builder.routeBaseProperties;
apiAuthenticationDirective = builder.httpAuthenticationDirective;
wsAuthenticationDirective = builder.wsAuthenticationDirective;
requestTimeoutHandlingDirective = RequestTimeoutHandlingDirective.getInstance(httpConfig);
Expand All @@ -144,7 +146,6 @@ private RootRoute(final Builder builder) {

public static RootRouteBuilder getBuilder(final HttpConfig httpConfig) {
return new Builder(httpConfig)
.customApiRoutesProvider(NoopCustomApiRoutesProvider.getInstance())
.customHeadersHandler(NoopCustomHeadersHandler.getInstance())
.rejectionHandler(DittoRejectionHandlerFactory.createInstance());
}
Expand Down Expand Up @@ -222,7 +223,7 @@ private Route api(final RequestContext ctx, final CharSequence correlationId,

return rawPathPrefix(PathMatchers.slash().concat(HTTP_PATH_API_PREFIX), () -> // /api
ensureSchemaVersion(apiVersion -> // /api/<apiVersion>
customApiRoutesProvider.unauthorized(apiVersion, correlationId).orElse(
customApiRoutesProvider.unauthorized(routeBaseProperties, apiVersion, correlationId).orElse(
apiAuthentication(apiVersion, correlationId, auth -> {
final CompletionStage<DittoHeaders> dittoHeadersPromise =
rootRouteHeadersStepBuilder
Expand Down Expand Up @@ -272,7 +273,7 @@ private Route apiAuthentication(final JsonSchemaVersion schemaVersion, final Cha
private Route buildApiSubRoutes(final RequestContext ctx, final DittoHeaders dittoHeaders,
final AuthenticationResult authenticationResult) {

final Route customApiSubRoutes = customApiRoutesProvider.authorized(dittoHeaders);
final Route customApiSubRoutes = customApiRoutesProvider.authorized(routeBaseProperties, dittoHeaders);

return concat(
// /api/{apiVersion}/policies
Expand Down Expand Up @@ -405,6 +406,7 @@ private static final class Builder implements RootRouteBuilder {
private CloudEventsRoute cloudEventsRoute;

private CustomApiRoutesProvider customApiRoutesProvider;
private RouteBaseProperties routeBaseProperties;
private GatewayAuthenticationDirective httpAuthenticationDirective;
private GatewayAuthenticationDirective wsAuthenticationDirective;
private ExceptionHandler exceptionHandler;
Expand Down Expand Up @@ -493,8 +495,11 @@ public RootRouteBuilder cloudEventsRoute(final CloudEventsRoute route) {
}

@Override
public RootRouteBuilder customApiRoutesProvider(final CustomApiRoutesProvider provider) {
public RootRouteBuilder customApiRoutesProvider(final CustomApiRoutesProvider provider,
final RouteBaseProperties routeBaseProperties) {

customApiRoutesProvider = provider;
this.routeBaseProperties = routeBaseProperties;
return this;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@
import org.eclipse.ditto.gateway.service.endpoints.routes.thingsearch.ThingSearchRoute;
import org.eclipse.ditto.gateway.service.endpoints.routes.websocket.WebSocketRouteBuilder;
import org.eclipse.ditto.gateway.service.endpoints.routes.whoami.WhoamiRoute;
import org.eclipse.ditto.protocol.HeaderTranslator;
import org.eclipse.ditto.internal.utils.health.routes.StatusRoute;
import org.eclipse.ditto.internal.utils.protocol.ProtocolAdapterProvider;
import org.eclipse.ditto.protocol.HeaderTranslator;

import akka.http.javadsl.server.ExceptionHandler;
import akka.http.javadsl.server.RejectionHandler;
Expand Down Expand Up @@ -186,7 +186,7 @@ public interface RootRouteBuilder {
* @param provider the provider to set.
* @return the Builder to allow method chaining.
*/
RootRouteBuilder customApiRoutesProvider(CustomApiRoutesProvider provider);
RootRouteBuilder customApiRoutesProvider(CustomApiRoutesProvider provider, RouteBaseProperties routeBaseProperties);

/**
* Sets the custom headers handler.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,28 +29,28 @@
import org.eclipse.ditto.placeholders.PlaceholderFactory;
import org.eclipse.ditto.policies.model.SubjectId;

import akka.actor.ActorSystem;

/**
* Implementation of {@link JwtAuthorizationSubjectsProvider} for Google JWTs.
*/
@Immutable
public final class DittoJwtAuthorizationSubjectsProvider implements JwtAuthorizationSubjectsProvider {
public final class DittoJwtAuthorizationSubjectsProvider extends JwtAuthorizationSubjectsProvider {

private final JwtSubjectIssuersConfig jwtSubjectIssuersConfig;

private DittoJwtAuthorizationSubjectsProvider(final JwtSubjectIssuersConfig jwtSubjectIssuersConfig) {
this.jwtSubjectIssuersConfig = jwtSubjectIssuersConfig;
private DittoJwtAuthorizationSubjectsProvider(final ActorSystem actorSystem,
final JwtSubjectIssuersConfig jwtSubjectIssuersConfig) {

super(actorSystem);
this.jwtSubjectIssuersConfig = checkNotNull(jwtSubjectIssuersConfig);
}

/**
* Returns a new {@code DittoAuthorizationSubjectsProvider}.
*
* @param jwtSubjectIssuersConfig the subject issuer configuration.
* @return the DittoAuthorizationSubjectsProvider.
* @throws NullPointerException if any argument is {@code null}.
*/
public static DittoJwtAuthorizationSubjectsProvider of(final JwtSubjectIssuersConfig jwtSubjectIssuersConfig) {
public static DittoJwtAuthorizationSubjectsProvider of(final ActorSystem actorSystem,
final JwtSubjectIssuersConfig jwtSubjectIssuersConfig) {

checkNotNull(jwtSubjectIssuersConfig);
return new DittoJwtAuthorizationSubjectsProvider(jwtSubjectIssuersConfig);
return new DittoJwtAuthorizationSubjectsProvider(actorSystem, jwtSubjectIssuersConfig);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public final class JwtAuthenticationFactory {
private final OAuthConfig oAuthConfig;
private final CacheConfig publicKeyCacheConfig;
private final HttpClientFacade httpClientFacade;
private final JwtAuthorizationSubjectsProviderFactory jwtAuthorizationSubjectsProviderFactory;
private final JwtAuthorizationSubjectsProvider jwtAuthorizationSubjectsProvider;

@Nullable private JwtValidator jwtValidator;
@Nullable private JwtSubjectIssuersConfig jwtSubjectIssuersConfig;
Expand All @@ -39,13 +39,13 @@ public final class JwtAuthenticationFactory {
private JwtAuthenticationFactory(final OAuthConfig oAuthConfig,
final CacheConfig publicKeyCacheConfig,
final HttpClientFacade httpClientFacade,
final JwtAuthorizationSubjectsProviderFactory jwtAuthorizationSubjectsProviderFactory) {
final JwtAuthorizationSubjectsProvider jwtAuthorizationSubjectsProvider) {

this.oAuthConfig = checkNotNull(oAuthConfig, "authenticationConfig");
this.publicKeyCacheConfig = checkNotNull(publicKeyCacheConfig, "publicKeyCacheConfig");
this.httpClientFacade = checkNotNull(httpClientFacade, "httpClientFacade");
this.jwtAuthorizationSubjectsProviderFactory =
checkNotNull(jwtAuthorizationSubjectsProviderFactory, "jwtAuthorizationSubjectsProviderFactory");
this.jwtAuthorizationSubjectsProvider =
checkNotNull(jwtAuthorizationSubjectsProvider, "jwtAuthorizationSubjectsProvider");
}

/**
Expand All @@ -54,16 +54,16 @@ private JwtAuthenticationFactory(final OAuthConfig oAuthConfig,
* @param oAuthConfig the OAuth configuration.
* @param publicKeyCacheConfig the public key cache configuration.
* @param httpClientFacade the client facade of the HTTP client.
* @param jwtAuthorizationSubjectsProviderFactory used to instantiate a new auth subjects provider.
* @param jwtAuthorizationSubjectsProvider the subjects provider.
* @return the new created instance.
*/
public static JwtAuthenticationFactory newInstance(final OAuthConfig oAuthConfig,
final CacheConfig publicKeyCacheConfig,
final HttpClientFacade httpClientFacade,
final JwtAuthorizationSubjectsProviderFactory jwtAuthorizationSubjectsProviderFactory) {
final JwtAuthorizationSubjectsProvider jwtAuthorizationSubjectsProvider) {

return new JwtAuthenticationFactory(oAuthConfig, publicKeyCacheConfig, httpClientFacade,
jwtAuthorizationSubjectsProviderFactory);
jwtAuthorizationSubjectsProvider);
}

public JwtValidator getJwtValidator() {
Expand Down Expand Up @@ -94,10 +94,7 @@ private JwtSubjectIssuersConfig getJwtSubjectIssuersConfig() {
}

public JwtAuthenticationResultProvider newJwtAuthenticationResultProvider() {
final var authorizationSubjectsProvider =
jwtAuthorizationSubjectsProviderFactory.newProvider(getJwtSubjectIssuersConfig());

return DefaultJwtAuthenticationResultProvider.of(authorizationSubjectsProvider);
return DefaultJwtAuthenticationResultProvider.of(jwtAuthorizationSubjectsProvider);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,29 @@
import java.util.List;

import org.eclipse.ditto.base.model.auth.AuthorizationSubject;
import org.eclipse.ditto.gateway.service.util.config.DittoGatewayConfig;
import org.eclipse.ditto.gateway.service.util.config.GatewayConfig;
import org.eclipse.ditto.internal.utils.akka.AkkaClassLoader;
import org.eclipse.ditto.internal.utils.config.DefaultScopedConfig;
import org.eclipse.ditto.jwt.model.JsonWebToken;

import akka.actor.AbstractExtensionId;
import akka.actor.ActorSystem;
import akka.actor.ExtendedActorSystem;
import akka.actor.Extension;

/**
* A provider for {@link AuthorizationSubject}s contained in a {@link JsonWebToken}.
*/
public interface JwtAuthorizationSubjectsProvider {
public abstract class JwtAuthorizationSubjectsProvider implements Extension {

private static final ExtensionId EXTENSION_ID = new ExtensionId();

protected final ActorSystem actorSystem;

protected JwtAuthorizationSubjectsProvider(final ActorSystem actorSystem) {
this.actorSystem = actorSystem;
}

/**
* Returns the {@code AuthorizationSubjects} of the given {@code JsonWebToken}.
Expand All @@ -29,6 +46,28 @@ public interface JwtAuthorizationSubjectsProvider {
* @return the authorization subjects.
* @throws NullPointerException if {@code jsonWebToken} is {@code null}.
*/
List<AuthorizationSubject> getAuthorizationSubjects(JsonWebToken jsonWebToken);
public abstract List<AuthorizationSubject> getAuthorizationSubjects(JsonWebToken jsonWebToken);

public static JwtAuthorizationSubjectsProvider get(final ActorSystem actorSystem) {
return EXTENSION_ID.get(actorSystem);
}

private static final class ExtensionId extends AbstractExtensionId<JwtAuthorizationSubjectsProvider> {

@Override
public JwtAuthorizationSubjectsProvider createExtension(final ExtendedActorSystem system) {
final GatewayConfig gatewayConfig =
DittoGatewayConfig.of(DefaultScopedConfig.dittoScoped(
system.settings().config()));

return AkkaClassLoader.instantiate(system, JwtAuthorizationSubjectsProvider.class,
gatewayConfig.getAuthenticationConfig()
.getOAuthConfig()
.getJwtAuthorizationSubjectsProvider(),
List.of(ActorSystem.class, JwtSubjectIssuersConfig.class),
List.of(system, JwtSubjectIssuersConfig.fromOAuthConfig(
gatewayConfig.getAuthenticationConfig().getOAuthConfig())));
}
}

}

This file was deleted.

Loading

0 comments on commit afcbf50

Please sign in to comment.