RM-477: RM 2.0 does not work with SOLR

* first pass at supporting SOLR in RM2
  * currently requires reimplementation of permissionServiceImpl due to visibility of methods and member variables
  * job execution delayed to try and prevent start up errors
  * fetchSOLR task added to gradle to retrieve SOLR zip from Maven (could do with some scripts to help with setup for module)
  * TODO more testing and refinement of solution



git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/modules/recordsmanagement/BRANCHES/V2.0-BUG-FIX@40344 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Roy Wetherall
2012-08-13 05:45:17 +00:00
parent 23920d760a
commit 3d2a5a0c8c
13 changed files with 3316 additions and 38 deletions

View File

@@ -239,6 +239,13 @@ subprojects {
} }
} }
task localisationPackage (type: Copy) << {
from configDir
include "**/*.properties"
into "${assembleDir}/i18n"
}
task deployExploded(dependsOn: 'jar') << { task deployExploded(dependsOn: 'jar') << {
def jarFileObj = file(jarFilePath) def jarFileObj = file(jarFilePath)

View File

@@ -13,6 +13,17 @@ dependencies {
testRuntime files(testResourceDir) testRuntime files(testResourceDir)
testRuntime group: 'org.alfresco.enterprise', name: 'alfresco', version: alfrescoBaseVersion, type: 'war' testRuntime group: 'org.alfresco.enterprise', name: 'alfresco', version: alfrescoBaseVersion, type: 'war'
testRuntime group: 'org.alfresco.enterprise', name: 'alfresco-solr', version: alfrescoBaseVersion, type: 'zip'
}
task fetchSOLR(type:Copy) {
from configurations.testRuntime
into '.'
include '*.zip'
rename { String filename -> solrFile }
}
task useSOLR << {
} }
task resetDatabase << { task resetDatabase << {

View File

@@ -2,6 +2,53 @@
<!DOCTYPE beans PUBLIC '-//SPRING//DTD BEAN//EN' 'http://www.springframework.org/dtd/spring-beans.dtd'> <!DOCTYPE beans PUBLIC '-//SPRING//DTD BEAN//EN' 'http://www.springframework.org/dtd/spring-beans.dtd'>
<beans> <beans>
<bean id="permissionServiceImpl" class="org.alfresco.module.org_alfresco_module_rm.permission.PermissionServiceImpl" init-method="init">
<property name="nodeService">
<ref bean="mtAwareNodeService" />
</property>
<property name="tenantService">
<ref bean="tenantService"/>
</property>
<property name="dictionaryService">
<ref bean="dictionaryService" />
</property>
<property name="permissionsDaoComponent">
<ref bean="permissionsDaoComponent" />
</property>
<property name="modelDAO">
<ref bean="permissionsModelDAO" />
</property>
<property name="authorityService">
<ref bean="authorityService" />
</property>
<property name="accessCache">
<ref bean="permissionsAccessCache" />
</property>
<property name="readersCache">
<ref bean="readersCache" />
</property>
<property name="readersDeniedCache">
<ref bean="readersDeniedCache" />
</property>
<property name="policyComponent">
<ref bean="policyComponent" />
</property>
<property name="aclDAO">
<ref bean="aclDAO" />
</property>
<property name="ownableService">
<ref bean="ownableService" />
</property>
<property name="anyDenyDenies">
<value>${security.anyDenyDenies}</value>
</property>
<property name="dynamicAuthorities">
<list>
<ref bean="ownerDynamicAuthority" />
<ref bean="lockOwnerDynamicAuthority" />
</list>
</property>
</bean>
<!-- Bootstrap records management data --> <!-- Bootstrap records management data -->
<bean id="org_alfresco_module_rm_bootstrapData" <bean id="org_alfresco_module_rm_bootstrapData"

View File

@@ -292,7 +292,7 @@
{ {
"name" : "Vital Records Due For Review", "name" : "Vital Records Due For Review",
"description" : "All records currently due for review.", "description" : "All records currently due for review.",
"search" : "reviewAsOf:[MIN TO TODAY]", "search" : "rma:reviewAsOf:[MIN TO TODAY]",
"searchparams" : "searchparams" :
{ {
"records" : true, "records" : true,

View File

@@ -3,5 +3,6 @@ moduleid=org_alfresco_module_rm
webAppName=alfresco webAppName=alfresco
warFile=alfresco.war warFile=alfresco.war
solrFile=alfresco-solr.zip
tomcatEnv=TOMCAT_HOME tomcatEnv=TOMCAT_HOME

View File

@@ -473,8 +473,11 @@ public class RMAfterInvocationProvider extends RMSecurityCommon
{ {
inclusionMask.set(i, true); inclusionMask.set(i, true);
} }
filteringResultSet.setResultSetMetaData(new SimpleResultSetMetaData(returnedObject.getResultSetMetaData().getLimitedBy(), PermissionEvaluationMode.EAGER, returnedObject.getResultSetMetaData() filteringResultSet.setResultSetMetaData(
.getSearchParameters())); new SimpleResultSetMetaData(
returnedObject.getResultSetMetaData().getLimitedBy(),
PermissionEvaluationMode.EAGER,
returnedObject.getResultSetMetaData().getSearchParameters()));
return filteringResultSet; return filteringResultSet;
} }
else else
@@ -483,27 +486,37 @@ public class RMAfterInvocationProvider extends RMSecurityCommon
{ {
inclusionMask.set(i, true); inclusionMask.set(i, true);
} }
filteringResultSet.setResultSetMetaData(new SimpleResultSetMetaData(returnedObject.getResultSetMetaData().getLimitedBy(), PermissionEvaluationMode.EAGER, returnedObject.getResultSetMetaData() filteringResultSet.setResultSetMetaData(
.getSearchParameters())); new SimpleResultSetMetaData(
returnedObject.getResultSetMetaData().getLimitedBy(),
PermissionEvaluationMode.EAGER,
returnedObject.getResultSetMetaData().getSearchParameters()));
return filteringResultSet; return filteringResultSet;
} }
} }
// record the start time // record the start time
long startTimeMillis = System.currentTimeMillis(); long startTimeMillis = System.currentTimeMillis();
// set the default, unlimited resultset type // set the default, unlimited resultset type
filteringResultSet.setResultSetMetaData(new SimpleResultSetMetaData(returnedObject.getResultSetMetaData().getLimitedBy(), PermissionEvaluationMode.EAGER, returnedObject.getResultSetMetaData() filteringResultSet.setResultSetMetaData(
.getSearchParameters())); new SimpleResultSetMetaData(
returnedObject.getResultSetMetaData().getLimitedBy(),
PermissionEvaluationMode.EAGER,
returnedObject.getResultSetMetaData().getSearchParameters()));
for (int i = 0; i < returnedObject.length(); i++) for (int i = 0; i < returnedObject.length(); i++)
{ {
long currentTimeMillis = System.currentTimeMillis(); long currentTimeMillis = System.currentTimeMillis();
if (i >= maxChecks || (currentTimeMillis - startTimeMillis) > maxCheckTime) // if (i >= maxChecks || (currentTimeMillis - startTimeMillis) > maxCheckTime)
{ // {
filteringResultSet.setResultSetMetaData(new SimpleResultSetMetaData(LimitBy.NUMBER_OF_PERMISSION_EVALUATIONS, PermissionEvaluationMode.EAGER, returnedObject // filteringResultSet.setResultSetMetaData(
.getResultSetMetaData().getSearchParameters())); // new SimpleResultSetMetaData(
break; // LimitBy.NUMBER_OF_PERMISSION_EVALUATIONS,
} // PermissionEvaluationMode.EAGER,
// returnedObject.getResultSetMetaData().getSearchParameters()));
// break;
// }
// All permission checks must pass // All permission checks must pass
inclusionMask.set(i, true); inclusionMask.set(i, true);

View File

@@ -106,13 +106,14 @@ public class RMSecurityCommon
*/ */
public int checkRead(NodeRef nodeRef) public int checkRead(NodeRef nodeRef)
{ {
int result = AccessDecisionVoter.ACCESS_ABSTAIN;
if (nodeRef != null) if (nodeRef != null)
{ {
// now we know the node - we can abstain for certain types and aspects (eg, rm) // now we know the node - we can abstain for certain types and aspects (eg, rm)
return checkRead(nodeRef, false); result = checkRead(nodeRef, false);
} }
return AccessDecisionVoter.ACCESS_ABSTAIN; return result;
} }
/** /**
@@ -123,35 +124,32 @@ public class RMSecurityCommon
*/ */
public int checkRead(NodeRef nodeRef, boolean allowDMRead) public int checkRead(NodeRef nodeRef, boolean allowDMRead)
{ {
if (nodeService.hasAspect(nodeRef, RecordsManagementModel.ASPECT_FILE_PLAN_COMPONENT)) int result = AccessDecisionVoter.ACCESS_ABSTAIN;
if (rmService.isFilePlanComponent(nodeRef) == true)
{ {
return checkRmRead(nodeRef); result = checkRmRead(nodeRef);
} }
else else if (allowDMRead == true)
{ {
if (allowDMRead) // Check DM read for copy etc
// DM does not grant - it can only deny
if (permissionService.hasPermission(nodeRef, PermissionService.READ) == AccessStatus.DENIED)
{ {
// Check DM read for copy etc if (logger.isDebugEnabled())
// DM does not grant - it can only deny
if (permissionService.hasPermission(nodeRef, PermissionService.READ) == AccessStatus.DENIED)
{ {
if (logger.isDebugEnabled()) logger.debug("\t\tPermission is denied");
{ Thread.dumpStack();
logger.debug("\t\tPermission is denied");
Thread.dumpStack();
}
return AccessDecisionVoter.ACCESS_DENIED;
}
else
{
return AccessDecisionVoter.ACCESS_GRANTED;
} }
result = AccessDecisionVoter.ACCESS_DENIED;
} }
else else
{ {
return AccessDecisionVoter.ACCESS_ABSTAIN; result = AccessDecisionVoter.ACCESS_GRANTED;
} }
} }
return result;
} }
/** /**

View File

@@ -72,7 +72,7 @@ public class DispositionLifecycleJobExecuter extends RecordsManagementJobExecute
/** /**
* @see org.alfresco.module.org_alfresco_module_rm.job.RecordsManagementJobExecuter#execute() * @see org.alfresco.module.org_alfresco_module_rm.job.RecordsManagementJobExecuter#execute()
*/ */
public void execute() public void executeImpl()
{ {
logger.debug("Job Starting"); logger.debug("Job Starting");

View File

@@ -69,7 +69,7 @@ public class NotifyOfRecordsDueForReviewJobExecuter extends RecordsManagementJob
/** /**
* @see org.alfresco.module.org_alfresco_module_rm.job.RecordsManagementJobExecuter#execute() * @see org.alfresco.module.org_alfresco_module_rm.job.RecordsManagementJobExecuter#execute()
*/ */
public void execute() public void executeImpl()
{ {
if (logger.isDebugEnabled()) if (logger.isDebugEnabled())
{ {

View File

@@ -80,7 +80,7 @@ public class PublishUpdatesJobExecuter extends RecordsManagementJobExecuter
this.behaviourFilter = behaviourFilter; this.behaviourFilter = behaviourFilter;
} }
public void execute() public void executeImpl()
{ {
if (logger.isDebugEnabled() == true) if (logger.isDebugEnabled() == true)
{ {

View File

@@ -20,18 +20,73 @@ package org.alfresco.module.org_alfresco_module_rm.job;
import org.alfresco.module.org_alfresco_module_rm.model.RecordsManagementModel; import org.alfresco.module.org_alfresco_module_rm.model.RecordsManagementModel;
import org.alfresco.repo.transaction.RetryingTransactionHelper; import org.alfresco.repo.transaction.RetryingTransactionHelper;
import org.springframework.context.ApplicationEvent;
import org.springframework.extensions.surf.util.AbstractLifecycleBean;
/** /**
* Records management job executer base class.
*
* @author Roy Wetherall * @author Roy Wetherall
*/ */
public abstract class RecordsManagementJobExecuter implements RecordsManagementModel public abstract class RecordsManagementJobExecuter extends AbstractLifecycleBean
implements RecordsManagementModel
{ {
/** Retrying transaction helper */
protected RetryingTransactionHelper retryingTransactionHelper; protected RetryingTransactionHelper retryingTransactionHelper;
/** Indicates whether the application bootstrap is complete or not */
protected boolean bootstrapComplete = false;
/**
* @param retryingTransactionHelper retrying transaction helper
*/
public void setRetryingTransactionHelper(RetryingTransactionHelper retryingTransactionHelper) public void setRetryingTransactionHelper(RetryingTransactionHelper retryingTransactionHelper)
{ {
this.retryingTransactionHelper = retryingTransactionHelper; this.retryingTransactionHelper = retryingTransactionHelper;
} }
public abstract void execute(); /**
* Executes the jobs work.
*/
public void execute()
{
// jobs not allowed to execute unless bootstrap is complete
if (bootstrapComplete == true)
{
executeImpl();
}
}
/**
* Jobs work implementation.
*/
public abstract void executeImpl();
/**
* @see org.springframework.extensions.surf.util.AbstractLifecycleBean#onBootstrap(org.springframework.context.ApplicationEvent)
*/
@Override
protected void onBootstrap(ApplicationEvent arg0)
{
// record that the bootstrap has complete
bootstrapComplete = true;
}
/**
* @see org.springframework.extensions.surf.util.AbstractLifecycleBean#onShutdown(org.springframework.context.ApplicationEvent)
*/
@Override
protected void onShutdown(ApplicationEvent arg0)
{
// no implementation
}
/**
* @see org.springframework.extensions.surf.util.AbstractLifecycleBean#onApplicationEvent(org.springframework.context.ApplicationEvent)
*/
@Override
public void onApplicationEvent(ApplicationEvent arg0)
{
// no implementation
}
} }

View File

@@ -0,0 +1,370 @@
package org.alfresco.module.org_alfresco_module_rm.permission;
import java.io.Serializable;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;
import org.alfresco.module.org_alfresco_module_rm.capability.RMPermissionModel;
import org.alfresco.repo.cache.SimpleCache;
import org.alfresco.repo.domain.permissions.AclDAO;
import org.alfresco.repo.security.permissions.AccessControlEntry;
import org.alfresco.repo.security.permissions.AccessControlList;
import org.alfresco.repo.security.permissions.PermissionEntry;
import org.alfresco.repo.security.permissions.PermissionReference;
import org.alfresco.repo.security.permissions.impl.ModelDAO;
import org.alfresco.repo.security.permissions.impl.PermissionServiceImpl;
import org.alfresco.repo.security.permissions.impl.RequiredPermission;
import org.alfresco.repo.security.permissions.impl.SimplePermissionReference;
import org.alfresco.service.cmr.security.AccessStatus;
import org.alfresco.service.cmr.security.PermissionService;
import org.alfresco.service.namespace.QName;
import org.alfresco.util.Pair;
public class OtherImpl extends PermissionServiceImpl
{
static SimplePermissionReference RM_OLD_ALL_PERMISSIONS_REFERENCE = SimplePermissionReference.getPermissionReference(
QName.createQName("", PermissionService.ALL_PERMISSIONS),
PermissionService.ALL_PERMISSIONS);
private SimpleCache<Serializable, Set<String>> rmReadersCache;
private AclDAO rmAclDaoComponent;
private ModelDAO rmModelDao;
public void setRmReadersCache(SimpleCache<Serializable, Set<String>> rmReadersCache)
{
this.rmReadersCache = rmReadersCache;
}
public void setRmAclDAO(AclDAO rmAclDaoComponent)
{
this.rmAclDaoComponent = rmAclDaoComponent;
}
public void setRmModelDAO(ModelDAO rmModelDao)
{
this.rmModelDao = rmModelDao;
}
@Override
public void setAnyDenyDenies(boolean anyDenyDenies)
{
super.setAnyDenyDenies(anyDenyDenies);
rmReadersCache.clear();
}
@Override
public Set<String> getReaders(Long aclId)
{
Set<String> dmReaders = super.getReaders(aclId);
Set<String> rmReaders = rmReadersCache.get(aclId);
if (rmReaders == null)
{
rmReaders = buildRMReaders(aclId);
rmReadersCache.put(aclId, rmReaders);
}
Set<String> result = new HashSet<String>();
result.addAll(dmReaders);
result.addAll(rmReaders);
return result;
}
private Set<String> buildRMReaders(Long aclId)
{
AccessControlList acl = rmAclDaoComponent.getAccessControlList(aclId);
if (acl == null)
{
return Collections.emptySet();
}
HashSet<String> assigned = new HashSet<String>();
HashSet<String> readers = new HashSet<String>();
for (AccessControlEntry ace : acl.getEntries())
{
assigned.add(ace.getAuthority());
}
PermissionReference permissionRef = getPermissionReference(RMPermissionModel.READ_RECORDS);
for (String authority : assigned)
{
RMUnconditionalAclTest rmTest = new RMUnconditionalAclTest(permissionRef);
if (rmTest.evaluate(authority, aclId))
{
readers.add(authority);
}
}
return Collections.unmodifiableSet(readers);
}
/**
* Ignores type and aspect requirements on the node
*
*/
private class RMUnconditionalAclTest
{
/*
* The required permission.
*/
PermissionReference required;
/*
* Granters of the permission
*/
Set<PermissionReference> granters;
/*
* The additional permissions required at the node level.
*/
Set<PermissionReference> nodeRequirements = new HashSet<PermissionReference>();
/*
* Constructor just gets the additional requirements
*/
RMUnconditionalAclTest(PermissionReference required)
{
this.required = required;
// Set the required node permissions
if (required.equals(getPermissionReference(ALL_PERMISSIONS)))
{
nodeRequirements = rmModelDao.getUnconditionalRequiredPermissions(getPermissionReference(PermissionService.FULL_CONTROL), RequiredPermission.On.NODE);
}
else
{
nodeRequirements = rmModelDao.getUnconditionalRequiredPermissions(required, RequiredPermission.On.NODE);
}
if (rmModelDao.getUnconditionalRequiredPermissions(required, RequiredPermission.On.PARENT).size() > 0)
{
throw new IllegalStateException("Parent permissions can not be checked for an acl");
}
if (rmModelDao.getUnconditionalRequiredPermissions(required, RequiredPermission.On.CHILDREN).size() > 0)
{
throw new IllegalStateException("Child permissions can not be checked for an acl");
}
// Find all the permissions that grant the allowed permission
// All permissions are treated specially.
granters = new LinkedHashSet<PermissionReference>(128, 1.0f);
granters.addAll(rmModelDao.getGrantingPermissions(required));
granters.add(getAllPermissionReference());
granters.add(RM_OLD_ALL_PERMISSIONS_REFERENCE);
}
/**
* Internal hook point for recursion
*
* @param authorisations
* @param nodeRef
* @param denied
* @param recursiveIn
* @return true if granted
*/
boolean evaluate(String authority, Long aclId)
{
// Start out true and "and" all other results
boolean success = true;
// Check the required permissions but not for sets they rely on
// their underlying permissions
//if (modelDAO.checkPermission(required))
//{
// We have to do the test as no parent will help us out
success &= hasSinglePermission(authority, aclId);
if (!success)
{
return false;
}
//}
// Check the other permissions required on the node
for (PermissionReference pr : nodeRequirements)
{
// Build a new test
RMUnconditionalAclTest nt = new RMUnconditionalAclTest(pr);
success &= nt.evaluate(authority, aclId);
if (!success)
{
return false;
}
}
return success;
}
boolean hasSinglePermission(String authority, Long aclId)
{
// Check global permission
if (checkGlobalPermissions(authority))
{
return true;
}
if(aclId == null)
{
return false;
}
else
{
return checkRequired(authority, aclId);
}
}
/**
* Check if we have a global permission
*
* @param authorisations
* @return true if granted
*/
private boolean checkGlobalPermissions(String authority)
{
for (PermissionEntry pe : rmModelDao.getGlobalPermissionEntries())
{
if (isGranted(pe, authority))
{
return true;
}
}
return false;
}
/**
* Check that a given authentication is available on a node
*
* @param authorisations
* @param nodeRef
* @param denied
* @return true if a check is required
*/
boolean checkRequired(String authority, Long aclId)
{
AccessControlList acl = rmAclDaoComponent.getAccessControlList(aclId);
if (acl == null)
{
return false;
}
Set<Pair<String, PermissionReference>> denied = new HashSet<Pair<String, PermissionReference>>();
// Check if each permission allows - the first wins.
// We could have other voting style mechanisms here
for (AccessControlEntry ace : acl.getEntries())
{
if (isGranted(ace, authority, denied))
{
return true;
}
}
return false;
}
/**
* Is a permission granted
*
* @param pe -
* the permissions entry to consider
* @param granters -
* the set of granters
* @param authorisations -
* the set of authorities
* @param denied -
* the set of denied permissions/authority pais
* @return true if granted
*/
private boolean isGranted(AccessControlEntry ace, String authority, Set<Pair<String, PermissionReference>> denied)
{
// If the permission entry denies then we just deny
if (ace.getAccessStatus() == AccessStatus.DENIED)
{
denied.add(new Pair<String, PermissionReference>(ace.getAuthority(), ace.getPermission()));
Set<PermissionReference> granters = rmModelDao.getGrantingPermissions(ace.getPermission());
for (PermissionReference granter : granters)
{
denied.add(new Pair<String, PermissionReference>(ace.getAuthority(), granter));
}
// All the things granted by this permission must be
// denied
Set<PermissionReference> grantees = rmModelDao.getGranteePermissions(ace.getPermission());
for (PermissionReference grantee : grantees)
{
denied.add(new Pair<String, PermissionReference>(ace.getAuthority(), grantee));
}
// All permission excludes all permissions available for
// the node.
if (ace.getPermission().equals(getAllPermissionReference()) || ace.getPermission().equals(RM_OLD_ALL_PERMISSIONS_REFERENCE))
{
for (PermissionReference deny : rmModelDao.getAllPermissions())
{
denied.add(new Pair<String, PermissionReference>(ace.getAuthority(), deny));
}
}
return false;
}
// The permission is allowed but we deny it as it is in the denied
// set
if (denied != null)
{
Pair<String, PermissionReference> specific = new Pair<String, PermissionReference>(ace.getAuthority(), required);
if (denied.contains(specific))
{
return false;
}
}
// If the permission has a match in both the authorities and
// granters list it is allowed
// It applies to the current user and it is granted
if (authority.equals(ace.getAuthority()) && granters.contains(ace.getPermission()))
{
{
return true;
}
}
// Default deny
return false;
}
private boolean isGranted(PermissionEntry pe, String authority)
{
// If the permission entry denies then we just deny
if (pe.isDenied())
{
return false;
}
// If the permission has a match in both the authorities and
// granters list it is allowed
// It applies to the current user and it is granted
if (granters.contains(pe.getPermissionReference()) && authority.equals(pe.getAuthority()))
{
{
return true;
}
}
// Default deny
return false;
}
}
}