From 5aacf7e84e81d389faaf44f4544bf1abbc05fe0b Mon Sep 17 00:00:00 2001 From: Nick Burch Date: Thu, 16 Jun 2011 11:10:58 +0000 Subject: [PATCH] ALF-8968 - Extend ScriptAuthorityService to also allow the fetching of users, so that the JS layer can find people without needing lucene queries git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@28423 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261 --- .../alfresco/authority-services-context.xml | 6 +- .../script/ScriptAuthorityService.java | 112 +++++- .../script/ScriptAuthorityServiceTest.java | 319 ++++++++++++++++++ .../security/authority/script/ScriptUser.java | 123 +++++-- .../alfresco/util/ScriptPagingDetails.java | 45 ++- 5 files changed, 542 insertions(+), 63 deletions(-) create mode 100644 source/java/org/alfresco/repo/security/authority/script/ScriptAuthorityServiceTest.java diff --git a/config/alfresco/authority-services-context.xml b/config/alfresco/authority-services-context.xml index b02685ca94..e71bcd83fa 100644 --- a/config/alfresco/authority-services-context.xml +++ b/config/alfresco/authority-services-context.xml @@ -100,7 +100,9 @@ groups - + + + - \ No newline at end of file + diff --git a/source/java/org/alfresco/repo/security/authority/script/ScriptAuthorityService.java b/source/java/org/alfresco/repo/security/authority/script/ScriptAuthorityService.java index b239c8735e..40dae8e200 100644 --- a/source/java/org/alfresco/repo/security/authority/script/ScriptAuthorityService.java +++ b/source/java/org/alfresco/repo/security/authority/script/ScriptAuthorityService.java @@ -20,13 +20,23 @@ package org.alfresco.repo.security.authority.script; import static org.alfresco.repo.security.authority.script.ScriptGroup.makeScriptGroups; +import java.util.ArrayList; import java.util.Collections; +import java.util.List; import java.util.Set; +import org.alfresco.model.ContentModel; import org.alfresco.repo.jscript.BaseScopableProcessorExtension; import org.alfresco.repo.security.authority.UnknownAuthorityException; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.security.AuthorityService; import org.alfresco.service.cmr.security.AuthorityType; +import org.alfresco.service.cmr.security.NoSuchPersonException; +import org.alfresco.service.cmr.security.PagingPersonResults; +import org.alfresco.service.cmr.security.PersonService; +import org.alfresco.service.namespace.QName; +import org.alfresco.util.Pair; import org.alfresco.util.ScriptPagingDetails; /** @@ -38,18 +48,24 @@ import org.alfresco.util.ScriptPagingDetails; */ public class ScriptAuthorityService extends BaseScopableProcessorExtension { - /** The service */ + /** The group/authority service */ private AuthorityService authorityService; + /** The person service */ + private PersonService personService; + + private ServiceRegistry serviceRegistry; - public void setAuthorityService(AuthorityService authorityService) - { - this.authorityService = authorityService; - } + public void setServiceRegistry(ServiceRegistry serviceRegistry) + { + this.serviceRegistry = serviceRegistry; + this.authorityService = serviceRegistry.getAuthorityService(); + this.personService = serviceRegistry.getPersonService(); + } - public AuthorityService getAuthorityService() - { - return authorityService; - } + public AuthorityService getAuthorityService() + { + return authorityService; + } /** * Search the root groups, those without a parent group. @@ -307,4 +323,82 @@ public class ScriptAuthorityService extends BaseScopableProcessorExtension } return makeScriptGroups(authorities, paging, sortBy, authorityService); } + + /** + * Get a user given their username + * @param username, the username of the user + * @return the user or null if they can't be found + */ + public ScriptUser getUser(String username) + { + try + { + NodeRef person = personService.getPerson(username, false); + return new ScriptUser(username, person, serviceRegistry); + } + catch(NoSuchPersonException e) + { + return null; + } + } + + /** + * Search for users + * Includes paging parameters to limit size of results returned. + * + * @param nameFilter partial match of the name (username, first name, last name). If empty then matches everyone. + * @param paging Paging object with max number to return, and items to skip + * @param sortBy What to sort on (firstName, lastName or userName) + * @return the users matching the query + */ + public ScriptUser[] searchUsers(String nameFilter, ScriptPagingDetails paging, String sortBy) + { + // Build the filter + List> filter = new ArrayList>(); + filter.add(new Pair(ContentModel.PROP_FIRSTNAME, nameFilter)); + filter.add(new Pair(ContentModel.PROP_LASTNAME, nameFilter)); + filter.add(new Pair(ContentModel.PROP_USERNAME, nameFilter)); + + // Build the sorting. The user controls the primary sort, we supply + // additional ones automatically + List> sort = new ArrayList>(); + if("lastName".equals(sortBy)) + { + sort.add(new Pair(ContentModel.PROP_LASTNAME, true)); + sort.add(new Pair(ContentModel.PROP_FIRSTNAME, true)); + sort.add(new Pair(ContentModel.PROP_USERNAME, true)); + } + else if("firstName".equals(sortBy)) + { + sort.add(new Pair(ContentModel.PROP_FIRSTNAME, true)); + sort.add(new Pair(ContentModel.PROP_LASTNAME, true)); + sort.add(new Pair(ContentModel.PROP_USERNAME, true)); + } + else + { + sort.add(new Pair(ContentModel.PROP_USERNAME, true)); + sort.add(new Pair(ContentModel.PROP_FIRSTNAME, true)); + sort.add(new Pair(ContentModel.PROP_LASTNAME, true)); + } + + // Do the search + PagingPersonResults results = personService.getPeople(filter, true, sort, paging); + + // Record the size of the results + paging.setTotalItems(results); + + // Now wrap up the users + List nodes = results.getPage(); + ScriptUser[] users = new ScriptUser[nodes.size()]; + for(int i=0; i. + */ +package org.alfresco.repo.security.authority.script; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; + +import javax.transaction.Status; +import javax.transaction.UserTransaction; + +import junit.framework.TestCase; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.model.ContentModel; +import org.alfresco.repo.domain.permissions.AclDAO; +import org.alfresco.repo.security.authentication.AuthenticationComponent; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.security.authentication.MutableAuthenticationDao; +import org.alfresco.repo.transaction.AlfrescoTransactionSupport; +import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; +import org.alfresco.service.cmr.security.AuthorityService; +import org.alfresco.service.cmr.security.AuthorityType; +import org.alfresco.service.cmr.security.MutableAuthenticationService; +import org.alfresco.service.cmr.security.PersonService; +import org.alfresco.service.namespace.QName; +import org.alfresco.service.transaction.TransactionService; +import org.alfresco.util.ApplicationContextHelper; +import org.alfresco.util.ScriptPagingDetails; +import org.springframework.context.ApplicationContext; + +/** + * Tests for the Script wrapper for the Authority Service, + * ScriptAuthorityService + */ +public class ScriptAuthorityServiceTest extends TestCase +{ + private static ApplicationContext ctx = ApplicationContextHelper.getApplicationContext(); + + private static final String GROUP_A = "testGroupA"; + private static final String GROUP_A_FULL = "GROUP_testGroupA"; + private static final String GROUP_B = "testGroupB"; + private static final String GROUP_B_FULL = "GROUP_testGroupB"; + private static final String GROUP_C = "testGroupC"; + private static final String GROUP_C_FULL = "GROUP_testGroupC"; + private static final String USER_A = "testUserA"; + private static final String USER_B = "testUserB"; + private static final String USER_C = "testUserC"; + + private AuthenticationComponent authenticationComponentImpl; + + private MutableAuthenticationService authenticationService; + + private MutableAuthenticationDao authenticationDAO; + + private AuthorityService authorityService; + + private AuthorityService pubAuthorityService; + + private PersonService personService; + + private UserTransaction tx; + + private AclDAO aclDaoComponent; + + private NodeService nodeService; + + private ScriptAuthorityService service; + + public ScriptAuthorityServiceTest() + { + super(); + + } + + public void setUp() throws Exception + { + if (AlfrescoTransactionSupport.getTransactionReadState() != TxnReadState.TXN_NONE) + { + throw new AlfrescoRuntimeException( + "A previous tests did not clean up transaction: " + + AlfrescoTransactionSupport.getTransactionId()); + } + + authenticationComponentImpl = (AuthenticationComponent) ctx.getBean("authenticationComponent"); + authenticationService = (MutableAuthenticationService) ctx.getBean("authenticationService"); + authorityService = (AuthorityService) ctx.getBean("authorityService"); + pubAuthorityService = (AuthorityService) ctx.getBean("AuthorityService"); + personService = (PersonService) ctx.getBean("personService"); + authenticationDAO = (MutableAuthenticationDao) ctx.getBean("authenticationDao"); + aclDaoComponent = (AclDAO) ctx.getBean("aclDAO"); + nodeService = (NodeService) ctx.getBean("nodeService"); + service = (ScriptAuthorityService) ctx.getBean("authorityServiceScript"); + + authenticationComponentImpl.setSystemUserAsCurrentUser(); + + // Clean up the users if they're already there + TransactionService transactionService = (TransactionService) ctx.getBean(ServiceRegistry.TRANSACTION_SERVICE.getLocalName()); + tx = transactionService.getUserTransaction(); + tx.begin(); + for (String user : new String[] { USER_A, USER_B, USER_C }) + { + if (personService.personExists(user)) + { + NodeRef person = personService.getPerson(user); + NodeRef hf = DefaultTypeConverter.INSTANCE.convert(NodeRef.class, nodeService.getProperty(person, ContentModel.PROP_HOMEFOLDER)); + if (hf != null) + { + nodeService.deleteNode(hf); + } + aclDaoComponent.deleteAccessControlEntries(user); + personService.deletePerson(user); + } + if (authenticationDAO.userExists(user)) + { + authenticationDAO.deleteUser(user); + } + } + + // And the group + if(authorityService.authorityExists(GROUP_A_FULL)) + { + authorityService.deleteAuthority(GROUP_A_FULL); + } + if(authorityService.authorityExists(GROUP_B_FULL)) + { + authorityService.deleteAuthority(GROUP_B_FULL); + } + if(authorityService.authorityExists(GROUP_C_FULL)) + { + authorityService.deleteAuthority(GROUP_C_FULL); + } + tx.commit(); + + // Now re-create them + tx = transactionService.getUserTransaction(); + tx.begin(); + + authorityService.createAuthority(AuthorityType.GROUP, GROUP_A); + authorityService.createAuthority(AuthorityType.GROUP, GROUP_B); + authorityService.createAuthority(AuthorityType.GROUP, GROUP_C); + + for (String user : new String[] { USER_A, USER_B, USER_C }) + { + if (!authenticationDAO.userExists(user)) + { + authenticationService.createAuthentication(user, user.toCharArray()); + } + + Map props = new HashMap(); + props.put(ContentModel.PROP_USERNAME, user); + props.put(ContentModel.PROP_FIRSTNAME, user); + props.put(ContentModel.PROP_LASTNAME, "Last_" + user); + personService.createPerson(props); + } + } + + @Override + protected void tearDown() throws Exception + { + if ((tx.getStatus() == Status.STATUS_ACTIVE) || (tx.getStatus() == Status.STATUS_MARKED_ROLLBACK)) + { + tx.rollback(); + } + AuthenticationUtil.clearCurrentSecurityContext(); + super.tearDown(); + } + + public void testBasics() + { + // Should return the same count for the root groups + int count = pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size(); + ScriptGroup[] groups = service.getAllRootGroups(); + assertEquals(count, groups.length); + + // And our test ones are in there + ScriptGroup groupA = null; + ScriptGroup groupB = null; + ScriptGroup groupC = null; + for(ScriptGroup group : groups) + { + if(group.getShortName().equals(GROUP_A)) groupA = group; + if(group.getShortName().equals(GROUP_B)) groupB = group; + if(group.getShortName().equals(GROUP_C)) groupC = group; + } + assertNotNull(GROUP_A + " not found in " + groups, groupA); + assertNotNull(GROUP_B + " not found in " + groups, groupB); + assertNotNull(GROUP_C + " not found in " + groups, groupC); + + // Check group A in more detail + assertEquals(GROUP_A, groupA.getShortName()); + assertEquals(GROUP_A, groupA.getDisplayName()); + assertEquals(GROUP_A_FULL, groupA.getFullName()); + } + + public void testZones() + { + } + + public void testFindGroups() + { + + } + + public void testGroupUsers() + { + + } + + public void testUsers() + { + // Getting by username + ScriptUser userA = service.getUser(USER_A); + ScriptUser userB = service.getUser(USER_B); + ScriptUser userC = service.getUser(USER_C); + ScriptUser userNA = service.getUser("DOESnotEXISTinTHEsystem"); + assertNotNull(userA); + assertNotNull(userB); + assertNotNull(userC); + assertNull(userNA); + + // Check the details on one user + assertEquals(USER_A, userA.getFullName()); + assertEquals(USER_A, userA.getShortName()); + assertEquals(USER_A, userA.getDisplayName()); + + NodeRef nodeA = personService.getPerson(USER_A, false); + assertNotNull(nodeA); + + // Check the person + assertEquals(nodeA, userA.getPersonNodeRef()); + assertEquals(nodeA, userA.getPerson().getNodeRef()); + assertEquals(USER_A, userA.getPerson().getProperties().get("userName")); + assertEquals(USER_A, userA.getPerson().getProperties().get("firstName")); + } + + public void testFindUsers() + { + // Try to find admin + ScriptUser[] users = service.searchUsers( + AuthenticationUtil.getAdminUserName(), + new ScriptPagingDetails(10, 0), + "userName" + ); + assertTrue("Admin not found", users.length > 0); + + // Try to find our test users + users = service.searchUsers( + USER_A.substring(0, USER_A.length()-1), + new ScriptPagingDetails(10, 0), + "userName" + ); + assertEquals("Users count wrong " + users, 3, users.length); + + // Check on the username sorting + assertEquals(USER_A, users[0].getPerson().getProperties().get("userName")); + assertEquals(USER_B, users[1].getPerson().getProperties().get("userName")); + assertEquals(USER_C, users[2].getPerson().getProperties().get("userName")); + + // Tweak names and re-check + ScriptUser userA = users[0]; + ScriptUser userB = users[1]; + ScriptUser userC = users[2]; + nodeService.setProperty(userB.getPersonNodeRef(), ContentModel.PROP_FIRSTNAME, "bbbbFIRST"); + nodeService.setProperty(userC.getPersonNodeRef(), ContentModel.PROP_LASTNAME, "ccccLAST"); + + users = service.searchUsers( + USER_A.substring(0, USER_A.length()-1), + new ScriptPagingDetails(10, 0), + "userName" + ); + assertEquals("Users count wrong " + users, 3, users.length); + assertEquals(USER_A, users[0].getPerson().getProperties().get("userName")); + assertEquals(USER_B, users[1].getPerson().getProperties().get("userName")); + assertEquals(USER_C, users[2].getPerson().getProperties().get("userName")); + + // Check sorting on firstname + users = service.searchUsers( + USER_A.substring(0, USER_A.length()-1), + new ScriptPagingDetails(10, 0), + "firstName" + ); + assertEquals("Users count wrong " + users, 3, users.length); + assertEquals(USER_B, users[0].getPerson().getProperties().get("userName")); + assertEquals(USER_A, users[1].getPerson().getProperties().get("userName")); + assertEquals(USER_C, users[2].getPerson().getProperties().get("userName")); + + // And lastname + users = service.searchUsers( + USER_A.substring(0, USER_A.length()-1), + new ScriptPagingDetails(10, 0), + "lastName" + ); + assertEquals("Users count wrong " + users, 3, users.length); + assertEquals(USER_C, users[0].getPerson().getProperties().get("userName")); + assertEquals(USER_A, users[1].getPerson().getProperties().get("userName")); + assertEquals(USER_B, users[2].getPerson().getProperties().get("userName")); + } +} diff --git a/source/java/org/alfresco/repo/security/authority/script/ScriptUser.java b/source/java/org/alfresco/repo/security/authority/script/ScriptUser.java index 1522720239..c9142080b0 100644 --- a/source/java/org/alfresco/repo/security/authority/script/ScriptUser.java +++ b/source/java/org/alfresco/repo/security/authority/script/ScriptUser.java @@ -20,8 +20,10 @@ package org.alfresco.repo.security.authority.script; import java.io.Serializable; +import org.alfresco.repo.jscript.ScriptNode; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.security.AuthorityService; -import org.alfresco.service.cmr.security.AuthorityType; /** * The Script user is a USER authority exposed to the scripting API @@ -30,51 +32,104 @@ import org.alfresco.service.cmr.security.AuthorityType; */ public class ScriptUser implements Authority, Serializable { - private transient AuthorityService authorityService; + private static final long serialVersionUID = 7865300693011208293L; + private transient ServiceRegistry serviceRegistry; + private transient AuthorityService authorityService; private ScriptAuthorityType authorityType = ScriptAuthorityType.USER; + private String userName; private String shortName; - private String fullName; private String displayName; + private NodeRef personNodeRef; - public ScriptUser(String fullName, AuthorityService authorityService) + public ScriptUser(String userName, NodeRef personNodeRef, ServiceRegistry serviceRegistry) { - this.authorityService = authorityService; - this.fullName = fullName; - shortName = authorityService.getShortName(fullName); - displayName = authorityService.getAuthorityDisplayName(fullName); - //isInternal = authorityService. + this.serviceRegistry = serviceRegistry; + this.authorityService = serviceRegistry.getAuthorityService(); + + this.personNodeRef = personNodeRef; + this.userName = userName; + shortName = authorityService.getShortName(userName); + displayName = authorityService.getAuthorityDisplayName(userName); } - public void setAuthorityType(ScriptAuthorityType authorityType) { - this.authorityType = authorityType; - } + /** + * @deprecated The ServiceRegistry is now needed + */ + public ScriptUser(String userName, AuthorityService authorityService) + { + this.authorityService = authorityService; + this.userName = userName; + shortName = authorityService.getShortName(userName); + displayName = authorityService.getAuthorityDisplayName(userName); + } + + public void setAuthorityType(ScriptAuthorityType authorityType) + { + this.authorityType = authorityType; + } - public ScriptAuthorityType getAuthorityType() { - return authorityType; - } + public ScriptAuthorityType getAuthorityType() + { + return authorityType; + } - public void setShortName(String shortName) { - this.shortName = shortName; - } + public void setShortName(String shortName) + { + this.shortName = shortName; + } - public String getShortName() { - return shortName; - } + public String getShortName() + { + return shortName; + } - public void setFullName(String fullName) { - this.fullName = fullName; - } + public void setFullName(String fullName) + { + this.userName = fullName; + } - public String getFullName() { - return fullName; - } + public String getFullName() + { + return userName; + } - public void setDisplayName(String displayName) { - this.displayName = displayName; - } + /** + * Return the User Name, also known as the + * Authority Full Name + */ + public String getUserName() + { + return userName; + } - public String getDisplayName() { - return displayName; - } - + public void setDisplayName(String displayName) + { + this.displayName = displayName; + } + + public String getDisplayName() + { + return displayName; + } + + /** + * Return the NodeRef of the person + */ + public NodeRef getPersonNodeRef() + { + if (personNodeRef == null) + { + // Lazy lookup for Authority based creation + personNodeRef = authorityService.getAuthorityNodeRef(userName); + } + return personNodeRef; + } + + /** + * Return a ScriptNode wrapping the person + */ + public ScriptNode getPerson() + { + return new ScriptNode(getPersonNodeRef(), serviceRegistry); + } } diff --git a/source/java/org/alfresco/util/ScriptPagingDetails.java b/source/java/org/alfresco/util/ScriptPagingDetails.java index 5c0cd83e66..4f8a5498c7 100644 --- a/source/java/org/alfresco/util/ScriptPagingDetails.java +++ b/source/java/org/alfresco/util/ScriptPagingDetails.java @@ -82,30 +82,39 @@ public class ScriptPagingDetails extends PagingRequest */ public void setTotalItems(PagingResults results) { - Integer min = results.getTotalResultCount().getFirst(); - Integer max = results.getTotalResultCount().getSecond(); - - // Get the total count and confidence - if(min == null) + if(results.getTotalResultCount() == null) { + // No count calculated this.totalItems = -1; this.confidence = ItemsSizeConfidence.UNKNOWN; } - else if(max == null) - { - this.totalItems = min; - this.confidence = ItemsSizeConfidence.AT_LEAST; - } - else if(min == max) - { - this.totalItems = min; - this.confidence = ItemsSizeConfidence.EXACT; - } else { - this.totalItems = min; - this.totalItemsRangeMax = max; - this.confidence = ItemsSizeConfidence.RANGE; + // Get the total count and confidence + Integer min = results.getTotalResultCount().getFirst(); + Integer max = results.getTotalResultCount().getSecond(); + + if(min == null) + { + this.totalItems = -1; + this.confidence = ItemsSizeConfidence.UNKNOWN; + } + else if(max == null) + { + this.totalItems = min; + this.confidence = ItemsSizeConfidence.AT_LEAST; + } + else if(min == max) + { + this.totalItems = min; + this.confidence = ItemsSizeConfidence.EXACT; + } + else + { + this.totalItems = min; + this.totalItemsRangeMax = max; + this.confidence = ItemsSizeConfidence.RANGE; + } } // Finally record the query execution ID