First pass at classification interceptor

git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/modules/recordsmanagement/DEV/ENFORCE@105194 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Roy Wetherall
2015-06-01 08:20:12 +00:00
parent 97d2051a7c
commit 2a398aab12
12 changed files with 445 additions and 15 deletions

View File

@@ -1,7 +1,12 @@
<?xml version='1.0' encoding='UTF-8'?> <?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE beans PUBLIC '-//SPRING//DTD BEAN//EN' 'http://www.springframework.org/dtd/spring-beans.dtd'> <beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
<beans> xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd ">
<!-- Classified content model bootstrap --> <!-- Classified content model bootstrap -->
@@ -28,6 +33,15 @@
</property> </property>
</bean> </bean>
<!-- Classification Method Interceptor -->
<bean id="classificationMethodInterceptor"
class="org.alfresco.module.org_alfresco_module_rm.classification.interceptor.ClassificationMethodInterceptor">
</bean>
<bean id="classificationMethodInterceptorPostProcessor"
class="org.alfresco.module.org_alfresco_module_rm.classification.interceptor.ClassificationMethodInterceptorPostProcessor" />
<!-- Classification service DAO --> <!-- Classification service DAO -->
<bean id="classificationServiceDAO" class="org.alfresco.module.org_alfresco_module_rm.classification.ClassificationServiceDAO"> <bean id="classificationServiceDAO" class="org.alfresco.module.org_alfresco_module_rm.classification.ClassificationServiceDAO">

View File

@@ -1,7 +1,9 @@
<?xml version='1.0' encoding='UTF-8'?> <?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE beans PUBLIC '-//SPRING//DTD BEAN//EN' 'http://www.springframework.org/dtd/spring-beans.dtd'>
<beans> <beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd ">
<!-- Authentication Helper --> <!-- Authentication Helper -->
<bean name="rm.authenticationUtil" class="org.alfresco.module.org_alfresco_module_rm.util.AuthenticationUtil"/> <bean name="rm.authenticationUtil" class="org.alfresco.module.org_alfresco_module_rm.util.AuthenticationUtil"/>

View File

@@ -149,6 +149,7 @@
<property name="runtimeActionService" ref="actionService"/> <property name="runtimeActionService" ref="actionService"/>
<property name="recordsManagementActionService" ref="recordsManagementActionService"/> <property name="recordsManagementActionService" ref="recordsManagementActionService"/>
<property name="recordsManagementAuditService" ref="recordsManagementAuditService"/> <property name="recordsManagementAuditService" ref="recordsManagementAuditService"/>
<property name="transactionService" ref="transactionService" />
<property name="proxyInterfaces"> <property name="proxyInterfaces">
<list> <list>
<value>org.alfresco.module.org_alfresco_module_rm.action.RecordsManagementAction</value> <value>org.alfresco.module.org_alfresco_module_rm.action.RecordsManagementAction</value>

View File

@@ -23,6 +23,8 @@ import org.alfresco.module.org_alfresco_module_rm.action.RecordsManagementAction
import org.alfresco.module.org_alfresco_module_rm.audit.RecordsManagementAuditService; import org.alfresco.module.org_alfresco_module_rm.audit.RecordsManagementAuditService;
import org.alfresco.repo.action.RuntimeActionService; import org.alfresco.repo.action.RuntimeActionService;
import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
import org.alfresco.service.transaction.TransactionService;
import org.springframework.aop.framework.ProxyFactoryBean; import org.springframework.aop.framework.ProxyFactoryBean;
/** /**
@@ -43,6 +45,9 @@ public class RMActionProxyFactoryBean extends ProxyFactoryBean
/** Records management audit service */ /** Records management audit service */
protected RecordsManagementAuditService recordsManagementAuditService; protected RecordsManagementAuditService recordsManagementAuditService;
/** transaction service */
private TransactionService transactionService;
/** /**
* Set action service * Set action service
* *
@@ -73,6 +78,15 @@ public class RMActionProxyFactoryBean extends ProxyFactoryBean
this.recordsManagementAuditService = recordsManagementAuditService; this.recordsManagementAuditService = recordsManagementAuditService;
} }
/**
* @param transactionService transaction service
* @since 3.0.a
*/
public void setTransactionService(TransactionService transactionService)
{
this.transactionService = transactionService;
}
/** /**
* Register the action * Register the action
*/ */
@@ -82,8 +96,15 @@ public class RMActionProxyFactoryBean extends ProxyFactoryBean
{ {
public Void doWork() public Void doWork()
{ {
RecordsManagementAction action = (RecordsManagementAction)getObject(); transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback<Void>()
recordsManagementActionService.register(action); {
public Void execute() throws Throwable
{
RecordsManagementAction action = (RecordsManagementAction)getObject();
recordsManagementActionService.register(action);
return null;
}
});
return null; return null;
} }

View File

@@ -31,10 +31,10 @@ import org.alfresco.module.org_alfresco_module_rm.classification.ClassificationS
*/ */
public class ClassificationLevelManager public class ClassificationLevelManager
{ {
/** Unclassified classification level */ /** Unclassified classification level */
public static final String UNCLASSIFIED_ID = "Unclassified"; public static final String UNCLASSIFIED_ID = "Unclassified";
private static final String UNCLASSIFIED_MSG = "rm.classification.unclassified"; private static final String UNCLASSIFIED_MSG = "rm.classification.unclassified";
public static final ClassificationLevel UNCLASSIFIED = new ClassificationLevel(UNCLASSIFIED_ID, UNCLASSIFIED_MSG); public static final ClassificationLevel UNCLASSIFIED = new ClassificationLevel(UNCLASSIFIED_ID, UNCLASSIFIED_MSG);
/** An immutable list of classification levels ordered from most to least secure. */ /** An immutable list of classification levels ordered from most to least secure. */
private ImmutableList<ClassificationLevel> classificationLevels; private ImmutableList<ClassificationLevel> classificationLevels;

View File

@@ -57,6 +57,8 @@ public class ClassificationServiceBootstrap extends AbstractLifecycleBean implem
private ClearanceLevelManager clearanceLevelManager = new ClearanceLevelManager(); private ClearanceLevelManager clearanceLevelManager = new ClearanceLevelManager();
private ClassificationServiceDAO classificationServiceDAO; private ClassificationServiceDAO classificationServiceDAO;
private boolean isInitialised = false;
public ClassificationServiceBootstrap(AuthenticationUtil authUtil, public ClassificationServiceBootstrap(AuthenticationUtil authUtil,
TransactionService txService, TransactionService txService,
AttributeService attributeService, AttributeService attributeService,
@@ -78,6 +80,11 @@ public class ClassificationServiceBootstrap extends AbstractLifecycleBean implem
public ClassificationReasonManager getClassificationReasonManager() { return classificationReasonManager; } public ClassificationReasonManager getClassificationReasonManager() { return classificationReasonManager; }
public ClearanceLevelManager getClearanceLevelManager() { return clearanceLevelManager; } public ClearanceLevelManager getClearanceLevelManager() { return clearanceLevelManager; }
public boolean isInitialised()
{
return isInitialised;
}
@Override public void onBootstrap(ApplicationEvent event) @Override public void onBootstrap(ApplicationEvent event)
{ {
authenticationUtil.runAsSystem(new org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork<Void>() authenticationUtil.runAsSystem(new org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork<Void>()
@@ -91,6 +98,7 @@ public class ClassificationServiceBootstrap extends AbstractLifecycleBean implem
initConfiguredClassificationLevels(); initConfiguredClassificationLevels();
initConfiguredClassificationReasons(); initConfiguredClassificationReasons();
initConfiguredClearanceLevels(classificationLevelManager.getClassificationLevels()); initConfiguredClearanceLevels(classificationLevelManager.getClassificationLevels());
isInitialised = true;
return null; return null;
} }
}; };

View File

@@ -55,6 +55,7 @@ public class ClassificationServiceImpl extends ServiceBaseImpl
} }
/** /**
* Create a list containing all classification levels up to and including the supplied level. * Create a list containing all classification levels up to and including the supplied level.
* *
* @param allLevels The list of all the classification levels starting with the highest security. * @param allLevels The list of all the classification levels starting with the highest security.

View File

@@ -50,6 +50,7 @@ public class SecurityClearanceServiceImpl extends ServiceBaseImpl implements Sec
private ClassificationServiceBootstrap classificationServiceBootstrap; private ClassificationServiceBootstrap classificationServiceBootstrap;
private ClassificationLevelComparator classificationLevelComparator; private ClassificationLevelComparator classificationLevelComparator;
/** dependency setters */
public void setClearanceManager(ClearanceLevelManager clearanceManager) { this.clearanceManager = clearanceManager; } public void setClearanceManager(ClearanceLevelManager clearanceManager) { this.clearanceManager = clearanceManager; }
public void setClassificationLevelManager(ClassificationLevelManager classificationLevelManager) { this.classificationLevelManager = classificationLevelManager; } public void setClassificationLevelManager(ClassificationLevelManager classificationLevelManager) { this.classificationLevelManager = classificationLevelManager; }
public void setPersonService(PersonService service) { this.personService = service; } public void setPersonService(PersonService service) { this.personService = service; }

View File

@@ -0,0 +1,180 @@
/*
* 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.classification.interceptor;
import java.lang.reflect.Method;
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.module.org_alfresco_module_rm.util.AuthenticationUtil;
import org.alfresco.repo.security.permissions.AccessDeniedException;
import org.alfresco.repo.transaction.RetryingTransactionHelper;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.transaction.TransactionService;
import org.alfresco.util.GUID;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
/**
* Classification method interceptor
*
* @author Roy Wetherall
* @since 3.0
*/
public class ClassificationMethodInterceptor implements MethodInterceptor, ApplicationContextAware
{
private static final String KEY_PROCESSING = GUID.generate();
/** application context */
private ApplicationContext applicationContext;
/**
* @param applicationContext application context
* @throws BeansException
*/
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException
{
this.applicationContext = applicationContext;
}
protected AuthenticationUtil getAuthenticationUtil()
{
return (AuthenticationUtil)applicationContext.getBean("rm.authenticationUtil");
}
/**
* @return {@link ContentClassificationService} content classification service
*/
protected ContentClassificationService getContentClassificaitonService()
{
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");
}
protected NodeService getNodeService()
{
return (NodeService)applicationContext.getBean("dbNodeService");
}
/**
* Check that the current user is cleared to see the items passed as parameters to the current
* method invocation.
*
* @param invocation method invocation
*/
@SuppressWarnings("rawtypes")
public void checkClassification(MethodInvocation invocation)
{
// do in transaction
getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback<Void>()
{
public Void execute() throws Throwable
{
// ensure classification service has been bootstrapped
if (getClassificationServiceBootstrap().isInitialised())
{
// check that we are not already processing a classification check
Object value = getAlfrescoTransactionSupport().getResource(KEY_PROCESSING);
if (value == null)
{
// check that we have an authenticated user and that they aren't "system"
if (getAuthenticationUtil().getFullyAuthenticatedUser() != null &&
!getAuthenticationUtil().isRunAsUserTheSystemUser())
{
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, Boolean.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
if (getNodeService().exists(testNodeRef) &&
!getContentClassificaitonService().hasClearance(testNodeRef))
{
// throw exception
throw new AccessDeniedException("You do not have clearance!");
}
}
finally
{
// clear the transaction as processed a classification check
getAlfrescoTransactionSupport().unbindResource(KEY_PROCESSING);
}
}
position++;
}
}
}
}
return null;
}
}, true);
}
/**
* @see org.aopalliance.intercept.MethodInterceptor#invoke(org.aopalliance.intercept.MethodInvocation)
*/
@Override
public Object invoke(MethodInvocation invocation) throws Throwable
{
// pre method invocation check
checkClassification(invocation);
// method proceed
Object result = invocation.proceed();
// post method invocation processing
// TODO
return result;
}
}

View File

@@ -0,0 +1,82 @@
/*
* 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.classification.interceptor;
import org.springframework.beans.BeansException;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.PropertyValue;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.RuntimeBeanNameReference;
import org.springframework.beans.factory.support.ManagedList;
/**
* Classification method interceptor bean factory post processor.
* <p>
* Bean factory post processor that inspects available beans and adds the classification method interceptor
* to all public services.
*
* @author Roy Wetherall
* @since 3.0.a
*/
public class ClassificationMethodInterceptorPostProcessor implements BeanFactoryPostProcessor
{
private static final String PROP_INTERCEPTOR_NAMES = "interceptorNames";
private static final String TYPE_PROXY_FACTORY_BEAN = "ProxyFactoryBean";
private static final String POSTFIX_SERVICE = "Service";
private static final String BEAN_NAME_CLASSIFICATION_METHOD_INTERCEPTOR = "classificationMethodInterceptor";
/**
* @see org.springframework.beans.factory.config.BeanFactoryPostProcessor#postProcessBeanFactory(org.springframework.beans.factory.config.ConfigurableListableBeanFactory)
*/
@SuppressWarnings("unchecked")
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException
{
// get all bean definition names
String beans[] = beanFactory.getBeanDefinitionNames();
for (String bean : beans)
{
// get bean definition
BeanDefinition beanDefinition = beanFactory.getBeanDefinition(bean);
// only modify proxy factory beans that follow the public service naming postfix convention
if (beanDefinition.getBeanClassName() != null &&
beanDefinition.getBeanClassName().endsWith(TYPE_PROXY_FACTORY_BEAN) &&
bean.endsWith(POSTFIX_SERVICE))
{
// get the property values for the bean definition
MutablePropertyValues propertyValues = beanDefinition.getPropertyValues();
if (propertyValues.contains(PROP_INTERCEPTOR_NAMES))
{
// get the current list of interceptor names
PropertyValue value = propertyValues.getPropertyValue(PROP_INTERCEPTOR_NAMES);
ManagedList<RuntimeBeanNameReference> list = (ManagedList<RuntimeBeanNameReference>)value.getValue();
if (!list.isEmpty())
{
// add reference to classification method interceptor
RuntimeBeanNameReference beanReference = new RuntimeBeanNameReference(BEAN_NAME_CLASSIFICATION_METHOD_INTERCEPTOR);
list.add(beanReference);
}
}
}
}
}
}

View File

@@ -42,4 +42,13 @@ public class AlfrescoTransactionSupport
{ {
org.alfresco.repo.transaction.AlfrescoTransactionSupport.unbindResource(key); org.alfresco.repo.transaction.AlfrescoTransactionSupport.unbindResource(key);
} }
/**
* @see org.alfresco.repo.transaction.AlfrescoTransactionSupport#getResource(Object)
* @since 3.0.a
*/
public Object getResource(Object key)
{
return org.alfresco.repo.transaction.AlfrescoTransactionSupport.getResource(key);
}
} }

View File

@@ -0,0 +1,111 @@
/*
* 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;
import java.util.Collections;
import java.util.List;
import org.alfresco.module.org_alfresco_module_rm.classification.ContentClassificationService;
import org.alfresco.module.org_alfresco_module_rm.classification.SecurityClearanceService;
import org.alfresco.module.org_alfresco_module_rm.classification.model.ClassifiedContentModel;
import org.alfresco.module.org_alfresco_module_rm.test.util.BaseRMTestCase;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork;
import org.alfresco.repo.security.permissions.AccessDeniedException;
import org.alfresco.repo.security.permissions.impl.model.PermissionModel;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.security.PermissionService;
import org.alfresco.util.GUID;
/**
* Enforce classification integration test
*
* @author Roy Wetherall
* @since 3.0
*/
public class EnforceClassificationTest extends BaseRMTestCase
{
/** test data */
private static final String CLASSIFICATION_LEVEL1 = "level1";
private static final String CLASSIFICATION_LEVEL2 = "level2";
private static final String CLASSIFICATION_LEVEL3 = "level3";
private static final String CLASSIFICATION_LEVEL4 = "level4";
private static final String CLASSIFICATION_REASON = "Test Reason 1";
private static final String CLASSIFICATION_AUTHORITY = "classification.authority";
private static final String RECORD_NAME = "recordname.txt";
private ContentClassificationService contentClassificationService;
@Override
protected void initServices()
{
super.initServices();
contentClassificationService = (ContentClassificationService)applicationContext.getBean("contentClassificationService");
}
@Override
protected boolean isCollaborationSiteTest()
{
return true;
}
/**
*
*/
public void testUserNotClearedDocument() throws Exception
{
doBehaviourDrivenTest(new BehaviourDrivenTest(AccessDeniedException.class)
{
private String userName;
public void given() throws Exception
{
// create test person and assign read permission to document
userName = GUID.generate();
createPerson(userName, true);
permissionService.setPermission(dmDocument, userName , PermissionService.READ, true);
// assign security clearance
securityClearanceService.setUserSecurityClearance(userName, CLASSIFICATION_LEVEL3);
// classify document
contentClassificationService.classifyContent(
CLASSIFICATION_LEVEL1,
CLASSIFICATION_AUTHORITY,
Collections.singleton(CLASSIFICATION_REASON),
dmDocument);
}
public void when() throws Exception
{
AuthenticationUtil.runAs(new RunAsWork<Void>()
{
public Void doWork() throws Exception
{
nodeService.getAspects(dmDocument);
return null;
}
}, userName);
}
});
}
}