use of org.eclipse.hono.util.ResourceIdentifier in project hono by eclipse.
the class MessageForwardingEndpointTest method testMessageHandlerRejectsMalformedMessage.
/**
* Verifies that the endpoint rejects messages that do not pass formal verification.
*/
@SuppressWarnings({ "unchecked" })
@Test
public void testMessageHandlerRejectsMalformedMessage() {
// GIVEN an endpoint with an attached client
final ResourceIdentifier targetAddress = ResourceIdentifier.fromString("telemetry/tenant");
final ProtonConnection connection = mock(ProtonConnection.class);
when(connection.getRemoteContainer()).thenReturn("test-client");
final ProtonReceiver receiver = mock(ProtonReceiver.class);
when(receiver.getRemoteQoS()).thenReturn(ProtonQoS.AT_MOST_ONCE);
final DownstreamAdapter adapter = mock(DownstreamAdapter.class);
doAnswer(invocation -> {
final Handler<AsyncResult<Void>> resultHandler = invocation.getArgument(1);
resultHandler.handle(Future.succeededFuture());
return null;
}).when(adapter).onClientAttach(any(UpstreamReceiver.class), any(Handler.class));
final MessageForwardingEndpoint<HonoMessagingConfigProperties> endpoint = getEndpoint(false);
endpoint.setDownstreamAdapter(adapter);
endpoint.onLinkAttach(connection, receiver, targetAddress);
final ArgumentCaptor<ProtonMessageHandler> messageHandler = ArgumentCaptor.forClass(ProtonMessageHandler.class);
verify(receiver).handler(messageHandler.capture());
// WHEN a client sends a malformed message
final Message message = ProtonHelper.message("malformed");
final ProtonDelivery upstreamDelivery = mock(ProtonDelivery.class);
messageHandler.getValue().handle(upstreamDelivery, message);
// THEN the endpoint rejects the message
final ArgumentCaptor<Rejected> deliveryState = ArgumentCaptor.forClass(Rejected.class);
verify(upstreamDelivery).disposition(deliveryState.capture(), eq(Boolean.TRUE));
assertThat(deliveryState.getValue().getError().getCondition(), is(AmqpError.DECODE_ERROR));
// but does not close the link
verify(receiver, never()).close();
// and the message is not forwarded to the downstream adapter
verify(adapter, never()).processMessage(any(UpstreamReceiver.class), eq(upstreamDelivery), eq(message));
}
use of org.eclipse.hono.util.ResourceIdentifier in project hono by eclipse.
the class TenantMessageFilterTest method testVerifyDetectsMissingTenantProperty.
/**
* Verifies that {@link TenantMessageFilter#verify(ResourceIdentifier, Message)} detects if the mandatory tenantId
* property is missing.
*/
@Test
public void testVerifyDetectsMissingTenantProperty() {
// GIVEN a valid tenant GET message without an AMQP value
final Message msg = givenAMessageHavingProperties(TenantConstants.TenantAction.get);
// WHEN receiving the message via a link with any tenant
final ResourceIdentifier linkTarget = getResourceIdentifier(DEFAULT_TENANT);
// THEN message validation fails
assertFalse(TenantMessageFilter.verify(linkTarget, msg));
}
use of org.eclipse.hono.util.ResourceIdentifier in project hono by eclipse.
the class RequestResponseEndpoint method onLinkAttach.
/**
* Handles a client's request to establish a link for receiving responses
* to service invocations.
* <p>
* This method registers a consumer on the vert.x event bus for the given reply-to address.
* Response messages received over the event bus are transformed into AMQP messages using
* the {@link #getAmqpReply(EventBusMessage)} method and sent to the client over the established
* link.
*
* @param con The AMQP connection that the link is part of.
* @param sender The link to establish.
* @param replyToAddress The reply-to address to create a consumer on the event bus for.
*/
@Override
public final void onLinkAttach(final ProtonConnection con, final ProtonSender sender, final ResourceIdentifier replyToAddress) {
if (isValidReplyToAddress(replyToAddress)) {
logger.debug("establishing sender link with client [{}]", sender.getName());
final MessageConsumer<JsonObject> replyConsumer = vertx.eventBus().consumer(replyToAddress.toString(), message -> {
// TODO check for correct session here...?
if (logger.isTraceEnabled()) {
logger.trace("forwarding reply to client [{}]: {}", sender.getName(), message.body().encodePrettily());
}
final EventBusMessage response = EventBusMessage.fromJson(message.body());
filterResponse(Constants.getClientPrincipal(con), response).recover(t -> {
final int status = Optional.of(t).map(cause -> {
if (cause instanceof ServiceInvocationException) {
return ((ServiceInvocationException) cause).getErrorCode();
} else {
return null;
}
}).orElse(HttpURLConnection.HTTP_INTERNAL_ERROR);
return Future.succeededFuture(response.getResponse(status));
}).map(filteredResponse -> {
final Message amqpReply = getAmqpReply(filteredResponse);
sender.send(amqpReply);
return null;
});
});
sender.setQoS(ProtonQoS.AT_LEAST_ONCE);
sender.closeHandler(senderClosed -> {
logger.debug("client [{}] closed sender link, removing associated event bus consumer [{}]", sender.getName(), replyConsumer.address());
replyConsumer.unregister();
if (senderClosed.succeeded()) {
senderClosed.result().close();
}
});
sender.open();
} else {
logger.debug("client [{}] provided invalid reply-to address", sender.getName());
sender.setCondition(ProtonHelper.condition(AmqpError.INVALID_FIELD, String.format("reply-to address must have the following format %s/<tenant>/<reply-address>", getName())));
sender.close();
}
}
use of org.eclipse.hono.util.ResourceIdentifier in project hono by eclipse.
the class TenantAmqpEndpoint method isAuthorized.
/**
* Checks if the client of the tenant API is authorized to execute a given operation.
* <p>
* This check makes use of the provided tenantId by enriching the resource with it.
* So permission checks can be done on a per tenant level, although the endpoint the client
* connects to does not include the tenantId (like for several other of Hono's APIs).
*
* @param clientPrincipal The client.
* @param resource The resource the operation belongs to.
* @param message The message for which the authorization shall be checked.
* @return The outcome of the check.
* @throws NullPointerException if any of the parameters is {@code null}.
*/
@Override
protected Future<Boolean> isAuthorized(final HonoUser clientPrincipal, final ResourceIdentifier resource, final Message message) {
Objects.requireNonNull(message);
final String tenantId = MessageHelper.getTenantId(message);
final ResourceIdentifier specificTenantAddress = ResourceIdentifier.from(resource.getEndpoint(), tenantId, null);
return getAuthorizationService().isAuthorized(clientPrincipal, specificTenantAddress, message.getSubject());
}
use of org.eclipse.hono.util.ResourceIdentifier in project hono by eclipse.
the class KuraProtocolAdapter method mapTopic.
Future<ResourceIdentifier> mapTopic(final MqttContext ctx) {
final Future<ResourceIdentifier> result = Future.future();
try {
final ResourceIdentifier topic = ResourceIdentifier.fromString(ctx.message().topicName());
ResourceIdentifier mappedTopic = null;
if (getConfig().getControlPrefix().equals(topic.getEndpoint())) {
// this is a "control" message
ctx.setContentType(getConfig().getCtrlMsgContentType());
final String[] mappedPath = Arrays.copyOf(topic.getResourcePath(), topic.getResourcePath().length);
mappedPath[0] = getEndpoint(ctx.message().qosLevel());
mappedTopic = ResourceIdentifier.fromPath(mappedPath);
} else {
// map "data" messages based on QoS
ctx.setContentType(getConfig().getDataMsgContentType());
final String[] mappedPath = new String[topic.getResourcePath().length + 1];
System.arraycopy(topic.getResourcePath(), 0, mappedPath, 1, topic.getResourcePath().length);
mappedPath[0] = getEndpoint(ctx.message().qosLevel());
mappedTopic = ResourceIdentifier.fromPath(mappedPath);
}
if (mappedTopic.getResourcePath().length < 3) {
// topic does not contain account_name and client_id
result.fail(new ClientErrorException(HttpURLConnection.HTTP_BAD_REQUEST, "topic does not comply with Kura format"));
} else {
LOG.debug("mapped Kura message [topic: {}, QoS: {}] to Hono message [to: {}, device_id: {}, content-type: {}]", topic, ctx.message().qosLevel(), mappedTopic.getBasePath(), mappedTopic.getResourceId(), ctx.contentType());
result.complete(mappedTopic);
}
} catch (final IllegalArgumentException e) {
result.fail(new ClientErrorException(HttpURLConnection.HTTP_BAD_REQUEST, "malformed topic name"));
}
return result;
}
Aggregations