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

com.eurodyn.qlack.fuse.aaa.aop.ResourceAccessInterceptor Maven / Gradle / Ivy

The newest version!
package com.eurodyn.qlack.fuse.aaa.aop;

import com.eurodyn.qlack.fuse.aaa.annotation.OperationAccess;
import com.eurodyn.qlack.fuse.aaa.annotation.ResourceAccess;
import com.eurodyn.qlack.fuse.aaa.annotation.ResourceId;
import com.eurodyn.qlack.fuse.aaa.dto.ResourceOperationDTO;
import com.eurodyn.qlack.fuse.aaa.model.*;
import lombok.RequiredArgsConstructor;
import lombok.extern.java.Log;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.saml2.provider.service.authentication.DefaultSaml2AuthenticatedPrincipal;
import org.springframework.stereotype.Component;

import java.lang.reflect.*;
import java.util.*;
import java.util.stream.Collectors;

/**
 * Authorizes requests by validating users' role, operation and resources permissions against the provided access rules.
 * The access rules are described using {@link ResourceAccess}, {@link OperationAccess} and {@link ResourceId} on the
 * REST endpoint the request is issued for or on the corresponding DTO fields that represent a resourceId field
 *
 * @author European Dynamics SA
 */
@Aspect
@Component
@Log
@RequiredArgsConstructor
public class ResourceAccessInterceptor {

  private final ResourceAccessInterceptorService resourceAccessInterceptorService;

  /**
   * List of allowed types as method parameters with ResourceId
   */
  private static final List> WRAPPER_TYPE_LIST;
  static {
    WRAPPER_TYPE_LIST = new ArrayList<>();
    WRAPPER_TYPE_LIST.add(int.class);
    WRAPPER_TYPE_LIST.add(byte.class);
    WRAPPER_TYPE_LIST.add(char.class);
    WRAPPER_TYPE_LIST.add(boolean.class);
    WRAPPER_TYPE_LIST.add(double.class);
    WRAPPER_TYPE_LIST.add(float.class);
    WRAPPER_TYPE_LIST.add(long.class);
    WRAPPER_TYPE_LIST.add(short.class);
    WRAPPER_TYPE_LIST.add(void.class);
    WRAPPER_TYPE_LIST.add(String.class);
    WRAPPER_TYPE_LIST.add(Integer.class);
    WRAPPER_TYPE_LIST.add(Boolean.class);
    WRAPPER_TYPE_LIST.add(Double.class);
    WRAPPER_TYPE_LIST.add(Float.class);
    WRAPPER_TYPE_LIST.add(Long.class);

  }

  @Pointcut("execution(@com.eurodyn.qlack.fuse.aaa.annotation.ResourceAccess * *(..)) || execution(@com.eurodyn.qlack.fuse.aaa.annotation.OperationAccess * *(..))")
  public void annotation() {
    //the pointcut declaration
  }

  /**
   * Authorizes user on a role or operation level, without resource
   *
   * @param joinPoint the annotations' joinPoint
   * @param operationAccess The {@link OperationAccess} object
   * @throws AccessDeniedException if authorization fails
   * @throws IllegalAccessException if a class field cannot be accessed through Java Reflection API
   */
  @Before("annotation() && @annotation(operationAccess)")
  public void authorizeOperation(JoinPoint joinPoint, OperationAccess operationAccess)
          throws IllegalAccessException {
    authorize(joinPoint, operationAccess.roleAccess(), operationAccess.operations(), false);
  }

  /**
   * Authorizes user on an operation with resources level
   *
   * @param joinPoint the annotations' joinPoint
   * @param resourceAccess The {@link ResourceAccess} object
   * @throws AccessDeniedException if authorization fails
   * @throws IllegalAccessException if a class field cannot be accessed through Java Reflection API
   */
  @Before("annotation() && @annotation(resourceAccess)")
  public void authorizeResource(JoinPoint joinPoint, ResourceAccess resourceAccess) throws IllegalAccessException {
    authorize(joinPoint, new String[0], resourceAccess.operations(), true);
  }

  /**
   * Retrieves user details and checks authorization
   *
   * @param joinPoint the annotations' joinPoint
   * @param roleAccess The roleAccess array
   * @param resourceOperations The resourceOperations array
   * @param withResourceId Whether to check for resource or not
   * @throws AccessDeniedException if authorization fails
   * @throws IllegalAccessException if a class field cannot be accessed through Java Reflection API
   */
  public void authorize(JoinPoint joinPoint, String[] roleAccess, String[] resourceOperations, boolean withResourceId)
          throws IllegalAccessException {
    Object principal = SecurityContextHolder.getContext().getAuthentication()
            .getPrincipal();

    //authorizeUserDetailsDTO method checks the fields of the com.eurodyn.qlack.fuse.aaa.dto.UserDetailsDTO
    //if your security implementation adds another type of object in the spring security principal, a custom implementation must be added
    User user = null;
    if (principal instanceof String) {
      user = resourceAccessInterceptorService.findUser((String) principal);
    } else if (principal instanceof DefaultSaml2AuthenticatedPrincipal) {
      user = resourceAccessInterceptorService.findByUsername((String) ((DefaultSaml2AuthenticatedPrincipal) principal).getName());
    }
    if (user != null) {
      authorizeUser(user, joinPoint,
              roleAccess, resourceOperations, withResourceId);
    } else {
      throwUnauthorizedException();
    }
  }

  /**
   * Authorizes user on a role, operation or resource level
   *
   * @param user the User
   * @param joinPoint the annotations' joinPoint
   * @param roleAccess The roleAccess array
   * @param resourceOperations The resourceOperations array
   * @param withResourceId Whether to check for resource or not
   * @throws AccessDeniedException if authorization fails
   * @throws IllegalAccessException if a class field cannot be accessed through Java Reflection API
   */
  private void authorizeUser(User user, JoinPoint joinPoint,
                             String[] roleAccess, String[] resourceOperations,
                             boolean withResourceId) throws IllegalAccessException {

    boolean allowAccess;

    // superadmin users are authorized
    if (user.isSuperadmin()) {
      allowAccess = true;
    } else {
      List groups = user.getUserGroups();

      boolean authorizesOnRole = Arrays.stream(roleAccess).anyMatch(
              new HashSet<>(
                      groups.stream().map(UserGroup::getName)
                              .collect(Collectors.toList()))::contains);

      // If not authorized on role level, check specific operation permissions
      if (authorizesOnRole) {
        allowAccess = true;
      } else {
        allowAccess = false;
        log.info(
                "The groups this user is assigned to are not authorized to access this resource.");

        for (String resourceOperation : resourceOperations) {
          allowAccess =
                  allowAccess || userHasOperation(user, resourceOperation, joinPoint, withResourceId);
        }
      }
    }

    if (!allowAccess) {
      throwUnauthorizedException();
    }
  }

  /**
   * Checks if user has the requested operation
   *
   * @param user a {@link java.net.UnknownServiceException} object that holds user information
   * @param operation the operation name to check permissions for
   * @param joinPoint the annotations' joinPoint
   * @param withResourceId Whether to check for resource or not
   * @throws IllegalAccessException if a class field cannot be accessed through Java Reflection API
   */
  private boolean userHasOperation(User user, String operation,
                                   JoinPoint joinPoint, boolean withResourceId)
          throws IllegalAccessException {

    // Get the user and group operations on resources
    List resourceOperations = getResourceOperations(user,
            operation, withResourceId);

    // If no operations for current user, the user is not authorized
    if (resourceOperations.isEmpty()) {
      return false;
    }

    // If operations found for current user and authorization level is on operation without objectId, the user is authorized
    if (!withResourceId) {
      return true;
    }

    if (findResourceInParameters(joinPoint, resourceOperations)) {
      return true;
    }

    // Find match between the parameters (GET, DELETE scenarios) or DTO fields(POST, PUT scenarios)
    // (Authorize on a resource level)
    Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
    Parameter[] params = method.getParameters();
    int index = 0;
    for (Parameter parameter : params) {
      if (!WRAPPER_TYPE_LIST.contains(parameter.getType())
              && userHasOperationWithResourceID(parameter, joinPoint,
              index,
              resourceOperations)) {
        return true;
      }
      index++;
    }
    return false;
  }

  /**
   * Retrieves User & Group operations from user principal
   *
   * @param user a {@link User} object that holds user information
   * @param operation the operation name to check permissions for
   * @param withResourceId filters the operations whether they should or not have a resource level
   * @return the operations and resources of the current user
   */
  private List getResourceOperations(User user,
      String operation, boolean withResourceId) {
    List resourceOperations;

    List uho = user.getUserHasOperations();
    List gho = new ArrayList<>();
    user.getUserGroups().forEach(userGroup -> gho.addAll(userGroup.getUserGroupHasOperations()));

    if (withResourceId) {
      resourceOperations = uho.stream().filter(o -> o.getOperation().getName().equals(operation) &&
              o.getResource() != null && o.getResource().getObjectId() != null)
              .map(o -> new ResourceOperationDTO(o.getOperation().getName(), o.getResource().getObjectId()))
              .collect(Collectors.toList());
      resourceOperations.addAll(gho.stream().filter(o -> o.getOperation().getName().equals(operation) &&
              o.getResource() != null && o.getResource().getObjectId() != null)
              .map(o -> new ResourceOperationDTO(o.getOperation().getName(), o.getResource().getObjectId()))
              .collect(Collectors.toList()));
    } else {
      resourceOperations = uho.stream().filter(o -> o.getOperation().getName().equals(operation)
                      && (o.getResource() == null || (o.getResource() != null && o.getResource().getObjectId() == null)))
              .map(o -> new ResourceOperationDTO(o.getOperation().getName(), ""))
              .collect(Collectors.toList());
      resourceOperations.addAll(gho.stream().filter(o -> o.getOperation().getName().equals(operation)
              && (o.getResource() == null || (o.getResource() != null && o.getResource().getObjectId() == null)))
              .map(o -> new ResourceOperationDTO(o.getOperation().getName(), null))
              .collect(Collectors.toList()));
    }

    return resourceOperations;
  }

  /**
   * Checks for resource match in the methods parameters
   *
   * @param joinPoint the annotations' joinPoint
   * @param resourceOperations the operations and resources of the current user
   */
  private boolean findResourceInParameters(JoinPoint joinPoint, List resourceOperations) {

    int i = 0;
    Parameter[] params  = ((MethodSignature) joinPoint.getSignature()).getMethod().getParameters();
    for (Parameter param : params) {
      if (WRAPPER_TYPE_LIST.contains(param.getType()) &&
              param.getAnnotation(ResourceId.class) != null && joinPoint.getArgs()[i] != null) {
        String paramValue = joinPoint.getArgs()[i].toString();
        for (ResourceOperationDTO roDTO : resourceOperations) {
          if (roDTO.getResourceId().equals(paramValue)) {
            return true;
          }
        }
      }
      i++;
    }
    return false;
  }


  /**
   * Checks for resource match in the attributes of the method's object
   *
   * @param parameter the object to look into
   * @param joinPoint the annotations' joinPoint
   * @param resourceOperations the operations and resources of the current user
   */
  @SuppressWarnings("squid:S3011")
  private boolean userHasOperationWithResourceID(Parameter parameter,
                                                 JoinPoint joinPoint, int index,
                                                 List resourceOperations)
      throws IllegalAccessException {
    Field[] fields = parameter.getType().getDeclaredFields();
    for (Field field : fields) {
      try {
        field.setAccessible(true);
      } catch (InaccessibleObjectException e) {
        continue;
      }
      ResourceId resourceIdAnnotation = field.getAnnotation(ResourceId.class);
      if (resourceIdAnnotation != null) {
        Object annotationResourceIdValue = field
            .get(joinPoint.getArgs()[index]);
        for (ResourceOperationDTO roDTO : resourceOperations) {
          if (roDTO.getResourceId()
              .equals(String.valueOf(annotationResourceIdValue))) {
            return true;
          }
        }
      }
    }

    return false;
  }

  /**
   * This method throws the QLACK Unauthorized exception
   */
  private void throwUnauthorizedException() {
    throw new AccessDeniedException(
        "403 - Unauthorized Access. This user is not authorized for the specific operation.");

  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy