org.opentestsystem.delivery.testreg.aop.SecuredAnnotationAspect.java Source code

Java tutorial

Introduction

Here is the source code for org.opentestsystem.delivery.testreg.aop.SecuredAnnotationAspect.java

Source

/*******************************************************************************
 * Educational Online Test Delivery System
 * Copyright (c) 2013 American Institutes for Research
 *
 * Distributed under the AIR Open Source License, Version 1.0
 * See accompanying file AIR-License-1_0.txt or at
 * http://www.smarterapp.org/documents/American_Institutes_for_Research_Open_Source_Software_License.pdf
 ******************************************************************************/
package org.opentestsystem.delivery.testreg.aop;

import static org.springframework.web.bind.annotation.RequestMethod.DELETE;
import static org.springframework.web.bind.annotation.RequestMethod.GET;
import static org.springframework.web.bind.annotation.RequestMethod.PATCH;
import static org.springframework.web.bind.annotation.RequestMethod.POST;
import static org.springframework.web.bind.annotation.RequestMethod.PUT;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.opentestsystem.delivery.testreg.aop.trigger.ResettableTrigger;
import org.opentestsystem.delivery.testreg.domain.FormatType;
import org.opentestsystem.delivery.testreg.domain.Sb11Entity;
import org.opentestsystem.delivery.testreg.domain.TestRegPermission;
import org.opentestsystem.delivery.testreg.domain.TestRegistrationBase;
import org.opentestsystem.delivery.testreg.domain.search.AbstractTestRegSearchRequest;
import org.opentestsystem.delivery.testreg.service.TestRegUberEntityRelationshipService;
import org.opentestsystem.delivery.testreg.service.TestRegUserDetailsService;
import org.opentestsystem.shared.search.domain.SearchResponse;
import org.opentestsystem.shared.security.domain.SbacUser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.annotation.Secured;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Component
@Aspect
public class SecuredAnnotationAspect {
    private static final Logger LOGGER = LoggerFactory.getLogger(SecuredAnnotationAspect.class);

    @Qualifier("userDetailService")
    @Autowired
    private TestRegUserDetailsService testRegUserDetailsService;

    @Autowired
    private TestRegUberEntityRelationshipService testRegUberEntityRelationshipService;

    @Autowired
    private ResettableTrigger trigger;

    private boolean triggerSet;

    @SuppressWarnings("unchecked")
    @Around("execution(* org.opentestsystem.shared.search.persistence.SearchableRepository+.search(..)) && args(searchRequest)")
    public <T extends TestRegistrationBase> SearchResponse<T> interceptSearchDomainObjects(ProceedingJoinPoint pjp,
            AbstractTestRegSearchRequest searchRequest) throws Throwable {

        if (testRegUserDetailsService.isElevatedAccessUser()) {
            // override filtering
            searchRequest.setFilter(false);
        } else {
            if (searchRequest.isFilter()) {
                LOGGER.debug("********************* SearchRequest: " + searchRequest
                        + " wants to filter by list of entities, retrieving *************");
                searchRequest.setPermissibleEntityIds(
                        testRegUserDetailsService.getMongoIdsOfEntitiesCurrentUserHasAccessTo());
                searchRequest.setInaccessibleProtectedRoleNames(
                        testRegUserDetailsService.getProtectedUserRoleNamesThatTheCurrentUserDoesNotHave());
                searchRequest.setCurrentUserId(testRegUserDetailsService.getCurrentUser().getUniqueId());
            }
        }

        return (SearchResponse<T>) pjp.proceed();
    }

    @AfterReturning(pointcut = "execution(* org.opentestsystem.delivery.testreg.persistence.Sb11EntityRepository+.save*(..))  && args(sb11EntityIn)", returning = "sb11EntityOut")
    public void handleEntityChanges(Sb11Entity sb11EntityIn, Sb11Entity sb11EntityOut) throws Throwable {
        testRegUberEntityRelationshipService.updateUberEntityRelationshipMap(sb11EntityOut);
    }

    @AfterReturning(pointcut = "execution(* org.opentestsystem.delivery.testreg.persistence.Sb11EntityRepository+.delete*(..)) && args(sb11EntityIn)")
    public void handleEntityChanges(Sb11Entity sb11EntityIn) throws Throwable {
        testRegUberEntityRelationshipService.updateUberEntityRelationshipMapDeletion(sb11EntityIn);
    }

    @AfterReturning(pointcut = "execution(* org.opentestsystem.delivery.testreg.persistence.Sb11EntityRepository+.delete*(..)) && args(pkId)")
    public void handleEntityChanges(String pkId) throws Throwable {
        testRegUberEntityRelationshipService.updateUberEntityRelationshipMapDeletion(pkId);
    }

    @AfterReturning("execution(* org.opentestsystem.delivery.testreg.persistence.Sb11EntityRepository+.save*(..)) || execution(* org.opentestsystem.delivery.testreg.persistence.Sb11EntityRepository.delete*(..))")
    public void handleEntityChanges() throws Throwable {
        LOGGER.debug(
                "intercepted a change that may affect the cached entity tree, evicting and rebuilding uber entity relationship map...");
        if (!triggerSet || (triggerSet && trigger.isTriggerFired())) {
            LOGGER.debug("setting up initial trigger");
            Method invokedMethod = testRegUberEntityRelationshipService.getClass()
                    .getDeclaredMethod("buildUberEntityRelationshipMapAsync", boolean.class);
            // setup trigger to fire the build map method after 10 seconds of silence
            trigger.setup(10000L, testRegUberEntityRelationshipService, invokedMethod, new Object[] { true });
            triggerSet = true;
        } else {
            trigger.resetTrigger();
        }
    }

    // handle both testreg and testadmin controller methods that are secured
    @Around("execution(* org.opentestsystem.delivery.test*.*.*Controller.*(..)) && @annotation(method) && @annotation(secured)")
    public Object aroundMethodInControllerClass(ProceedingJoinPoint pjp, final RequestMapping method,
            final Secured secured) throws Throwable {

        String[] permissions = secured.value();
        Object[] params = pjp.getArgs();
        Object retVal = null;

        if (method.method() != null) {
            for (RequestMethod requestMethod : method.method()) {
                LOGGER.debug("intercepting " + requestMethod + " for " + pjp.toString() + " secured by "
                        + permissionArrayToString(permissions));
                if (requestMethod.equals(POST) || requestMethod.equals(PUT) || requestMethod.equals(DELETE)
                        || requestMethod.equals(PATCH)) {
                    // in these situations we need to check the incoming params to deduce if we have access prior to the
                    // action
                    if (params != null) {
                        if (!checkIncomingParameters(params, permissions)) {
                            throw new AccessDeniedException("permission denied");
                        }
                        // in this special case we want to check if the file upload type specified in the params matches
                        // what file upload permission(s) the user has...
                        // they are allowed to upload based on having any one of these: "ROLE_Accommodations Upload",
                        // "ROLE_Student Upload", "ROLE_Entity Upload", "ROLE_StudentGroup Upload", "ROLE_User Upload, "ROLE_ExplicitEligibility Upload"
                        if (pjp.getSignature().getName().equals("uploadFile")
                                && pjp.getSignature().getDeclaringType().equals(Class.forName(
                                        "org.opentestsystem.delivery.testreg.rest.FileUploadDataController"))) {
                            // We should throw AccessDeniedException here if they don't have the proper upload
                            // role based on the file upload type (check params)
                            SbacUser currentUser = testRegUserDetailsService.getCurrentUser();
                            String testRegPermission = getCorrectRolePermissionByFormatType((String) params[1]);
                            // checking if logged-in-user has permission to upload corresponding file(user/student/institutions/..)
                            if (currentUser != null && !currentUser.hasPermission(testRegPermission)) {
                                throw new AccessDeniedException("permission denied");
                            }
                        } else if (pjp.getSignature().getName().equals("saveStudents")
                                && pjp.getSignature().getDeclaringType().equals(Class.forName(
                                        "org.opentestsystem.delivery.testreg.rest.ExternalStudentController"))) {
                            // We should throw AccessDeniedException here if they don't have the proper upload
                            // role based on the file upload type (check params)
                            SbacUser currentUser = testRegUserDetailsService.getCurrentUser();
                            String testRegPermission = getCorrectRolePermissionByFormatType("STUDENT");
                            // checking if logged-in-user has permission to upload corresponding file(user/student/institutions/..)
                            if (currentUser != null && !currentUser.hasPermission(testRegPermission)) {
                                throw new AccessDeniedException("permission denied");
                            }
                        }
                    }
                    // we let things continue normally...
                    retVal = pjp.proceed();
                } else if (requestMethod.equals(GET)) {
                    // in these situations we need to check the return values to determine if we have access
                    retVal = pjp.proceed();
                    if (retVal != null && !checkReturnValue(retVal, permissions)) {
                        throw new AccessDeniedException("permission denied");
                    }
                } else {
                    // for now we only handle securing methods get/post/put/delete (enhance code here for additional
                    // request methods)
                    throw new AccessDeniedException(
                            "non-standard request type for a @Secured rest request: " + requestMethod);
                }
            }
        }
        return retVal;
    }

    // AOP: checking for access to something after executing the controller method (i.e. checking return value)
    private boolean checkReturnValue(final Object retVal, final String[] permissions) {
        return hasAccessTo(retVal, permissions);
    }

    // AOP: checking for access to something before executing the controller method (i.e. using params to check)
    private boolean checkIncomingParameters(final Object[] inputParams, final String[] permissions) {
        for (Object param : inputParams) {
            LOGGER.debug("looping over elements in the args making sure we have access to all objects... "
                    + param.getClass().getName());
            if (!hasAccessTo(param, permissions)) {
                return false;
            }
        }
        return true;
    }

    // check the object against each permission to see if we have access to it via at least one of the permissions
    private boolean hasAccessTo(final Object obj, final String[] permissions) {
        for (String permission : permissions) {
            LOGGER.debug("checking permission: " + permission);
            if (testRegUserDetailsService.hasAccess(obj, TestRegPermission.lookup(permission))) {
                return true;
            }
        }
        // none of our permissions allowed us to access the object
        LOGGER.debug("access denied: you do not have access to this object: " + obj);
        return false;
    }

    private String permissionArrayToString(final String[] permissions) {
        String str = "";
        for (String permission : permissions) {
            str += permission + " ";
        }
        return str;
    }

    /**
     * Getting upload role permission based on file-upload type logged-in-user selected.
     *
     * @param type              file-upload type  
     * @return                  returns corresponding matched permission based on upload type     
     */
    private String getCorrectRolePermissionByFormatType(String type) {

        String rolePermission = null;
        FormatType formatType = FormatType.valueOf(type);
        switch (formatType) {
        case CLIENT:
        case DISTRICT:
        case GROUPOFDISTRICTS:
        case GROUPOFINSTITUTIONS:
        case GROUPOFSTATES:
        case INSTITUTION:
        case STATE:
            rolePermission = "ROLE_Entity Upload";
            break;
        case DESIGNATEDSUPPORTSANDACCOMMODATIONS:
            rolePermission = "ROLE_Accommodations Upload";
            break;
        case USER:
            rolePermission = "ROLE_User Upload";
            break;
        case STUDENT:
            rolePermission = "ROLE_Student Upload";
            break;
        case STUDENTGROUP:
            rolePermission = "ROLE_StudentGroup Upload";
            break;
        case EXPLICITELIGIBILITY:
            rolePermission = "ROLE_ExplicitEligibility Upload";
            break;
        default:
            // noop
            break;
        }

        return rolePermission;
    }
}