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

com.clevercloud.biscuitpulsar.AuthenticationProviderBiscuit Maven / Gradle / Ivy

There is a newer version: 3.7.1
Show newest version
package com.clevercloud.biscuitpulsar;

import com.clevercloud.biscuit.crypto.PublicKey;
import org.apache.pulsar.broker.ServiceConfiguration;
import org.apache.pulsar.broker.authentication.AuthenticationDataSource;
import org.apache.pulsar.broker.authentication.AuthenticationProvider;
import com.clevercloud.biscuit.token.Biscuit;
import com.clevercloud.biscuit.error.Error;

import io.vavr.control.Either;

import javax.naming.AuthenticationException;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Base64;
import java.util.List;
import org.apache.commons.lang3.StringUtils;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class AuthenticationProviderBiscuit implements AuthenticationProvider {
  private static final Logger log = LoggerFactory.getLogger(AuthenticationProviderBiscuit.class);

  final static String HTTP_HEADER_NAME = "Authorization";
  final static String HTTP_HEADER_VALUE_PREFIX = "Bearer ";

  final static String BISCUIT = "biscuit";

  final static String CONF_BISCUIT_SEALING_KEY = "biscuitSealingKey";
  final static String CONF_BISCUIT_PUBLIC_ROOT_KEY = "biscuitPublicRootKey";

  private PublicKey rootKey;
  static String SEALING_KEY;

  public void close() throws IOException {
    // noop
  }

  public void initialize(ServiceConfiguration serviceConfiguration) throws IOException {
    log.info("Initialize Apache Pulsar Biscuit authentication plugin");
    String key = (String) serviceConfiguration.getProperty(CONF_BISCUIT_PUBLIC_ROOT_KEY);
    log.debug("Got biscuit root public key: {}", key);
    SEALING_KEY = (String) serviceConfiguration.getProperty(CONF_BISCUIT_SEALING_KEY);
    log.debug("Got biscuit sealing key: {}", SEALING_KEY);

    try {
      rootKey = new PublicKey(hexStringToByteArray(key));
    } catch (Exception e) {
      log.error("Could not decode Biscuit root public key: {}", e);
      throw new IOException();
    }
  }

  public String getAuthMethodName() {
    return BISCUIT;
  }

  public String authenticate(AuthenticationDataSource authData) throws AuthenticationException {
    String biscuit = getBiscuit(authData);
    return parseBiscuit(biscuit);
  }

  public static String getBiscuit(AuthenticationDataSource authData) throws AuthenticationException {
    if (authData.hasDataFromCommand()) {
      // Authenticate Pulsar binary connection
      return authData.getCommandData();
    } else if (authData.hasDataFromHttp()) {
      // Authentication HTTP request. The format here should be compliant to RFC-6750
      // (https://tools.ietf.org/html/rfc6750#section-2.1). Eg: Authorization: Bearer xxxxxxxxxxxxx
      String httpHeaderValue = authData.getHttpHeader(HTTP_HEADER_NAME);
      if (httpHeaderValue == null || !httpHeaderValue.startsWith(HTTP_HEADER_VALUE_PREFIX)) {
        throw new AuthenticationException("Invalid HTTP Authorization header");
      }

      // Remove prefix
      String biscuit = httpHeaderValue.substring(HTTP_HEADER_VALUE_PREFIX.length());
      return validateBiscuit(biscuit);
    } else {
      throw new AuthenticationException("No biscuit credentials passed");
    }
  }

  private static String validateBiscuit(final String biscuit) throws AuthenticationException {
    if (StringUtils.isNotBlank(biscuit)) {
      return biscuit;
    } else {
      throw new AuthenticationException("Blank biscuit found");
    }
  }

  private String parseBiscuit(final String biscuit) throws AuthenticationException {
    log.debug("Biscuit to parse: {}", biscuit);
    try {
      Either deser = Biscuit.from_b64(biscuit);

      if (deser.isLeft()) {
        throw new AuthenticationException("Could not deserialize biscuit");
      } else {
        Biscuit realBiscuit = deser.get();
        log.debug("Deserialized biscuit");

        if (realBiscuit.check_root_key(rootKey).isLeft()) {
          throw new AuthenticationException("This biscuit was not generated with the expected root key");
        }
        log.debug("Root key is valid");

        byte[] sealed = realBiscuit.seal(SEALING_KEY.getBytes()).get();
        log.debug("Biscuit deserialized and sealed");
        return "biscuit:" + Base64.getEncoder().encodeToString(sealed);
      }
    } catch (IllegalArgumentException e) {
      throw new AuthenticationException(e.getMessage());
    }
  }

  // using that instead of Hex.decodeHex from commons-codec because there's an incompatibility with Pulsar's dependencies
  public static byte[] hexStringToByteArray(String hex) {
    int l = hex.length();
    byte[] data = new byte[l/2];
    for (int i = 0; i < l; i += 2) {
      data[i/2] = (byte) ((Character.digit(hex.charAt(i), 16) << 4)
              + Character.digit(hex.charAt(i+1), 16));
    }
    return data;
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy