fix(java-client): redact operator password on async acknowledge-alarm failures
Claude-Session: https://claude.ai/code/session_016J6isfcgqy4SB2RK3ZvFvT
This commit is contained in:
+15
-1
@@ -308,10 +308,24 @@ public final class MxGatewayClient implements AutoCloseable {
|
|||||||
*/
|
*/
|
||||||
public CompletableFuture<AcknowledgeAlarmReply> acknowledgeAlarmAsync(AcknowledgeAlarmRequest request) {
|
public CompletableFuture<AcknowledgeAlarmReply> acknowledgeAlarmAsync(AcknowledgeAlarmRequest request) {
|
||||||
CompletableFuture<AcknowledgeAlarmReply> future = toCompletable(rawFutureStub().acknowledgeAlarm(request));
|
CompletableFuture<AcknowledgeAlarmReply> future = toCompletable(rawFutureStub().acknowledgeAlarm(request));
|
||||||
return future.thenApply(reply -> {
|
String secret = request.getOperatorPassword();
|
||||||
|
CompletableFuture<AcknowledgeAlarmReply> checked = future.thenApply(reply -> {
|
||||||
MxGatewayErrors.ensureProtocolSuccess("acknowledge alarm", reply.getProtocolStatus(), null);
|
MxGatewayErrors.ensureProtocolSuccess("acknowledge alarm", reply.getProtocolStatus(), null);
|
||||||
return reply;
|
return reply;
|
||||||
});
|
});
|
||||||
|
if (secret.isEmpty()) {
|
||||||
|
return checked;
|
||||||
|
}
|
||||||
|
// Scrub the operator password from any failure surfaced through the future.
|
||||||
|
return checked.exceptionally(error -> {
|
||||||
|
Throwable cause = error instanceof java.util.concurrent.CompletionException && error.getCause() != null
|
||||||
|
? error.getCause()
|
||||||
|
: error;
|
||||||
|
if (cause instanceof MxGatewayException gatewayError) {
|
||||||
|
throw new java.util.concurrent.CompletionException(MxGatewayErrors.redacted(gatewayError, secret));
|
||||||
|
}
|
||||||
|
throw new java.util.concurrent.CompletionException(cause);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+46
@@ -74,6 +74,52 @@ final class MxGatewayCredentialReplyTests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void acknowledgeAlarmAsyncRedactsOperatorPasswordFromFailure() throws Exception {
|
||||||
|
String serverName = "mxgw-java-ack-" + UUID.randomUUID();
|
||||||
|
MxAccessGatewayGrpc.MxAccessGatewayImplBase service = new MxAccessGatewayGrpc.MxAccessGatewayImplBase() {
|
||||||
|
@Override
|
||||||
|
public void acknowledgeAlarm(
|
||||||
|
mxaccess_gateway.v1.MxaccessGateway.AcknowledgeAlarmRequest request,
|
||||||
|
StreamObserver<mxaccess_gateway.v1.MxaccessGateway.AcknowledgeAlarmReply> observer) {
|
||||||
|
observer.onNext(mxaccess_gateway.v1.MxaccessGateway.AcknowledgeAlarmReply.newBuilder()
|
||||||
|
.setProtocolStatus(ProtocolStatus.newBuilder()
|
||||||
|
.setCode(ProtocolStatusCode.PROTOCOL_STATUS_CODE_MXACCESS_FAILURE)
|
||||||
|
.setMessage("auth failed for operator1 password " + CREDENTIAL))
|
||||||
|
.build());
|
||||||
|
observer.onCompleted();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Server server = InProcessServerBuilder.forName(serverName)
|
||||||
|
.directExecutor().addService(service).build().start();
|
||||||
|
ManagedChannel channel = InProcessChannelBuilder.forName(serverName).directExecutor().build();
|
||||||
|
try (MxGatewayClient client = new MxGatewayClient(
|
||||||
|
channel,
|
||||||
|
MxGatewayClientOptions.builder()
|
||||||
|
.endpoint("in-process").apiKey("").plaintext(true)
|
||||||
|
.callTimeout(Duration.ofSeconds(5)).build())) {
|
||||||
|
var request = mxaccess_gateway.v1.MxaccessGateway.AcknowledgeAlarmRequest.newBuilder()
|
||||||
|
.setAlarmFullReference("Tank01.Level.HiHi")
|
||||||
|
.setOperatorUser("operator1")
|
||||||
|
.setOperatorPassword(CREDENTIAL)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
java.util.concurrent.ExecutionException async = assertThrows(
|
||||||
|
java.util.concurrent.ExecutionException.class,
|
||||||
|
() -> client.acknowledgeAlarmAsync(request).get());
|
||||||
|
assertTrue(async.getCause() instanceof MxGatewayException, String.valueOf(async.getCause()));
|
||||||
|
assertFalse(async.getCause().getMessage().contains(CREDENTIAL));
|
||||||
|
assertFalse(((MxGatewayCommandException) async.getCause()).protocolStatus().getMessage()
|
||||||
|
.contains(CREDENTIAL));
|
||||||
|
|
||||||
|
MxGatewayException sync = assertThrows(MxGatewayException.class, () -> client.acknowledgeAlarm(request));
|
||||||
|
assertFalse(sync.getMessage().contains(CREDENTIAL));
|
||||||
|
} finally {
|
||||||
|
channel.shutdownNow();
|
||||||
|
server.shutdownNow();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void authenticateUserMissingPayloadThrowsMalformedReply() throws Exception {
|
void authenticateUserMissingPayloadThrowsMalformedReply() throws Exception {
|
||||||
MxCommandReply reply = loadReply("authenticate-user.missing-payload.reply.json");
|
MxCommandReply reply = loadReply("authenticate-user.missing-payload.reply.json");
|
||||||
|
|||||||
Reference in New Issue
Block a user