private boolean hasPermissionForTenant(final String subject, final ResourceIdentifier resource, final Permission permission) { if (resource.getResourceId() != null) { final ResourceIdentifier tenantResource = ResourceIdentifier.from(resource.getEndpoint(), resource.getTenantId(), null); return hasPermissionInternal(subject, tenantResource, permission); } return false; }
@Override public boolean isAuthorized(final ResourceIdentifier resource, final Activity intent) { boolean allowed = false; if (resource.getResourceId() != null) { allowed = isAuthorized(String.format(resTemplate, resource.toString()), intent); } if (!allowed && resource.getTenantId() != null) { allowed = isAuthorized(String.format(resTemplate, resource.getEndpoint() + "/" + resource.getTenantId()), intent) || isAuthorized(String.format(resTemplate, resource.getEndpoint() + "/*"), intent); } if (!allowed) { allowed = isAuthorized(String.format(resTemplate, resource.getEndpoint()), intent) || isAuthorized(String.format(resTemplate, "*"), intent); } return allowed; }
private static String getTenantOnlyTargetAddress(final String address) { ResourceIdentifier targetAddress = ResourceIdentifier.fromString(address); return String.format("%s/%s", targetAddress.getEndpoint(), targetAddress.getTenantId()); }
private ResourceIdentifier(final ResourceIdentifier resourceIdentifier, final String tenantId, final String resourceId) { String[] path = resourceIdentifier.getResourcePath(); if (path.length < 3) { path = new String[3]; path[IDX_ENDPOINT] = resourceIdentifier.getEndpoint(); } path[IDX_TENANT_ID] = tenantId; path[IDX_RESOURCE_ID] = resourceId; setResourcePath(path); }
Objects.requireNonNull(message); switch (EndpointType.fromString(resource.getEndpoint())) { case TELEMETRY: return uploadTelemetryMessage( ctx, resource.getTenantId(), resource.getResourceId(), message.payload()); case EVENT: return uploadEventMessage( ctx, resource.getTenantId(), resource.getResourceId(), message.payload()); case CONTROL:
private ResourceIdentifier getResourceIdentifier(final String tenant) { return ResourceIdentifier.from(TenantConstants.TENANT_ENDPOINT, tenant, null); }
Future<ResourceIdentifier> checkAddress(final MqttContext ctx, final ResourceIdentifier address) { final Future<ResourceIdentifier> result = Future.future(); if (ctx.authenticatedDevice() == null) { if (address.getTenantId() == null || address.getResourceId() == null) { result.fail(new ClientErrorException(HttpURLConnection.HTTP_BAD_REQUEST, "topic of unauthenticated message must contain tenant and device ID")); } else { result.complete(address); } } else { if (address.getTenantId() != null && address.getResourceId() == null) { result.fail(new ClientErrorException(HttpURLConnection.HTTP_BAD_REQUEST, "topic of authenticated message must not contain tenant ID only")); } else if (address.getTenantId() == null && address.getResourceId() == null) { // use authenticated device's tenant to fill in missing information final ResourceIdentifier downstreamAddress = ResourceIdentifier.from(address, ctx.authenticatedDevice().getTenantId(), ctx.authenticatedDevice().getDeviceId()); result.complete(downstreamAddress); } else { result.complete(address); } } return result; } }
/** * Checks whether an AMQP message contains required standard properties. * <p> * This method checks if the message contains a valid device identifier using * the {@link #hasValidDeviceId(ResourceIdentifier, Message)} method. * <p> * After successful verification the following properties are added to the message's <em>annotations</em>: * <ul> * <li>{@link MessageHelper#APP_PROPERTY_DEVICE_ID} - the ID of the device that reported the data.</li> * <li>{@link MessageHelper#APP_PROPERTY_TENANT_ID} - the ID of the tenant as indicated by the link target's second segment.</li> * <li>{@link MessageHelper#APP_PROPERTY_RESOURCE} - the full resource path including the endpoint, the tenant and the device ID.</li> * </ul> * * @param linkTarget The resource path to check the message's properties against for consistency. * @param msg The AMQP 1.0 message to perform the checks on. * @return {@code true} if the message passes all checks. */ protected static final boolean verifyStandardProperties(final ResourceIdentifier linkTarget, final Message msg) { if (!hasValidDeviceId(linkTarget, msg)) { return false; } else { final ResourceIdentifier targetResource = ResourceIdentifier .from(linkTarget.getEndpoint(), linkTarget.getTenantId(), MessageHelper.getDeviceId(msg)); MessageHelper.annotate(msg, targetResource); return true; } }
Future<ResourceIdentifier> mapTopic(final MqttContext ctx) { final Future<ResourceIdentifier> result = Future.future(); final ResourceIdentifier topic = ctx.topic(); 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); } return result; }
/** * {@inheritDoc} */ @Override public Future<CommandClient> getOrCreateCommandClient(final String tenantId, final String deviceId, final String replyId) { Objects.requireNonNull(tenantId); Objects.requireNonNull(deviceId); Objects.requireNonNull(replyId); log.debug("get or create command client for [tenantId: {}, deviceId: {}, replyId: {}]", tenantId, deviceId, replyId); return getOrCreateRequestResponseClient( ResourceIdentifier.from(CommandConstants.COMMAND_ENDPOINT, tenantId, deviceId).toString(), () -> newCommandClient(tenantId, deviceId, replyId)).map(c -> (CommandClient) c); }
/** * Adds several AMQP 1.0 message <em>annotations</em> to the given message that are used to process/route the * message. * <p> * In particular, the following annotations are added: * <ul> * <li>{@link #APP_PROPERTY_TENANT_ID} - the tenant ID segment of the resource identifier</li> * <li>{@link #APP_PROPERTY_DEVICE_ID} - the resource ID segment of the resource identifier (if not * {@code null}</li> * <li>{@link #APP_PROPERTY_RESOURCE} - the full resource path including the endpoint, the tenant and the resource * ID</li> * </ul> * * @param msg the message to add the message annotations to. * @param resourceIdentifier the resource identifier that will be added as annotation. */ public static void annotate(final Message msg, final ResourceIdentifier resourceIdentifier) { MessageHelper.addAnnotation(msg, APP_PROPERTY_TENANT_ID, resourceIdentifier.getTenantId()); if (resourceIdentifier.getResourceId() != null) { MessageHelper.addAnnotation(msg, APP_PROPERTY_DEVICE_ID, resourceIdentifier.getResourceId()); } MessageHelper.addAnnotation(msg, APP_PROPERTY_RESOURCE, resourceIdentifier.toString()); }
Objects.requireNonNull(targetAddress); final String[] addressPath = targetAddress.getResourcePath(); final String reqId = addressPath[CommandConstants.TOPIC_POSITION_RESPONSE_REQ_ID]; final CommandResponse commandResponse = CommandResponse.from( reqId, targetAddress.getResourceId(), ctx.message().payload(), ctx.contentType(), status); .withTag(Tags.COMPONENT.getKey(), getTypeName()) .withTag(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_CLIENT) .withTag(MessageHelper.APP_PROPERTY_TENANT_ID, targetAddress.getTenantId()) .withTag(MessageHelper.APP_PROPERTY_DEVICE_ID, targetAddress.getResourceId()) .withTag(Constants.HEADER_COMMAND_RESPONSE_STATUS, status) .withTag(Constants.HEADER_COMMAND_REQUEST_ID, reqId) .start(); return sendCommandResponse(targetAddress.getTenantId(), commandResponse, currentSpan.context()) .map(delivery -> { LOG.trace("successfully forwarded command response from device [tenant-id: {}, device-id: {}]", targetAddress.getTenantId(), targetAddress.getResourceId()); metrics.incrementCommandResponseDeliveredToApplication(targetAddress.getTenantId());
.getRegistrationJson(RegistrationConstants.ACTION_GET, resource.getTenantId(), resource.getResourceId());
ResourceIdentifier t = null; try { t = ResourceIdentifier.fromString(publishedMessage.topicName()); if (t.getEndpoint().length() == 1) { final String[] path = t.getResourcePath(); path[0] = EndpointType.fromString(t.getEndpoint()).canonicalName(); t = ResourceIdentifier.fromPath(path);
final ResourceIdentifier replyTo = ResourceIdentifier.fromString(message.getReplyTo()); if (!CommandConstants.COMMAND_ENDPOINT.equals(replyTo.getEndpoint())) { } else if (!tenantId.equals(replyTo.getTenantId())) { replyToId = replyTo.getPathWithoutBase(); if (replyToId == null) { valid = false;
private void forwardMessage(final UpstreamReceiver link, final ProtonDelivery delivery, final Message msg) { final ResourceIdentifier messageAddress = ResourceIdentifier.fromString(getAnnotation(msg, APP_PROPERTY_RESOURCE, String.class)); checkDeviceExists(messageAddress, deviceExists -> { if (deviceExists) { downstreamAdapter.processMessage(link, delivery, msg); } else { logger.debug("device {}/{} does not exist, closing link", messageAddress.getTenantId(), messageAddress.getResourceId()); MessageHelper.rejected(delivery, AmqpError.PRECONDITION_FAILED.toString(), "device does not exist"); link.close(condition(AmqpError.PRECONDITION_FAILED.toString(), "device does not exist")); } }); }
private void createStringRepresentation() { resource = createStringRepresentation(0); final StringBuilder b = new StringBuilder(getEndpoint()); if (getTenantId() != null) { b.append("/").append(getTenantId()); } basePath = b.toString(); }
/** * Gets the endpoint name of the context's resource. * * @return The endpoint name. */ String getEndpoint() { return resource.getEndpoint(); }
/** * Get extended device. * * @param exchange coap exchange with URI and/or peer's principal. * @param handler handler for determined extended device */ public void getExtendedDevice(final CoapExchange exchange, final Handler<ExtendedDevice> handler) { try { final List<String> pathList = exchange.getRequestOptions().getUriPath(); final String[] path = pathList.toArray(new String[pathList.size()]); final ResourceIdentifier identifier = ResourceIdentifier.fromPath(path); final Device device = new Device(identifier.getTenantId(), identifier.getResourceId()); final Principal peer = exchange.advanced().getRequest().getSourceContext().getPeerIdentity(); if (peer == null) { final ExtendedDevice extendedDevice = new ExtendedDevice(device, device); log.debug("use {}", extendedDevice); handler.handle(extendedDevice); } else { getAuthenticatedExtendedDevice(device, exchange, handler); } } catch (NullPointerException cause) { CoapErrorResponse.respond(exchange, "missing tenant and device!", ResponseCode.BAD_REQUEST); } catch (Throwable cause) { CoapErrorResponse.respond(exchange, cause, ResponseCode.INTERNAL_SERVER_ERROR); } }
/** * Gets the tenant that the device belongs to that published * the message. * * @return The tenant identifier or {@code null} if the tenant cannot * be determined from the message's topic. */ public String tenant() { return Optional.ofNullable(topic).map(t -> t.getTenantId()).orElse(null); }