Java tutorial
/******************************************************************************* * 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; } }