Skip to content

Commit

Permalink
Pass on execution context from Hono AuthHandler to AuthProvider.
Browse files Browse the repository at this point in the history
The Hono AuthHandler now uses an AuthProvider that also supports
working with an ExecutionContext. This makes TraceContext
serialization/deserialization unnecessary in scenarios where a
HonoClientBasedAuthProvider is used with a Hono AuthHandler.

Signed-off-by: Carsten Lohmann <carsten.lohmann@bosch.io>
  • Loading branch information
calohmn committed Sep 2, 2020
1 parent 5174e57 commit b5e6e24
Show file tree
Hide file tree
Showing 12 changed files with 129 additions and 68 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@
import org.eclipse.hono.tracing.TracingHelper;
import org.eclipse.hono.util.AuthenticationConstants;
import org.eclipse.hono.util.CredentialsConstants;
import org.eclipse.hono.util.ExecutionContext;
import org.eclipse.hono.util.GenericExecutionContext;
import org.eclipse.hono.util.MessageHelper;
import org.eclipse.hono.util.TenantObject;
import org.slf4j.Logger;
Expand Down Expand Up @@ -312,7 +314,8 @@ private Future<DeviceUser> verifyPlain(final String[] saslResponseFields) {
currentSpan.log(items);

final Promise<DeviceUser> authResult = Promise.promise();
usernamePasswordAuthProvider.authenticate(credentials, currentSpan.context(), authResult);
final ExecutionContext executionContext = new GenericExecutionContext(currentSpan.context());
usernamePasswordAuthProvider.authenticate(credentials, executionContext, authResult);
return authResult.future()
.recover(t -> Future.failedFuture(new AuthorizationException(
credentials.getTenantId(),
Expand Down Expand Up @@ -361,7 +364,8 @@ private Future<DeviceUser> verifyExternal(final Certificate[] peerCertificateCha
final Promise<DeviceUser> authResult = Promise.promise();
final SubjectDnCredentials credentials = SubjectDnCredentials.create(tenant.getTenantId(),
deviceCert.getSubjectX500Principal(), clientContext);
clientCertAuthProvider.authenticate(credentials, currentSpan.context(), authResult);
final ExecutionContext executionContext = new GenericExecutionContext(currentSpan.context());
clientCertAuthProvider.authenticate(credentials, executionContext, authResult);
return authResult.future()
.recover(t -> Future.failedFuture(new AuthorizationException(
credentials.getTenantId(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
import org.eclipse.hono.service.auth.device.ExecutionContextAuthHandler;
import org.eclipse.hono.service.auth.device.HonoClientBasedAuthProvider;
import org.eclipse.hono.service.auth.device.UsernamePasswordCredentials;
import org.eclipse.hono.tracing.TracingHelper;

import io.opentracing.Tracer;
import io.vertx.core.Future;
Expand Down Expand Up @@ -98,7 +97,8 @@ public Future<JsonObject> parseCredentials(final MqttContext context) {
.put("username", auth.getUsername())
.put("password", auth.getPassword())
.put(PROPERTY_CLIENT_IDENTIFIER, context.deviceEndpoint().clientIdentifier());
TracingHelper.injectSpanContext(tracer, context.getTracingContext(), credentialsJSON);
// spanContext of MqttContext not injected into json here since authenticateDevice(mqttContext) will
// pass on the MqttContext as well as the json into authProvider.authenticate()
result.complete(credentialsJSON);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
import org.apache.qpid.proton.amqp.transport.ErrorCondition;
import org.eclipse.hono.tracing.TracingHelper;
import org.eclipse.hono.util.MapBasedExecutionContext;
import org.eclipse.hono.util.QoS;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand Down Expand Up @@ -208,9 +207,4 @@ public void disposition(final DeliveryState deliveryState) {
}
span.finish();
}

@Override
public QoS getRequestedQos() {
return delivery.remotelySettled() ? QoS.AT_MOST_ONCE : QoS.AT_LEAST_ONCE;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,4 @@ public interface ExecutionContext {
*/
SpanContext getTracingContext();

/**
* Gets the QoS level as set in the request by the device.
*
* @return The QoS level requested by the device.
*/
QoS getRequestedQos();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*******************************************************************************
* Copyright (c) 2020 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.hono.util;

import io.opentracing.SpanContext;

/**
* A generic execution context that stores properties in a {@code Map}.
*
*/
public class GenericExecutionContext extends MapBasedExecutionContext {

/**
* Creates a new execution context.
*/
public GenericExecutionContext() {
//
}

/**
* Creates a new execution context.
*
* @param spanContext The <em>OpenTracing</em> context to use for tracking the processing of this context.
*/
public GenericExecutionContext(final SpanContext spanContext) {
setTracingContext(spanContext);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,11 @@ public interface TelemetryExecutionContext extends ExecutionContext {
default boolean isDeviceAuthenticated() {
return getAuthenticatedDevice() != null;
}

/**
* Gets the QoS level as set in the request by the device.
*
* @return The QoS level requested by the device.
*/
QoS getRequestedQos();
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@

import io.vertx.core.Future;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.auth.AuthProvider;


/**
Expand Down Expand Up @@ -56,5 +55,5 @@ public interface AuthHandler<T extends ExecutionContext> {
*
* @return The provider.
*/
AuthProvider getAuthProvider();
HonoClientBasedAuthProvider<?> getAuthProvider();
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
import org.eclipse.hono.service.auth.DeviceUser;
import org.eclipse.hono.tracing.TracingHelper;
import org.eclipse.hono.util.CredentialsObject;
import org.eclipse.hono.util.ExecutionContext;
import org.eclipse.hono.util.GenericExecutionContext;
import org.eclipse.hono.util.MessageHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand Down Expand Up @@ -98,13 +100,14 @@ protected final Future<CredentialsObject> getCredentialsForDevice(final DeviceCr
@Override
public final void authenticate(
final T deviceCredentials,
final SpanContext spanContext,
final ExecutionContext executionContext,
final Handler<AsyncResult<DeviceUser>> resultHandler) {

Objects.requireNonNull(deviceCredentials);
Objects.requireNonNull(executionContext);
Objects.requireNonNull(resultHandler);

final Span currentSpan = TracingHelper.buildServerChildSpan(tracer, spanContext, "authenticate device", getClass().getSimpleName())
final Span currentSpan = TracingHelper.buildServerChildSpan(tracer, executionContext.getTracingContext(), "authenticate device", getClass().getSimpleName())
.withTag(MessageHelper.APP_PROPERTY_TENANT_ID, deviceCredentials.getTenantId())
.withTag(TracingHelper.TAG_AUTH_ID.getKey(), deviceCredentials.getAuthId())
.start();
Expand Down Expand Up @@ -202,18 +205,32 @@ protected abstract Future<Device> doValidateCredentials(

@Override
public final void authenticate(final JsonObject authInfo, final Handler<AsyncResult<User>> resultHandler) {
final ExecutionContext executionContext = new GenericExecutionContext(
TracingHelper.extractSpanContext(tracer, authInfo));
authenticate(authInfo, executionContext, s -> {
if (s.succeeded()) {
resultHandler.handle(Future.succeededFuture(s.result()));
} else {
resultHandler.handle(Future.failedFuture(s.cause()));
}
});
}

final T credentials = getCredentials(Objects.requireNonNull(authInfo));
@Override
public final void authenticate(
final JsonObject authInfo,
final ExecutionContext executionContext,
final Handler<AsyncResult<DeviceUser>> resultHandler) {

Objects.requireNonNull(authInfo);
Objects.requireNonNull(executionContext);
Objects.requireNonNull(resultHandler);

final T credentials = getCredentials(authInfo);
if (credentials == null) {
resultHandler.handle(Future.failedFuture(new ClientErrorException(HttpURLConnection.HTTP_UNAUTHORIZED, "malformed credentials")));
} else {
authenticate(credentials, TracingHelper.extractSpanContext(tracer, authInfo), s -> {
if (s.succeeded()) {
resultHandler.handle(Future.succeededFuture(s.result()));
} else {
resultHandler.handle(Future.failedFuture(s.cause()));
}
});
authenticate(credentials, executionContext, resultHandler);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,15 @@

package org.eclipse.hono.service.auth.device;

import java.net.HttpURLConnection;
import java.util.Objects;

import org.eclipse.hono.client.ClientErrorException;
import org.eclipse.hono.service.auth.DeviceUser;
import org.eclipse.hono.util.ExecutionContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import io.vertx.core.Future;
import io.vertx.core.Promise;
import io.vertx.ext.auth.AuthProvider;
import io.vertx.ext.auth.User;


/**
Expand All @@ -49,19 +45,19 @@ public abstract class ExecutionContextAuthHandler<T extends ExecutionContext> im
*/
protected final Logger log = LoggerFactory.getLogger(getClass());

private final AuthProvider authProvider;
private final HonoClientBasedAuthProvider<?> authProvider;

/**
* Creates a new handler for authenticating MQTT clients.
*
* @param authProvider The auth provider to use for verifying a client's credentials.
*/
protected ExecutionContextAuthHandler(final AuthProvider authProvider) {
protected ExecutionContextAuthHandler(final HonoClientBasedAuthProvider<?> authProvider) {
this.authProvider = authProvider;
}

@Override
public final AuthProvider getAuthProvider() {
public final HonoClientBasedAuthProvider<?> getAuthProvider() {
return authProvider;
}

Expand All @@ -70,35 +66,21 @@ public Future<DeviceUser> authenticateDevice(final T context) {

Objects.requireNonNull(context);

final Promise<DeviceUser> result = Promise.promise();
parseCredentials(context)
return parseCredentials(context)
.compose(authInfo -> {
final Promise<User> authResult = Promise.promise();
getAuthProvider(context).authenticate(authInfo, authResult);
final Promise<DeviceUser> authResult = Promise.promise();
getAuthProvider(context).authenticate(authInfo, context, authResult);
return authResult.future();
}).onComplete(authAttempt -> {
if (authAttempt.succeeded()) {
if (authAttempt.result() instanceof DeviceUser) {
result.complete((DeviceUser) authAttempt.result());
} else {
log.warn("configured AuthProvider does not return DeviceUser instances [type returned: {}",
authAttempt.result().getClass().getName());
result.fail(new ClientErrorException(HttpURLConnection.HTTP_UNAUTHORIZED));
}
} else {
result.fail(authAttempt.cause());
}
});
return result.future();
}

private AuthProvider getAuthProvider(final T ctx) {
private HonoClientBasedAuthProvider<?> getAuthProvider(final T ctx) {

final Object obj = ctx.get(AUTH_PROVIDER_CONTEXT_KEY);
if (obj instanceof AuthProvider) {
if (obj instanceof HonoClientBasedAuthProvider<?>) {
log.debug("using auth provider found in context [type: {}]", obj.getClass().getName());
// we're overruling the configured one for this request
return (AuthProvider) obj;
return (HonoClientBasedAuthProvider<?>) obj;
} else {
// bad type, ignore and return default
return authProvider;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,15 @@
package org.eclipse.hono.service.auth.device;

import org.eclipse.hono.service.auth.DeviceUser;
import org.eclipse.hono.util.ExecutionContext;

import io.opentracing.SpanContext;
import io.vertx.core.AsyncResult;
import io.vertx.core.Handler;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.auth.AuthProvider;


/**
* An authentication provider for verifying credentials that also supports monitoring by
* means of health checks.
* An authentication provider for verifying credentials.
*
* @param <T> The type of credentials that this provider can validate.
*/
Expand All @@ -37,10 +36,35 @@ public interface HonoClientBasedAuthProvider<T extends AbstractDeviceCredentials
* <a href="https://www.eclipse.org/hono/docs/api/credentials/">Credentials API</a>.
*
* @param credentials The credentials provided by the device.
* @param spanContext The SpanContext (may be null).
* @param executionContext The execution context concerning the request of the device.
* @param resultHandler The handler to notify about the outcome of the validation. If validation succeeds,
* the result contains an object representing the authenticated device.
* @throws NullPointerException if credentials or result handler are {@code null}.
* @throws NullPointerException if any of the parameters is {@code null}.
*/
void authenticate(T credentials, ExecutionContext executionContext, Handler<AsyncResult<DeviceUser>> resultHandler);

/**
* Authenticates a device.
* <p>
* The first argument is a JSON object containing information for authenticating the device. What this actually
* contains depends on the specific implementation. In the case of a simple username/password based authentication
* it is likely to contain a JSON object with the following structure:
* <pre>
* {
* "username": "tim",
* "password": "mypassword"
* }
* </pre>
* For other types of authentication it contain different information - for example a JWT token or OAuth bearer
* token.
* <p>
* If the device is successfully authenticated a {@link DeviceUser} object is passed to the handler in an
* {@link io.vertx.core.AsyncResult}.
*
* @param authInfo The auth information.
* @param executionContext The execution context concerning the request of the device.
* @param resultHandler The result handler.
* @throws NullPointerException if any of the parameters is {@code null}.
*/
void authenticate(T credentials, SpanContext spanContext, Handler<AsyncResult<DeviceUser>> resultHandler);
void authenticate(JsonObject authInfo, ExecutionContext executionContext, Handler<AsyncResult<DeviceUser>> resultHandler);
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import org.eclipse.hono.client.CredentialsClientFactory;
import org.eclipse.hono.client.ServerErrorException;
import org.eclipse.hono.util.CredentialsObject;
import org.eclipse.hono.util.GenericExecutionContext;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
Expand Down Expand Up @@ -138,7 +139,7 @@ public void testValidateFailsIfCredentialsAreDisabled(final VertxTestContext ctx
.addSecret(CredentialsObject.emptySecret(Instant.now().minusSeconds(120), null));
when(credentialsClient.get(eq("type"), eq("identity"), any(JsonObject.class), any()))
.thenReturn(Future.succeededFuture(credentialsOnRecord));
provider.authenticate(creds, null, ctx.failing(t -> {
provider.authenticate(creds, new GenericExecutionContext(), ctx.failing(t -> {
// THEN authentication fails with a 401 client error
ctx.verify(() -> assertThat(((ClientErrorException) t).getErrorCode()).isEqualTo(HttpURLConnection.HTTP_UNAUTHORIZED));
ctx.completeNow();
Expand Down
Loading

0 comments on commit b5e6e24

Please sign in to comment.