PermissionsFilter.java

package de.dlr.shepard.filters;

import de.dlr.shepard.exceptions.ApiError;
import de.dlr.shepard.security.PermissionGracePeriod;
import de.dlr.shepard.security.PermissionsUtil;
import de.dlr.shepard.util.AccessType;
import de.dlr.shepard.util.Constants;
import io.quarkus.logging.Log;
import jakarta.annotation.Priority;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.ws.rs.HttpMethod;
import jakarta.ws.rs.Priorities;
import jakarta.ws.rs.container.ContainerRequestContext;
import jakarta.ws.rs.container.ContainerRequestFilter;
import jakarta.ws.rs.core.PathSegment;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.Response.Status;
import jakarta.ws.rs.ext.Provider;
import java.util.List;

@Provider
@Priority(Priorities.AUTHORIZATION)
@ApplicationScoped
public class PermissionsFilter implements ContainerRequestFilter {

  private static final List<String> writerMethods = List.of(HttpMethod.PUT, HttpMethod.POST, HttpMethod.DELETE);
  private static final List<String> readerMethods = List.of(HttpMethod.GET);

  private PermissionGracePeriod lastSeen;

  private PermissionsUtil permissionsUtil;

  PermissionsFilter() {}

  @Inject
  public PermissionsFilter(PermissionGracePeriod lastSeen, PermissionsUtil permissionsUtil) {
    this.lastSeen = lastSeen;
    this.permissionsUtil = permissionsUtil;
  }

  @Override
  public void filter(ContainerRequestContext requestContext) {
    if (PublicEndpointRegistry.isRequestPathPublic(requestContext)) return;
    var principal = requestContext.getSecurityContext().getUserPrincipal();
    if (principal == null || principal.getName() == null) {
      Log.warnf("Unknown principal %s", principal);
      abort(requestContext, "User could not be read from the request context");
      return;
    }

    var lastSeenKey = principal.getName() + requestContext.getMethod() + requestContext.getUriInfo().getPath();
    if (lastSeen.elementIsKnown(lastSeenKey)) {
      return;
    }
    var accessType = getAccessType(requestContext.getUriInfo().getPathSegments(), requestContext.getMethod());
    if (permissionsUtil.isAllowed(requestContext, accessType, principal.getName())) {
      lastSeen.elementSeen(lastSeenKey);
      return;
    }

    abort(requestContext, "The requested action is forbidden by the permission policies");
  }

  private void abort(ContainerRequestContext requestContext, String reason) {
    Log.warn(reason);
    requestContext.abortWith(
      Response.status(Status.FORBIDDEN)
        .entity(new ApiError(Status.FORBIDDEN.getStatusCode(), "AuthenticationException", reason))
        .build()
    );
  }

  private AccessType getAccessType(List<PathSegment> pathSegments, String requestMethod) {
    if (pathSegments.stream().anyMatch(seg -> seg.getPath().equals(Constants.PERMISSIONS))) return AccessType.Manage;
    if (readerMethods.contains(requestMethod)) return AccessType.Read;
    if (writerMethods.contains(requestMethod)) return AccessType.Write;
    return AccessType.None;
  }
}