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

org.sdase.commons.server.auth.AuthBundle Maven / Gradle / Ivy

Go to download

A libraries to bootstrap services easily that follow the patterns and specifications promoted by the SDA SE

There is a newer version: 7.0.88
Show newest version
package org.sdase.commons.server.auth;

import static org.sdase.commons.server.opentracing.client.ClientTracingUtil.registerTracing;

import io.dropwizard.Configuration;
import io.dropwizard.ConfiguredBundle;
import io.dropwizard.auth.AuthDynamicFeature;
import io.dropwizard.client.JerseyClientBuilder;
import io.dropwizard.setup.Bootstrap;
import io.dropwizard.setup.Environment;
import io.opentracing.Tracer;
import io.opentracing.util.GlobalTracer;
import java.net.ProxySelector;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.concurrent.ScheduledExecutorService;
import javax.ws.rs.client.Client;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.impl.conn.SystemDefaultRoutePlanner;
import org.sdase.commons.server.auth.config.AuthConfig;
import org.sdase.commons.server.auth.config.AuthConfigProvider;
import org.sdase.commons.server.auth.config.KeyLocation;
import org.sdase.commons.server.auth.error.ForbiddenExceptionMapper;
import org.sdase.commons.server.auth.error.JwtAuthExceptionMapper;
import org.sdase.commons.server.auth.filter.JwtAuthFilter;
import org.sdase.commons.server.auth.key.JwksKeySource;
import org.sdase.commons.server.auth.key.KeyLoaderScheduler;
import org.sdase.commons.server.auth.key.KeySource;
import org.sdase.commons.server.auth.key.OpenIdProviderDiscoveryKeySource;
import org.sdase.commons.server.auth.key.PemKeySource;
import org.sdase.commons.server.auth.key.PublicKeyLoader;
import org.sdase.commons.server.auth.service.AuthService;
import org.sdase.commons.server.auth.service.JwtAuthenticator;
import org.sdase.commons.server.auth.service.TokenAuthorizer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class AuthBundle implements ConfiguredBundle {

  private static final Logger LOG = LoggerFactory.getLogger(AuthBundle.class);

  private AuthConfigProvider configProvider;
  private boolean useAnnotatedAuthorization;
  private final Tracer tracer;

  public static ProviderBuilder builder() {
    return new Builder<>();
  }

  private AuthBundle(
      AuthConfigProvider configProvider, boolean useAnnotatedAuthorization, Tracer tracer) {
    this.configProvider = configProvider;
    this.useAnnotatedAuthorization = useAnnotatedAuthorization;
    this.tracer = tracer;
  }

  @Override
  public void initialize(Bootstrap bootstrap) {
    // no initialization needed
  }

  @Override
  public void run(T configuration, Environment environment) {

    AuthConfig config = configProvider.apply(configuration);

    if (config.isDisableAuth()) {
      LOG.warn("Authentication is disabled. This setting should NEVER be used in production.");
    }

    Tracer currentTracer = tracer == null ? GlobalTracer.get() : tracer;

    Client client = createKeyLoaderClient(environment, config, currentTracer);
    PublicKeyLoader keyLoader = new PublicKeyLoader();
    config.getKeys().stream()
        .map(k -> this.createKeySources(k, client))
        .forEach(keyLoader::addKeySource);

    ScheduledExecutorService executorService =
        environment.lifecycle().scheduledExecutorService("reloadKeysExecutorService").build();
    KeyLoaderScheduler.create(keyLoader, executorService).start();

    TokenAuthorizer authService = new AuthService(keyLoader, config.getLeeway());
    JwtAuthenticator authenticator = new JwtAuthenticator(authService, config.isDisableAuth());

    JwtAuthFilter authFilter =
        new JwtAuthFilter.Builder()
            .withTracer(currentTracer)
            .setAcceptAnonymous(!useAnnotatedAuthorization)
            .setAuthenticator(authenticator)
            .buildAuthFilter();

    if (useAnnotatedAuthorization) {
      // Use the AuthDynamicFeature to only affect endpoints that are
      // annotated
      environment.jersey().register(new AuthDynamicFeature(authFilter));
    } else {
      // Apply the filter for all calls
      environment.jersey().register(authFilter);
    }

    environment.jersey().register(JwtAuthExceptionMapper.class);
    environment.jersey().register(ForbiddenExceptionMapper.class);
  }

  private Client createKeyLoaderClient(Environment environment, AuthConfig config, Tracer tracer) {
    JerseyClientBuilder jerseyClientBuilder = new JerseyClientBuilder(environment);

    // a specific proxy configuration always overrides the system proxy
    if (config.getKeyLoaderClient() == null
        || config.getKeyLoaderClient().getProxyConfiguration() == null) {
      // register a route planner that uses the default proxy variables (e.g. http.proxyHost)
      jerseyClientBuilder.using(new SystemDefaultRoutePlanner(ProxySelector.getDefault()));
    }

    if (config.getKeyLoaderClient() != null) {
      jerseyClientBuilder.using(config.getKeyLoaderClient());
    }

    Client client = jerseyClientBuilder.build("keyLoader");

    registerTracing(client, tracer);
    return client;
  }

  private KeySource createKeySources(KeyLocation keyLocation, Client client) {
    switch (keyLocation.getType()) {
      case PEM:
        return new PemKeySource(
            keyLocation.getPemKeyId(),
            keyLocation.getPemSignAlg(),
            keyLocation.getLocation(),
            keyLocation.getRequiredIssuer());
      case OPEN_ID_DISCOVERY:
        validateKeyLocation(keyLocation.getLocation(), keyLocation.getRequiredIssuer());
        return new OpenIdProviderDiscoveryKeySource(
            keyLocation.getLocation().toASCIIString(), client, keyLocation.getRequiredIssuer());
      case JWKS:
        validateKeyLocation(keyLocation.getLocation(), keyLocation.getRequiredIssuer());
        return new JwksKeySource(
            keyLocation.getLocation().toASCIIString(), client, keyLocation.getRequiredIssuer());
      default:
        throw new IllegalArgumentException(
            "KeyLocation has no valid type: " + keyLocation.getType());
    }
  }

  /**
   * Validate, that if a required issuer is set as URI the host name of the key location source and
   * the requiredIssuer must be the same.
   *
   * @param location The source {@link URI} of the key(s) as discovery or jwks endpoint.
   * @param requiredIssuer The required issuer as {@link String} for the correlated key source.
   */
  private void validateKeyLocation(URI location, String requiredIssuer) {
    if (StringUtils.isNotBlank(requiredIssuer) && StringUtils.contains(requiredIssuer, ':')) {
      try {
        URI issuerUri = new URI(requiredIssuer);
        if (!StringUtils.equalsIgnoreCase(location.getHost(), issuerUri.getHost())) {
          LOG.warn(
              "The required issuer host name <{}> for the key <{}> does not match to the key"
                  + " source uri host name <{}>.",
              issuerUri.getHost(),
              location,
              location.getHost());
        }
      } catch (URISyntaxException e) {
        throw new IllegalArgumentException(
            "The requiredIssuer <" + requiredIssuer + "> is no valid stringOrURI", e);
      }
    }
  }

  //
  // Builder
  //

  public interface ProviderBuilder {
     AuthorizationBuilder withAuthConfigProvider(
        AuthConfigProvider authConfigProvider);
  }

  public interface AuthorizationBuilder {
    /**
     * Configures the bundle to require valid tokens for all endpoints that are annotated with
     * {@code @PermitAll}.
     *
     * @return the builder
     */
    AuthBuilder withAnnotatedAuthorization();

    /**
     * Configures the bundle to validate tokens but also permit requests without Authorization
     * header. Authorization decisions need be made separately e.g. by the {@link
     * org.sdase.commons.server.opa.OpaBundle}.
     *
     * @return the builder
     */
    AuthBuilder withExternalAuthorization();
  }

  public interface AuthBuilder {

    AuthBuilder withTracer(Tracer tracer);

    AuthBundle build();
  }

  public static class Builder
      implements ProviderBuilder, AuthorizationBuilder, AuthBuilder {

    private AuthConfigProvider authConfigProvider;
    private boolean useAnnotatedAuthorization = true;
    private Tracer tracer;

    private Builder() {}

    private Builder(AuthConfigProvider authConfigProvider) {
      this.authConfigProvider = authConfigProvider;
    }

    @Override
    public  AuthorizationBuilder withAuthConfigProvider(
        AuthConfigProvider authConfigProvider) {
      return new Builder<>(authConfigProvider);
    }

    @Override
    public AuthBuilder withAnnotatedAuthorization() {
      this.useAnnotatedAuthorization = true;
      return this;
    }

    @Override
    public AuthBuilder withExternalAuthorization() {
      this.useAnnotatedAuthorization = false;
      return this;
    }

    @Override
    public AuthBuilder withTracer(Tracer tracer) {
      this.tracer = tracer;
      return this;
    }

    @Override
    public AuthBundle build() {
      return new AuthBundle<>(authConfigProvider, useAnnotatedAuthorization, tracer);
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy