RM-2129 (Check classification before method execution)

+review RM @rwetherall

git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/modules/recordsmanagement/HEAD@107649 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Tuna Aksoy
2015-07-02 18:05:18 +00:00
parent f3f7fbf7e4
commit e6fe911fcf
6 changed files with 207 additions and 250 deletions

View File

@@ -44,14 +44,6 @@
<bean id="preMethodInvocationProcessor"
class="org.alfresco.module.org_alfresco_module_rm.classification.interceptor.processor.PreMethodInvocationProcessor">
<!--
<property name="transactionService" ref="transactionService" />
<property name="classificationServiceBootstrap" ref="classificationServiceBootstrap" />
<property name="alfrescoTransactionSupport" ref="rm.alfrescoTransactionSupport" />
<property name="nodeService" ref="dbNodeService" />
<property name="dictionaryService" ref="dictionaryService" />
<property name="contentClassificationService" ref="contentClassificationService" />
-->
</bean>
<bean id="postMethodInvocationProcessor"

View File

@@ -69,8 +69,6 @@ public class ClassificationServiceBootstrap extends AbstractLifecycleBean implem
private ExemptionCategoryFieldsValidator exemptionCategoryFieldsValidator = new ExemptionCategoryFieldsValidator();
private ClassificationSchemeEntityValidator<ExemptionCategory> exemptionCategoryValidator = new ClassificationSchemeEntityValidator<>(exemptionCategoryFieldsValidator);
private boolean isInitialised = false;
public ClassificationServiceBootstrap(AuthenticationUtil authUtil,
TransactionService txService,
AttributeService attributeService,
@@ -95,11 +93,6 @@ public class ClassificationServiceBootstrap extends AbstractLifecycleBean implem
public ExemptionCategoryManager getExemptionCategoryManager() { return exemptionCategoryManager; }
public ClearanceLevelManager getClearanceLevelManager() { return clearanceLevelManager; }
public boolean isInitialised()
{
return isInitialised;
}
@Override public void onBootstrap(ApplicationEvent event)
{
authenticationUtil.runAsSystem(new org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork<Void>()
@@ -123,7 +116,6 @@ public class ClassificationServiceBootstrap extends AbstractLifecycleBean implem
exemptionCategoryManager.setExemptionCategories(exemptionCategories);
initConfiguredClearanceLevels(classificationLevelManager.getClassificationLevels());
isInitialised = true;
return null;
}
};

View File

@@ -83,28 +83,21 @@ public class ClassificationMethodInterceptor implements MethodInterceptor
{
mandatory("invocation", invocation);
Object result = null;
boolean canProceed = true;
boolean isUserValid = isUserValid();
// Pre method invocation processing
if (isUserValid)
{
//FIXME!!!
// Pre method invocation processing
//canProceed = getPreMethodInvocationProcessor().process(invocation);
getPreMethodInvocationProcessor().process(invocation);
}
if (canProceed)
{
// Method invocation
result = invocation.proceed();
// Method invocation
Object result = invocation.proceed();
// Post method invocation processing
if (isUserValid && result != null)
{
result = getPostMethodInvocationProcessor().process(result);
}
// Post method invocation processing
if (isUserValid && result != null)
{
result = getPostMethodInvocationProcessor().process(result);
}
return result;

View File

@@ -26,8 +26,6 @@ import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.apache.log4j.Logger;
/**
* Registry for post method invocation processors
*
@@ -36,11 +34,8 @@ import org.apache.log4j.Logger;
*/
public class PostMethodInvocationProcessor
{
/** Logger */
private static Logger LOG = Logger.getLogger(PostMethodInvocationProcessor.class);
/** Post method invocation processors */
private Map<Class<?>, BasePostMethodInvocationProcessor> processors = new HashMap<Class<?>, BasePostMethodInvocationProcessor>();
private Map<Class<?>, BasePostMethodInvocationProcessor> processors = new HashMap<>();
/**
* Registers a post method invocation processor
@@ -51,7 +46,7 @@ public class PostMethodInvocationProcessor
{
mandatory("object", object);
processors.put(object.getClassName(), object);
getProcessors().put(object.getClassName(), object);
}
/**
@@ -72,25 +67,27 @@ public class PostMethodInvocationProcessor
*/
protected BasePostMethodInvocationProcessor getProcessor(Object object)
{
mandatory("object", object);
BasePostMethodInvocationProcessor result = null;
Class<? extends Object> clazz = object.getClass();
if (clazz.isArray())
if (object != null)
{
result = getProcessors().get(Array.class);
}
Class<? extends Object> clazz = object.getClass();
if (result == null)
{
Set<Entry<Class<?>, BasePostMethodInvocationProcessor>> processorsEntrySet = getProcessors().entrySet();
for (Map.Entry<Class<?>, BasePostMethodInvocationProcessor> processorEntry : processorsEntrySet)
if (clazz.isArray())
{
if (processorEntry.getKey().isAssignableFrom(clazz))
result = getProcessors().get(Array.class);
}
if (result == null)
{
Set<Entry<Class<?>, BasePostMethodInvocationProcessor>> processorsEntrySet = getProcessors().entrySet();
for (Map.Entry<Class<?>, BasePostMethodInvocationProcessor> processorEntry : processorsEntrySet)
{
result = processorEntry.getValue();
break;
if (processorEntry.getKey().isAssignableFrom(clazz))
{
result = processorEntry.getValue();
break;
}
}
}
}
@@ -115,10 +112,6 @@ public class PostMethodInvocationProcessor
{
result = processor.process(result);
}
else
{
LOG.debug("No processor found for '" + result + "'.");
}
}
return result;

View File

@@ -18,22 +18,18 @@
*/
package org.alfresco.module.org_alfresco_module_rm.classification.interceptor.processor;
import static java.lang.Boolean.TRUE;
import static com.google.common.collect.Lists.newArrayList;
import static org.alfresco.model.ContentModel.TYPE_CONTENT;
import static org.alfresco.util.GUID.generate;
import static org.alfresco.util.ParameterCheck.mandatory;
import java.lang.reflect.Method;
import java.util.List;
import org.alfresco.module.org_alfresco_module_rm.classification.ClassificationServiceBootstrap;
import org.alfresco.module.org_alfresco_module_rm.classification.ContentClassificationService;
import org.alfresco.module.org_alfresco_module_rm.util.AlfrescoTransactionSupport;
import org.alfresco.repo.transaction.RetryingTransactionHelper;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
import org.alfresco.repo.security.permissions.AccessDeniedException;
import org.alfresco.service.cmr.dictionary.DictionaryService;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.transaction.TransactionService;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
@@ -48,221 +44,88 @@ import org.springframework.context.ApplicationContextAware;
*/
public class PreMethodInvocationProcessor implements ApplicationContextAware
{
/** Key to mark the transaction as processing */
private static final String KEY_PROCESSING = generate();
/** Application context */
private ApplicationContext applicationContext;
/**
* @see org.springframework.context.ApplicationContextAware#setApplicationContext(org.springframework.context.ApplicationContext)
*/
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException
{
this.applicationContext = applicationContext;
}
/**
* Gets the content classification service
*
* @return The content classification service
*/
protected ContentClassificationService getContentClassificationService()
{
return (ContentClassificationService)applicationContext.getBean("contentClassificationService");
}
protected AlfrescoTransactionSupport getAlfrescoTransactionSupport()
{
return (AlfrescoTransactionSupport)applicationContext.getBean("rm.alfrescoTransactionSupport");
}
protected RetryingTransactionHelper getRetryingTransactionHelper()
{
return ((TransactionService)applicationContext.getBean("transactionService")).getRetryingTransactionHelper();
}
protected ClassificationServiceBootstrap getClassificationServiceBootstrap()
{
return (ClassificationServiceBootstrap)applicationContext.getBean("classificationServiceBootstrap");
return (ContentClassificationService) applicationContext.getBean("contentClassificationService");
}
/**
* Gets the node service
*
* @return The node service
*/
protected NodeService getNodeService()
{
return (NodeService)applicationContext.getBean("dbNodeService");
return (NodeService) applicationContext.getBean("dbNodeService");
}
/**
* Gets the dictionary service
*
* @return The dictionary service
*/
protected DictionaryService getDictionaryService()
{
return (DictionaryService)applicationContext.getBean("dictionaryService");
return (DictionaryService) applicationContext.getBean("dictionaryService");
}
// /** Transaction service */
// private TransactionService transactionService;
//
// /** Classification service bootstrap */
// private ClassificationServiceBootstrap classificationServiceBootstrap;
//
// /** Alfresco transaction support */
// private AlfrescoTransactionSupport alfrescoTransactionSupport;
//
// /** Node service */
// private NodeService nodeService;
//
// /** Dictionary service */
// private DictionaryService dictionaryService;
//
// /** Content classification service */
// private ContentClassificationService contentClassificationService;
//
// /**
// * @return the transactionService
// */
// protected TransactionService getTransactionService()
// {
// return this.transactionService;
// }
//
// /**
// * @return the classificationServiceBootstrap
// */
// protected ClassificationServiceBootstrap getClassificationServiceBootstrap()
// {
// return this.classificationServiceBootstrap;
// }
//
// /**
// * @return the alfrescoTransactionSupport
// */
// protected AlfrescoTransactionSupport getAlfrescoTransactionSupport()
// {
// return this.alfrescoTransactionSupport;
// }
//
// /**
// * @return the nodeService
// */
// protected NodeService getNodeService()
// {
// return this.nodeService;
// }
//
// /**
// * @return the dictionaryService
// */
// protected DictionaryService getDictionaryService()
// {
// return this.dictionaryService;
// }
//
// /**
// * @return the contentClassificationService
// */
// protected ContentClassificationService getContentClassificationService()
// {
// return this.contentClassificationService;
// }
//
// /**
// * @param transactionService the transactionService to set
// */
// public void setTransactionService(TransactionService transactionService)
// {
// this.transactionService = transactionService;
// }
//
// /**
// * @param classificationServiceBootstrap the classificationServiceBootstrap to set
// */
// public void setClassificationServiceBootstrap(ClassificationServiceBootstrap classificationServiceBootstrap)
// {
// this.classificationServiceBootstrap = classificationServiceBootstrap;
// }
//
// /**
// * @param alfrescoTransactionSupport the alfrescoTransactionSupport to set
// */
// public void setAlfrescoTransactionSupport(AlfrescoTransactionSupport alfrescoTransactionSupport)
// {
// this.alfrescoTransactionSupport = alfrescoTransactionSupport;
// }
//
// /**
// * @param nodeService the nodeService to set
// */
// public void setNodeService(NodeService nodeService)
// {
// this.nodeService = nodeService;
// }
//
// /**
// * @param dictionaryService the dictionaryService to set
// */
// public void setDictionaryService(DictionaryService dictionaryService)
// {
// this.dictionaryService = dictionaryService;
// }
//
// /**
// * @param contentClassificationService the contentClassificationService to set
// */
// public void setContentClassificationService(ContentClassificationService contentClassificationService)
// {
// this.contentClassificationService = contentClassificationService;
// }
/**
* Checks if the current user is cleared to see the items
* passed as parameters to the current method invocation.
*
* @param invocation The current method invocation
* @return <code>true</code> if the user is cleared to see the items, <code>false</code> otherwise
*/
public boolean process(final MethodInvocation invocation)
public void process(MethodInvocation invocation)
{
mandatory("invocation", invocation);
// do in transaction
return /*getTransactionService().*/getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback<Boolean>()
Method method = invocation.getMethod();
String className = method.getDeclaringClass().getSimpleName();
String methodName = method.getName();
String name = className + "." + methodName;
Object[] args = invocation.getArguments();
if (getMethodNames().contains(name))
{
@SuppressWarnings("rawtypes")
public Boolean execute() throws Throwable
for (Object arg : args)
{
Boolean result = true;
// ensure classification service has been bootstrapped
if (getClassificationServiceBootstrap().isInitialised())
if (arg != null && NodeRef.class.isAssignableFrom(arg.getClass()))
{
// check that we are not already processing a classification check
Object value = getAlfrescoTransactionSupport().getResource(KEY_PROCESSING);
if (value == null)
{
Method method = invocation.getMethod();
Class[] params = method.getParameterTypes();
int position = 0;
for (Class param : params)
{
// if the param is a node reference
if (NodeRef.class.isAssignableFrom(param))
{
// mark the transaction as processing a classification check
getAlfrescoTransactionSupport().bindResource(KEY_PROCESSING, TRUE);
try
{
// get the value of the parameter
NodeRef testNodeRef = (NodeRef) invocation.getArguments()[position];
// if node exists then see if the current user has clearance
result = isNodeCleared(testNodeRef);
}
finally
{
// clear the transaction as processed a classification check
getAlfrescoTransactionSupport().unbindResource(KEY_PROCESSING);
}
}
position++;
}
}
isNodeCleared(((NodeRef) arg), name);
}
return result;
}
}, true);
}
}
/**
* Returns a list of method names to check before invocation
*
* @return List of method names to check before invocation
*/
private List<String> getMethodNames()
{
return newArrayList(
"NodeService.setProperty",
//"NodeService.getProperty",
"FileFolderService.copy"
);
}
/**
@@ -270,19 +133,15 @@ public class PreMethodInvocationProcessor implements ApplicationContextAware
* the currently logged in user is cleared to see it.
*
* @param nodeRef Node reference to check
* @return <code>true</code> if the node passes the checks, <code>false</code> otherwise
* @param name The name of the invoked method
*/
private boolean isNodeCleared(NodeRef nodeRef)
private void isNodeCleared(NodeRef nodeRef, String name)
{
boolean result = true;
if (getNodeService().exists(nodeRef) &&
getDictionaryService().isSubClass(getNodeService().getType(nodeRef), TYPE_CONTENT) &&
!getContentClassificationService().hasClearance(nodeRef))
{
result = false;
throw new AccessDeniedException("The method '" + name + "' was called, but you are not cleared for the node.");
}
return result;
}
}

View File

@@ -0,0 +1,128 @@
/*
* Copyright (C) 2005-2015 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.module.org_alfresco_module_rm.test.integration.classification.interceptor;
import static com.google.common.collect.Sets.newHashSet;
import static org.alfresco.repo.site.SiteModel.SITE_MANAGER;
import static org.alfresco.util.GUID.generate;
import org.alfresco.module.org_alfresco_module_rm.test.util.BaseRMTestCase;
import org.alfresco.repo.security.permissions.AccessDeniedException;
import org.alfresco.service.cmr.repository.NodeRef;
/**
* Classification enforcement pre method invocation test
*
* @author Tuna Aksoy
* @since 3.0
*/
public class ClassificationEnforcementPreMethodInvocationTest extends BaseRMTestCase
{
private String testUser;
private static final String LEVEL1 = "level1";
private static final String REASON = "Test Reason 1";
/**
* @see org.alfresco.module.org_alfresco_module_rm.test.util.BaseRMTestCase#isCollaborationSiteTest()
*/
@Override
protected boolean isCollaborationSiteTest()
{
return true;
}
public void testClassificationEnforcementPreMethodInvocation()
{
/**
* Given that I am a site manager and not cleared to see a document
* When I try to set a property on that document an exception will be thrown
* and I try to copy that document an exception will be thrown
*/
doBehaviourDrivenTest(new BehaviourDrivenTest()
{
private NodeRef folder1;
private NodeRef folder2;
private NodeRef doc1;
private NodeRef doc2;
private String propertyValue = generate();
/**
* @see org.alfresco.module.org_alfresco_module_rm.test.util.BaseRMTestCase.BehaviourDrivenTest#given()
*/
@Override
public void given() throws Exception
{
testUser = generate();
createPerson(testUser);
siteService.setMembership(collabSiteId, testUser, SITE_MANAGER);
folder1 = fileFolderService.create(documentLibrary, generate(), TYPE_FOLDER).getNodeRef();
folder2 = fileFolderService.create(documentLibrary, generate(), TYPE_FOLDER).getNodeRef();
doc1 = fileFolderService.create(folder1, generate(), TYPE_CONTENT).getNodeRef();
doc2 = fileFolderService.create(folder1, generate(), TYPE_CONTENT).getNodeRef();
contentClassificationService.classifyContent(LEVEL1, generate(), generate(), newHashSet(REASON), doc1);
}
/**
* @see org.alfresco.module.org_alfresco_module_rm.test.util.BaseRMTestCase.BehaviourDrivenTest#when()
*/
@Override
public void when() throws Exception
{
doTestInTransaction(new Test<Void>()
{
@Override
public Void run()
{
nodeService.setProperty(doc2, PROP_ADDRESSEE, propertyValue);
return null;
}
}, testUser);
doTestInTransaction(new FailureTest(AccessDeniedException.class)
{
@Override
public void run() throws Exception
{
nodeService.setProperty(doc1, PROP_ADDRESSEE, propertyValue);
}
}, testUser);
// doTestInTransaction(new FailureTest(AccessDeniedException.class)
// {
// @Override
// public void run() throws Exception
// {
// nodeService.getProperty(doc1, PROP_ADDRESSEE);
// }
// }, testUser);
doTestInTransaction(new FailureTest(AccessDeniedException.class)
{
@Override
public void run() throws Exception
{
fileFolderService.copy(doc1, folder2, null);
}
}, testUser);
}
});
}
}