org.eclipse.hono.service.auth.AbstractHonoAuthenticationService.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.hono.service.auth.AbstractHonoAuthenticationService.java

Source

/**
 * Copyright (c) 2016, 2017 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.service.auth;

import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

import javax.security.auth.login.CredentialException;

import org.eclipse.hono.auth.HonoUser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import io.vertx.core.AsyncResult;
import io.vertx.core.Future;
import io.vertx.core.Handler;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.json.JsonObject;

/**
 * A base class for implementing an authentication service supporting <a href="https://tools.ietf.org/html/rfc4616">
 * SASL PLAIN and EXTERNAL</a> mechanisms.
 * 
 * @param <T> The type of configuration properties this service supports.
 */
public abstract class AbstractHonoAuthenticationService<T> extends BaseAuthenticationService<T> {

    /**
     * A logger to be used by subclasses.
     */
    protected final Logger log = LoggerFactory.getLogger(getClass());

    /**
     * The authentication request is required to contain the SASL mechanism in property {@link AuthenticationConstants#FIELD_MECHANISM}
     * and the client's SASL response (Base64 encoded byte array) in property {@link AuthenticationConstants#FIELD_SASL_RESPONSE}.
     * When the mechanism is {@linkplain AuthenticationConstants#MECHANISM_EXTERNAL EXTERNAL}, the request must also contain
     * the <em>subject</em> distinguished name of the verified client certificate in property {@link AuthenticationConstants#FIELD_SUBJECT_DN}.
     * <p>
     * An example request for a client using SASL EXTERNAL wishing to act as <em>NEW_IDENTITY</em> instead of <em>ORIG_IDENTITY</em>
     * looks like this:
     * <pre>
     * {
     *   "mechanism": "EXTERNAL",
     *   "sasl-response": "TkVXX0lERU5USVRZ",  // the Base64 encoded UTF-8 representation of "NEW_IDENTITY"
     *   "subject-dn": "CN=ORIG_IDENTITY" // the subject Distinguished Name from the verified client certificate
     * }
     * </pre>
     */
    @Override
    public final void authenticate(final JsonObject authRequest,
            final Handler<AsyncResult<HonoUser>> resultHandler) {

        final String mechanism = Objects.requireNonNull(authRequest)
                .getString(AuthenticationConstants.FIELD_MECHANISM);
        log.debug("received authentication request [mechanism: {}]", mechanism);

        if (AuthenticationConstants.MECHANISM_PLAIN.equals(mechanism)) {

            byte[] saslResponse = authRequest.getBinary(AuthenticationConstants.FIELD_SASL_RESPONSE, new byte[0]);

            try {
                String[] fields = readFields(saslResponse);
                String authzid = fields[0];
                String authcid = fields[1];
                String pwd = fields[2];
                log.debug("processing PLAIN authentication request [authzid: {}, authcid: {}, pwd: *****]", authzid,
                        authcid);
                verifyPlain(authzid, authcid, pwd, resultHandler);
            } catch (CredentialException e) {
                // response did not contain expected values
                resultHandler.handle(Future.failedFuture(e));
            }

        } else if (AuthenticationConstants.MECHANISM_EXTERNAL.equals(mechanism)) {

            final String authzid = new String(authRequest.getBinary(AuthenticationConstants.FIELD_SASL_RESPONSE),
                    StandardCharsets.UTF_8);
            final String subject = authRequest.getString(AuthenticationConstants.FIELD_SUBJECT_DN);
            log.debug("processing EXTERNAL authentication request [Subject DN: {}]", subject);
            verifyExternal(authzid, subject, resultHandler);

        } else {
            resultHandler.handle(Future.failedFuture("unsupported SASL mechanism"));
        }
    }

    private String[] readFields(final byte[] buffer) throws CredentialException {
        List<String> fields = new ArrayList<>();
        int pos = 0;
        Buffer b = Buffer.buffer();
        while (pos < buffer.length) {
            byte val = buffer[pos];
            if (val == 0x00) {
                fields.add(b.toString(StandardCharsets.UTF_8));
                b = Buffer.buffer();
            } else {
                b.appendByte(val);
            }
            pos++;
        }
        fields.add(b.toString(StandardCharsets.UTF_8));

        if (fields.size() != 3) {
            throw new CredentialException("client provided malformed PLAIN response");
        } else if (fields.get(1) == null || fields.get(1).length() == 0) {
            throw new CredentialException("PLAIN response must contain an authentication ID");
        } else if (fields.get(2) == null || fields.get(2).length() == 0) {
            throw new CredentialException("PLAIN response must contain a password");
        } else {
            return fields.toArray(new String[3]);
        }
    }

    /**
     * Verifies username/password credentials provided by a client in a SASL PLAIN response.
     * <p>
     * Subclasses should implement the actual verification of the given credentials in this method.
     * 
     * @param authzid The identity the client wants to act as. If {@code null} the username is used.
     * @param authcid The username.
     * @param password The password.
     * @param authenticationResultHandler The handler to invoke with the authentication result. On successful authentication,
     *                                    the result contains the authenticated user.
     */
    public abstract void verifyPlain(final String authzid, final String authcid, final String password,
            final Handler<AsyncResult<HonoUser>> authenticationResultHandler);

    /**
     * Verifies a Subject DN that has been provided by a client in a SASL EXTERNAL exchange.
     * <p>
     * Subclasses should implement the actual verification of the given credentials in this method.
     * 
     * @param authzid The identity the client wants to act as. If {@code null} the granted authorization identity is derived from the subject DN.
     * @param subjectDn The Subject DN.
     * @param authenticationResultHandler The handler to invoke with the authentication result. On successful authentication,
     *                                    the result contains the authenticated user.
     */
    public abstract void verifyExternal(final String authzid, final String subjectDn,
            final Handler<AsyncResult<HonoUser>> authenticationResultHandler);

    @Override
    public String toString() {
        return getClass().getSimpleName();
    }
}