use of org.eclipse.hono.util.ResourceIdentifier in project hono by eclipse.
the class FileBasedAuthenticationServiceTest method testVerifyPlainAddsAuthoritiesForOperations.
/**
* Verifies that the token issued by the service on successful verification
* of credentials contains the user's authorities for executing operations.
*
* @param ctx The vert.x test context.
*/
@Test
public void testVerifyPlainAddsAuthoritiesForOperations(final VertxTestContext ctx) {
final ResourceIdentifier registration = ResourceIdentifier.fromString("registration/tenant");
givenAStartedService().onSuccess(ok -> authService.verifyPlain(null, "hono-client@HONO", "secret", ctx.succeeding(res -> {
ctx.verify(() -> {
assertThat(res.getAuthorities().isAuthorized(registration, "assert")).isTrue();
assertThat(res.getAuthorities().isAuthorized(registration, "add")).isTrue();
ctx.completeNow();
});
})));
}
use of org.eclipse.hono.util.ResourceIdentifier in project hono by eclipse.
the class AbstractProtocolAdapterBase method validateAddress.
/**
* Validates a message's target address for consistency with Hono's addressing rules.
*
* @param address The address to validate.
* @param authenticatedDevice The device that has uploaded the message.
* @return A future indicating the outcome of the check.
* <p>
* The future will be completed with the validated target address if all
* checks succeed. Otherwise the future will be failed with a
* {@link ClientErrorException}.
* @throws NullPointerException if address is {@code null}.
*/
protected final Future<ResourceIdentifier> validateAddress(final ResourceIdentifier address, final Device authenticatedDevice) {
Objects.requireNonNull(address);
final Promise<ResourceIdentifier> result = Promise.promise();
if (authenticatedDevice == null) {
if (Strings.isNullOrEmpty(address.getTenantId()) || Strings.isNullOrEmpty(address.getResourceId())) {
result.fail(new ClientErrorException(HttpURLConnection.HTTP_BAD_REQUEST, "unauthenticated client must provide tenant and device ID in message address"));
} else {
result.complete(address);
}
} else {
if (!Strings.isNullOrEmpty(address.getTenantId()) && Strings.isNullOrEmpty(address.getResourceId())) {
result.fail(new ClientErrorException(HttpURLConnection.HTTP_BAD_REQUEST, "message address must not contain tenant ID only"));
} else if (!Strings.isNullOrEmpty(address.getTenantId()) && !address.getTenantId().equals(authenticatedDevice.getTenantId())) {
result.fail(new ClientErrorException(HttpURLConnection.HTTP_FORBIDDEN, "can only publish for device of same tenant"));
} else if (Strings.isNullOrEmpty(address.getTenantId()) && Strings.isNullOrEmpty(address.getResourceId())) {
// use authenticated device's tenant and device ID
final ResourceIdentifier resource = ResourceIdentifier.from(address, authenticatedDevice.getTenantId(), authenticatedDevice.getDeviceId());
result.complete(resource);
} else if (Strings.isNullOrEmpty(address.getTenantId())) {
// use authenticated device's tenant ID
final ResourceIdentifier resource = ResourceIdentifier.from(address, authenticatedDevice.getTenantId(), address.getResourceId());
result.complete(resource);
} else {
result.complete(address);
}
}
return result.future().recover(t -> {
log.debug("validation failed for address [{}], device [{}]: {}", address, authenticatedDevice, t.getMessage());
return Future.failedFuture(t);
});
}
use of org.eclipse.hono.util.ResourceIdentifier in project hono by eclipse.
the class ProtonBasedMappingAndDelegatingCommandHandler method mapAndDelegateIncomingCommandMessage.
/**
* Delegates an incoming command to the protocol adapter instance that the target
* device is connected to.
* <p>
* Determines the target gateway (if applicable) and protocol adapter instance for an incoming command
* and delegates the command to the resulting protocol adapter instance.
*
* @param tenantId The tenant that the command target must belong to.
* @param messageDelivery The delivery of the command message.
* @param message The command message.
* @throws NullPointerException if any of the parameters is {@code null}.
*/
public void mapAndDelegateIncomingCommandMessage(final String tenantId, final ProtonDelivery messageDelivery, final Message message) {
Objects.requireNonNull(tenantId);
Objects.requireNonNull(messageDelivery);
Objects.requireNonNull(message);
final Timer.Sample timer = getMetrics().startTimer();
// this is the place where a command message on the "command/${tenant}" address arrives *first*
if (!ResourceIdentifier.isValid(message.getAddress())) {
log.debug("command message has no valid address");
final Rejected rejected = new Rejected();
rejected.setError(new ErrorCondition(Constants.AMQP_BAD_REQUEST, "missing or invalid command target address"));
messageDelivery.disposition(rejected, true);
return;
}
final ResourceIdentifier targetAddress = ResourceIdentifier.fromString(message.getAddress());
final String deviceId = targetAddress.getResourceId();
if (!tenantId.equals(targetAddress.getTenantId())) {
log.debug("command message address contains invalid tenant [expected: {}, found: {}]", tenantId, targetAddress.getTenantId());
final Rejected rejected = new Rejected();
rejected.setError(new ErrorCondition(AmqpError.UNAUTHORIZED_ACCESS, "unauthorized to send command to tenant"));
messageDelivery.disposition(rejected, true);
return;
} else if (Strings.isNullOrEmpty(deviceId)) {
log.debug("invalid command message address: {}", message.getAddress());
final Rejected rejected = new Rejected();
rejected.setError(new ErrorCondition(Constants.AMQP_BAD_REQUEST, "invalid command target address"));
messageDelivery.disposition(rejected, true);
return;
}
final ProtonBasedCommand command = ProtonBasedCommand.from(message);
if (command.isValid()) {
log.trace("received valid command message: {}", command);
} else {
log.debug("received invalid command message: {}", command);
}
final SpanContext spanContext = TracingHelper.extractSpanContext(tracer, message);
final Span currentSpan = createSpan(tenantId, deviceId, spanContext);
command.logToSpan(currentSpan);
final ProtonBasedCommandContext commandContext = new ProtonBasedCommandContext(command, messageDelivery, currentSpan);
if (command.isValid()) {
mapAndDelegateIncomingCommand(commandContext, timer);
} else {
// command message is invalid
commandContext.reject("malformed command message");
reportInvalidCommand(commandContext, timer);
}
}
use of org.eclipse.hono.util.ResourceIdentifier in project hono by eclipse.
the class HttpBasedMessageMappingTest method testMapMessageSucceeds.
/**
* Verifies that the result returned by the mapping service contains the
* mapped payload, device ID and additional properties.
*
* @param ctx The helper to use for running tests on vert.x.
*/
@SuppressWarnings("unchecked")
@Test
public void testMapMessageSucceeds(final VertxTestContext ctx) {
config.setMapperEndpoints(Map.of("mapper", MapperEndpoint.from("host", 1234, "/uri", false)));
final ResourceIdentifier targetAddress = ResourceIdentifier.from(TelemetryConstants.TELEMETRY_ENDPOINT, TEST_TENANT_ID, "gateway");
final String newDeviceId = "new-device";
final HttpRequest<Buffer> httpRequest = mock(HttpRequest.class, withSettings().defaultAnswer(RETURNS_SELF));
final MultiMap responseHeaders = MultiMap.caseInsensitiveMultiMap();
responseHeaders.add(MessageHelper.APP_PROPERTY_DEVICE_ID, newDeviceId);
responseHeaders.add("foo", "bar");
final Buffer responseBody = Buffer.buffer("changed");
final HttpResponse<Buffer> httpResponse = mock(HttpResponse.class);
when(httpResponse.headers()).thenReturn(responseHeaders);
when(httpResponse.bodyAsBuffer()).thenReturn(responseBody);
when(httpResponse.statusCode()).thenReturn(HttpURLConnection.HTTP_OK);
when(mapperWebClient.post(anyInt(), anyString(), anyString())).thenReturn(httpRequest);
final String topic = String.format("%s/?content-type=%s", TelemetryConstants.TELEMETRY_ENDPOINT, URLEncoder.encode("text/plain", StandardCharsets.UTF_8));
final MqttPublishMessage message = newMessage(MqttQoS.AT_LEAST_ONCE, topic);
final MqttContext context = newContext(message, span, new Device(TEST_TENANT_ID, "gateway"));
final RegistrationAssertion assertion = new RegistrationAssertion("gateway").setDownstreamMessageMapper("mapper");
messageMapping.mapDownstreamMessage(context, targetAddress, assertion).onComplete(ctx.succeeding(mappedMessage -> {
ctx.verify(() -> {
assertThat(mappedMessage.getTargetAddress().getResourceId()).isEqualTo("new-device");
assertThat(mappedMessage.getPayload()).isEqualTo(responseBody);
assertThat(mappedMessage.getAdditionalProperties()).doesNotContainKey(MessageHelper.APP_PROPERTY_DEVICE_ID);
assertThat(mappedMessage.getAdditionalProperties()).containsEntry("foo", "bar");
});
ctx.completeNow();
}));
final ArgumentCaptor<Handler<AsyncResult<HttpResponse<Buffer>>>> handleCaptor = VertxMockSupport.argumentCaptorHandler();
verify(httpRequest).sendBuffer(any(Buffer.class), handleCaptor.capture());
handleCaptor.getValue().handle(Future.succeededFuture(httpResponse));
final ArgumentCaptor<MultiMap> headersCaptor = ArgumentCaptor.forClass(MultiMap.class);
verify(httpRequest).putHeaders(headersCaptor.capture());
final MultiMap addedHeaders = headersCaptor.getValue();
assertThat(addedHeaders.contains(MessageHelper.APP_PROPERTY_ORIG_ADDRESS, topic, false)).isTrue();
assertThat(addedHeaders.contains(HttpHeaders.CONTENT_TYPE.toString(), "text/plain", false));
}
use of org.eclipse.hono.util.ResourceIdentifier in project hono by eclipse.
the class HttpBasedMessageMappingTest method testMappingFailsForWhenPayloadCannotMapped.
/**
* Verifies that the downstream mapper returns a failed future with a ServerErrorException if the downstream mapper has been configured
* for an adapter but the remote service returns a 403 status code indicating that the device payload cannot be mapped.
*
* @param ctx The Vert.x test context.
*/
@Test
@SuppressWarnings("unchecked")
public void testMappingFailsForWhenPayloadCannotMapped(final VertxTestContext ctx) {
config.setMapperEndpoints(Map.of("mapper", MapperEndpoint.from("host", 1234, "/uri", false)));
final ResourceIdentifier targetAddress = ResourceIdentifier.from(TelemetryConstants.TELEMETRY_ENDPOINT, TEST_TENANT_ID, "gateway");
final HttpRequest<Buffer> httpRequest = mock(HttpRequest.class, withSettings().defaultAnswer(RETURNS_SELF));
final HttpResponse<Buffer> httpResponse = mock(HttpResponse.class);
when(httpResponse.statusCode()).thenReturn(HttpURLConnection.HTTP_FORBIDDEN);
when(mapperWebClient.post(anyInt(), anyString(), anyString())).thenReturn(httpRequest);
final MqttPublishMessage message = newMessage(MqttQoS.AT_LEAST_ONCE, "mqtt-topic");
final MqttContext context = newContext(message, span, new Device(TEST_TENANT_ID, "gateway"));
final RegistrationAssertion assertion = new RegistrationAssertion("gateway").setDownstreamMessageMapper("mapper");
messageMapping.mapDownstreamMessage(context, targetAddress, assertion).onComplete(ctx.failing(t -> {
ctx.verify(() -> {
assertThat(t).isInstanceOf(ServerErrorException.class);
assertThat((((ServerErrorException) t).getErrorCode())).isEqualTo(HttpURLConnection.HTTP_UNAVAILABLE);
});
ctx.completeNow();
}));
final ArgumentCaptor<Handler<AsyncResult<HttpResponse<Buffer>>>> handlerCaptor = VertxMockSupport.argumentCaptorHandler();
verify(httpRequest).sendBuffer(any(Buffer.class), handlerCaptor.capture());
handlerCaptor.getValue().handle(Future.succeededFuture(httpResponse));
}
Aggregations