org.eclipse.hono.adapter.http.vertx.VertxBasedHttpProtocolAdapter.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.hono.adapter.http.vertx.VertxBasedHttpProtocolAdapter.java

Source

/**
 * Copyright (c) 2016, 2018 Bosch Software Innovations GmbH.
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *    Bosch Software Innovations GmbH - initial creation
 */

package org.eclipse.hono.adapter.http.vertx;

import java.net.HttpURLConnection;
import java.util.Objects;
import java.util.Optional;

import org.eclipse.hono.adapter.http.AbstractVertxBasedHttpProtocolAdapter;
import org.eclipse.hono.adapter.http.HonoBasicAuthHandler;
import org.eclipse.hono.adapter.http.HttpProtocolAdapterProperties;
import org.eclipse.hono.adapter.http.X509AuthHandler;
import org.eclipse.hono.service.auth.device.Device;
import org.eclipse.hono.service.auth.device.HonoChainAuthHandler;
import org.eclipse.hono.service.auth.device.HonoClientBasedAuthProvider;
import org.eclipse.hono.service.auth.device.X509AuthProvider;
import org.eclipse.hono.service.auth.device.UsernamePasswordAuthProvider;
import org.eclipse.hono.service.http.HttpUtils;
import org.eclipse.hono.util.Constants;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import io.vertx.core.Handler;
import io.vertx.core.http.HttpHeaders;
import io.vertx.core.http.HttpMethod;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.RoutingContext;
import io.vertx.ext.web.handler.ChainAuthHandler;
import io.vertx.ext.web.handler.CorsHandler;

/**
 * A Vert.x based Hono protocol adapter for accessing Hono's Telemetry & Event API using HTTP.
 */
public final class VertxBasedHttpProtocolAdapter
        extends AbstractVertxBasedHttpProtocolAdapter<HttpProtocolAdapterProperties> {

    private static final Logger LOG = LoggerFactory.getLogger(VertxBasedHttpProtocolAdapter.class);
    private static final String PARAM_TENANT = "tenant";
    private static final String PARAM_DEVICE_ID = "device_id";

    private HonoClientBasedAuthProvider usernamePasswordAuthProvider;
    private HonoClientBasedAuthProvider clientCertAuthProvider;

    /**
     * Sets the provider to use for authenticating devices based on
     * a username and password.
     * <p>
     * If not set explicitly using this method, a {@code UsernamePasswordAuthProvider}
     * will be created during startup.
     * 
     * @param provider The provider to use.
     * @throws NullPointerException if provider is {@code null}.
     */
    public void setUsernamePasswordAuthProvider(final HonoClientBasedAuthProvider provider) {
        this.usernamePasswordAuthProvider = Objects.requireNonNull(provider);
    }

    /**
     * Sets the provider to use for authenticating devices based on
     * a client certificate.
     * <p>
     * If not set explicitly using this method, a {@code SubjectDnAuthProvider}
     * will be created during startup.
     * 
     * @param provider The provider to use.
     * @throws NullPointerException if provider is {@code null}.
     */
    public void setClientCertAuthProvider(final HonoClientBasedAuthProvider provider) {
        this.clientCertAuthProvider = Objects.requireNonNull(provider);
    }

    /**
     * {@inheritDoc}
     * 
     * @return {@link Constants#PROTOCOL_ADAPTER_TYPE_HTTP}
     */
    @Override
    protected String getTypeName() {
        return Constants.PROTOCOL_ADAPTER_TYPE_HTTP;
    }

    @Override
    protected void addRoutes(final Router router) {

        if (getConfig().isAuthenticationRequired()) {

            final ChainAuthHandler authHandler = new HonoChainAuthHandler();
            authHandler.append(new X509AuthHandler(
                    Optional.ofNullable(clientCertAuthProvider)
                            .orElse(new X509AuthProvider(getCredentialsServiceClient(), getConfig())),
                    getTenantServiceClient()));
            authHandler.append(new HonoBasicAuthHandler(
                    Optional.ofNullable(usernamePasswordAuthProvider)
                            .orElse(new UsernamePasswordAuthProvider(getCredentialsServiceClient(), getConfig())),
                    getConfig().getRealm()));
            addTelemetryApiRoutes(router, authHandler);
            addEventApiRoutes(router, authHandler);

        } else {

            LOG.warn("device authentication has been disabled");
            LOG.warn("any device may publish data on behalf of all other devices");
            addTelemetryApiRoutes(router, null);
            addEventApiRoutes(router, null);
        }
    }

    private void addTelemetryApiRoutes(final Router router, final Handler<RoutingContext> authHandler) {

        // support CORS headers for PUTing telemetry
        router.routeWithRegex("\\/telemetry\\/[^\\/]+\\/.*")
                .handler(CorsHandler.create(getConfig().getCorsAllowedOrigin()).allowedMethod(HttpMethod.PUT)
                        .allowedHeader(HttpHeaders.AUTHORIZATION.toString())
                        .allowedHeader(HttpHeaders.CONTENT_TYPE.toString()));

        if (getConfig().isAuthenticationRequired()) {

            // support CORS headers for POSTing telemetry
            router.route("/telemetry")
                    .handler(CorsHandler.create(getConfig().getCorsAllowedOrigin()).allowedMethod(HttpMethod.POST)
                            .allowedHeader(HttpHeaders.AUTHORIZATION.toString())
                            .allowedHeader(HttpHeaders.CONTENT_TYPE.toString()));

            // require auth for POSTing telemetry
            router.route(HttpMethod.POST, "/telemetry").handler(authHandler);

            // route for posting telemetry data using tenant and device ID determined as part of
            // device authentication
            router.route(HttpMethod.POST, "/telemetry").handler(this::handlePostTelemetry);

            // require auth for PUTing telemetry
            router.route(HttpMethod.PUT, "/telemetry/*").handler(authHandler);
            // assert that authenticated device's tenant matches tenant from path variables
            router.route(HttpMethod.PUT, String.format("/telemetry/:%s/:%s", PARAM_TENANT, PARAM_DEVICE_ID))
                    .handler(this::assertTenant);
        }

        // route for uploading telemetry data
        router.route(HttpMethod.PUT, String.format("/telemetry/:%s/:%s", PARAM_TENANT, PARAM_DEVICE_ID))
                .handler(ctx -> uploadTelemetryMessage(ctx, getTenantParam(ctx), getDeviceIdParam(ctx)));
    }

    private void addEventApiRoutes(final Router router, final Handler<RoutingContext> authHandler) {

        // support CORS headers for PUTing events
        router.routeWithRegex("\\/event\\/[^\\/]+\\/.*")
                .handler(CorsHandler.create(getConfig().getCorsAllowedOrigin()).allowedMethod(HttpMethod.PUT)
                        .allowedHeader(HttpHeaders.AUTHORIZATION.toString())
                        .allowedHeader(HttpHeaders.CONTENT_TYPE.toString()));

        if (getConfig().isAuthenticationRequired()) {

            // support CORS headers for POSTing events
            router.route("/event")
                    .handler(CorsHandler.create(getConfig().getCorsAllowedOrigin()).allowedMethod(HttpMethod.POST)
                            .allowedHeader(HttpHeaders.AUTHORIZATION.toString())
                            .allowedHeader(HttpHeaders.CONTENT_TYPE.toString()));

            // require auth for POSTing events
            router.route(HttpMethod.POST, "/event").handler(authHandler);

            // route for posting events using tenant and device ID determined as part of
            // device authentication
            router.route(HttpMethod.POST, "/event").handler(this::handlePostEvent);

            // require auth for PUTing events
            router.route(HttpMethod.PUT, "/event/*").handler(authHandler);
            // route for asserting that authenticated device's tenant matches tenant from path variables
            router.route(HttpMethod.PUT, String.format("/event/:%s/:%s", PARAM_TENANT, PARAM_DEVICE_ID))
                    .handler(this::assertTenant);
        }

        // route for sending event messages
        router.route(HttpMethod.PUT, String.format("/event/:%s/:%s", PARAM_TENANT, PARAM_DEVICE_ID))
                .handler(ctx -> uploadEventMessage(ctx, getTenantParam(ctx), getDeviceIdParam(ctx)));
    }

    private static String getTenantParam(final RoutingContext ctx) {
        return ctx.request().getParam(PARAM_TENANT);
    }

    private static String getDeviceIdParam(final RoutingContext ctx) {
        return ctx.request().getParam(PARAM_DEVICE_ID);
    }

    private void handle401(final RoutingContext ctx) {
        HttpUtils.unauthorized(ctx, "Basic realm=\"" + getConfig().getRealm() + "\"");
    }

    void handlePostTelemetry(final RoutingContext ctx) {

        if (Device.class.isInstance(ctx.user())) {
            final Device device = (Device) ctx.user();
            uploadTelemetryMessage(ctx, device.getTenantId(), device.getDeviceId());
        } else {
            handle401(ctx);
        }
    }

    void handlePostEvent(final RoutingContext ctx) {

        if (Device.class.isInstance(ctx.user())) {
            final Device device = (Device) ctx.user();
            uploadEventMessage(ctx, device.getTenantId(), device.getDeviceId());
        } else {
            handle401(ctx);
        }
    }

    void assertTenant(final RoutingContext ctx) {

        if (Device.class.isInstance(ctx.user())) {
            final Device device = (Device) ctx.user();
            if (device.getTenantId().equals(getTenantParam(ctx))) {
                ctx.next();
            } else {
                ctx.fail(HttpURLConnection.HTTP_FORBIDDEN);
            }
        } else {
            handle401(ctx);
        }
    }
}