Fix ALF-4118 - PersonServiceImpl - prevent creation of duplicate people

- add validation to prevent direct creation / deletion (if using people container)
- should fix other possible inconsistencies with personCache
- add unit tests
- minor cleanup

git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@23038 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Jan Vonka
2010-10-12 10:44:22 +00:00
parent 43b583c613
commit 3eea2d35e7
3 changed files with 620 additions and 132 deletions

View File

@@ -35,6 +35,7 @@ import org.alfresco.model.ContentModel;
import org.alfresco.repo.cache.SimpleCache; import org.alfresco.repo.cache.SimpleCache;
import org.alfresco.repo.domain.permissions.AclDAO; import org.alfresco.repo.domain.permissions.AclDAO;
import org.alfresco.repo.node.NodeServicePolicies; import org.alfresco.repo.node.NodeServicePolicies;
import org.alfresco.repo.node.NodeServicePolicies.BeforeCreateNodePolicy;
import org.alfresco.repo.node.NodeServicePolicies.BeforeDeleteNodePolicy; import org.alfresco.repo.node.NodeServicePolicies.BeforeDeleteNodePolicy;
import org.alfresco.repo.node.NodeServicePolicies.OnCreateNodePolicy; import org.alfresco.repo.node.NodeServicePolicies.OnCreateNodePolicy;
import org.alfresco.repo.node.NodeServicePolicies.OnUpdatePropertiesPolicy; import org.alfresco.repo.node.NodeServicePolicies.OnUpdatePropertiesPolicy;
@@ -75,10 +76,13 @@ import org.alfresco.util.PropertyCheck;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
public class PersonServiceImpl extends TransactionListenerAdapter implements PersonService, NodeServicePolicies.OnCreateNodePolicy, NodeServicePolicies.BeforeDeleteNodePolicy, public class PersonServiceImpl extends TransactionListenerAdapter implements PersonService,
NodeServicePolicies.BeforeCreateNodePolicy,
NodeServicePolicies.OnCreateNodePolicy,
NodeServicePolicies.BeforeDeleteNodePolicy,
NodeServicePolicies.OnUpdatePropertiesPolicy NodeServicePolicies.OnUpdatePropertiesPolicy
{ {
private static Log s_logger = LogFactory.getLog(PersonServiceImpl.class); private static Log logger = LogFactory.getLog(PersonServiceImpl.class);
private static final String DELETE = "DELETE"; private static final String DELETE = "DELETE";
@@ -144,6 +148,9 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per
private UserNameMatcher userNameMatcher; private UserNameMatcher userNameMatcher;
private JavaBehaviour beforeCreateNodeValidationBehaviour;
private JavaBehaviour beforeDeleteNodeValidationBehaviour;
static static
{ {
Set<QName> props = new HashSet<QName>(); Set<QName> props = new HashSet<QName>();
@@ -186,6 +193,18 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per
PropertyCheck.mandatory(this, "aclDao", aclDao); PropertyCheck.mandatory(this, "aclDao", aclDao);
PropertyCheck.mandatory(this, "homeFolderManager", homeFolderManager); PropertyCheck.mandatory(this, "homeFolderManager", homeFolderManager);
beforeCreateNodeValidationBehaviour = new JavaBehaviour(this, "beforeCreateNodeValidation");
this.policyComponent.bindClassBehaviour(
BeforeCreateNodePolicy.QNAME,
ContentModel.TYPE_PERSON,
beforeCreateNodeValidationBehaviour);
beforeDeleteNodeValidationBehaviour = new JavaBehaviour(this, "beforeDeleteNodeValidation");
this.policyComponent.bindClassBehaviour(
BeforeDeleteNodePolicy.QNAME,
ContentModel.TYPE_PERSON,
beforeDeleteNodeValidationBehaviour);
this.policyComponent.bindClassBehaviour( this.policyComponent.bindClassBehaviour(
OnCreateNodePolicy.QNAME, OnCreateNodePolicy.QNAME,
ContentModel.TYPE_PERSON, ContentModel.TYPE_PERSON,
@@ -264,13 +283,7 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per
} }
/** /**
* Retrieve the person NodeRef for a username key. Depending on configuration missing people will be created if not * {@inheritDoc}
* found, else a NoSuchPersonException exception will be thrown.
*
* @param userName
* of the person NodeRef to retrieve
* @return NodeRef of the person as specified by the username
* @throws NoSuchPersonException
*/ */
public NodeRef getPerson(String userName) public NodeRef getPerson(String userName)
{ {
@@ -278,17 +291,7 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per
} }
/** /**
* Retrieve the person NodeRef for a username key. Depending on the <code>autoCreate</code> parameter and * {@inheritDoc}
* configuration missing people will be created if not found, else a NoSuchPersonException exception will be thrown.
*
* @param userName
* of the person NodeRef to retrieve
* @param autoCreate
* should we auto-create the person node and home folder if they don't exist? (and configuration allows
* us to)
* @return NodeRef of the person as specified by the username
* @throws NoSuchPersonException
* if the person doesn't exist and can't be created
*/ */
public NodeRef getPerson(final String userName, final boolean autoCreate) public NodeRef getPerson(final String userName, final boolean autoCreate)
{ {
@@ -342,6 +345,9 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per
return personNode; return personNode;
} }
/**
* {@inheritDoc}
*/
public boolean personExists(String caseSensitiveUserName) public boolean personExists(String caseSensitiveUserName)
{ {
return getPersonOrNull(caseSensitiveUserName) != null; return getPersonOrNull(caseSensitiveUserName) != null;
@@ -349,14 +355,13 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per
private NodeRef getPersonOrNull(String searchUserName) private NodeRef getPersonOrNull(String searchUserName)
{ {
String cacheKey = searchUserName.toLowerCase(); Set<NodeRef> allRefs = getFromCache(searchUserName);
Set<NodeRef> allRefs = this.personCache.get(cacheKey);
if (allRefs == null) if (allRefs == null)
{ {
List<ChildAssociationRef> childRefs = nodeService.getChildAssocs( List<ChildAssociationRef> childRefs = nodeService.getChildAssocs(
getPeopleContainer(), getPeopleContainer(),
ContentModel.ASSOC_CHILDREN, ContentModel.ASSOC_CHILDREN,
QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, searchUserName.toLowerCase()), getChildNameLower(searchUserName),
false); false);
allRefs = new LinkedHashSet<NodeRef>(childRefs.size() * 2); allRefs = new LinkedHashSet<NodeRef>(childRefs.size() * 2);
@@ -366,6 +371,7 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per
allRefs.add(nodeRef); allRefs.add(nodeRef);
} }
} }
List<NodeRef> refs = new ArrayList<NodeRef>(allRefs.size()); List<NodeRef> refs = new ArrayList<NodeRef>(allRefs.size());
for (NodeRef nodeRef : allRefs) for (NodeRef nodeRef : allRefs)
{ {
@@ -376,6 +382,7 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per
refs.add(nodeRef); refs.add(nodeRef);
} }
} }
NodeRef returnRef = null; NodeRef returnRef = null;
if (refs.size() > 1) if (refs.size() > 1)
{ {
@@ -386,7 +393,7 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per
returnRef = refs.get(0); returnRef = refs.get(0);
// Don't bother caching unless we get a result that doesn't need duplicate processing // Don't bother caching unless we get a result that doesn't need duplicate processing
personCache.put(cacheKey, allRefs); putToCache(searchUserName, allRefs);
} }
return returnRef; return returnRef;
} }
@@ -467,18 +474,18 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per
// Allow UIDs to be updated in this transaction // Allow UIDs to be updated in this transaction
AlfrescoTransactionSupport.bindResource(KEY_ALLOW_UID_UPDATE, Boolean.TRUE); AlfrescoTransactionSupport.bindResource(KEY_ALLOW_UID_UPDATE, Boolean.TRUE);
split(postTxnDuplicates); split(postTxnDuplicates);
s_logger.info("Split duplicate person objects"); logger.info("Split duplicate person objects");
} }
else if (duplicateMode.equalsIgnoreCase(DELETE)) else if (duplicateMode.equalsIgnoreCase(DELETE))
{ {
delete(postTxnDuplicates); delete(postTxnDuplicates);
s_logger.info("Deleted duplicate person objects"); logger.info("Deleted duplicate person objects");
} }
else else
{ {
if (s_logger.isDebugEnabled()) if (logger.isDebugEnabled())
{ {
s_logger.debug("Duplicate person objects exist"); logger.debug("Duplicate person objects exist");
} }
} }
} }
@@ -579,21 +586,33 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per
return true; return true;
} }
/**
* {@inheritDoc}
*/
public boolean createMissingPeople() public boolean createMissingPeople()
{ {
return createMissingPeople; return createMissingPeople;
} }
/**
* {@inheritDoc}
*/
public Set<QName> getMutableProperties() public Set<QName> getMutableProperties()
{ {
return mutableProperties; return mutableProperties;
} }
/**
* {@inheritDoc}
*/
public void setPersonProperties(String userName, Map<QName, Serializable> properties) public void setPersonProperties(String userName, Map<QName, Serializable> properties)
{ {
setPersonProperties(userName, properties, true); setPersonProperties(userName, properties, true);
} }
/**
* {@inheritDoc}
*/
public void setPersonProperties(String userName, Map<QName, Serializable> properties, boolean autoCreate) public void setPersonProperties(String userName, Map<QName, Serializable> properties, boolean autoCreate)
{ {
NodeRef personNode = getPersonOrNull(userName); NodeRef personNode = getPersonOrNull(userName);
@@ -631,6 +650,9 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per
nodeService.setProperties(personNode, update); nodeService.setProperties(personNode, update);
} }
/**
* {@inheritDoc}
*/
public boolean isMutable() public boolean isMutable()
{ {
return true; return true;
@@ -681,11 +703,17 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per
return properties; return properties;
} }
/**
* {@inheritDoc}
*/
public NodeRef createPerson(Map<QName, Serializable> properties) public NodeRef createPerson(Map<QName, Serializable> properties)
{ {
return createPerson(properties, authorityService.getDefaultZones()); return createPerson(properties, authorityService.getDefaultZones());
} }
/**
* {@inheritDoc}
*/
public NodeRef createPerson(Map<QName, Serializable> properties, Set<String> zones) public NodeRef createPerson(Map<QName, Serializable> properties, Set<String> zones)
{ {
String userName = DefaultTypeConverter.INSTANCE.convert(String.class, properties.get(ContentModel.PROP_USERNAME)); String userName = DefaultTypeConverter.INSTANCE.convert(String.class, properties.get(ContentModel.PROP_USERNAME));
@@ -705,11 +733,21 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per
properties.put(ContentModel.PROP_USERNAME, userName); properties.put(ContentModel.PROP_USERNAME, userName);
properties.put(ContentModel.PROP_SIZE_CURRENT, 0L); properties.put(ContentModel.PROP_SIZE_CURRENT, 0L);
NodeRef personRef = nodeService.createNode( NodeRef personRef = null;
try
{
beforeCreateNodeValidationBehaviour.disable();
personRef = nodeService.createNode(
getPeopleContainer(), getPeopleContainer(),
ContentModel.ASSOC_CHILDREN, ContentModel.ASSOC_CHILDREN,
QName.createQName("cm", userName.toLowerCase(), namespacePrefixResolver), // Lowercase: getChildNameLower(userName), // Lowercase:
ContentModel.TYPE_PERSON, properties).getChildRef(); ContentModel.TYPE_PERSON, properties).getChildRef();
}
finally
{
beforeCreateNodeValidationBehaviour.enable();
}
if (zones != null) if (zones != null)
{ {
@@ -717,14 +755,18 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per
{ {
// Add the person to an authentication zone (corresponding to an external user registry) // Add the person to an authentication zone (corresponding to an external user registry)
// Let's preserve case on this child association // Let's preserve case on this child association
nodeService.addChild(authorityService.getOrCreateZone(zone), personRef, ContentModel.ASSOC_IN_ZONE, QName.createQName("cm", userName, namespacePrefixResolver)); nodeService.addChild(authorityService.getOrCreateZone(zone), personRef, ContentModel.ASSOC_IN_ZONE, QName.createQName(NamespaceService.CONTENT_MODEL_PREFIX, userName, namespacePrefixResolver));
} }
} }
personCache.remove(userName.toLowerCase()); removeFromCache(userName);
return personRef; return personRef;
} }
/**
* {@inheritDoc}
*/
public NodeRef getPeopleContainer() public NodeRef getPeopleContainer()
{ {
String cacheKey = tenantService.getCurrentUserDomain(); String cacheKey = tenantService.getCurrentUserDomain();
@@ -758,6 +800,9 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per
return peopleNodeRef; return peopleNodeRef;
} }
/**
* {@inheritDoc}
*/
public void deletePerson(String userName) public void deletePerson(String userName)
{ {
// Normalize the username to avoid case sensitivity issues // Normalize the username to avoid case sensitivity issues
@@ -767,6 +812,32 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per
return; return;
} }
NodeRef personRef = getPersonOrNull(userName);
deletePersonImpl(userName, personRef);
}
/**
* {@inheritDoc}
*/
public void deletePerson(NodeRef personRef)
{
QName typeQName = nodeService.getType(personRef);
if (typeQName.equals(ContentModel.TYPE_PERSON))
{
String userName = (String) this.nodeService.getProperty(personRef, ContentModel.PROP_USERNAME);
deletePersonImpl(userName, personRef);
}
else
{
throw new AlfrescoRuntimeException("deletePerson: invalid type of node "+personRef+" (actual="+typeQName+", expected="+ContentModel.TYPE_PERSON+")");
}
}
private void deletePersonImpl(String userName, NodeRef personRef)
{
if (userName != null)
{
// Remove internally-stored password information, if any // Remove internally-stored password information, if any
try try
{ {
@@ -789,16 +860,27 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per
// remove any user permissions // remove any user permissions
permissionServiceSPI.deletePermissions(userName); permissionServiceSPI.deletePermissions(userName);
}
// delete the person // delete the person
NodeRef personNodeRef = getPersonOrNull(userName); if (personRef != null)
if (personNodeRef != null)
{ {
nodeService.deleteNode(personNodeRef); try
{
beforeDeleteNodeValidationBehaviour.disable();
nodeService.deleteNode(personRef);
}
finally
{
beforeDeleteNodeValidationBehaviour.enable();
}
} }
personCache.remove(userName.toLowerCase());
} }
/**
* {@inheritDoc}
*/
public Set<NodeRef> getAllPeople() public Set<NodeRef> getAllPeople()
{ {
List<ChildAssociationRef> childRefs = nodeService.getChildAssocs(getPeopleContainer(), List<ChildAssociationRef> childRefs = nodeService.getChildAssocs(getPeopleContainer(),
@@ -811,6 +893,9 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per
return refs; return refs;
} }
/**
* {@inheritDoc}
*/
public Set<NodeRef> getPeopleFilteredByProperty(QName propertyKey, Serializable propertyValue) public Set<NodeRef> getPeopleFilteredByProperty(QName propertyKey, Serializable propertyValue)
{ {
// check that given property key is defined for content model type 'cm:person' // check that given property key is defined for content model type 'cm:person'
@@ -866,9 +951,15 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per
public void onCreateNode(ChildAssociationRef childAssocRef) public void onCreateNode(ChildAssociationRef childAssocRef)
{ {
NodeRef personRef = childAssocRef.getChildRef(); NodeRef personRef = childAssocRef.getChildRef();
String username = (String) this.nodeService.getProperty(personRef, ContentModel.PROP_USERNAME);
personCache.remove(username.toLowerCase()); String userName = (String) this.nodeService.getProperty(personRef, ContentModel.PROP_USERNAME);
permissionsManager.setPermissions(personRef, username, username);
if (getPeopleContainer().equals(childAssocRef.getParentRef()))
{
removeFromCache(userName);
}
permissionsManager.setPermissions(personRef, userName, userName);
// Make sure there is an authority entry - with a DB constraint for uniqueness // Make sure there is an authority entry - with a DB constraint for uniqueness
// aclDao.createAuthority(username); // aclDao.createAuthority(username);
@@ -877,21 +968,95 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per
homeFolderManager.onCreateNode(childAssocRef); homeFolderManager.onCreateNode(childAssocRef);
} }
/** private QName getChildNameLower(String userName)
* {@inheritDoc} {
*/ return QName.createQName(NamespaceService.CONTENT_MODEL_PREFIX, userName.toLowerCase(), namespacePrefixResolver);
}
public void beforeCreateNode(
NodeRef parentRef,
QName assocTypeQName,
QName assocQName,
QName nodeTypeQName)
{
// NOOP
}
public void beforeCreateNodeValidation(
NodeRef parentRef,
QName assocTypeQName,
QName assocQName,
QName nodeTypeQName)
{
if (getPeopleContainer().equals(parentRef))
{
throw new AlfrescoRuntimeException("beforeCreateNode: use PersonService to create person");
}
else
{
logger.info("Person node is not being created under the people container (actual="+parentRef+", expected="+getPeopleContainer()+")");
}
}
public void beforeDeleteNode(NodeRef nodeRef) public void beforeDeleteNode(NodeRef nodeRef)
{ {
String username = (String) this.nodeService.getProperty(nodeRef, ContentModel.PROP_USERNAME); String userName = (String) this.nodeService.getProperty(nodeRef, ContentModel.PROP_USERNAME);
if (this.authorityService.isGuestAuthority(username)) if (this.authorityService.isGuestAuthority(userName))
{ {
throw new AlfrescoRuntimeException("The " + username + " user cannot be deleted."); throw new AlfrescoRuntimeException("The " + userName + " user cannot be deleted.");
} }
this.personCache.remove(username.toLowerCase());
NodeRef parentRef = null;
ChildAssociationRef parentAssocRef = nodeService.getPrimaryParent(nodeRef);
if (parentAssocRef != null)
{
parentRef = parentAssocRef.getParentRef();
if (getPeopleContainer().equals(parentRef))
{
removeFromCache(userName);
}
}
}
public void beforeDeleteNodeValidation(NodeRef nodeRef)
{
NodeRef parentRef = null;
ChildAssociationRef parentAssocRef = nodeService.getPrimaryParent(nodeRef);
if (parentAssocRef != null)
{
parentRef = parentAssocRef.getParentRef();
}
if (getPeopleContainer().equals(parentRef))
{
throw new AlfrescoRuntimeException("beforeDeleteNode: use PersonService to delete person");
}
else
{
logger.info("Person node that is being deleted is not under the parent people container (actual="+parentRef+", expected="+getPeopleContainer()+")");
}
}
private Set<NodeRef> getFromCache(String userName)
{
return this.personCache.get(userName.toLowerCase());
}
private void putToCache(String userName, Set<NodeRef> refs)
{
this.personCache.put(userName.toLowerCase(), refs);
}
private void removeFromCache(String userName)
{
this.personCache.remove(userName.toLowerCase());
} }
// IOC Setters // IOC Setters
/**
* {@inheritDoc}
*/
public void setCreateMissingPeople(boolean createMissingPeople) public void setCreateMissingPeople(boolean createMissingPeople)
{ {
this.createMissingPeople = createMissingPeople; this.createMissingPeople = createMissingPeople;
@@ -957,6 +1122,9 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per
this.storeRef = new StoreRef(storeUrl); this.storeRef = new StoreRef(storeUrl);
} }
/**
* {@inheritDoc}
*/
public String getUserIdentifier(String caseSensitiveUserName) public String getUserIdentifier(String caseSensitiveUserName)
{ {
NodeRef nodeRef = getPersonOrNull(caseSensitiveUserName); NodeRef nodeRef = getPersonOrNull(caseSensitiveUserName);
@@ -1011,6 +1179,9 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per
} }
} }
/**
* {@inheritDoc}
*/
public boolean getUserNamesAreCaseSensitive() public boolean getUserNamesAreCaseSensitive()
{ {
return userNameMatcher.getUserNamesAreCaseSensitive(); return userNameMatcher.getUserNamesAreCaseSensitive();
@@ -1045,15 +1216,14 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per
aclDao.renameAuthority(uidBefore, uidAfter); aclDao.renameAuthority(uidBefore, uidAfter);
} }
// Fix primary association local name // Fix primary association local name
QName newAssocQName = QName.createQName("cm", uidAfter.toLowerCase(), namespacePrefixResolver); QName newAssocQName = getChildNameLower(uidAfter);
ChildAssociationRef assoc = nodeService.getPrimaryParent(nodeRef); ChildAssociationRef assoc = nodeService.getPrimaryParent(nodeRef);
nodeService.moveNode(nodeRef, assoc.getParentRef(), assoc.getTypeQName(), newAssocQName); nodeService.moveNode(nodeRef, assoc.getParentRef(), assoc.getTypeQName(), newAssocQName);
// Fix other non-case sensitive parent associations // Fix other non-case sensitive parent associations
QName oldAssocQName = QName.createQName("cm", uidBefore, namespacePrefixResolver); QName oldAssocQName = QName.createQName(NamespaceService.CONTENT_MODEL_PREFIX, uidBefore, namespacePrefixResolver);
newAssocQName = QName.createQName("cm", uidAfter, namespacePrefixResolver); newAssocQName = QName.createQName(NamespaceService.CONTENT_MODEL_PREFIX, uidAfter, namespacePrefixResolver);
for (ChildAssociationRef parent : nodeService.getParentAssocs(nodeRef)) for (ChildAssociationRef parent : nodeService.getParentAssocs(nodeRef))
{ {
if (!parent.isPrimary() && parent.getQName().equals(oldAssocQName)) if (!parent.isPrimary() && parent.getQName().equals(oldAssocQName))
@@ -1064,7 +1234,7 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per
} }
// Fix cache // Fix cache
personCache.remove(uidBefore.toLowerCase()); removeFromCache(uidBefore);
} }
else else
{ {

View File

@@ -34,7 +34,9 @@ import javax.transaction.UserTransaction;
import junit.framework.Assert; import junit.framework.Assert;
import junit.framework.TestCase; import junit.framework.TestCase;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.model.ContentModel; import org.alfresco.model.ContentModel;
import org.alfresco.repo.policy.BehaviourFilter;
import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.transaction.RetryingTransactionHelper; import org.alfresco.repo.transaction.RetryingTransactionHelper;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
@@ -57,12 +59,14 @@ import org.springframework.context.ApplicationContext;
public class PersonTest extends TestCase public class PersonTest extends TestCase
{ {
private static ApplicationContext applicationContext = ApplicationContextHelper.getApplicationContext(); private static ApplicationContext ctx = ApplicationContextHelper.getApplicationContext();
private TransactionService transactionService; private TransactionService transactionService;
private PersonService personService; private PersonService personService;
private BehaviourFilter policyBehaviourFilter;
private NodeService nodeService; private NodeService nodeService;
private NodeRef rootNodeRef; private NodeRef rootNodeRef;
@@ -81,12 +85,12 @@ public class PersonTest extends TestCase
public void setUp() throws Exception public void setUp() throws Exception
{ {
transactionService = (TransactionService) applicationContext.getBean("transactionService"); transactionService = (TransactionService) ctx.getBean("transactionService");
personService = (PersonService) applicationContext.getBean("personService"); personService = (PersonService) ctx.getBean("personService");
nodeService = (NodeService) applicationContext.getBean("nodeService"); nodeService = (NodeService) ctx.getBean("nodeService");
permissionService = (PermissionService) applicationContext.getBean("permissionService"); permissionService = (PermissionService) ctx.getBean("permissionService");
authorityService = (AuthorityService) applicationContext.getBean("authorityService"); authorityService = (AuthorityService) ctx.getBean("authorityService");
policyBehaviourFilter = (BehaviourFilter) ctx.getBean("policyBehaviourFilter");
testTX = transactionService.getUserTransaction(); testTX = transactionService.getUserTransaction();
testTX.begin(); testTX.begin();
@@ -99,7 +103,7 @@ public class PersonTest extends TestCase
String uid = DefaultTypeConverter.INSTANCE.convert(String.class, nodeService.getProperty(nodeRef, ContentModel.PROP_USERNAME)); String uid = DefaultTypeConverter.INSTANCE.convert(String.class, nodeService.getProperty(nodeRef, ContentModel.PROP_USERNAME));
if (!uid.equals("admin") && !uid.equals("guest") ) if (!uid.equals("admin") && !uid.equals("guest") )
{ {
nodeService.deleteNode(nodeRef); personService.deletePerson(nodeRef);
} }
} }
@@ -784,7 +788,6 @@ public class PersonTest extends TestCase
final NodeRef[] duplicates = transactionService.getRetryingTransactionHelper().doInTransaction( final NodeRef[] duplicates = transactionService.getRetryingTransactionHelper().doInTransaction(
new RetryingTransactionCallback<NodeRef[]>() new RetryingTransactionCallback<NodeRef[]>()
{ {
public NodeRef[] execute() throws Throwable public NodeRef[] execute() throws Throwable
{ {
NodeRef[] duplicates = new NodeRef[10]; NodeRef[] duplicates = new NodeRef[10];
@@ -796,6 +799,10 @@ public class PersonTest extends TestCase
List<ChildAssociationRef> parents = nodeService.getParentAssocs(duplicates[0]); List<ChildAssociationRef> parents = nodeService.getParentAssocs(duplicates[0]);
// Generate some duplicates // Generate some duplicates
try
{
policyBehaviourFilter.disableBehaviour(ContentModel.TYPE_PERSON);
for (int i = 1; i < duplicates.length; i++) for (int i = 1; i < duplicates.length; i++)
{ {
// Create the node with the same parent assocs // Create the node with the same parent assocs
@@ -810,6 +817,12 @@ public class PersonTest extends TestCase
} }
} }
} }
}
finally
{
policyBehaviourFilter.enableBehaviour(ContentModel.TYPE_PERSON);
}
// With the default settings, the last created node should be the one that wins // With the default settings, the last created node should be the one that wins
assertEquals(duplicates[duplicates.length - 1], personService.getPerson(duplicateUserName)); assertEquals(duplicates[duplicates.length - 1], personService.getPerson(duplicateUserName));
return duplicates; return duplicates;
@@ -842,4 +855,301 @@ public class PersonTest extends TestCase
} }
}, false, true); }, false, true);
} }
public void testCheckForDuplicateCaseInsensitive()
{
final String TEST_PERSON_MIXED = "Test_Person_One";
final String TEST_PERSON_UPPER = TEST_PERSON_MIXED.toUpperCase();
final String TEST_PERSON_LOWER = TEST_PERSON_MIXED.toLowerCase();
AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getAdminUserName());
final NodeRef peopleContainer = personService.getPeopleContainer();
final Map<QName, Serializable> personProps = new HashMap<QName, Serializable>();
personProps.put(ContentModel.PROP_HOMEFOLDER, peopleContainer);
personProps.put(ContentModel.PROP_FIRSTNAME, "test first name");
personProps.put(ContentModel.PROP_LASTNAME, "test last name");
personProps.put(ContentModel.PROP_SIZE_CURRENT, 0);
RetryingTransactionHelper txnHelper = transactionService.getRetryingTransactionHelper();
RetryingTransactionCallback<Void> callback = new RetryingTransactionCallback<Void>()
{
public Void execute() throws Throwable
{
if (! personService.personExists(TEST_PERSON_UPPER))
{
personProps.put(ContentModel.PROP_USERNAME, TEST_PERSON_MIXED);
personService.createPerson(personProps);
}
return null;
}
};
txnHelper.doInTransaction(callback);
@SuppressWarnings("unused")
NodeRef personRef = null;
// -ve test
try
{
@SuppressWarnings("unused")
ChildAssociationRef childAssocRef = nodeService.createNode(
peopleContainer,
ContentModel.ASSOC_CHILDREN,
QName.createQName("{test}testperson"),
ContentModel.TYPE_PERSON,
personProps);
fail("Shouldn't be able to create person node directly (within people container) - use createPerson instead");
}
catch (AlfrescoRuntimeException are)
{
if (! are.getMessage().contains("use PersonService"))
{
throw are;
}
// ignore - expected
}
// -ve test
try
{
personProps.put(ContentModel.PROP_USERNAME, TEST_PERSON_LOWER);
personRef = personService.createPerson(personProps);
fail("Shouldn't be able to create duplicate person");
}
catch (AlfrescoRuntimeException are)
{
if (! are.getMessage().contains("already exists"))
{
throw are;
}
// ignore - expected
}
// -ve test
try
{
personProps.put(ContentModel.PROP_USERNAME, TEST_PERSON_UPPER);
personRef = personService.createPerson(personProps);
fail("Shouldn't be able to create duplicate person");
}
catch (AlfrescoRuntimeException are)
{
if (! are.getMessage().contains("already exists"))
{
throw are;
}
// ignore - expected
}
}
public void testCheckForDuplicateCaseSensitive()
{
final String TEST_PERSON_MIXED = "Test_Person_Two";
final String TEST_PERSON_UPPER = TEST_PERSON_MIXED.toUpperCase();
final String TEST_PERSON_LOWER = TEST_PERSON_MIXED.toLowerCase();
UserNameMatcherImpl usernameMatcher = new UserNameMatcherImpl();
usernameMatcher.setUserNamesAreCaseSensitive(true);
((PersonServiceImpl)personService).setUserNameMatcher(usernameMatcher); // case-sensitive
AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getAdminUserName());
final NodeRef peopleContainer = personService.getPeopleContainer();
final Map<QName, Serializable> personProps = new HashMap<QName, Serializable>();
personProps.put(ContentModel.PROP_HOMEFOLDER, peopleContainer);
personProps.put(ContentModel.PROP_FIRSTNAME, "test first name");
personProps.put(ContentModel.PROP_LASTNAME, "test last name");
personProps.put(ContentModel.PROP_SIZE_CURRENT, 0);
RetryingTransactionHelper txnHelper = transactionService.getRetryingTransactionHelper();
RetryingTransactionCallback<Void> callback = new RetryingTransactionCallback<Void>()
{
public Void execute() throws Throwable
{
if (! personService.personExists(TEST_PERSON_MIXED))
{
personProps.put(ContentModel.PROP_USERNAME, TEST_PERSON_MIXED);
personService.createPerson(personProps);
}
return null;
}
};
txnHelper.doInTransaction(callback);
@SuppressWarnings("unused")
NodeRef personRef = null;
personProps.put(ContentModel.PROP_USERNAME, TEST_PERSON_LOWER);
personRef = personService.createPerson(personProps);
personProps.put(ContentModel.PROP_USERNAME, TEST_PERSON_UPPER);
personRef = personService.createPerson(personProps);
// -ve test
try
{
personProps.put(ContentModel.PROP_USERNAME, TEST_PERSON_MIXED);
personRef = personService.createPerson(personProps);
fail("Shouldn't be able to create duplicate person");
}
catch (AlfrescoRuntimeException are)
{
if (! are.getMessage().contains("already exists"))
{
throw are;
}
// ignore - expected
}
// -ve test
try
{
personProps.put(ContentModel.PROP_USERNAME, TEST_PERSON_LOWER);
personRef = personService.createPerson(personProps);
fail("Shouldn't be able to create duplicate person");
}
catch (AlfrescoRuntimeException are)
{
if (! are.getMessage().contains("already exists"))
{
throw are;
}
// ignore - expected
}
// -ve test
try
{
personProps.put(ContentModel.PROP_USERNAME, TEST_PERSON_MIXED);
personRef = personService.createPerson(personProps);
fail("Shouldn't be able to create duplicate person");
}
catch (AlfrescoRuntimeException are)
{
if (! are.getMessage().contains("already exists"))
{
throw are;
}
// ignore - expected
}
usernameMatcher.setUserNamesAreCaseSensitive(false);
((PersonServiceImpl)personService).setUserNameMatcher(usernameMatcher); // case-insensitive
}
public void testUpdateUserNameCase()
{
final String TEST_PERSON_UPPER = "TEST_PERSON_THREE";
final String TEST_PERSON_LOWER = TEST_PERSON_UPPER.toLowerCase();
AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getAdminUserName());
RetryingTransactionHelper txnHelper = transactionService.getRetryingTransactionHelper();
final Map<QName, Serializable> personProps = new HashMap<QName, Serializable>();
personProps.put(ContentModel.PROP_HOMEFOLDER, rootNodeRef);
personProps.put(ContentModel.PROP_FIRSTNAME, "test first name ");
personProps.put(ContentModel.PROP_LASTNAME, "test last name");
personProps.put(ContentModel.PROP_SIZE_CURRENT, 0);
RetryingTransactionCallback<NodeRef> callback = new RetryingTransactionCallback<NodeRef>()
{
public NodeRef execute() throws Throwable
{
personProps.put(ContentModel.PROP_USERNAME, TEST_PERSON_LOWER);
return personService.createPerson(personProps);
}
};
final NodeRef personRef = txnHelper.doInTransaction(callback);
RetryingTransactionCallback<Void> callback2 = new RetryingTransactionCallback<Void>()
{
public Void execute() throws Throwable
{
nodeService.setProperty(personRef, ContentModel.PROP_USERNAME, TEST_PERSON_UPPER);
return null;
}
};
txnHelper.doInTransaction(callback2);
}
public void testCheckForIndirectUsage()
{
final String TEST_PERSON = "Test_Person_Four";
AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getAdminUserName());
final NodeRef peopleContainer = personService.getPeopleContainer();
final Map<QName, Serializable> personProps = new HashMap<QName, Serializable>();
personProps.put(ContentModel.PROP_USERNAME, TEST_PERSON);
personProps.put(ContentModel.PROP_HOMEFOLDER, peopleContainer);
personProps.put(ContentModel.PROP_FIRSTNAME, "test first name");
personProps.put(ContentModel.PROP_LASTNAME, "test last name");
personProps.put(ContentModel.PROP_SIZE_CURRENT, 0);
// -ve test
try
{
@SuppressWarnings("unused")
ChildAssociationRef childAssocRef = nodeService.createNode(
peopleContainer,
ContentModel.ASSOC_CHILDREN,
QName.createQName("{test}testperson"),
ContentModel.TYPE_PERSON,
personProps);
fail("Shouldn't be able to create person node directly (within people container) - use createPerson instead");
}
catch (AlfrescoRuntimeException are)
{
if (! are.getMessage().contains("use PersonService"))
{
throw are;
}
// ignore - expected
}
NodeRef personRef = personService.createPerson(personProps);
// -ve test
try
{
nodeService.deleteNode(personRef);
fail("Shouldn't be able to delete person node directly (within people container) - use deletePerson instead");
}
catch (AlfrescoRuntimeException are)
{
if (! are.getMessage().contains("use PersonService"))
{
throw are;
}
// ignore - expected
}
personService.deletePerson(TEST_PERSON);
}
} }

View File

@@ -185,6 +185,14 @@ public interface PersonService
@Auditable(parameters = {"userName"}) @Auditable(parameters = {"userName"})
public void deletePerson(String userName); public void deletePerson(String userName);
/**
* Delete the person identified by the given ref.
*
* @param personRef
*/
@Auditable(parameters = {"personRef"})
public void deletePerson(NodeRef personRef);
/** /**
* Get all the people we know about. * Get all the people we know about.
* *