All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.eclipse.ditto.connectivity.service.messaging.validation.CredentialsValidationVisitor Maven / Gradle / Ivy

/*
 * Copyright (c) 2021 Contributors to the Eclipse Foundation
 *
 * See the NOTICE file(s) distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0
 *
 * SPDX-License-Identifier: EPL-2.0
 */
package org.eclipse.ditto.connectivity.service.messaging.validation;

import java.net.URL;
import java.util.Set;
import java.util.regex.Pattern;

import javax.annotation.concurrent.Immutable;

import org.eclipse.ditto.base.model.headers.DittoHeaders;
import org.eclipse.ditto.connectivity.model.ClientCertificateCredentials;
import org.eclipse.ditto.connectivity.model.Connection;
import org.eclipse.ditto.connectivity.model.ConnectionConfigurationInvalidException;
import org.eclipse.ditto.connectivity.model.ConnectionType;
import org.eclipse.ditto.connectivity.model.CredentialsVisitor;
import org.eclipse.ditto.connectivity.model.HmacCredentials;
import org.eclipse.ditto.connectivity.model.OAuthClientCredentials;
import org.eclipse.ditto.connectivity.model.SshPublicKeyCredentials;
import org.eclipse.ditto.connectivity.model.UserPasswordCredentials;
import org.eclipse.ditto.connectivity.service.config.ConnectivityConfig;

import akka.http.javadsl.model.Uri;

/**
 * Validate credentials in a connection.
 */
@Immutable
final class CredentialsValidationVisitor implements CredentialsVisitor {

    private final Connection connection;
    private final DittoHeaders dittoHeaders;
    private final Set algorithms;
    private final HostValidator hostValidator;

    private static final String ALLOWED_CHARACTERS = "\\x21\\x23-\\x5B\\x5D-\\x7E";
    private static final Pattern REQUESTED_SCOPES_REGEX =
            Pattern.compile("^[" + ALLOWED_CHARACTERS + "]+( [" + ALLOWED_CHARACTERS + "]+)*$");

    private CredentialsValidationVisitor(final Connection connection, final DittoHeaders dittoHeaders,
            final ConnectivityConfig config, final HostValidator hostValidator) {
        this.connection = connection;
        this.dittoHeaders = dittoHeaders;
        algorithms = config.getConnectionConfig().getHttpPushConfig().getHmacAlgorithms().keySet();
        this.hostValidator = hostValidator;
    }

    static CredentialsValidationVisitor of(final Connection connection, final DittoHeaders dittoHeaders,
            final ConnectivityConfig connectivityConfig, final HostValidator hostValidator) {
        return new CredentialsValidationVisitor(connection, dittoHeaders, connectivityConfig, hostValidator);
    }

    @Override
    public Void clientCertificate(final ClientCertificateCredentials credentials) {
        return null;
    }

    @Override
    public Void usernamePassword(final UserPasswordCredentials credentials) {
        return null;
    }

    @Override
    public Void sshPublicKeyAuthentication(final SshPublicKeyCredentials credentials) {
        return null;
    }

    @Override
    public Void hmac(final HmacCredentials credentials) {
        switch (connection.getConnectionType()) {
            case AMQP_10:
            case HTTP_PUSH:
                break;
            default:
                throw ConnectionConfigurationInvalidException.newBuilder(
                        "HMAC credentials are not supported for the connection type.")
                        .description("Only HTTP and AMQP 1.0 connections support HMAC credentials.")
                        .dittoHeaders(dittoHeaders)
                        .build();
        }
        final Uri uri = Uri.create(connection.getUri());
        if (!uri.getUserInfo().isEmpty()) {
            throw ConnectionConfigurationInvalidException.newBuilder(
                    "There are conflicting authentication mechanisms.")
                    .description("HMAC credentials and URI userinfo may not be present at the same time.")
                    .dittoHeaders(dittoHeaders)
                    .build();
        }
        if (!algorithms.contains(credentials.getAlgorithm())) {
            throw ConnectionConfigurationInvalidException.newBuilder(
                            "Unsupported HMAC algorithm: " + credentials.getAlgorithm())
                    .description("Supported algorithms: " + String.join(", ", algorithms))
                    .dittoHeaders(dittoHeaders)
                    .build();
        }
        return null;
    }

    @Override
    public Void oauthClientCredentials(final OAuthClientCredentials credentials) {
        if (ConnectionType.HTTP_PUSH != connection.getConnectionType()) {
            throw ConnectionConfigurationInvalidException.newBuilder(
                            "OAuth client credentials are only supported for HTTP connection type.")
                    .description("Only HTTP connections support OAuth client credentials.")
                    .dittoHeaders(dittoHeaders)
                    .build();
        }
        validateTokenEndpoint(credentials.getTokenEndpoint());
        if (!REQUESTED_SCOPES_REGEX.matcher(credentials.getRequestedScopes()).matches()) {
            throw ConnectionConfigurationInvalidException.newBuilder(
                            "Invalid format of requested scopes: " + credentials.getRequestedScopes())
                    .description("Provide scopes as space separated list (RFC6749 section 3.3).")
                    .href("https://datatracker.ietf.org/doc/html/rfc6749#section-3.3")
                    .dittoHeaders(dittoHeaders)
                    .build();
        }
        return null;
    }

    private void validateTokenEndpoint(final String tokenEndpoint) {
        final URL url;
        try {
            url = new URL(tokenEndpoint);
        } catch (final Exception e) {
            throw ConnectionConfigurationInvalidException.newBuilder(
                            String.format("Invalid token endpoint '%s' provided: %s", tokenEndpoint, e.getMessage()))
                    .description("Provide a valid URL as token endpoint.")
                    .dittoHeaders(dittoHeaders)
                    .build();
        }
        hostValidator.validateHostname(url.getHost(), dittoHeaders);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy