Skip to content

Commit

Permalink
Use Akka Extensions for gateway authentication
Browse files Browse the repository at this point in the history
Signed-off-by: David Schwilk <david.schwilk@bosch.io>
  • Loading branch information
DerSchwilk committed Apr 27, 2022
1 parent a5da06a commit 2c049b0
Show file tree
Hide file tree
Showing 36 changed files with 840 additions and 238 deletions.
Expand Up @@ -30,6 +30,7 @@ private DevopsAuthenticationDirectiveFactory(final JwtAuthenticationProvider jwt

public static DevopsAuthenticationDirectiveFactory newInstance(
final JwtAuthenticationFactory jwtAuthenticationFactory, final DevOpsConfig devOpsConfig) {

final JwtAuthenticationProvider jwtAuthenticationProvider = JwtAuthenticationProvider.newInstance(
jwtAuthenticationFactory.newJwtAuthenticationResultProvider(),
jwtAuthenticationFactory.getJwtValidator());
Expand Down
Expand Up @@ -12,8 +12,6 @@
*/
package org.eclipse.ditto.gateway.service.endpoints.directives.auth;

import static org.eclipse.ditto.base.model.common.ConditionChecker.checkNotNull;

import java.util.ArrayList;
import java.util.Collection;
import java.util.concurrent.Executor;
Expand All @@ -22,62 +20,71 @@
import org.eclipse.ditto.gateway.service.security.authentication.AuthenticationFailureAggregator;
import org.eclipse.ditto.gateway.service.security.authentication.AuthenticationFailureAggregators;
import org.eclipse.ditto.gateway.service.security.authentication.AuthenticationProvider;
import org.eclipse.ditto.gateway.service.security.authentication.AuthenticationResult;
import org.eclipse.ditto.gateway.service.security.authentication.jwt.JwtAuthenticationFactory;
import org.eclipse.ditto.gateway.service.security.authentication.jwt.JwtAuthenticationProvider;
import org.eclipse.ditto.gateway.service.security.authentication.jwt.JwtAuthenticationResultProvider;
import org.eclipse.ditto.gateway.service.security.authentication.jwt.JwtValidator;
import org.eclipse.ditto.gateway.service.util.config.security.AuthenticationConfig;
import org.eclipse.ditto.gateway.service.security.authentication.preauth.PreAuthenticatedAuthenticationProvider;
import org.eclipse.ditto.gateway.service.util.config.security.AuthenticationConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.mongodb.lang.Nullable;

import akka.actor.ActorSystem;

/**
* Ditto's default factory for building authentication directives.
*/
public final class DittoGatewayAuthenticationDirectiveFactory implements GatewayAuthenticationDirectiveFactory {
public final class DittoGatewayAuthenticationDirectiveFactory extends GatewayAuthenticationDirectiveFactory {

private static final Logger LOGGER = LoggerFactory.getLogger(DittoGatewayAuthenticationDirectiveFactory.class);
private static final String AUTHENTICATION_DISPATCHER_NAME = "authentication-dispatcher";

private final GatewayAuthenticationDirective gatewayHttpAuthenticationDirective;
private final GatewayAuthenticationDirective gatewayWsAuthenticationDirective;

public DittoGatewayAuthenticationDirectiveFactory(final AuthenticationConfig authConfig,
final JwtAuthenticationFactory jwtAuthenticationFactory,
final Executor authenticationDispatcher) {
checkNotNull(jwtAuthenticationFactory, "jwtAuthenticationFactory");

checkNotNull(authConfig, "AuthenticationConfig");
checkNotNull(authenticationDispatcher, "authentication dispatcher");

final JwtAuthenticationResultProvider jwtAuthenticationResultProvider =
jwtAuthenticationFactory.newJwtAuthenticationResultProvider();
final JwtValidator jwtValidator = jwtAuthenticationFactory.getJwtValidator();
private final AuthenticationConfig authConfig;
private final Executor authenticationDispatcher;
@Nullable private GatewayAuthenticationDirective gatewayHttpAuthenticationDirective;
@Nullable private GatewayAuthenticationDirective gatewayWsAuthenticationDirective;

final JwtAuthenticationProvider jwtHttpAuthenticationProvider =
JwtAuthenticationProvider.newInstance(jwtAuthenticationResultProvider, jwtValidator);
final JwtAuthenticationProvider jwtWsAuthenticationProvider =
JwtAuthenticationProvider.newWsInstance(jwtAuthenticationResultProvider, jwtValidator);

gatewayHttpAuthenticationDirective =
generateGatewayAuthenticationDirective(authConfig, jwtHttpAuthenticationProvider,
authenticationDispatcher);
gatewayWsAuthenticationDirective =
generateGatewayAuthenticationDirective(authConfig, jwtWsAuthenticationProvider,
authenticationDispatcher);
public DittoGatewayAuthenticationDirectiveFactory(final ActorSystem actorSystem) {
super(actorSystem);
authConfig = getAuthConfig(actorSystem);
authenticationDispatcher = actorSystem.dispatchers().lookup(AUTHENTICATION_DISPATCHER_NAME);
}

@Override
public GatewayAuthenticationDirective buildHttpAuthentication() {
public GatewayAuthenticationDirective buildHttpAuthentication(
final JwtAuthenticationFactory jwtAuthenticationFactory) {

if (null == gatewayHttpAuthenticationDirective) {
final JwtAuthenticationProvider jwtHttpAuthenticationProvider =
JwtAuthenticationProvider.newInstance(jwtAuthenticationFactory.newJwtAuthenticationResultProvider(),
jwtAuthenticationFactory.getJwtValidator());
gatewayHttpAuthenticationDirective =
generateGatewayAuthenticationDirective(authConfig, jwtHttpAuthenticationProvider,
authenticationDispatcher);
}
return gatewayHttpAuthenticationDirective;
}

@Override
public GatewayAuthenticationDirective buildWsAuthentication() {
public GatewayAuthenticationDirective buildWsAuthentication(
final JwtAuthenticationFactory jwtAuthenticationFactory) {

if (null == gatewayWsAuthenticationDirective) {
final JwtAuthenticationProvider jwtWsAuthenticationProvider =
JwtAuthenticationProvider.newWsInstance(
jwtAuthenticationFactory.newJwtAuthenticationResultProvider(),
jwtAuthenticationFactory.getJwtValidator());
gatewayWsAuthenticationDirective =
generateGatewayAuthenticationDirective(authConfig, jwtWsAuthenticationProvider,
authenticationDispatcher);
}
return gatewayWsAuthenticationDirective;
}

private static GatewayAuthenticationDirective generateGatewayAuthenticationDirective(
final AuthenticationConfig authConfig, final JwtAuthenticationProvider jwtAuthenticationProvider,
final AuthenticationConfig authConfig,
final AuthenticationProvider<AuthenticationResult> jwtAuthenticationProvider,
final Executor authenticationDispatcher) {

final Collection<AuthenticationProvider<?>> authenticationProviders = new ArrayList<>();
Expand Down
Expand Up @@ -12,22 +12,64 @@
*/
package org.eclipse.ditto.gateway.service.endpoints.directives.auth;

import static org.eclipse.ditto.base.model.common.ConditionChecker.checkNotNull;

import org.eclipse.ditto.base.service.DittoExtensionPoint;
import org.eclipse.ditto.gateway.service.security.authentication.jwt.JwtAuthenticationFactory;
import org.eclipse.ditto.gateway.service.util.config.DittoGatewayConfig;
import org.eclipse.ditto.gateway.service.util.config.security.AuthenticationConfig;
import org.eclipse.ditto.internal.utils.config.DefaultScopedConfig;

import akka.actor.ActorSystem;

/**
* Factory for authentication directives.
*/
public interface GatewayAuthenticationDirectiveFactory {
public abstract class GatewayAuthenticationDirectiveFactory extends DittoExtensionPoint {

protected static final String AUTHENTICATION_DISPATCHER_NAME = "authentication-dispatcher";

/**
* @param actorSystem the actor system in which to load the extension.
*/
protected GatewayAuthenticationDirectiveFactory(final ActorSystem actorSystem) {
super(actorSystem);
}

/**
* Builds the {@link GatewayAuthenticationDirective authentication directive} that should be used for HTTP API.
*
* @return The built {@link GatewayAuthenticationDirective authentication directive}.
*/
GatewayAuthenticationDirective buildHttpAuthentication();
public abstract GatewayAuthenticationDirective buildHttpAuthentication(
JwtAuthenticationFactory jwtAuthenticationFactory);

/**
* Builds the {@link GatewayAuthenticationDirective authentication directive} that should be used for WebSocket API.
*
* @return The built {@link GatewayAuthenticationDirective authentication directive}.
*/
GatewayAuthenticationDirective buildWsAuthentication();
public abstract GatewayAuthenticationDirective buildWsAuthentication(
JwtAuthenticationFactory jwtAuthenticationFactory);

/**
* Loads the implementation of {@code GatewayAuthenticationDirectiveFactory} which is configured for the
* {@code ActorSystem}.
*
* @param actorSystem the actorSystem in which the {@code GatewayAuthenticationDirectiveFactory} should be loaded.
* @return the {@code GatewayAuthenticationDirectiveFactory} implementation.
* @throws NullPointerException if {@code actorSystem} is {@code null}.
* @since 3.0.0
*/
public static GatewayAuthenticationDirectiveFactory get(final ActorSystem actorSystem) {
checkNotNull(actorSystem, "actorSystem");
final var implementation = getAuthConfig(actorSystem).getGatewayAuthenticationDirectiveFactory();

return new ExtensionId<>(implementation, GatewayAuthenticationDirectiveFactory.class).get(actorSystem);
}

protected static AuthenticationConfig getAuthConfig(final ActorSystem actorSystem) {
return DittoGatewayConfig.of(DefaultScopedConfig.dittoScoped(
actorSystem.settings().config())).getAuthenticationConfig();
}
}
Expand Up @@ -12,6 +12,8 @@
*/
package org.eclipse.ditto.gateway.service.endpoints.routes;

import static org.eclipse.ditto.base.model.common.ConditionChecker.checkNotNull;

import org.eclipse.ditto.base.model.headers.DittoHeaders;
import org.eclipse.ditto.base.model.json.JsonSchemaVersion;
import org.eclipse.ditto.base.service.DittoExtensionPoint;
Expand Down Expand Up @@ -56,8 +58,11 @@ public abstract Route unauthorized(RouteBaseProperties routeBaseProperties, Json
*
* @param actorSystem the actorSystem in which the {@code CustomApiRoutesProvider} should be loaded.
* @return the {@code CustomApiRoutesProvider} implementation.
* @throws NullPointerException if {@code actorSystem} is {@code null}.
* @since 3.0.0
*/
public static CustomApiRoutesProvider get(final ActorSystem actorSystem) {
checkNotNull(actorSystem, "actorSystem");
final var implementation = DittoGatewayConfig.of(DefaultScopedConfig.dittoScoped(
actorSystem.settings().config())).getHttpConfig().getCustomApiRoutesProvider();

Expand Down
@@ -0,0 +1,41 @@
/*
* Copyright (c) 2022 Contributors to the Eclipse Foundation
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.ditto.gateway.service.endpoints.routes.sse;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;

import org.eclipse.ditto.base.model.headers.DittoHeaders;

import akka.actor.ActorSystem;
import akka.http.javadsl.server.RequestContext;

/**
* Null implementation for {@link SseAuthorizationEnforcer}.
*/
public final class NoOpSseAuthorizationEnforcer extends SseAuthorizationEnforcer {

/**
* @param actorSystem the actor system in which to load the extension.
*/
public NoOpSseAuthorizationEnforcer(final ActorSystem actorSystem) {
super(actorSystem);
}

@Override
public CompletionStage<Void> checkAuthorization(final RequestContext requestContext,
final DittoHeaders dittoHeaders) {
return CompletableFuture.completedStage(null);
}

}
@@ -0,0 +1,39 @@
/*
* Copyright (c) 2022 Contributors to the Eclipse Foundation
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.ditto.gateway.service.endpoints.routes.sse;

import org.eclipse.ditto.base.model.headers.DittoHeaders;
import org.eclipse.ditto.gateway.service.streaming.actors.SupervisedStream;

import akka.actor.ActorSystem;

/**
* Null implementation for {@link SseConnectionSupervisor}.
*/
public final class NoOpSseConnectionSupervisor extends SseConnectionSupervisor {

/**
* @param actorSystem the actor system in which to load the extension.
*/
public NoOpSseConnectionSupervisor(final ActorSystem actorSystem) {
super(actorSystem);
}

@Override
public void supervise(final SupervisedStream supervisedStream, final CharSequence connectionCorrelationId,
final DittoHeaders dittoHeaders) {

// Does nothing.
}

}
Expand Up @@ -12,27 +12,58 @@
*/
package org.eclipse.ditto.gateway.service.endpoints.routes.sse;

import static org.eclipse.ditto.base.model.common.ConditionChecker.checkNotNull;

import java.util.concurrent.CompletionStage;

import org.eclipse.ditto.base.model.headers.DittoHeaders;
import org.eclipse.ditto.base.service.DittoExtensionPoint;
import org.eclipse.ditto.gateway.service.util.config.DittoGatewayConfig;
import org.eclipse.ditto.internal.utils.config.DefaultScopedConfig;

import akka.actor.ActorSystem;
import akka.http.javadsl.server.RequestContext;

/**
* Enforces authorization in order to establish a SSE connection.
* If the authorization check is successful nothing will happen, else a
* {@link org.eclipse.ditto.base.model.exceptions.DittoRuntimeException DittoRuntimeException} is thrown.
*/
public interface SseAuthorizationEnforcer {
public abstract class SseAuthorizationEnforcer extends DittoExtensionPoint {

/**
* @param actorSystem the actor system in which to load the extension.
*/
protected SseAuthorizationEnforcer(final ActorSystem actorSystem) {
super(actorSystem);
}

/**
* Ensures that the establishment of a SSE connection is authorized for the given arguments.
*
* @param requestContext the context of the HTTP request for opening the connection.
* @param dittoHeaders the DittoHeaders with authentication information for opening the connection.
* @throws NullPointerException if any argument is {@code null}.
* @return a successful future if validation succeeds or a failed future if validation fails.
* @throws NullPointerException if any argument is {@code null}.
*/
CompletionStage<Void> checkAuthorization(RequestContext requestContext, DittoHeaders dittoHeaders);
protected abstract CompletionStage<Void> checkAuthorization(RequestContext requestContext,
DittoHeaders dittoHeaders);

/**
* Loads the implementation of {@code SseAuthorizationEnforcer} which is configured for the
* {@code ActorSystem}.
*
* @param actorSystem the actorSystem in which the {@code SseAuthorizationEnforcer} should be loaded.
* @return the {@code SseAuthorizationEnforcer} implementation.
* @throws NullPointerException if {@code actorSystem} is {@code null}.
* @since 3.0.0
*/
public static SseAuthorizationEnforcer get(final ActorSystem actorSystem) {
checkNotNull(actorSystem, "actorSystem");
final var implementation = DittoGatewayConfig.of(DefaultScopedConfig.dittoScoped(
actorSystem.settings().config())).getStreamingConfig().getSseConfig().getAuthorizationEnforcer();

return new ExtensionId<>(implementation, SseAuthorizationEnforcer.class).get(actorSystem);
}

}

0 comments on commit 2c049b0

Please sign in to comment.