authenticate(final JsonObject authRequest) {
final String authzid = new String(authRequest.getBinary(AuthenticationConstants.FIELD_SASL_RESPONSE), StandardCharsets.UTF_8);
final String subject = authRequest.getString(AuthenticationConstants.FIELD_SUBJECT_DN);
- log.debug("processing EXTERNAL authentication request [Subject DN: {}]", subject);
+ log.debug("processing EXTERNAL authentication request [Subject DN: {}, Authz ID: {}]", subject, authzid);
return verifyExternal(authzid, subject);
} else {
diff --git a/service-base/src/main/java/org/eclipse/hono/service/auth/delegating/AuthenticationServerClient.java b/service-base/src/main/java/org/eclipse/hono/service/auth/delegating/AuthenticationServerClient.java
index d54482e1b5..a4a2c78a2b 100644
--- a/service-base/src/main/java/org/eclipse/hono/service/auth/delegating/AuthenticationServerClient.java
+++ b/service-base/src/main/java/org/eclipse/hono/service/auth/delegating/AuthenticationServerClient.java
@@ -66,9 +66,6 @@ public AuthenticationServerClient(
/**
* Verifies a Subject DN with a remote authentication server using SASL EXTERNAL.
- *
- * This method currently always fails the handler because there is no way (yet) in vertx-proton
- * to perform a SASL EXTERNAL exchange including an authorization id.
*
* @param authzid The identity to act as.
* @param subjectDn The Subject DN.
@@ -76,10 +73,23 @@ public AuthenticationServerClient(
* the result contains a JWT with the authenticated user's claims.
*/
public Future verifyExternal(final String authzid, final String subjectDn) {
- // unsupported mechanism (until we get better control over client SASL params in vertx-proton)
- return Future.failedFuture(new ClientErrorException(
- HttpURLConnection.HTTP_BAD_REQUEST,
- "unsupported mechanism"));
+ final ProtonClientOptions options = new ProtonClientOptions();
+ options.setReconnectAttempts(3).setReconnectInterval(50);
+ options.addEnabledSaslMechanism(AuthenticationConstants.MECHANISM_EXTERNAL);
+ options.setAuthorizationId(AuthenticationConstants.getCommonName(subjectDn));
+
+ final var connectAttempt = factory.connect(options, null, null);
+
+ return connectAttempt
+ .compose(openCon -> getToken(openCon))
+ .recover(t -> Future.failedFuture(mapConnectionFailureToServiceInvocationException(t)))
+ .onComplete(s -> {
+ Optional.ofNullable(connectAttempt.result())
+ .ifPresent(con -> {
+ LOG.debug("closing connection to Authentication service");
+ con.close();
+ });
+ });
}
/**
diff --git a/services/auth-base/src/main/java/org/eclipse/hono/authentication/file/FileBasedAuthenticationService.java b/services/auth-base/src/main/java/org/eclipse/hono/authentication/file/FileBasedAuthenticationService.java
index 1fcd76804d..bbb24f5a7b 100644
--- a/services/auth-base/src/main/java/org/eclipse/hono/authentication/file/FileBasedAuthenticationService.java
+++ b/services/auth-base/src/main/java/org/eclipse/hono/authentication/file/FileBasedAuthenticationService.java
@@ -280,7 +280,13 @@ private Future verify(final String authenticationId, final JsonObject
JsonObject effectiveUser = user;
String effectiveAuthorizationId = authenticationId;
- if (authorizationId != null && !authorizationId.isEmpty() && isAuthorizedToImpersonate(user)) {
+ if (authorizationId != null && !authorizationId.isEmpty()) {
+ if (!isAuthorizedToImpersonate(user)) {
+ return Future.failedFuture(new ClientErrorException(
+ HttpURLConnection.HTTP_UNAUTHORIZED,
+ UNAUTHORIZED));
+ }
+
final JsonObject impersonatedUser = users.get(authorizationId);
if (impersonatedUser != null) {
effectiveUser = impersonatedUser;
diff --git a/services/auth-base/src/test/java/org/eclipse/hono/authentication/file/FileBasedAuthenticationServiceTest.java b/services/auth-base/src/test/java/org/eclipse/hono/authentication/file/FileBasedAuthenticationServiceTest.java
index 2a26907237..97833d3520 100644
--- a/services/auth-base/src/test/java/org/eclipse/hono/authentication/file/FileBasedAuthenticationServiceTest.java
+++ b/services/auth-base/src/test/java/org/eclipse/hono/authentication/file/FileBasedAuthenticationServiceTest.java
@@ -21,11 +21,13 @@
import static com.google.common.truth.Truth.assertThat;
import java.io.FileNotFoundException;
+import java.net.HttpURLConnection;
import java.time.Duration;
import java.util.concurrent.TimeUnit;
import org.eclipse.hono.auth.Authorities;
import org.eclipse.hono.auth.HonoUser;
+import org.eclipse.hono.client.ClientErrorException;
import org.eclipse.hono.service.auth.AuthTokenFactory;
import org.eclipse.hono.util.ResourceIdentifier;
import org.junit.jupiter.api.BeforeEach;
@@ -187,8 +189,11 @@ public void testVerifyPlainRefusesAuthorizationId(final VertxTestContext ctx) {
givenAStartedService()
.compose(ok -> authService.verifyPlain("userB", "hono-client@HONO", "secret"))
- .onComplete(ctx.succeeding(res -> {
- assertUserAndToken(ctx, res, "hono-client@HONO", TOKEN);
+ .onComplete(ctx.failing(t -> {
+ ctx.verify(() -> {
+ assertThat(t).isInstanceOf(ClientErrorException.class);
+ assertThat(((ClientErrorException) t).getErrorCode()).isEqualTo(HttpURLConnection.HTTP_UNAUTHORIZED);
+ });
ctx.completeNow();
}));
}
@@ -287,8 +292,11 @@ public void testVerifyExternalRefusesAuthorizationId(final VertxTestContext ctx)
givenAStartedService()
.compose(ok -> authService.verifyExternal("userB", "CN=userA"))
- .onComplete(ctx.succeeding(res -> {
- assertUserAndToken(ctx, res, "userA", TOKEN);
+ .onComplete(ctx.failing(t -> {
+ ctx.verify(() -> {
+ assertThat(t).isInstanceOf(ClientErrorException.class);
+ assertThat(((ClientErrorException) t).getErrorCode()).isEqualTo(HttpURLConnection.HTTP_UNAUTHORIZED);
+ });
ctx.completeNow();
}));
}