From d5e0432589c9b37817c7a310cf93cba4bfc0623a Mon Sep 17 00:00:00 2001 From: Dave Ward Date: Mon, 8 Jun 2009 16:16:32 +0000 Subject: [PATCH] Merged BRANCHES/DEV/DAVEW/LDAP to HEAD 14587: Added new node service method getNodesWithoutParentAssocsOfType to public-services-security-context.xml (or at least my best guess at it!) 14586: Use US spelling of synchronization in filenames for consistency 14585: Lower the default user registry sync frequency to daily instead of hourly. Now users and groups are pulled over incrementally on login of missing users. 14583: Unit test for ChainingUserRegistrySynchronizer 14571: Migration patch for existing authorities previously held in users store - Uses AuthorityService to recreate authorities in spaces store with new structure 14555: Authority service changes for LDAP sync improvements - Moved sys:authorities container to spaces store - All authorities now stored directly under sys:authorities - Authorities can now be looked up directly by node service - Secondary child associations used to model group relationships - 'Root' groups for UI navigation determined dynamically by node service query - cm:member association used to relate both authority containers and persons to other authorities - New cm:inZone association relates persons and authority containers to synchronization 'zones' stored under sys:zones - Look up of authority zone and all authorities in a zone to enable multi-zone LDAP sync 14524: Dev branch for finishing LDAP zones and upgrade impact git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@14588 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261 --- .../authentication-services-context.xml | 25 + .../alfresco/authority-services-context.xml | 9 +- config/alfresco/bootstrap-context.xml | 20 +- config/alfresco/bootstrap/adminGroup.xml | 23 - .../bootstrap/alfrescoAuthorityStore.xml | 57 +- .../alfrescoAuthorityStorePermission.xml | 16 - config/alfresco/bootstrap/emailServer.xml | 29 - .../emailserver/email-service-context.xml | 3 + config/alfresco/import-export-context.xml | 23 +- .../messages/patch-service.properties | 7 +- config/alfresco/model/bpmModel.xml | 4 +- config/alfresco/model/contentModel.xml | 65 +- .../alfresco/patch/patch-services-context.xml | 132 ++- config/alfresco/public-services-context.xml | 28 + .../public-services-security-context.xml | 1 + config/alfresco/repository.properties | 7 +- .../ldap/ldap-authentication-context.xml | 191 ++++ .../ldap/ldap-authentication.properties | 71 +- .../default-synchronization-context.xml | 33 + .../default-synchronization.properties | 13 + .../ldap/ldap-synchronisation-context.xml | 398 -------- .../ldap/ldap-synchronisation.properties | 74 -- config/alfresco/version.properties | 2 +- .../email/server/EmailServiceImpl.java | 40 +- .../java/org/alfresco/model/ContentModel.java | 25 +- .../patch/impl/AuthorityMigrationPatch.java | 198 ++++ .../MoveWCMToGroupBasedPermissionsPatch.java | 2 +- .../impl/SitePermissionRefactorPatch.java | 3 +- ...lGroupParentChildAssociationTypePatch.java | 4 +- .../org/alfresco/repo/avm/AVMNodeService.java | 9 + .../avm/locking/AVMLockingServiceTest.java | 6 +- .../repo/domain/hibernate/Node.hbm.xml | 21 + .../repo/importer/FileSourceImporter.java | 1 - .../org/alfresco/repo/jscript/People.java | 9 +- .../ChildApplicationContextFactory.java | 10 +- .../repo/node/db/DbNodeServiceImpl.java | 20 + .../alfresco/repo/node/db/NodeDaoService.java | 20 +- .../HibernateNodeDaoServiceImpl.java | 36 +- .../impl/lucene/ADMLuceneIndexerImpl.java | 3 +- .../impl/lucene/AVMLuceneIndexerImpl.java | 3 +- .../search/impl/lucene/LuceneAnalyser.java | 3 +- .../search/impl/lucene/LuceneQueryParser.java | 6 +- .../AbstractAuthenticationComponent.java | 63 +- .../AuthenticationServiceImpl.java | 17 +- .../ldap/LDAPAuthenticationComponentImpl.java | 23 +- .../ldap/LDAPGroupExportSource.java | 782 ---------------- .../ldap/LDAPPersonExportSource.java | 341 ------- ...systemChainingAuthenticationComponent.java | 11 +- ...ubsystemChainingAuthenticationService.java | 21 +- .../security/authentication/userModel.xml | 41 +- .../repo/security/authority/AuthorityDAO.java | 40 +- .../security/authority/AuthorityDAOImpl.java | 571 +++++------- .../authority/AuthorityServiceImpl.java | 23 +- .../authority/AuthorityServiceTest.java | 128 +-- .../ExtendedPermissionServiceTest.java | 4 +- .../authority/SimpleAuthorityServiceImpl.java | 28 +- .../script/ScriptAuthorityService.java | 2 +- .../authority/script/ScriptGroup.java | 3 +- .../impl/PermissionServiceTest.java | 2 +- .../security/person/PersonServiceImpl.java | 29 +- .../ChainingUserRegistrySynchronizer.java | 479 ++++++++++ .../ChainingUserRegistrySynchronizerTest.java | 524 +++++++++++ .../repo/security/sync/NodeDescription.java | 89 ++ .../repo/security/sync/UserRegistry.java | 62 ++ .../sync/UserRegistrySynchronizer.java | 50 + .../sync/UserRegistrySynchronizerJob.java | 64 ++ .../security/sync/ldap/LDAPUserRegistry.java | 857 ++++++++++++++++++ .../alfresco/repo/site/SiteServiceImpl.java | 11 +- .../repo/site/SiteServiceImplTest.java | 9 +- .../alfresco/repo/tenant/MultiTDemoTest.java | 7 +- .../repo/version/NodeServiceImpl.java | 9 + .../service/cmr/repository/NodeService.java | 20 +- .../cmr/security/AuthorityService.java | 82 +- .../service/cmr/security/PersonService.java | 19 +- .../alfresco/wcm/sandbox/SandboxFactory.java | 4 +- .../webproject/WebProjectServiceImplTest.java | 2 +- source/test-resources/sync-test-context.xml | 26 + 77 files changed, 3674 insertions(+), 2419 deletions(-) delete mode 100644 config/alfresco/bootstrap/adminGroup.xml delete mode 100644 config/alfresco/bootstrap/alfrescoAuthorityStorePermission.xml delete mode 100644 config/alfresco/bootstrap/emailServer.xml create mode 100644 config/alfresco/subsystems/Synchronization/default/default-synchronization-context.xml create mode 100644 config/alfresco/subsystems/Synchronization/default/default-synchronization.properties delete mode 100644 config/alfresco/subsystems/Synchronization/ldap/ldap-synchronisation-context.xml delete mode 100644 config/alfresco/subsystems/Synchronization/ldap/ldap-synchronisation.properties create mode 100644 source/java/org/alfresco/repo/admin/patch/impl/AuthorityMigrationPatch.java delete mode 100644 source/java/org/alfresco/repo/security/authentication/ldap/LDAPGroupExportSource.java delete mode 100644 source/java/org/alfresco/repo/security/authentication/ldap/LDAPPersonExportSource.java create mode 100644 source/java/org/alfresco/repo/security/sync/ChainingUserRegistrySynchronizer.java create mode 100644 source/java/org/alfresco/repo/security/sync/ChainingUserRegistrySynchronizerTest.java create mode 100644 source/java/org/alfresco/repo/security/sync/NodeDescription.java create mode 100644 source/java/org/alfresco/repo/security/sync/UserRegistry.java create mode 100644 source/java/org/alfresco/repo/security/sync/UserRegistrySynchronizer.java create mode 100644 source/java/org/alfresco/repo/security/sync/UserRegistrySynchronizerJob.java create mode 100644 source/java/org/alfresco/repo/security/sync/ldap/LDAPUserRegistry.java create mode 100644 source/test-resources/sync-test-context.xml diff --git a/config/alfresco/authentication-services-context.xml b/config/alfresco/authentication-services-context.xml index 9c1296f965..56c557e81d 100644 --- a/config/alfresco/authentication-services-context.xml +++ b/config/alfresco/authentication-services-context.xml @@ -195,6 +195,12 @@ + + + + + ${authentication.syncWhenMissingPeopleLogIn} + @@ -217,6 +223,25 @@ + + + + + + + + + + + + + + + + userRegistry + + + diff --git a/config/alfresco/authority-services-context.xml b/config/alfresco/authority-services-context.xml index 9b28647865..9167b2eabe 100644 --- a/config/alfresco/authority-services-context.xml +++ b/config/alfresco/authority-services-context.xml @@ -47,18 +47,21 @@ + + ${spaces.store} + - - - + + + diff --git a/config/alfresco/bootstrap-context.xml b/config/alfresco/bootstrap-context.xml index eff0a017dd..e416e5f22d 100644 --- a/config/alfresco/bootstrap-context.xml +++ b/config/alfresco/bootstrap-context.xml @@ -449,20 +449,12 @@ - - - - true - - - ${synchronization.chain} - - - - ldap - - + + + + true + + diff --git a/config/alfresco/bootstrap/adminGroup.xml b/config/alfresco/bootstrap/adminGroup.xml deleted file mode 100644 index 0077fdc3ac..0000000000 --- a/config/alfresco/bootstrap/adminGroup.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - user - alfrescoUserStore - GROUP_ALFRESCO_ADMINISTRATORS - GROUP_ALFRESCO_ADMINISTRATORS - GROUP_ALFRESCO_ADMINISTRATORS - - - - - \ No newline at end of file diff --git a/config/alfresco/bootstrap/alfrescoAuthorityStore.xml b/config/alfresco/bootstrap/alfrescoAuthorityStore.xml index 790cd3f928..8febc87fb4 100644 --- a/config/alfresco/bootstrap/alfrescoAuthorityStore.xml +++ b/config/alfresco/bootstrap/alfrescoAuthorityStore.xml @@ -3,8 +3,61 @@ xmlns:sys="http://www.alfresco.org/model/system/1.0" xmlns:usr="http://www.alfresco.org/model/user/1.0" xmlns:app="http://www.alfresco.org/model/application/1.0"> - - + + + + + GROUP_EVERYONE + Read + + + + + + + + + + GROUP_ALFRESCO_ADMINISTRATORS + GROUP_ALFRESCO_ADMINISTRATORS + GROUP_ALFRESCO_ADMINISTRATORS + + + + + + + + + + + + + GROUP_EMAIL_CONTRIBUTORS + GROUP_EMAIL_CONTRIBUTORS + GROUP_EMAIL_CONTRIBUTORS + + + + + + + + + + + + + + + GROUP_EVERYONE + Read + + \ No newline at end of file diff --git a/config/alfresco/bootstrap/alfrescoAuthorityStorePermission.xml b/config/alfresco/bootstrap/alfrescoAuthorityStorePermission.xml deleted file mode 100644 index 90e4deb25c..0000000000 --- a/config/alfresco/bootstrap/alfrescoAuthorityStorePermission.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - GROUP_EVERYONE - Read - - - - - \ No newline at end of file diff --git a/config/alfresco/bootstrap/emailServer.xml b/config/alfresco/bootstrap/emailServer.xml deleted file mode 100644 index 4c3a6ed71a..0000000000 --- a/config/alfresco/bootstrap/emailServer.xml +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - user - alfrescoUserStore - - - admin - - - - GROUP_EMAIL_CONTRIBUTORS - GROUP_EMAIL_CONTRIBUTORS - GROUP_EMAIL_CONTRIBUTORS - - - - - \ No newline at end of file diff --git a/config/alfresco/emailserver/email-service-context.xml b/config/alfresco/emailserver/email-service-context.xml index 48722a1afa..d09f885219 100644 --- a/config/alfresco/emailserver/email-service-context.xml +++ b/config/alfresco/emailserver/email-service-context.xml @@ -82,6 +82,9 @@ + + + diff --git a/config/alfresco/import-export-context.xml b/config/alfresco/import-export-context.xml index e98746534b..d2bf98e1da 100644 --- a/config/alfresco/import-export-context.xml +++ b/config/alfresco/import-export-context.xml @@ -253,7 +253,6 @@ 209c6174da490caeb422f3fa5a7ae634 ${alfresco_user_store.system_container.childname} ${alfresco_user_store.user_container.childname} - ${alfresco_user_store.authorities_container.childname} @@ -311,6 +310,8 @@ ${spaces.company_home.childname} ${spaces.guest_home.childname} ${system.system_container.childname} + ${system.authorities_container.childname} + ${system.zones_container.childname} ${system.people_container.childname} ${system.workflow_container.childname} ${spaces.dictionary.childname} @@ -360,22 +361,6 @@ / alfresco/bootstrap/alfrescoUserStore.xml - - /${alfresco_user_store.system_container.childname} - alfresco/bootstrap/alfrescoAuthorityStore.xml - - - /${alfresco_user_store.system_container.childname} - alfresco/bootstrap/alfrescoAuthorityStorePermission.xml - - - /${alfresco_user_store.system_container.childname}/sys:authorities - alfresco/bootstrap/emailServer.xml - - - /${alfresco_user_store.system_container.childname}/sys:authorities - alfresco/bootstrap/adminGroup.xml - @@ -520,6 +505,10 @@ alfresco/messages/bootstrap-spaces + + /${system.system_container.childname} + alfresco/bootstrap/alfrescoAuthorityStore.xml + diff --git a/config/alfresco/messages/patch-service.properties b/config/alfresco/messages/patch-service.properties index 28de79ac2a..5ec5daa8b6 100644 --- a/config/alfresco/messages/patch-service.properties +++ b/config/alfresco/messages/patch-service.properties @@ -1,4 +1,4 @@ -# PatchService messages +# PatchService messages patch.service.preceeded_by_alternative=Preceeded by alternative patch ''{0}''. patch.service.not_relevant=Not relevant to schema {0} patch.executer.checking=Checking for patches to apply ... @@ -269,3 +269,8 @@ patch.imapFolders.result.created=The 'Imap Configs' folder was successfully crea patch.imapUserFolders.description=Creates folders tree necessary for IMAP functionality patch.imapUserFolders.result.exists=The 'IMAP Home' folder already exists patch.imapUserFolders.result.created=The 'IMAP Home' folder was successfully created + +patch.zonedAuthorities.description=Adds the remodelled cm:authority container to the spaces store + +patch.authorityMigration.description=Copies any old authorities from the user store to the spaces store. +patch.authorityMigration.result=Migrated {0} authorities to the spaces store. diff --git a/config/alfresco/model/bpmModel.xml b/config/alfresco/model/bpmModel.xml index 3a8b30ca78..28bcc37d5d 100644 --- a/config/alfresco/model/bpmModel.xml +++ b/config/alfresco/model/bpmModel.xml @@ -364,7 +364,7 @@ - usr:authorityContainer + cm:authorityContainer true false @@ -387,7 +387,7 @@ - usr:authorityContainer + cm:authorityContainer true true diff --git a/config/alfresco/model/contentModel.xml b/config/alfresco/model/contentModel.xml index 70941d9363..8b42534c00 100644 --- a/config/alfresco/model/contentModel.xml +++ b/config/alfresco/model/contentModel.xml @@ -2,8 +2,8 @@ Alfresco Content Domain Model Alfresco - 2005-09-29 - 1.0 + 2009-06-04 + 1.1 @@ -20,6 +20,7 @@ false + defaultStoreSelector @@ -148,9 +149,14 @@ cm:folder + + Alfresco Authority Abstract Type + sys:base + + Person - sys:base + cm:authority @@ -268,6 +274,59 @@ + + Alfresco Authority Type + cm:authority + + + + + d:text + + + + + + d:text + + + + + + false + true + + + cm:authority + false + true + + false + + + + + + Alfresco Authentication Zone Type + cm:cmobject + + + + + + false + true + + + cm:authority + false + true + + false + + + + diff --git a/config/alfresco/patch/patch-services-context.xml b/config/alfresco/patch/patch-services-context.xml index 0c82160df5..c28e131a9e 100644 --- a/config/alfresco/patch/patch-services-context.xml +++ b/config/alfresco/patch/patch-services-context.xml @@ -111,26 +111,6 @@ - - patch.authoritiesFolder - patch.authoritiesFolder.description - 0 - 0 - 6 - - - - - - /${alfresco_user_store.system_container.childname}/${alfresco_user_store.authorities_container.childname} - - - - /${alfresco_user_store.system_container.childname} - alfresco/bootstrap/alfrescoAuthorityStore.xml - - - patch.savedSearchesFolder patch.savedSearchesFolder.description @@ -550,24 +530,6 @@ - - patch.authoritiesFolderPermission - patch.authoritiesFolderPermission.description - 0 - 32 - 33 - - - - - - - /${alfresco_user_store.system_container.childname} - alfresco/bootstrap/alfrescoAuthorityStorePermission.xml - - - - patch.LinkNodeFileExtension patch.linkNodeExtension.description @@ -996,27 +958,6 @@ - - patch.emailContributorGroup - patch.emailContributorGroup.description - 0 - 108 - 109 - - - - - - /${alfresco_user_store.system_container.childname}/sys:authorities/usr:GROUP_EMAIL_CONTRIBUTORS - - - - /${alfresco_user_store.system_container.childname}/sys:authorities - alfresco/bootstrap/emailServer.xml - - - - patch.avmStoreAsIdentifier patch.avmStoreAsIdentifier.description @@ -1438,6 +1379,11 @@ + + + + + @@ -1549,28 +1495,7 @@ - - - patch.administratorGroup - patch.administratorGroup.description - 0 - 1001 - 1002 - - - - - - /${alfresco_user_store.system_container.childname}/sys:authorities/usr:GROUP_ALFRESCO_ADMINISTRATORS - - - - /${alfresco_user_store.system_container.childname}/sys:authorities - alfresco/bootstrap/adminGroup.xml - - - - + patch.redeploySubmitProcess5 @@ -1634,6 +1559,11 @@ ContentManager + + + + + @@ -1837,5 +1767,45 @@ classpath:alfresco/dbscripts/create/3.2/${db.script.dialect}/AlfrescoPostCreate-3.2-LockTables.sql + + + patch.zonedAuthorities + patch.zonedAuthorities.description + 0 + 2011 + 2012 + + + + + + /${system.system_container.childname}/${system.authorities_container.childname} + + + + /${system.system_container.childname} + alfresco/bootstrap/alfrescoAuthorityStore.xml + + + + + + patch.authorityMigration + patch.authorityMigration.description + 0 + 2012 + 2013 + + + + + + + + + + + + diff --git a/config/alfresco/public-services-context.xml b/config/alfresco/public-services-context.xml index a8e9ab3390..10e655d4cd 100644 --- a/config/alfresco/public-services-context.xml +++ b/config/alfresco/public-services-context.xml @@ -1473,5 +1473,33 @@ + + + + + + + + + synchronize + + + + + + + org.alfresco.repo.security.sync.UserRegistrySynchronizer + + + + + + + userRegistrySynchronizerWriteTxnAdvisor + + checkTxnAdvisor + + + diff --git a/config/alfresco/public-services-security-context.xml b/config/alfresco/public-services-security-context.xml index 58f209f1fc..48596f0498 100644 --- a/config/alfresco/public-services-security-context.xml +++ b/config/alfresco/public-services-security-context.xml @@ -382,6 +382,7 @@ org.alfresco.service.cmr.repository.NodeService.getPaths=ACL_NODE.0.sys:base.ReadProperties org.alfresco.service.cmr.repository.NodeService.getStoreArchiveNode=ACL_NODE.0.sys:base.Read org.alfresco.service.cmr.repository.NodeService.restoreNode=ACL_NODE.0.sys:base.DeleteNode,ACL_NODE.1.sys:base.CreateChildren + org.alfresco.service.cmr.repository.NodeService.getNodesWithoutParentAssocsOfType=ACL_NODE.0.sys:base.ReadProperties,AFTER_ACL_NODE.sys:base.ReadProperties diff --git a/config/alfresco/repository.properties b/config/alfresco/repository.properties index ec7e27275d..efc9ddd4c0 100644 --- a/config/alfresco/repository.properties +++ b/config/alfresco/repository.properties @@ -213,7 +213,6 @@ system.descriptor.current.childname=sys:descriptor-current alfresco_user_store.store=user://alfrescoUserStore alfresco_user_store.system_container.childname=sys:system alfresco_user_store.user_container.childname=sys:people -alfresco_user_store.authorities_container.childname=sys:authorities # note: default admin username - should not be changed alfresco_user_store.adminusername=admin @@ -254,6 +253,8 @@ version.store.onlyUseDeprecatedV1=false # Folders for storing people system.system_container.childname=sys:system system.people_container.childname=sys:people +system.authorities_container.childname=sys:authorities +system.zones_container.childname=sys:zones # Folders for storing workflow related info system.workflow_container.childname=sys:workflow @@ -327,9 +328,7 @@ V2.1-A.fixes.to.schema=0 # The default authentication chain authentication.chain=alfrescoNtlm1:alfrescoNtlm - -# The default synchronization chain. Empty by default -synchronization.chain= +authentication.syncWhenMissingPeopleLogIn=true # Default NFS user mappings nfs.user.mappings=admin diff --git a/config/alfresco/subsystems/Authentication/ldap/ldap-authentication-context.xml b/config/alfresco/subsystems/Authentication/ldap/ldap-authentication-context.xml index 4544b8b249..b86ef53aa9 100644 --- a/config/alfresco/subsystems/Authentication/ldap/ldap-authentication-context.xml +++ b/config/alfresco/subsystems/Authentication/ldap/ldap-authentication-context.xml @@ -25,6 +25,9 @@ + + ${ldap.authentication.active} + @@ -140,4 +143,192 @@ + + + + + ${ldap.synchronization.active} + + + + ${ldap.synchronization.groupQuery} + + + + + ${ldap.synchronization.groupDifferentialQuery} + + + + + ${ldap.synchronization.personQuery} + + + + + ${ldap.synchronization.personDifferentialQuery} + + + + + ${ldap.synchronization.groupSearchBase} + + + + + ${ldap.synchronization.userSearchBase} + + + + + ${ldap.synchronization.userIdAttributeName} + + + + + ${ldap.synchronization.modifyTimestampAttributeName} + + + + + ${ldap.synchronization.groupIdAttributeName} + + + + + ${ldap.synchronization.groupType} + + + + + ${ldap.synchronization.personType} + + + + + ${ldap.synchronization.groupMemberAttributeName} + + + + + + + + ${ldap.synchronization.userIdAttributeName} + + + + + ${ldap.synchronization.userFirstNameAttributeName} + + + + + ${ldap.synchronization.userLastNameAttributeName} + + + + + ${ldap.synchronization.userEmailAttributeName} + + + + + ${ldap.synchronization.userOrganizationalIdAttributeName} + + + + + + + + + + + + + ${ldap.synchronization.defaultHomeFolderProvider} + + + + + + + + + + + + + + \ No newline at end of file diff --git a/config/alfresco/subsystems/Authentication/ldap/ldap-authentication.properties b/config/alfresco/subsystems/Authentication/ldap/ldap-authentication.properties index 48d7bee9c6..7fdf0e9bd9 100644 --- a/config/alfresco/subsystems/Authentication/ldap/ldap-authentication.properties +++ b/config/alfresco/subsystems/Authentication/ldap/ldap-authentication.properties @@ -1,3 +1,8 @@ +# This flag enables use of this LDAP subsystem for authentication. It may be +# that this subsytem should only be used for synchronization, in which case +# this flag should be set to false. +ldap.authentication.active=true + # # This properties file brings together the common options for LDAP authentication rather than editing the bean definitions # @@ -5,11 +10,11 @@ ldap.authentication.allowGuestLogin=true # How to map the user id entered by the user to taht passed through to LDAP # - simple # - this must be a DN and would be something like -# CN=%s,DC=company,DC=com +# uid=%s,ou=People,dc=company,dc=com # - digest # - usually pass through what is entered # %s -ldap.authentication.userNameFormat=%s +ldap.authentication.userNameFormat=uid\=%s,ou\=People,dc\=company,dc\=com # The LDAP context factory to use ldap.authentication.java.naming.factory.initial=com.sun.jndi.ldap.LdapCtxFactory @@ -18,10 +23,10 @@ ldap.authentication.java.naming.factory.initial=com.sun.jndi.ldap.LdapCtxFactory ldap.authentication.java.naming.provider.url=ldap://openldap.domain.com:389 # The authentication mechanism to use -ldap.authentication.java.naming.security.authentication=DIGEST-MD5 +ldap.authentication.java.naming.security.authentication=simple # The default principal to use (only used for LDAP sync) -ldap.authentication.java.naming.security.principal=reader +ldap.authentication.java.naming.security.principal=cn\=Manager,dc\=company,dc\=com # The password for the default principal (only used for LDAP sync) ldap.authentication.java.naming.security.credentials=secret @@ -36,5 +41,61 @@ ldap.authentication.escapeCommasInBind=false # If this option is set to true it will break the default home folder provider as space names can not contain \ ldap.authentication.escapeCommasInUid=false -# Comma separated list of user names who should be considered users by default +# Comma separated list of user names who should be considered administrators by default ldap.authentication.defaultAdministratorUserNames= + +# This flag enables use of this LDAP subsystem for user and group +# synchronization. It may be that this subsytem should only be used for +# authentication, in which case this flag should be set to false. +ldap.synchronization.active=true + +# The query to select all objects that represent the groups to import. +ldap.synchronization.groupQuery=(objectclass\=groupOfNames) + +# The query to select objects that represent the groups to import that have changed since a certain time. +ldap.synchronization.groupDifferentialQuery=(&(objectclass\=groupOfNames)(!(modifyTimestamp<\={0}))) + +# The query to select all objects that represent the users to import. +ldap.synchronization.personQuery=(objectclass\=inetOrgPerson) + +# The query to select objects that represent the users to import that have changed since a certain time. +ldap.synchronization.personDifferentialQuery=(&(objectclass\=inetOrgPerson)(!(modifyTimestamp<\={0}))) + +# The group search base restricts the LDAP group query to a sub section of tree on the LDAP server. +ldap.synchronization.groupSearchBase=ou\=Groups,dc\=company,dc\=com + +# The user search base restricts the LDAP user query to a sub section of tree on the LDAP server. +ldap.synchronization.userSearchBase=ou\=People,dc\=company,dc\=com + +# The name of the operational attribute recording the last update time for a group or user. +ldap.synchronization.modifyTimestampAttributeName=modifyTimestamp + +# The attribute name on people objects found in LDAP to use as the uid in Alfresco +ldap.synchronization.userIdAttributeName=uid + +# The attribute on person objects in LDAP to map to the first name property in Alfresco +ldap.synchronization.userFirstNameAttributeName=givenName + +# The attribute on person objects in LDAP to map to the last name property in Alfresco +ldap.synchronization.userLastNameAttributeName=sn + +# The attribute on person objects in LDAP to map to the email property in Alfresco +ldap.synchronization.userEmailAttributeName=mail + +# The attribute on person objects in LDAP to map to the organizational id property in Alfresco +ldap.synchronization.userOrganizationalIdAttributeName=o + +# The default home folder provider to use for people created via LDAP import +ldap.synchronization.defaultHomeFolderProvider=personalHomeFolderProvider + +# The attribute on LDAP group objects to map to the gid property in Alfrecso +ldap.synchronization.groupIdAttributeName=cn + +# The group type in LDAP +ldap.synchronization.groupType=groupOfNames + +# The person type in LDAP +ldap.synchronization.personType=inetOrgPerson + +# The attribute in LDAP on group objects that defines the DN for its members +ldap.synchronization.groupMemberAttributeName=member diff --git a/config/alfresco/subsystems/Synchronization/default/default-synchronization-context.xml b/config/alfresco/subsystems/Synchronization/default/default-synchronization-context.xml new file mode 100644 index 0000000000..652502fa81 --- /dev/null +++ b/config/alfresco/subsystems/Synchronization/default/default-synchronization-context.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + org.alfresco.repo.security.sync.UserRegistrySynchronizerJob + + + + + + + + ${synchronization.synchronizeChangesOnly} + + + + + + + ${synchronization.import.cron} + + + + + + + \ No newline at end of file diff --git a/config/alfresco/subsystems/Synchronization/default/default-synchronization.properties b/config/alfresco/subsystems/Synchronization/default/default-synchronization.properties new file mode 100644 index 0000000000..b875ff5706 --- /dev/null +++ b/config/alfresco/subsystems/Synchronization/default/default-synchronization.properties @@ -0,0 +1,13 @@ +# +# This properties file is used to configure scheduled user registry syncronisation (e.g. LDAP) +# + +# Should the scheduled sync job only query users and groups changed since the +# last sync? Note that when true, the sync job will not be able to detect which +# users or groups have been removed from the directory (but obviously group +# membership changes would still be reflected). When false, a more regular +# differential sync on login can still be enabled on the person service. +synchronization.synchronizeChangesOnly=false + +# The cron expression defining when imports should take place +synchronization.import.cron=0 0 0 * * ? diff --git a/config/alfresco/subsystems/Synchronization/ldap/ldap-synchronisation-context.xml b/config/alfresco/subsystems/Synchronization/ldap/ldap-synchronisation-context.xml deleted file mode 100644 index 2640e41c0e..0000000000 --- a/config/alfresco/subsystems/Synchronization/ldap/ldap-synchronisation-context.xml +++ /dev/null @@ -1,398 +0,0 @@ - - - - - - - - - - - - - ${ldap.synchronisation.java.naming.factory.initial} - - - - - - - ${ldap.synchronisation.java.naming.provider.url} - - - - - - - - ${ldap.synchronisation.java.naming.security.authentication} - - - - - - ${ldap.synchronisation.java.naming.security.principal} - - - - - ${ldap.synchronisation.java.naming.security.credentials} - - - - - - - - - - - - - - - - ${ldap.synchronisation.personQuery} - - - - - ${ldap.synchronisation.personSearchBase} - - - - - ${ldap.synchronisation.userIdAttributeName} - - - - - - - - - - - - - - - - - - - ${ldap.synchronisation.userIdAttributeName} - - - - - ${ldap.synchronisation.userFirstNameAttributeName} - - - - - ${ldap.synchronisation.userLastNameAttributeName} - - - - - ${ldap.synchronisation.userEmailAttributeName} - - - - - ${ldap.synchronisation.userOrganizationalIdAttributeName} - - - - - - - - - - - - - ${ldap.synchronisation.defaultHomeFolderProvider} - - - - - - - - - - - ${ldap.synchronisation.groupQuery} - - - - - ${ldap.synchronisation.groupSearchBase} - - - - - ${ldap.synchronisation.userIdAttributeName} - - - - - ${ldap.synchronisation.groupIdAttributeName} - - - - - ${ldap.synchronisation.groupType} - - - - - ${ldap.synchronisation.personType} - - - - - - - - - - - ${ldap.synchronisation.groupMemberAttributeName} - - - - - - - - - - - - - - - - - - - - - org.alfresco.repo.importer.ImporterJob - - - - - - - - - - - - ${ldap.synchronisation.import.person.cron} - - - - - - - - - - - org.alfresco.repo.importer.ImporterJob - - - - - - - - - - - - ${ldap.synchronisation.import.group.cron} - - - - - - - - - - - - - - - - - - - - - - ${spaces.store} - - - - - /${system.system_container.childname}/${system.people_container.childname} - - - - - false - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ${alfresco_user_store.store} - - - - - /${alfresco_user_store.system_container.childname}/${alfresco_user_store.authorities_container.childname} - - - - - ${ldap.synchronisation.import.group.clearAllChildren} - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/config/alfresco/subsystems/Synchronization/ldap/ldap-synchronisation.properties b/config/alfresco/subsystems/Synchronization/ldap/ldap-synchronisation.properties deleted file mode 100644 index de4d485827..0000000000 --- a/config/alfresco/subsystems/Synchronization/ldap/ldap-synchronisation.properties +++ /dev/null @@ -1,74 +0,0 @@ -# -# This properties file is used to configure LDAP syncronisation -# - -# The LDAP context factory to use -ldap.synchronisation.java.naming.factory.initial=com.sun.jndi.ldap.LdapCtxFactory - -# The URL to connect to the LDAP server -ldap.synchronisation.java.naming.provider.url=ldap://openldap.domain.com:389 - -# The synchronisation mechanism to use -ldap.synchronisation.java.naming.security.authentication=DIGEST-MD5 - -# The default principal to use (only used for LDAP sync) -ldap.synchronisation.java.naming.security.principal=reader - -# The password for the default principal (only used for LDAP sync) -ldap.synchronisation.java.naming.security.credentials=secret - -# The query to find the people to import -ldap.synchronisation.personQuery=(objectclass=inetOrgPerson) - -# The search base of the query to find people to import -ldap.synchronisation.personSearchBase=dc=company,dc=com - -# The attribute name on people objects found in LDAP to use as the uid in Alfresco -ldap.synchronisation.userIdAttributeName=uid - -# The attribute on person objects in LDAP to map to the first name property in Alfresco -ldap.synchronisation.userFirstNameAttributeName=givenName - -# The attribute on person objects in LDAP to map to the last name property in Alfresco -ldap.synchronisation.userLastNameAttributeName=sn - -# The attribute on person objects in LDAP to map to the email property in Alfresco -ldap.synchronisation.userEmailAttributeName=mail - -# The attribute on person objects in LDAP to map to the organizational id property in Alfresco -ldap.synchronisation.userOrganizationalIdAttributeName=o - -# The default home folder provider to use for people created via LDAP import -ldap.synchronisation.defaultHomeFolderProvider=personalHomeFolderProvider - -# The query to find group objects -ldap.synchronisation.groupQuery=(objectclass=groupOfNames) - -# The search base to use to find group objects -ldap.synchronisation.groupSearchBase=dc=company,dc=com - -# The attribute on LDAP group objects to map to the gid property in Alfrecso -ldap.synchronisation.groupIdAttributeName=cn - -# The group type in LDAP -ldap.synchronisation.groupType=groupOfNames - -# The person type in LDAP -ldap.synchronisation.personType=inetOrgPerson - -# The attribute in LDAP on group objects that defines the DN for its members -ldap.synchronisation.groupMemberAttributeName=member - -# The cron expression defining when people imports should take place -ldap.synchronisation.import.person.cron=0 0 * * * ? - -# The cron expression defining when group imports should take place -ldap.synchronisation.import.group.cron=0 30 * * * ? - -# Should all groups be cleared out at import time? -# - this is safe as groups are not used in Alfresco for other things (unlike person objects which you should never clear out during an import) -# - setting this to true means old group definitions will be tidied up. -ldap.synchronisation.import.group.clearAllChildren=true - - - diff --git a/config/alfresco/version.properties b/config/alfresco/version.properties index e3fa0af564..046e12648c 100644 --- a/config/alfresco/version.properties +++ b/config/alfresco/version.properties @@ -19,4 +19,4 @@ version.build=@build-number@ # Schema number -version.schema=2011 +version.schema=2013 diff --git a/source/java/org/alfresco/email/server/EmailServiceImpl.java b/source/java/org/alfresco/email/server/EmailServiceImpl.java index bb89e9b324..30de32bd89 100644 --- a/source/java/org/alfresco/email/server/EmailServiceImpl.java +++ b/source/java/org/alfresco/email/server/EmailServiceImpl.java @@ -24,7 +24,6 @@ */ package org.alfresco.email.server; -import java.util.Collection; import java.util.Map; import org.alfresco.email.server.handler.EmailMessageHandler; @@ -45,6 +44,8 @@ import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; import org.alfresco.service.cmr.search.ResultSet; import org.alfresco.service.cmr.search.SearchService; +import org.alfresco.service.cmr.security.AuthorityService; +import org.alfresco.service.cmr.security.AuthorityType; import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; import org.alfresco.util.ParameterCheck; @@ -62,7 +63,6 @@ public class EmailServiceImpl implements EmailService private static final String ERR_ACCESS_DENIED = "email.server.err.access_denied"; private static final String ERR_UNKNOWN_SOURCE_ADDRESS = "email.server.err.unknown_source_address"; private static final String ERR_USER_NOT_EMAIL_CONTRIBUTOR = "email.server.err.user_not_email_contributor"; - private static final String ERR_NO_EMAIL_CONTRIBUTOR_GROUP = "email.server.err.no_email_contributor_group"; private static final String ERR_INVALID_NODE_ADDRESS = "email.server.err.invalid_node_address"; private static final String ERR_HANDLER_NOT_FOUND = "email.server.err.handler_not_found"; @@ -70,6 +70,7 @@ public class EmailServiceImpl implements EmailService private NodeService nodeService; private SearchService searchService; private RetryingTransactionHelper retryingTransactionHelper; + private AuthorityService authorityService; private boolean emailInboundEnabled; /** Login of user that is set as unknown. */ @@ -109,7 +110,15 @@ public class EmailServiceImpl implements EmailService { this.retryingTransactionHelper = retryingTransactionHelper; } - + + /** + * @param authorityService Alfresco authority service + */ + public void setAuthorityService(AuthorityService authorityService) + { + this.authorityService = authorityService; + } + /** * @return Map of message handlers */ @@ -359,28 +368,7 @@ public class EmailServiceImpl implements EmailService */ private boolean isEmailContributeUser(String userName) { - String searchQuery = "TYPE:\"{http://www.alfresco.org/model/user/1.0}authorityContainer\" +@usr\\:authorityName:\"GROUP_EMAIL_CONTRIBUTORS\""; - StoreRef storeRef = new StoreRef("user", "alfrescoUserStore"); - ResultSet resultSet = searchService.query(storeRef, SearchService.LANGUAGE_LUCENE, searchQuery); - - if (resultSet.length() == 0) - { - throw new EmailMessageException(ERR_NO_EMAIL_CONTRIBUTOR_GROUP); - } - - NodeRef groupNode = resultSet.getNodeRef(0); - - Collection memberCollection = DefaultTypeConverter.INSTANCE.getCollection( - String.class, - nodeService.getProperty(groupNode, ContentModel.PROP_MEMBERS)); - - if (memberCollection.contains(userName)) - { - return true; - } - else - { - return false; - } + return this.authorityService.getContainingAuthorities(AuthorityType.GROUP, userName, false).contains( + authorityService.getName(AuthorityType.GROUP, "EMAIL_CONTRIBUTORS")); } } diff --git a/source/java/org/alfresco/model/ContentModel.java b/source/java/org/alfresco/model/ContentModel.java index 1c2cd866d2..682b4dc102 100644 --- a/source/java/org/alfresco/model/ContentModel.java +++ b/source/java/org/alfresco/model/ContentModel.java @@ -201,6 +201,19 @@ public interface ContentModel static final QName ASSOC_AVATAR = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "avatar"); + // Authority + static final QName TYPE_AUTHORITY = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "authority"); + + static final QName TYPE_AUTHORITY_CONTAINER = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "authorityContainer"); + static final QName PROP_AUTHORITY_NAME = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "authorityName"); + static final QName PROP_AUTHORITY_DISPLAY_NAME = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "authorityDisplayName"); + + static final QName ASSOC_MEMBER = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "member"); + + // Zone + static final QName TYPE_ZONE = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "zone"); + static final QName ASSOC_IN_ZONE = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "inZone"); + // Ownable aspect static final QName ASPECT_OWNABLE = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "ownable"); static final QName PROP_OWNER = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "owner"); @@ -292,15 +305,5 @@ public interface ContentModel static final QName PROP_CREDENTIALS_EXPIRE = QName.createQName(USER_MODEL_URI, "credentialsExpire"); static final QName PROP_CREDENTIALS_EXPIRY_DATE = QName.createQName(USER_MODEL_URI, "credentialsExpiryDate"); static final QName PROP_ACCOUNT_LOCKED = QName.createQName(USER_MODEL_URI, "accountLocked"); - static final QName PROP_SALT = QName.createQName(USER_MODEL_URI, "salt"); - - static final QName TYPE_AUTHORITY = QName.createQName(USER_MODEL_URI, "authority"); - - static final QName TYPE_AUTHORITY_CONTAINER = QName.createQName(USER_MODEL_URI, "authorityContainer"); - static final QName PROP_AUTHORITY_NAME = QName.createQName(USER_MODEL_URI, "authorityName"); - static final QName PROP_AUTHORITY_DISPLAY_NAME = QName.createQName(USER_MODEL_URI, "authorityDisplayName"); - - static final QName ASSOC_MEMBER = QName.createQName(USER_MODEL_URI, "member"); - static final QName PROP_MEMBERS = QName.createQName(USER_MODEL_URI, "members"); - + static final QName PROP_SALT = QName.createQName(USER_MODEL_URI, "salt"); } diff --git a/source/java/org/alfresco/repo/admin/patch/impl/AuthorityMigrationPatch.java b/source/java/org/alfresco/repo/admin/patch/impl/AuthorityMigrationPatch.java new file mode 100644 index 0000000000..84522c4c32 --- /dev/null +++ b/source/java/org/alfresco/repo/admin/patch/impl/AuthorityMigrationPatch.java @@ -0,0 +1,198 @@ +/* + * Copyright (C) 2005-2009 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program 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 General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have received a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.repo.admin.patch.impl; + +import java.util.Collection; +import java.util.List; + +import org.alfresco.i18n.I18NUtil; +import org.alfresco.model.ContentModel; +import org.alfresco.repo.admin.patch.AbstractPatch; +import org.alfresco.repo.importer.ImporterBootstrap; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.NodeRef; +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.namespace.QName; +import org.alfresco.service.namespace.RegexQNamePattern; + +/** + * Migrates authority information previously stored in the user store to the spaces store, using the new structure used + * by AuthorityService. + * + * @author dward + */ +public class AuthorityMigrationPatch extends AbstractPatch +{ + + /** Success message. */ + private static final String MSG_SUCCESS = "patch.authorityMigration.result"; + + /** The old authority name property */ + private static final QName PROP_AUTHORITY_NAME = QName.createQName(ContentModel.USER_MODEL_URI, "authorityName"); + + /** The old authority display name property */ + private static final QName PROP_AUTHORITY_DISPLAY_NAME = QName.createQName(ContentModel.USER_MODEL_URI, + "authorityDisplayName"); + + /** The old authority members property */ + private static final QName PROP_MEMBERS = QName.createQName(ContentModel.USER_MODEL_URI, "members"); + + /** The authority service. */ + private AuthorityService authorityService; + + /** The user bootstrap. */ + private ImporterBootstrap userBootstrap; + + /** + * Sets the authority service. + * + * @param authorityService + * the authority service + */ + public void setAuthorityService(AuthorityService authorityService) + { + this.authorityService = authorityService; + } + + /** + * Sets the user bootstrap. + * + * @param userBootstrap + * the user bootstrap + */ + public void setUserBootstrap(ImporterBootstrap userBootstrap) + { + this.userBootstrap = userBootstrap; + } + + /** + * Recursively migrates the authorities under the given node + * + * @param parentAuthority + * the full name of the parent authority corresponding to the given node, or null if it is + * not an authority node. + * @param nodeRef + * the node to find authorities below + * @return the number of processed authorities + */ + private int migrateAuthorities(String parentAuthority, NodeRef nodeRef) + { + int processedCount = 0; + List cars = this.nodeService.getChildAssocs(nodeRef); + for (ChildAssociationRef car : cars) + { + NodeRef current = car.getChildRef(); + String authorityName = DefaultTypeConverter.INSTANCE.convert(String.class, this.nodeService.getProperty( + current, AuthorityMigrationPatch.PROP_AUTHORITY_NAME)); + boolean existed = this.authorityService.authorityExists(authorityName); + if (!existed) + { + String authorityDisplayName = DefaultTypeConverter.INSTANCE.convert(String.class, this.nodeService + .getProperty(current, AuthorityMigrationPatch.PROP_AUTHORITY_DISPLAY_NAME)); + this.authorityService.createAuthority(AuthorityType.getAuthorityType(authorityName), + this.authorityService.getShortName(authorityName), authorityDisplayName, null); + processedCount++; + } + if (parentAuthority != null + && (!existed || !this.authorityService.getContainingAuthorities(AuthorityType.GROUP, authorityName, + true).contains(parentAuthority))) + { + this.authorityService.addAuthority(parentAuthority, authorityName); + } + + // loop over properties + Collection members = DefaultTypeConverter.INSTANCE.getCollection(String.class, this.nodeService + .getProperty(current, AuthorityMigrationPatch.PROP_MEMBERS)); + if (members != null) + { + for (String user : members) + { + // Believe it or not, some old authorities have null members in them! + if (user != null + && (!existed || !this.authorityService.getContainingAuthorities(AuthorityType.GROUP, user, + true).contains(authorityName))) + { + this.authorityService.addAuthority(authorityName, user); + } + } + } + processedCount += migrateAuthorities(authorityName, current); + } + return processedCount; + } + + /** + * Gets the old authority container. + * + * @return Returns the old authority container or null if not found + */ + private NodeRef getAuthorityContainer() + { + NodeRef rootNodeRef = this.nodeService.getRootNode(this.userBootstrap.getStoreRef()); + QName qnameAssocSystem = QName.createQName("sys", "system", this.namespaceService); + List results = this.nodeService.getChildAssocs(rootNodeRef, RegexQNamePattern.MATCH_ALL, + qnameAssocSystem); + NodeRef sysNodeRef = null; + if (results.size() == 0) + { + return null; + } + else + { + sysNodeRef = results.get(0).getChildRef(); + } + QName qnameAssocAuthorities = QName.createQName("sys", "authorities", this.namespaceService); + results = this.nodeService.getChildAssocs(sysNodeRef, RegexQNamePattern.MATCH_ALL, qnameAssocAuthorities); + NodeRef authNodeRef = null; + if (results.size() == 0) + { + return null; + } + else + { + authNodeRef = results.get(0).getChildRef(); + } + return authNodeRef; + } + + /* + * (non-Javadoc) + * @see org.alfresco.repo.admin.patch.AbstractPatch#applyInternal() + */ + @Override + protected String applyInternal() throws Exception + { + int processedCount = 0; + NodeRef authorityContainer = getAuthorityContainer(); + if (authorityContainer != null) + { + processedCount = migrateAuthorities(null, authorityContainer); + } + // build the result message + return I18NUtil.getMessage(AuthorityMigrationPatch.MSG_SUCCESS, processedCount); + } +} \ No newline at end of file diff --git a/source/java/org/alfresco/repo/admin/patch/impl/MoveWCMToGroupBasedPermissionsPatch.java b/source/java/org/alfresco/repo/admin/patch/impl/MoveWCMToGroupBasedPermissionsPatch.java index df9066c77b..8312c96b94 100644 --- a/source/java/org/alfresco/repo/admin/patch/impl/MoveWCMToGroupBasedPermissionsPatch.java +++ b/source/java/org/alfresco/repo/admin/patch/impl/MoveWCMToGroupBasedPermissionsPatch.java @@ -203,7 +203,7 @@ public class MoveWCMToGroupBasedPermissionsPatch extends AbstractPatch String group = this.authorityService.getName(AuthorityType.GROUP, shortName); if (!this.authorityService.authorityExists(group)) { - String newGroup = this.authorityService.createAuthority(AuthorityType.GROUP, null, shortName); + String newGroup = this.authorityService.createAuthority(AuthorityType.GROUP, shortName); this.permissionService.setPermission(dirRef, newGroup, permission, true); } } diff --git a/source/java/org/alfresco/repo/admin/patch/impl/SitePermissionRefactorPatch.java b/source/java/org/alfresco/repo/admin/patch/impl/SitePermissionRefactorPatch.java index 63a233e895..74fa1124d8 100644 --- a/source/java/org/alfresco/repo/admin/patch/impl/SitePermissionRefactorPatch.java +++ b/source/java/org/alfresco/repo/admin/patch/impl/SitePermissionRefactorPatch.java @@ -100,7 +100,6 @@ public class SitePermissionRefactorPatch extends AbstractPatch // Create the site's groups String siteGroup = authorityService.createAuthority( AuthorityType.GROUP, - null, ((SiteServiceImpl)this.siteService).getSiteGroup(siteInfo.getShortName(), false)); Set permissions = permissionService.getSettablePermissions(SiteModel.TYPE_SITE); @@ -109,11 +108,11 @@ public class SitePermissionRefactorPatch extends AbstractPatch // Create a group for the permission String permissionGroup = authorityService.createAuthority( AuthorityType.GROUP, - siteGroup, ((SiteServiceImpl)this.siteService).getSiteRoleGroup( siteInfo.getShortName(), permission, false)); + authorityService.addAuthority(siteGroup, permissionGroup); // Assign the group the relevant permission on the site permissionService.setPermission(siteInfo.getNodeRef(), permissionGroup, permission, true); diff --git a/source/java/org/alfresco/repo/admin/patch/impl/TopLevelGroupParentChildAssociationTypePatch.java b/source/java/org/alfresco/repo/admin/patch/impl/TopLevelGroupParentChildAssociationTypePatch.java index 5496fa094d..8a8794a821 100644 --- a/source/java/org/alfresco/repo/admin/patch/impl/TopLevelGroupParentChildAssociationTypePatch.java +++ b/source/java/org/alfresco/repo/admin/patch/impl/TopLevelGroupParentChildAssociationTypePatch.java @@ -30,9 +30,9 @@ import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.i18n.I18NUtil; import org.alfresco.model.ContentModel; import org.alfresco.repo.admin.patch.AbstractPatch; -import org.alfresco.repo.security.authority.AuthorityDAOImpl; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.service.namespace.QName; import org.alfresco.service.namespace.RegexQNamePattern; @@ -73,7 +73,7 @@ public class TopLevelGroupParentChildAssociationTypePatch extends AbstractPatch QName qnameAssocAuthorities = QName.createQName("sys", "authorities", this.namespaceService); - NodeRef rootNodeRef = nodeService.getRootNode(AuthorityDAOImpl.STOREREF_USERS); + NodeRef rootNodeRef = nodeService.getRootNode(new StoreRef("user", "alfrescoUserStore")); List results = nodeService.getChildAssocs(rootNodeRef, RegexQNamePattern.MATCH_ALL, qnameAssocSystem); NodeRef sysNodeRef = null; diff --git a/source/java/org/alfresco/repo/avm/AVMNodeService.java b/source/java/org/alfresco/repo/avm/AVMNodeService.java index 1c9a5fa3ec..a4c2ddeef0 100644 --- a/source/java/org/alfresco/repo/avm/AVMNodeService.java +++ b/source/java/org/alfresco/repo/avm/AVMNodeService.java @@ -1829,4 +1829,13 @@ public class AVMNodeService extends AbstractNodeServiceImpl implements NodeServi { throw new UnsupportedOperationException("AVM does not support this operation."); } + + /* (non-Javadoc) + * @see org.alfresco.service.cmr.repository.NodeService#getNodesWithoutParentAssocsOfType(org.alfresco.service.cmr.repository.StoreRef, org.alfresco.service.namespace.QName, org.alfresco.service.namespace.QName) + */ + public Collection getNodesWithoutParentAssocsOfType(StoreRef storeRef, QName nodeTypeQName, + QName assocTypeQName) + { + throw new UnsupportedOperationException("AVM does not support this operation."); + } } diff --git a/source/java/org/alfresco/repo/avm/locking/AVMLockingServiceTest.java b/source/java/org/alfresco/repo/avm/locking/AVMLockingServiceTest.java index c233025fb9..c523cd0439 100644 --- a/source/java/org/alfresco/repo/avm/locking/AVMLockingServiceTest.java +++ b/source/java/org/alfresco/repo/avm/locking/AVMLockingServiceTest.java @@ -106,9 +106,9 @@ public class AVMLockingServiceTest extends TestCase // Set up sample users groups and roles. fAuthenticationService.createAuthentication("Buffy", "Buffy".toCharArray()); fPersonService.getPerson("Buffy"); - fAuthorityService.createAuthority(AuthorityType.GROUP, null, "Scoobies"); + fAuthorityService.createAuthority(AuthorityType.GROUP, "Scoobies"); fAuthorityService.addAuthority("GROUP_Scoobies", "Buffy"); - fAuthorityService.createAuthority(AuthorityType.ROLE, null, "SUPER_POWERED"); + fAuthorityService.createAuthority(AuthorityType.ROLE, "SUPER_POWERED"); fAuthorityService.addAuthority("ROLE_SUPER_POWERED", "Buffy"); fAuthenticationService.createAuthentication("Willow", "Willow".toCharArray()); fPersonService.getPerson("Willow"); @@ -121,7 +121,7 @@ public class AVMLockingServiceTest extends TestCase fAuthenticationService.createAuthentication("Spike", "Spike".toCharArray()); fPersonService.getPerson("Spike"); fAuthorityService.addAuthority("ROLE_SUPER_POWERED", "Spike"); - fAuthorityService.createAuthority(AuthorityType.GROUP, null, "vampires"); + fAuthorityService.createAuthority(AuthorityType.GROUP, "vampires"); fAuthorityService.addAuthority("GROUP_vampires", "Spike"); } diff --git a/source/java/org/alfresco/repo/domain/hibernate/Node.hbm.xml b/source/java/org/alfresco/repo/domain/hibernate/Node.hbm.xml index 5a1e3b9645..359a72edf8 100644 --- a/source/java/org/alfresco/repo/domain/hibernate/Node.hbm.xml +++ b/source/java/org/alfresco/repo/domain/hibernate/Node.hbm.xml @@ -768,5 +768,26 @@ node.id asc ]]> + + + + + + + SELECT + n.id, + s.protocol, + s.identifier, + n.uuid + FROM + alf_node n + JOIN alf_store s ON (s.id = n.store_id) + LEFT OUTER JOIN alf_child_assoc a ON (a.child_node_id = n.id AND a.type_qname_id = :assocTypeQNameID) + WHERE + s.protocol = :storeProtocol AND + s.identifier = :storeIdentifier AND + n.type_qname_id = :nodeTypeQNameID AND + a.child_node_id IS NULL + diff --git a/source/java/org/alfresco/repo/importer/FileSourceImporter.java b/source/java/org/alfresco/repo/importer/FileSourceImporter.java index d1194f5299..81a61210ba 100644 --- a/source/java/org/alfresco/repo/importer/FileSourceImporter.java +++ b/source/java/org/alfresco/repo/importer/FileSourceImporter.java @@ -34,7 +34,6 @@ import javax.transaction.UserTransaction; import org.alfresco.repo.cache.SimpleCache; import org.alfresco.repo.security.authentication.AuthenticationContext; -import org.alfresco.repo.security.authentication.ldap.LDAPGroupExportSource; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; diff --git a/source/java/org/alfresco/repo/jscript/People.java b/source/java/org/alfresco/repo/jscript/People.java index 04b414444f..a04d09d761 100644 --- a/source/java/org/alfresco/repo/jscript/People.java +++ b/source/java/org/alfresco/repo/jscript/People.java @@ -585,12 +585,15 @@ public final class People extends BaseScopableProcessorExtension String actualName = services.getAuthorityService().getName(AuthorityType.GROUP, groupName); if (authorityService.authorityExists(actualName) == false) { - String parentGroupName = null; + String result = authorityService.createAuthority(AuthorityType.GROUP, groupName); if (parentGroup != null) { - parentGroupName = (String)parentGroup.getProperties().get(ContentModel.PROP_AUTHORITY_NAME); + String parentGroupName = (String)parentGroup.getProperties().get(ContentModel.PROP_AUTHORITY_NAME); + if (parentGroupName != null) + { + authorityService.addAuthority(parentGroupName, actualName); + } } - String result = authorityService.createAuthority(AuthorityType.GROUP, parentGroupName, groupName); group = getGroup(result); } diff --git a/source/java/org/alfresco/repo/management/subsystems/ChildApplicationContextFactory.java b/source/java/org/alfresco/repo/management/subsystems/ChildApplicationContextFactory.java index d3086da59c..5257fa6a26 100644 --- a/source/java/org/alfresco/repo/management/subsystems/ChildApplicationContextFactory.java +++ b/source/java/org/alfresco/repo/management/subsystems/ChildApplicationContextFactory.java @@ -463,7 +463,15 @@ public class ChildApplicationContextFactory extends AbstractPropertyBackedBean i if (this.applicationContext != null) { ChildApplicationContextFactory.logger.info("Stopping '" + getCategory() + "' subsystem, ID: " + getId()); - this.applicationContext.close(); + try + { + this.applicationContext.close(); + } + catch (Exception e) + { + ChildApplicationContextFactory.logger.error(e); + // Continue anyway. Perhaps it didn't start properly + } this.applicationContext = null; ChildApplicationContextFactory.logger.info("Stopped '" + getCategory() + "' subsystem, ID: " + getId()); } diff --git a/source/java/org/alfresco/repo/node/db/DbNodeServiceImpl.java b/source/java/org/alfresco/repo/node/db/DbNodeServiceImpl.java index 7207329966..6bd2d931c5 100644 --- a/source/java/org/alfresco/repo/node/db/DbNodeServiceImpl.java +++ b/source/java/org/alfresco/repo/node/db/DbNodeServiceImpl.java @@ -32,6 +32,7 @@ import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; +import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; @@ -1632,6 +1633,25 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl return assocRef; } + public Collection getNodesWithoutParentAssocsOfType(final StoreRef storeRef, final QName nodeTypeQName, + final QName assocTypeQName) + { + final Collection results = new LinkedList(); + + NodeDaoService.NodeRefQueryCallback callback = new NodeDaoService.NodeRefQueryCallback() + { + public boolean handle(Pair nodePair) + { + results.add(nodePair.getSecond()); + return true; + } + }; + + nodeDaoService.getNodesWithoutParentAssocsOfType(storeRef, nodeTypeQName, assocTypeQName, callback); + + return results; + } + public void removeAssociation(NodeRef sourceRef, NodeRef targetRef, QName assocTypeQName) throws InvalidNodeRefException { diff --git a/source/java/org/alfresco/repo/node/db/NodeDaoService.java b/source/java/org/alfresco/repo/node/db/NodeDaoService.java index 4d7af7a2d1..d895686161 100644 --- a/source/java/org/alfresco/repo/node/db/NodeDaoService.java +++ b/source/java/org/alfresco/repo/node/db/NodeDaoService.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2007 Alfresco Software Limited. + * Copyright (C) 2005-2009 Alfresco Software Limited. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -18,7 +18,7 @@ * As a special exception to the terms and conditions of version 2.0 of * the GPL, you may redistribute this Program in connection with Free/Libre * and Open Source Software ("FLOSS") applications as described in Alfresco's - * FLOSS exception. You should have recieved a copy of the text describing + * FLOSS exception. You should have received a copy of the text describing * the FLOSS exception, and it is also available here: * http://www.alfresco.com/legal/licensing" */ @@ -350,6 +350,22 @@ public interface NodeDaoService @DirtySessionAnnotation(markDirty=false) public void getNodesWithAspect(QName aspectQName, Long minNodeId, int count, NodeRefQueryCallback resultsCallback); + /** + * Gets the set of nodes of a certain type without parent associations of a certain type. In effect the 'orphans' + * with respect to a certain association type. + * + * @param storeRef + * the store reference + * @param nodeTypeQName + * the node type QName + * @param assocTypeQName + * the association type QName + * @param resultsCallback + * the node callback + */ + @DirtySessionAnnotation(markDirty=false) + public void getNodesWithoutParentAssocsOfType(final StoreRef storeRef, final QName nodeTypeQName, final QName assocTypeQName, + NodeRefQueryCallback resultsCallback); /** * @return Returns an association matching the given parent, type and child name (cm:name) - or null if not found */ diff --git a/source/java/org/alfresco/repo/node/db/hibernate/HibernateNodeDaoServiceImpl.java b/source/java/org/alfresco/repo/node/db/hibernate/HibernateNodeDaoServiceImpl.java index ce0c82a02a..5b38abf7b6 100644 --- a/source/java/org/alfresco/repo/node/db/hibernate/HibernateNodeDaoServiceImpl.java +++ b/source/java/org/alfresco/repo/node/db/hibernate/HibernateNodeDaoServiceImpl.java @@ -148,6 +148,7 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements private static final String QUERY_GET_PRIMARY_CHILD_ASSOCS_NOT_IN_SAME_STORE = "node.GetPrimaryChildAssocsNotInSameStore"; private static final String QUERY_GET_NODES_WITH_CHILDREN_IN_DIFFERENT_STORE ="node.GetNodesWithChildrenInDifferentStore"; private static final String QUERY_GET_NODES_WITH_ASPECT ="node.GetNodesWithAspect"; + private static final String QUERY_GET_NODES_WITHOUT_PARENT_ASSOCS_OF_TYPE ="node.GetNodesWithoutParentAssocsOfType"; private static final String QUERY_GET_PARENT_ASSOCS = "node.GetParentAssocs"; private static final String QUERY_GET_NODE_ASSOC = "node.GetNodeAssoc"; private static final String QUERY_GET_NODE_ASSOCS_TO_AND_FROM = "node.GetNodeAssocsToAndFrom"; @@ -2774,7 +2775,40 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements // Done } - + + public void getNodesWithoutParentAssocsOfType(final StoreRef storeRef, final QName nodeTypeQName, + final QName assocTypeQName, NodeRefQueryCallback resultsCallback) + { + HibernateCallback callback = new HibernateCallback() + { + public Object doInHibernate(Session session) + { + Query query = session.getNamedQuery( + HibernateNodeDaoServiceImpl.QUERY_GET_NODES_WITHOUT_PARENT_ASSOCS_OF_TYPE).setString( + "storeProtocol", storeRef.getProtocol()).setString("storeIdentifier", storeRef.getIdentifier()) + .setLong("nodeTypeQNameID", qnameDAO.getOrCreateQName(nodeTypeQName).getFirst()).setLong( + "assocTypeQNameID", qnameDAO.getOrCreateQName(assocTypeQName).getFirst()); + DirtySessionMethodInterceptor.setQueryFlushMode(session, query); + return query.scroll(ScrollMode.FORWARD_ONLY); + } + }; + ScrollableResults queryResults = null; + try + { + queryResults = (ScrollableResults) getHibernateTemplate().execute(callback); + processNodeResults(queryResults, resultsCallback); + } + finally + { + if (queryResults != null) + { + queryResults.close(); + } + } + + // Done + } + /** *
             Node ID = (Long) row[0];
diff --git a/source/java/org/alfresco/repo/search/impl/lucene/ADMLuceneIndexerImpl.java b/source/java/org/alfresco/repo/search/impl/lucene/ADMLuceneIndexerImpl.java
index 470322f919..d4e260f18b 100644
--- a/source/java/org/alfresco/repo/search/impl/lucene/ADMLuceneIndexerImpl.java
+++ b/source/java/org/alfresco/repo/search/impl/lucene/ADMLuceneIndexerImpl.java
@@ -1105,8 +1105,7 @@ public class ADMLuceneIndexerImpl extends AbstractLuceneIndexerImpl imp
                     {
                         // Temporary special case for uids and gids
                         if (propertyName.equals(ContentModel.PROP_USER_USERNAME)
-                                || propertyName.equals(ContentModel.PROP_USERNAME) || propertyName.equals(ContentModel.PROP_AUTHORITY_NAME)
-                                || propertyName.equals(ContentModel.PROP_MEMBERS))
+                                || propertyName.equals(ContentModel.PROP_USERNAME) || propertyName.equals(ContentModel.PROP_AUTHORITY_NAME))
                         {
                             doc.add(new Field(attributeName, strValue, fieldStore, fieldIndex, Field.TermVector.NO));
                         }
diff --git a/source/java/org/alfresco/repo/search/impl/lucene/AVMLuceneIndexerImpl.java b/source/java/org/alfresco/repo/search/impl/lucene/AVMLuceneIndexerImpl.java
index 9836545b62..256de0275a 100644
--- a/source/java/org/alfresco/repo/search/impl/lucene/AVMLuceneIndexerImpl.java
+++ b/source/java/org/alfresco/repo/search/impl/lucene/AVMLuceneIndexerImpl.java
@@ -1001,8 +1001,7 @@ public class AVMLuceneIndexerImpl extends AbstractLuceneIndexerImpl impl
                     {
                         // Temporary special case for uids and gids
                         if (propertyName.equals(ContentModel.PROP_USER_USERNAME)
-                                || propertyName.equals(ContentModel.PROP_USERNAME) || propertyName.equals(ContentModel.PROP_AUTHORITY_NAME)
-                                || propertyName.equals(ContentModel.PROP_MEMBERS))
+                                || propertyName.equals(ContentModel.PROP_USERNAME) || propertyName.equals(ContentModel.PROP_AUTHORITY_NAME))
                         {
                             doc.add(new Field(attributeName, strValue, fieldStore, fieldIndex, Field.TermVector.NO));
                         }
diff --git a/source/java/org/alfresco/repo/search/impl/lucene/LuceneAnalyser.java b/source/java/org/alfresco/repo/search/impl/lucene/LuceneAnalyser.java
index 3e26f99a4e..63de25c943 100644
--- a/source/java/org/alfresco/repo/search/impl/lucene/LuceneAnalyser.java
+++ b/source/java/org/alfresco/repo/search/impl/lucene/LuceneAnalyser.java
@@ -174,8 +174,7 @@ public class LuceneAnalyser extends Analyzer
                 // Temporary fix for person and user uids
 
                 if (propertyQName.equals(ContentModel.PROP_USER_USERNAME)
-                        || propertyQName.equals(ContentModel.PROP_USERNAME) || propertyQName.equals(ContentModel.PROP_AUTHORITY_NAME)
-                        || propertyQName.equals(ContentModel.PROP_MEMBERS))
+                        || propertyQName.equals(ContentModel.PROP_USERNAME) || propertyQName.equals(ContentModel.PROP_AUTHORITY_NAME))
                 {
                     analyser = new VerbatimAnalyser(true);
                 }
diff --git a/source/java/org/alfresco/repo/search/impl/lucene/LuceneQueryParser.java b/source/java/org/alfresco/repo/search/impl/lucene/LuceneQueryParser.java
index 9c5dfa0525..90717991a9 100644
--- a/source/java/org/alfresco/repo/search/impl/lucene/LuceneQueryParser.java
+++ b/source/java/org/alfresco/repo/search/impl/lucene/LuceneQueryParser.java
@@ -3082,8 +3082,7 @@ public class LuceneQueryParser extends QueryParser
         else if ((propertyDef != null) && (propertyDef.getDataType().getName().equals(DataTypeDefinition.TEXT)))
         {
             if (propertyQName.equals(ContentModel.PROP_USER_USERNAME)
-                    || propertyQName.equals(ContentModel.PROP_USERNAME) || propertyQName.equals(ContentModel.PROP_AUTHORITY_NAME)
-                    || propertyQName.equals(ContentModel.PROP_MEMBERS))
+                    || propertyQName.equals(ContentModel.PROP_USERNAME) || propertyQName.equals(ContentModel.PROP_AUTHORITY_NAME))
             {
                 return subQueryBuilder.getQuery(expandedFieldName, queryText, analysisMode, luceneFunction);
             }
@@ -3276,8 +3275,7 @@ public class LuceneQueryParser extends QueryParser
         else if ((propertyDef != null) && (propertyDef.getDataType().getName().equals(DataTypeDefinition.TEXT)))
         {
             if (propertyQName.equals(ContentModel.PROP_USER_USERNAME)
-                    || propertyQName.equals(ContentModel.PROP_USERNAME) || propertyQName.equals(ContentModel.PROP_AUTHORITY_NAME)
-                    || propertyQName.equals(ContentModel.PROP_MEMBERS))
+                    || propertyQName.equals(ContentModel.PROP_USERNAME) || propertyQName.equals(ContentModel.PROP_AUTHORITY_NAME))
             {
                 throw new UnsupportedOperationException("Functions are not supported agaisnt special text fields");
             }
diff --git a/source/java/org/alfresco/repo/security/authentication/AbstractAuthenticationComponent.java b/source/java/org/alfresco/repo/security/authentication/AbstractAuthenticationComponent.java
index f4609760fb..c880f59ad7 100644
--- a/source/java/org/alfresco/repo/security/authentication/AbstractAuthenticationComponent.java
+++ b/source/java/org/alfresco/repo/security/authentication/AbstractAuthenticationComponent.java
@@ -35,9 +35,9 @@ import net.sf.acegisecurity.GrantedAuthorityImpl;
 import net.sf.acegisecurity.UserDetails;
 import net.sf.acegisecurity.providers.dao.User;
 
-import org.alfresco.error.AlfrescoRuntimeException;
 import org.alfresco.model.ContentModel;
 import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork;
+import org.alfresco.repo.security.sync.UserRegistrySynchronizer;
 import org.alfresco.repo.tenant.TenantService;
 import org.alfresco.repo.transaction.AlfrescoTransactionSupport;
 import org.alfresco.repo.transaction.RetryingTransactionHelper;
@@ -63,6 +63,8 @@ public abstract class AbstractAuthenticationComponent implements AuthenticationC
 
     private Set defaultAdministratorUserNames = Collections.emptySet();
 
+    private boolean syncWhenMissingPeopleLogIn = true;
+
     private boolean autoCreatePeopleOnLogin = true;
     
     private AuthenticationContext authenticationContext;
@@ -72,6 +74,8 @@ public abstract class AbstractAuthenticationComponent implements AuthenticationC
     private NodeService nodeService;
 
     private TransactionService transactionService;
+    
+    private UserRegistrySynchronizer userRegistrySynchronizer;
 
     public AbstractAuthenticationComponent()
     {
@@ -107,6 +111,11 @@ public abstract class AbstractAuthenticationComponent implements AuthenticationC
     {
         this.transactionService = transactionService;
     }
+    
+    public void setUserRegistrySynchronizer(UserRegistrySynchronizer userRegistrySynchronizer)
+    {
+        this.userRegistrySynchronizer = userRegistrySynchronizer;
+    }
 
     public TransactionService getTransactionService()
     {
@@ -138,6 +147,11 @@ public abstract class AbstractAuthenticationComponent implements AuthenticationC
         this.autoCreatePeopleOnLogin = autoCreatePeopleOnLogin;
     }
         
+    public void setSyncWhenMissingPeopleLogIn(boolean syncWhenMissingPeopleLogIn)
+    {
+        this.syncWhenMissingPeopleLogIn = syncWhenMissingPeopleLogIn;
+    }
+
     public void authenticate(String userName, char[] password) throws AuthenticationException
     {
         // Support guest login from the login screen
@@ -434,7 +448,30 @@ public abstract class AbstractAuthenticationComponent implements AuthenticationC
                 {
                     public String doWork() throws Exception
                     {
-                        if (personService.personExists(userName))
+                        boolean personExists = personService.personExists(userName);
+                        
+                        // If the person is missing, synchronize or auto-create the missing person if we are allowed
+                        if (!personExists)
+                        {
+                            if ((userName != null) && !userName.equals(AuthenticationUtil.getSystemUserName()))
+                            {
+                                if (syncWhenMissingPeopleLogIn)
+                                {
+                                    userRegistrySynchronizer.synchronize(false);
+                                    personExists = personService.personExists(userName);
+                                }
+                                if (!personExists && autoCreatePeopleOnLogin && personService.createMissingPeople())
+                                {
+                                    AuthorityType authorityType = AuthorityType.getAuthorityType(userName);
+                                    if (authorityType == AuthorityType.USER)
+                                    {
+                                        personService.getPerson(userName);
+                                    }
+                                }
+                            }
+                        }
+
+                        if (personExists)
                         {
                             NodeRef userNode = personService.getPerson(userName);
                             if (userNode != null)
@@ -443,28 +480,8 @@ public abstract class AbstractAuthenticationComponent implements AuthenticationC
                                 // checks
                                 return (String) nodeService.getProperty(userNode, ContentModel.PROP_USERNAME);
                             }
-                            else
-                            {
-                                // Get user name
-                                return userName;
-                            }
-                        }
-                        else
-                        {
-                            if (autoCreatePeopleOnLogin && (userName != null) && !userName.equals(AuthenticationUtil.getSystemUserName()))
-                            {
-                                if (personService.createMissingPeople())
-                                {
-                                    AuthorityType authorityType = AuthorityType.getAuthorityType(userName);
-                                    if (authorityType == AuthorityType.USER)
-                                    {
-                                        personService.getPerson(userName);
-                                    }
-                                }
-                            }
-                            // Get user name
-                            return userName;
                         }
+                        return userName;
                     }
                 }, getSystemUserName(getUserDomain(userName)));
 
diff --git a/source/java/org/alfresco/repo/security/authentication/AuthenticationServiceImpl.java b/source/java/org/alfresco/repo/security/authentication/AuthenticationServiceImpl.java
index 0d22e96aaf..76fc077ffd 100644
--- a/source/java/org/alfresco/repo/security/authentication/AuthenticationServiceImpl.java
+++ b/source/java/org/alfresco/repo/security/authentication/AuthenticationServiceImpl.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2005-2007 Alfresco Software Limited.
+ * Copyright (C) 2005-2009 Alfresco Software Limited.
  *
  * This program is free software; you can redistribute it and/or
  * modify it under the terms of the GNU General Public License
@@ -18,7 +18,7 @@
  * As a special exception to the terms and conditions of version 2.0 of 
  * the GPL, you may redistribute this Program in connection with Free/Libre 
  * and Open Source Software ("FLOSS") applications as described in Alfresco's 
- * FLOSS exception.  You should have recieved a copy of the text describing 
+ * FLOSS exception.  You should have received a copy of the text describing 
  * the FLOSS exception, and it is also available here: 
  * http://www.alfresco.com/legal/licensing"
  */
@@ -27,10 +27,11 @@ package org.alfresco.repo.security.authentication;
 import java.util.Collections;
 import java.util.Set;
 
+import org.alfresco.repo.management.subsystems.ActivateableBean;
 import org.alfresco.repo.security.authentication.AuthenticationComponent.UserNameValidationMode;
 import org.alfresco.service.cmr.security.PermissionService;
 
-public class AuthenticationServiceImpl extends AbstractAuthenticationService
+public class AuthenticationServiceImpl extends AbstractAuthenticationService implements ActivateableBean
 {
     MutableAuthenticationDao authenticationDao;
 
@@ -65,6 +66,16 @@ public class AuthenticationServiceImpl extends AbstractAuthenticationService
     {
         this.authenticationComponent = authenticationComponent;
     }
+    
+    /*
+     * (non-Javadoc)
+     * @see org.alfresco.repo.management.subsystems.ActivateableBean#isActive()
+     */
+    public boolean isActive()
+    {
+        return !(this.authenticationComponent instanceof ActivateableBean)
+                || ((ActivateableBean) this.authenticationComponent).isActive();
+    }
 
     public void createAuthentication(String userName, char[] password) throws AuthenticationException
     {
diff --git a/source/java/org/alfresco/repo/security/authentication/ldap/LDAPAuthenticationComponentImpl.java b/source/java/org/alfresco/repo/security/authentication/ldap/LDAPAuthenticationComponentImpl.java
index 3a82fb5052..0b052c07ef 100644
--- a/source/java/org/alfresco/repo/security/authentication/ldap/LDAPAuthenticationComponentImpl.java
+++ b/source/java/org/alfresco/repo/security/authentication/ldap/LDAPAuthenticationComponentImpl.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2005-2007 Alfresco Software Limited.
+ * Copyright (C) 2005-2009 Alfresco Software Limited.
  *
  * This program is free software; you can redistribute it and/or
  * modify it under the terms of the GNU General Public License
@@ -18,7 +18,7 @@
  * As a special exception to the terms and conditions of version 2.0 of 
  * the GPL, you may redistribute this Program in connection with Free/Libre 
  * and Open Source Software ("FLOSS") applications as described in Alfresco's 
- * FLOSS exception.  You should have recieved a copy of the text describing 
+ * FLOSS exception.  You should have received a copy of the text describing 
  * the FLOSS exception, and it is also available here: 
  * http://www.alfresco.com/legal/licensing"
  */
@@ -27,6 +27,7 @@ package org.alfresco.repo.security.authentication.ldap;
 import javax.naming.NamingException;
 import javax.naming.directory.InitialDirContext;
 
+import org.alfresco.repo.management.subsystems.ActivateableBean;
 import org.alfresco.repo.security.authentication.AbstractAuthenticationComponent;
 import org.alfresco.repo.security.authentication.AuthenticationException;
 
@@ -35,11 +36,13 @@ import org.alfresco.repo.security.authentication.AuthenticationException;
  * 
  * @author Andy Hind
  */
-public class LDAPAuthenticationComponentImpl extends AbstractAuthenticationComponent
+public class LDAPAuthenticationComponentImpl extends AbstractAuthenticationComponent implements ActivateableBean
 {
     private boolean escapeCommasInBind = false;
     
     private boolean escapeCommasInUid = false;
+    
+    private boolean active = true;
 
     private String userNameFormat;
 
@@ -69,6 +72,20 @@ public class LDAPAuthenticationComponentImpl extends AbstractAuthenticationCompo
     {
         this.escapeCommasInUid = escapeCommasInUid;
     }
+    
+    public void setActive(boolean active)
+    {
+        this.active = active;
+    }
+
+    /*
+     * (non-Javadoc)
+     * @see org.alfresco.repo.management.subsystems.ActivateableBean#isActive()
+     */
+    public boolean isActive()
+    {
+        return this.active;
+    }
 
     /**
      * Implement the authentication method
diff --git a/source/java/org/alfresco/repo/security/authentication/ldap/LDAPGroupExportSource.java b/source/java/org/alfresco/repo/security/authentication/ldap/LDAPGroupExportSource.java
deleted file mode 100644
index b9424eecb8..0000000000
--- a/source/java/org/alfresco/repo/security/authentication/ldap/LDAPGroupExportSource.java
+++ /dev/null
@@ -1,782 +0,0 @@
-/*
- * Copyright (C) 2005-2007 Alfresco Software Limited.
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License
- * as published by the Free Software Foundation; either version 2
- * of the License, or (at your option) any later version.
-
- * This program 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 General Public License for more details.
-
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-
- * As a special exception to the terms and conditions of version 2.0 of 
- * the GPL, you may redistribute this Program in connection with Free/Libre 
- * and Open Source Software ("FLOSS") applications as described in Alfresco's 
- * FLOSS exception.  You should have recieved a copy of the text describing 
- * the FLOSS exception, and it is also available here: 
- * http://www.alfresco.com/legal/licensing"
- */
-package org.alfresco.repo.security.authentication.ldap;
-
-import java.io.BufferedWriter;
-import java.io.File;
-import java.io.FileWriter;
-import java.io.Writer;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.HashSet;
-
-import javax.naming.NamingEnumeration;
-import javax.naming.NamingException;
-import javax.naming.directory.Attribute;
-import javax.naming.directory.Attributes;
-import javax.naming.directory.InitialDirContext;
-import javax.naming.directory.SearchControls;
-import javax.naming.directory.SearchResult;
-import javax.transaction.UserTransaction;
-
-import org.alfresco.model.ContentModel;
-import org.alfresco.repo.importer.ExportSource;
-import org.alfresco.repo.importer.ExportSourceImporterException;
-import org.alfresco.repo.security.authority.AuthorityDAO;
-import org.alfresco.service.cmr.repository.NodeRef;
-import org.alfresco.service.namespace.NamespaceService;
-import org.alfresco.service.namespace.QName;
-import org.alfresco.service.transaction.TransactionService;
-import org.alfresco.util.ApplicationContextHelper;
-import org.alfresco.util.EqualsHelper;
-import org.alfresco.util.GUID;
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.dom4j.io.OutputFormat;
-import org.dom4j.io.XMLWriter;
-import org.springframework.beans.factory.InitializingBean;
-import org.springframework.context.ApplicationContext;
-import org.xml.sax.SAXException;
-import org.xml.sax.helpers.AttributesImpl;
-
-public class LDAPGroupExportSource implements ExportSource, InitializingBean
-{
-    private static Log s_logger = LogFactory.getLog(LDAPGroupExportSource.class);
-
-    private String groupQuery = "(objectclass=groupOfNames)";
-
-    private String searchBase;
-
-    private String groupIdAttributeName = "cn";
-
-    private String userIdAttributeName = "uid";
-
-    private String groupType = "groupOfNames";
-
-    private String personType = "inetOrgPerson";
-
-    private LDAPInitialDirContextFactory ldapInitialContextFactory;
-
-    private NamespaceService namespaceService;
-
-    private String memberAttribute = "member";
-
-    private boolean errorOnMissingMembers = false;
-
-    private boolean errorOnDuplicateGID = false;
-
-    private QName viewRef;
-
-    private QName viewId;
-
-    private QName viewAssociations;
-
-    private QName childQName;
-
-    private QName viewValueQName;
-
-    private QName viewIdRef;
-
-    private AuthorityDAO authorityDAO;
-
-    private boolean errorOnMissingGID;
-
-    private boolean errorOnMissingUID;
-
-    public LDAPGroupExportSource()
-    {
-        super();
-    }
-
-    public void setGroupIdAttributeName(String groupIdAttributeName)
-    {
-        this.groupIdAttributeName = groupIdAttributeName;
-    }
-
-    public void setGroupQuery(String groupQuery)
-    {
-        this.groupQuery = groupQuery;
-    }
-
-    public void setGroupType(String groupType)
-    {
-        this.groupType = groupType;
-    }
-
-    public void setLDAPInitialDirContextFactory(LDAPInitialDirContextFactory ldapInitialDirContextFactory)
-    {
-        this.ldapInitialContextFactory = ldapInitialDirContextFactory;
-    }
-
-    public void setMemberAttribute(String memberAttribute)
-    {
-        this.memberAttribute = memberAttribute;
-    }
-
-    public void setNamespaceService(NamespaceService namespaceService)
-    {
-        this.namespaceService = namespaceService;
-    }
-
-    public void setPersonType(String personType)
-    {
-        this.personType = personType;
-    }
-
-    public void setSearchBase(String searchBase)
-    {
-        this.searchBase = searchBase;
-    }
-
-    public void setUserIdAttributeName(String userIdAttributeName)
-    {
-        this.userIdAttributeName = userIdAttributeName;
-    }
-
-    public void setErrorOnMissingMembers(boolean errorOnMissingMembers)
-    {
-        this.errorOnMissingMembers = errorOnMissingMembers;
-    }
-
-    public void setErrorOnMissingGID(boolean errorOnMissingGID)
-    {
-        this.errorOnMissingGID = errorOnMissingGID;
-    }
-
-    public void setErrorOnMissingUID(boolean errorOnMissingUID)
-    {
-        this.errorOnMissingUID = errorOnMissingUID;
-    }
-
-    public void setErrorOnDuplicateGID(boolean errorOnDuplicateGID)
-    {
-        this.errorOnDuplicateGID = errorOnDuplicateGID;
-    }
-
-    public void setAuthorityDAO(AuthorityDAO authorityDAO)
-    {
-        this.authorityDAO = authorityDAO;
-    }
-
-    public void generateExport(XMLWriter writer)
-    {
-        HashSet rootGroups = new HashSet();
-        HashMap lookup = new HashMap();
-        HashSet secondaryLinks = new HashSet();
-
-        buildGroupsAndRoots(rootGroups, lookup, secondaryLinks);
-
-        buildXML(rootGroups, lookup, secondaryLinks, writer);
-
-    }
-
-    private void buildXML(HashSet rootGroups, HashMap lookup, HashSet secondaryLinks, XMLWriter writer)
-    {
-
-        Collection prefixes = namespaceService.getPrefixes();
-        QName childQName = QName.createQName(NamespaceService.REPOSITORY_VIEW_PREFIX, "childName", namespaceService);
-
-        try
-        {
-            AttributesImpl attrs = new AttributesImpl();
-            attrs.addAttribute(NamespaceService.REPOSITORY_VIEW_1_0_URI, childQName.getLocalName(), childQName.toPrefixString(), null, ContentModel.TYPE_PERSON
-                    .toPrefixString(namespaceService));
-
-            writer.startDocument();
-
-            for (String prefix : prefixes)
-            {
-                if (!prefix.equals("xml"))
-                {
-                    String uri = namespaceService.getNamespaceURI(prefix);
-                    writer.startPrefixMapping(prefix, uri);
-                }
-            }
-
-            writer.startElement(NamespaceService.REPOSITORY_VIEW_PREFIX, "view", NamespaceService.REPOSITORY_VIEW_PREFIX + ":" + "view", new AttributesImpl());
-
-            // Create group structure
-
-            for (Group group : rootGroups)
-            {
-                addRootGroup(lookup, group, writer);
-            }
-
-            // Create secondary links.
-
-            for (SecondaryLink sl : secondaryLinks)
-            {
-                addSecondarylink(lookup, sl, writer);
-            }
-
-            for (String prefix : prefixes)
-            {
-                if (!prefix.equals("xml"))
-                {
-                    writer.endPrefixMapping(prefix);
-                }
-            }
-
-            writer.endElement(NamespaceService.REPOSITORY_VIEW_PREFIX, "view", NamespaceService.REPOSITORY_VIEW_PREFIX + ":" + "view");
-
-            writer.endDocument();
-        }
-        catch (SAXException e)
-        {
-            throw new ExportSourceImporterException("Failed to create file for import.", e);
-        }
-
-    }
-
-    private void addSecondarylink(HashMap lookup, SecondaryLink sl, XMLWriter writer) throws SAXException
-    {
-
-        String fromId = lookup.get(sl.from).guid;
-        String toId = lookup.get(sl.to).guid;
-
-        AttributesImpl attrs = new AttributesImpl();
-        attrs.addAttribute(viewIdRef.getNamespaceURI(), viewIdRef.getLocalName(), viewIdRef.toPrefixString(), null, fromId);
-
-        writer.startElement(viewRef.getNamespaceURI(), viewRef.getLocalName(), viewRef.toPrefixString(namespaceService), attrs);
-
-        writer.startElement(viewAssociations.getNamespaceURI(), viewAssociations.getLocalName(), viewAssociations.toPrefixString(namespaceService), new AttributesImpl());
-
-        writer.startElement(ContentModel.ASSOC_MEMBER.getNamespaceURI(), ContentModel.ASSOC_MEMBER.getLocalName(), ContentModel.ASSOC_MEMBER.toPrefixString(namespaceService),
-                new AttributesImpl());
-
-        AttributesImpl attrsRef = new AttributesImpl();
-        attrsRef.addAttribute(viewIdRef.getNamespaceURI(), viewIdRef.getLocalName(), viewIdRef.toPrefixString(), null, toId);
-        attrsRef.addAttribute(childQName.getNamespaceURI(), childQName.getLocalName(), childQName.toPrefixString(), null, QName.createQName(ContentModel.USER_MODEL_URI, sl.to)
-                .toPrefixString(namespaceService));
-
-        writer.startElement(viewRef.getNamespaceURI(), viewRef.getLocalName(), viewRef.toPrefixString(namespaceService), attrsRef);
-
-        writer.endElement(viewRef.getNamespaceURI(), viewRef.getLocalName(), viewRef.toPrefixString(namespaceService));
-
-        writer.endElement(ContentModel.ASSOC_MEMBER.getNamespaceURI(), ContentModel.ASSOC_MEMBER.getLocalName(), ContentModel.ASSOC_MEMBER.toPrefixString(namespaceService));
-
-        writer.endElement(viewAssociations.getNamespaceURI(), viewAssociations.getLocalName(), viewAssociations.toPrefixString(namespaceService));
-
-        writer.endElement(viewRef.getNamespaceURI(), viewRef.getLocalName(), viewRef.toPrefixString(namespaceService));
-
-    }
-
-    private void addRootGroup(HashMap lookup, Group group, XMLWriter writer) throws SAXException
-    {
-        QName nodeUUID = QName.createQName("sys:node-uuid", namespaceService);
-
-        AttributesImpl attrs = new AttributesImpl();
-        attrs.addAttribute(NamespaceService.REPOSITORY_VIEW_1_0_URI, childQName.getLocalName(), childQName.toPrefixString(), null, QName.createQName(ContentModel.USER_MODEL_URI,
-                group.gid).toPrefixString(namespaceService));
-        attrs.addAttribute(viewId.getNamespaceURI(), viewId.getLocalName(), viewId.toPrefixString(), null, group.guid);
-
-        writer.startElement(ContentModel.TYPE_AUTHORITY_CONTAINER.getNamespaceURI(), ContentModel.TYPE_AUTHORITY_CONTAINER.getLocalName(), ContentModel.TYPE_AUTHORITY_CONTAINER
-                .toPrefixString(namespaceService), attrs);
-
-        if ((authorityDAO != null) && authorityDAO.authorityExists(group.gid))
-        {
-            NodeRef authNodeRef = authorityDAO.getAuthorityNodeRefOrNull(group.gid);
-            if (authNodeRef != null)
-            {
-                String uguid = authorityDAO.getAuthorityNodeRefOrNull(group.gid).getId();
-
-                writer.startElement(nodeUUID.getNamespaceURI(), nodeUUID.getLocalName(), nodeUUID.toPrefixString(namespaceService), new AttributesImpl());
-
-                writer.characters(uguid.toCharArray(), 0, uguid.length());
-
-                writer.endElement(nodeUUID.getNamespaceURI(), nodeUUID.getLocalName(), nodeUUID.toPrefixString(namespaceService));
-            }
-        }
-
-        writer.startElement(ContentModel.PROP_AUTHORITY_NAME.getNamespaceURI(), ContentModel.PROP_AUTHORITY_NAME.getLocalName(), ContentModel.PROP_AUTHORITY_NAME
-                .toPrefixString(namespaceService), new AttributesImpl());
-
-        writer.characters(group.gid.toCharArray(), 0, group.gid.length());
-
-        writer.endElement(ContentModel.PROP_AUTHORITY_NAME.getNamespaceURI(), ContentModel.PROP_AUTHORITY_NAME.getLocalName(), ContentModel.PROP_AUTHORITY_NAME
-                .toPrefixString(namespaceService));
-
-        if (group.members.size() > 0)
-        {
-            writer.startElement(ContentModel.PROP_MEMBERS.getNamespaceURI(), ContentModel.PROP_MEMBERS.getLocalName(), ContentModel.PROP_MEMBERS.toPrefixString(namespaceService),
-                    new AttributesImpl());
-
-            for (String member : group.members)
-            {
-                writer.startElement(viewValueQName.getNamespaceURI(), viewValueQName.getLocalName(), viewValueQName.toPrefixString(namespaceService), new AttributesImpl());
-
-                writer.characters(member.toCharArray(), 0, member.length());
-
-                writer.endElement(viewValueQName.getNamespaceURI(), viewValueQName.getLocalName(), viewValueQName.toPrefixString(namespaceService));
-            }
-
-            writer.endElement(ContentModel.PROP_MEMBERS.getNamespaceURI(), ContentModel.PROP_MEMBERS.getLocalName(), ContentModel.PROP_MEMBERS.toPrefixString(namespaceService));
-        }
-
-        for (Group child : group.children)
-        {
-            addgroup(lookup, child, writer);
-        }
-
-        writer.endElement(ContentModel.TYPE_AUTHORITY_CONTAINER.getNamespaceURI(), ContentModel.TYPE_AUTHORITY_CONTAINER.getLocalName(), ContentModel.TYPE_AUTHORITY_CONTAINER
-                .toPrefixString(namespaceService));
-
-    }
-
-    private void addgroup(HashMap lookup, Group group, XMLWriter writer) throws SAXException
-    {
-        AttributesImpl attrs = new AttributesImpl();
-
-        writer.startElement(ContentModel.ASSOC_MEMBER.getNamespaceURI(), ContentModel.ASSOC_MEMBER.getLocalName(), ContentModel.ASSOC_MEMBER.toPrefixString(namespaceService),
-                attrs);
-
-        addRootGroup(lookup, group, writer);
-
-        writer.endElement(ContentModel.ASSOC_MEMBER.getNamespaceURI(), ContentModel.ASSOC_MEMBER.getLocalName(), ContentModel.ASSOC_MEMBER.toPrefixString(namespaceService));
-    }
-
-    private void buildGroupsAndRoots(HashSet rootGroups, HashMap lookup, HashSet secondaryLinks)
-    {
-        InitialDirContext ctx = null;
-        try
-        {
-            ctx = ldapInitialContextFactory.getDefaultIntialDirContext();
-
-            SearchControls userSearchCtls = new SearchControls();
-            userSearchCtls.setSearchScope(SearchControls.SUBTREE_SCOPE);
-
-            NamingEnumeration searchResults = ctx.search(searchBase, groupQuery, userSearchCtls);
-            while (searchResults.hasMoreElements())
-            {
-                SearchResult result = (SearchResult) searchResults.next();
-                Attributes attributes = result.getAttributes();
-                Attribute gidAttribute = attributes.get(groupIdAttributeName);
-                if (gidAttribute == null)
-                {
-                    if (errorOnMissingGID)
-                    {
-                        throw new ExportSourceImporterException("Group returned by group search does not have mandatory group id attribute " + attributes);
-                    }
-                    else
-                    {
-                        s_logger.warn("Missing GID on " + attributes);
-                        continue;
-                    }
-                }
-                String gid = (String) gidAttribute.get(0);
-
-                Group group = lookup.get("GROUP_" + gid);
-                if (group == null)
-                {
-                    group = new Group(gid);
-                    lookup.put(group.gid, group);
-                    rootGroups.add(group);
-                }
-                else
-                {
-                    if (errorOnDuplicateGID)
-                    {
-                        throw new ExportSourceImporterException("Duplicate group id found for " + gid);
-                    }
-                    else
-                    {
-                        s_logger.warn("Duplicate gid found for " + gid + " -> merging definitions");
-                        // Currently we will merge the two definitions together we do not support duplciate GIDs
-                    }
-                }
-                Attribute memAttribute = attributes.get(memberAttribute);
-                // check for null
-                if (memAttribute != null)
-                {
-                    for (int i = 0; i < memAttribute.size(); i++)
-                    {
-                        String attribute = (String) memAttribute.get(i);
-                        if (attribute != null)
-                        {
-                            group.distinguishedNames.add(attribute);
-                        }
-                    }
-                }
-            }
-
-            if (s_logger.isDebugEnabled())
-            {
-                s_logger.debug("Found " + lookup.size());
-            }
-
-            for (Group group : lookup.values())
-            {
-                if (s_logger.isDebugEnabled())
-                {
-                    s_logger.debug("Linking " + group.gid);
-                }
-                for (String dn : group.distinguishedNames)
-                {
-                    if (s_logger.isDebugEnabled())
-                    {
-                        s_logger.debug("... " + dn);
-                    }
-                    String id;
-                    Boolean isGroup = null;
-
-                    SearchControls memberSearchCtls = new SearchControls();
-                    memberSearchCtls.setSearchScope(SearchControls.OBJECT_SCOPE);
-                    NamingEnumeration memberSearchResults;
-                    try
-                    {
-                        memberSearchResults = ctx.search(dn, "(objectClass=*)", memberSearchCtls);
-                    }
-                    catch (NamingException e)
-                    {
-                        if (errorOnMissingMembers)
-                        {
-                            throw e;
-                        }
-                        s_logger.warn("Failed to resolve distinguished name: " + dn);
-                        continue;
-                    }
-                    while (memberSearchResults.hasMoreElements())
-                    {
-                        id = null;
-
-                        SearchResult result;
-                        try
-                        {
-                            result = (SearchResult) memberSearchResults.next();
-                        }
-                        catch (NamingException e)
-                        {
-                            if (errorOnMissingMembers)
-                            {
-                                throw e;
-                            }
-                            s_logger.warn("Failed to resolve distinguished name: " + dn);
-                            continue;
-                        }
-                        Attributes attributes = result.getAttributes();
-                        Attribute objectclass = attributes.get("objectclass");
-                        if (objectclass == null)
-                        {
-                            if (errorOnMissingMembers)
-                            {
-                                throw new ExportSourceImporterException("Failed to find attribute objectclass for DN " + dn);
-                            }
-                            else
-                            {
-                                continue;
-                            }
-                        }
-                        for (int i = 0; i < objectclass.size(); i++)
-                        {
-                            String testType;
-                            try
-                            {
-                                testType = (String) objectclass.get(i);
-                            }
-                            catch (NamingException e)
-                            {
-                                if (errorOnMissingMembers)
-                                {
-                                    throw e;
-                                }
-                                s_logger.warn("Failed to resolve object class attribute for distinguished name: " + dn);
-                                continue;
-                            }
-                            if (testType.equals(groupType))
-                            {
-                                isGroup = true;
-                                try
-                                {
-                                    Attribute groupIdAttribute = attributes.get(groupIdAttributeName);
-                                    if (groupIdAttribute == null)
-                                    {
-                                        if (errorOnMissingGID)
-                                        {
-                                            throw new ExportSourceImporterException("Group missing group id attribute DN =" + dn + "  att = " + groupIdAttributeName);
-                                        }
-                                        else
-                                        {
-                                            s_logger.warn("Group missing group id attribute DN =" + dn + "  att = " + groupIdAttributeName);
-                                            continue;
-                                        }
-                                    }
-                                    id = (String) groupIdAttribute.get(0);
-                                }
-                                catch (NamingException e)
-                                {
-                                    if (errorOnMissingMembers)
-                                    {
-                                        throw e;
-                                    }
-                                    s_logger.warn("Failed to resolve group identifier " + groupIdAttributeName + " for distinguished name: " + dn);
-                                    id = "Unknown sub group";
-                                }
-                                break;
-                            }
-                            else if (testType.equals(personType))
-                            {
-                                isGroup = false;
-                                try
-                                {
-                                    Attribute userIdAttribute = attributes.get(userIdAttributeName);
-                                    if (userIdAttribute == null)
-                                    {
-                                        if (errorOnMissingUID)
-                                        {
-                                            throw new ExportSourceImporterException("User missing user id attribute DN =" + dn + "  att = " + userIdAttributeName);
-                                        }
-                                        else
-                                        {
-                                            s_logger.warn("User missing user id attribute DN =" + dn + "  att = " + userIdAttributeName);
-                                            continue;
-                                        }
-                                    }
-                                    id = (String) userIdAttribute.get(0);
-                                }
-                                catch (NamingException e)
-                                {
-                                    if (errorOnMissingMembers)
-                                    {
-                                        throw e;
-                                    }
-                                    s_logger.warn("Failed to resolve group identifier " + userIdAttributeName + " for distinguished name: " + dn);
-                                    id = "Unknown member";
-                                }
-                                break;
-                            }
-                        }
-
-                        if (id != null)
-                        {
-                            if (isGroup == null)
-                            {
-                                if (errorOnMissingMembers)
-                                {
-                                    throw new ExportSourceImporterException("Type not recognised for DN" + dn);
-                                }
-                                else
-                                {
-                                    continue;
-                                }
-                            }
-                            else if (isGroup)
-                            {
-                                if (s_logger.isDebugEnabled())
-                                {
-                                    s_logger.debug("... is sub group");
-                                }
-                                Group child = lookup.get("GROUP_" + id);
-                                if (child == null)
-                                {
-                                    if (errorOnMissingMembers)
-                                    {
-                                        throw new ExportSourceImporterException("Failed to find child group " + id);
-                                    }
-                                    else
-                                    {
-                                        continue;
-                                    }
-                                }
-                                if (rootGroups.contains(child))
-                                {
-                                    if (s_logger.isDebugEnabled())
-                                    {
-                                        s_logger.debug("...       Primary created from " + group.gid + " to " + child.gid);
-                                    }
-                                    group.children.add(child);
-                                    rootGroups.remove(child);
-                                }
-                                else
-                                {
-                                    if (s_logger.isDebugEnabled())
-                                    {
-                                        s_logger.debug("...      Secondary created from " + group.gid + " to " + child.gid);
-                                    }
-                                    secondaryLinks.add(new SecondaryLink(group.gid, child.gid));
-                                }
-                            }
-                            else
-                            {
-                                if (s_logger.isDebugEnabled())
-                                {
-                                    s_logger.debug("... is member");
-                                }
-                                group.members.add(id);
-                            }
-                        }
-                    }
-                }
-            }
-            if (s_logger.isDebugEnabled())
-            {
-                s_logger.debug("Top " + rootGroups.size());
-                s_logger.debug("Secondary " + secondaryLinks.size());
-            }
-        }
-        catch (NamingException e)
-        {
-            throw new ExportSourceImporterException("Failed to import people.", e);
-        }
-        finally
-        {
-            if (ctx != null)
-            {
-                try
-                {
-                    ctx.close();
-                }
-                catch (NamingException e)
-                {
-                    throw new ExportSourceImporterException("Failed to import people.", e);
-                }
-            }
-        }
-    }
-
-    private static class Group
-    {
-        String gid;
-
-        String guid = GUID.generate();
-
-        HashSet children = new HashSet();
-
-        HashSet members = new HashSet();
-
-        HashSet distinguishedNames = new HashSet();
-
-        private Group(String gid)
-        {
-            this.gid = "GROUP_" + gid;
-        }
-
-        @Override
-        public boolean equals(Object o)
-        {
-            if (this == o)
-            {
-                return true;
-            }
-            if (!(o instanceof Group))
-            {
-                return false;
-            }
-            Group g = (Group) o;
-            return this.gid.equals(g.gid);
-        }
-
-        @Override
-        public int hashCode()
-        {
-            return gid.hashCode();
-        }
-    }
-
-    private static class SecondaryLink
-    {
-        String from;
-
-        String to;
-
-        private SecondaryLink(String from, String to)
-        {
-            this.from = from;
-            this.to = to;
-        }
-
-        @Override
-        public boolean equals(Object o)
-        {
-            if (this == o)
-            {
-                return true;
-            }
-            if (!(o instanceof Group))
-            {
-                return false;
-            }
-            SecondaryLink l = (SecondaryLink) o;
-            return EqualsHelper.nullSafeEquals(this.from, l.from) && EqualsHelper.nullSafeEquals(this.to, l.to);
-        }
-
-        @Override
-        public int hashCode()
-        {
-            int hashCode = 0;
-            if (from != null)
-            {
-                hashCode = hashCode * 37 + from.hashCode();
-            }
-            if (to != null)
-            {
-                hashCode = hashCode * 37 + to.hashCode();
-            }
-            return hashCode;
-        }
-    }
-
-    public static void main(String[] args) throws Exception
-    {
-        ApplicationContext ctx = ApplicationContextHelper.getApplicationContext();
-        ExportSource source = (ExportSource) ctx.getBean("ldapGroupExportSource");
-
-        TransactionService txs = (TransactionService) ctx.getBean("transactionComponent");
-        UserTransaction tx = txs.getUserTransaction();
-        tx.begin();
-
-        File file = new File(args[0]);
-        Writer writer = new BufferedWriter(new FileWriter(file));
-        XMLWriter xmlWriter = createXMLExporter(writer);
-        source.generateExport(xmlWriter);
-        xmlWriter.close();
-
-        tx.commit();
-    }
-
-    private static XMLWriter createXMLExporter(Writer writer)
-    {
-        // Define output format
-        OutputFormat format = OutputFormat.createPrettyPrint();
-        format.setNewLineAfterDeclaration(false);
-        format.setIndentSize(3);
-        format.setEncoding("UTF-8");
-
-        // Construct an XML Exporter
-
-        XMLWriter xmlWriter = new XMLWriter(writer, format);
-        return xmlWriter;
-    }
-
-    public void afterPropertiesSet() throws Exception
-    {
-        viewRef = QName.createQName(NamespaceService.REPOSITORY_VIEW_PREFIX, "reference", namespaceService);
-        viewId = QName.createQName(NamespaceService.REPOSITORY_VIEW_PREFIX, "id", namespaceService);
-        viewIdRef = QName.createQName(NamespaceService.REPOSITORY_VIEW_PREFIX, "idref", namespaceService);
-        viewAssociations = QName.createQName(NamespaceService.REPOSITORY_VIEW_PREFIX, "associations", namespaceService);
-        childQName = QName.createQName(NamespaceService.REPOSITORY_VIEW_PREFIX, "childName", namespaceService);
-        viewValueQName = QName.createQName(NamespaceService.REPOSITORY_VIEW_PREFIX, "value", namespaceService);
-
-    }
-}
diff --git a/source/java/org/alfresco/repo/security/authentication/ldap/LDAPPersonExportSource.java b/source/java/org/alfresco/repo/security/authentication/ldap/LDAPPersonExportSource.java
deleted file mode 100644
index c94c6df89a..0000000000
--- a/source/java/org/alfresco/repo/security/authentication/ldap/LDAPPersonExportSource.java
+++ /dev/null
@@ -1,341 +0,0 @@
-/*
- * Copyright (C) 2005-2007 Alfresco Software Limited.
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License
- * as published by the Free Software Foundation; either version 2
- * of the License, or (at your option) any later version.
-
- * This program 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 General Public License for more details.
-
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-
- * As a special exception to the terms and conditions of version 2.0 of 
- * the GPL, you may redistribute this Program in connection with Free/Libre 
- * and Open Source Software ("FLOSS") applications as described in Alfresco's 
- * FLOSS exception.  You should have recieved a copy of the text describing 
- * the FLOSS exception, and it is also available here: 
- * http://www.alfresco.com/legal/licensing"
- */
-package org.alfresco.repo.security.authentication.ldap;
-
-import java.io.BufferedWriter;
-import java.io.File;
-import java.io.FileWriter;
-import java.io.Writer;
-import java.util.Collection;
-import java.util.HashSet;
-import java.util.Map;
-
-import javax.naming.NamingEnumeration;
-import javax.naming.NamingException;
-import javax.naming.directory.Attribute;
-import javax.naming.directory.Attributes;
-import javax.naming.directory.InitialDirContext;
-import javax.naming.directory.SearchControls;
-import javax.naming.directory.SearchResult;
-import javax.transaction.UserTransaction;
-
-import org.alfresco.model.ContentModel;
-import org.alfresco.repo.importer.ExportSource;
-import org.alfresco.repo.importer.ExportSourceImporterException;
-import org.alfresco.service.cmr.security.PersonService;
-import org.alfresco.service.namespace.NamespaceService;
-import org.alfresco.service.namespace.QName;
-import org.alfresco.service.transaction.TransactionService;
-import org.alfresco.util.ApplicationContextHelper;
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.dom4j.io.OutputFormat;
-import org.dom4j.io.XMLWriter;
-import org.springframework.context.ApplicationContext;
-import org.xml.sax.SAXException;
-import org.xml.sax.helpers.AttributesImpl;
-
-public class LDAPPersonExportSource implements ExportSource
-{
-    private static Log s_logger = LogFactory.getLog(LDAPPersonExportSource.class);
-
-    private String personQuery = "(objectclass=inetOrgPerson)";
-
-    private String searchBase;
-
-    private String userIdAttributeName;
-
-    private LDAPInitialDirContextFactory ldapInitialContextFactory;
-
-    private PersonService personService;
-
-    private Map attributeMapping;
-
-    private NamespaceService namespaceService;
-
-    private Map attributeDefaults;
-
-    private boolean errorOnMissingUID;
-
-    public LDAPPersonExportSource()
-    {
-        super();
-    }
-
-    public void setPersonQuery(String personQuery)
-    {
-        this.personQuery = personQuery;
-    }
-
-    public void setSearchBase(String searchBase)
-    {
-        this.searchBase = searchBase;
-    }
-
-    public void setUserIdAttributeName(String userIdAttributeName)
-    {
-        this.userIdAttributeName = userIdAttributeName;
-    }
-
-    public void setLDAPInitialDirContextFactory(LDAPInitialDirContextFactory ldapInitialDirContextFactory)
-    {
-        this.ldapInitialContextFactory = ldapInitialDirContextFactory;
-    }
-
-    public void setPersonService(PersonService personService)
-    {
-        this.personService = personService;
-    }
-
-    public void setAttributeDefaults(Map attributeDefaults)
-    {
-        this.attributeDefaults = attributeDefaults;
-    }
-
-    public void setNamespaceService(NamespaceService namespaceService)
-    {
-        this.namespaceService = namespaceService;
-    }
-
-    public void setAttributeMapping(Map attributeMapping)
-    {
-        this.attributeMapping = attributeMapping;
-    }
-
-    public void setErrorOnMissingUID(boolean errorOnMissingUID)
-    {
-        this.errorOnMissingUID = errorOnMissingUID;
-    }
-
-    public void generateExport(XMLWriter writer)
-    {
-        QName nodeUUID = QName.createQName("sys:node-uuid", namespaceService);
-
-        Collection prefixes = namespaceService.getPrefixes();
-        QName childQName = QName.createQName(NamespaceService.REPOSITORY_VIEW_PREFIX, "childName", namespaceService);
-
-        try
-        {
-            writer.startDocument();
-
-            for (String prefix : prefixes)
-            {
-                if (!prefix.equals("xml"))
-                {
-                    String uri = namespaceService.getNamespaceURI(prefix);
-                    writer.startPrefixMapping(prefix, uri);
-                }
-            }
-
-            writer.startElement(NamespaceService.REPOSITORY_VIEW_PREFIX, "view",
-                    NamespaceService.REPOSITORY_VIEW_PREFIX + ":" + "view", new AttributesImpl());
-
-            HashSet uids = new HashSet();
-
-            InitialDirContext ctx = null;
-            try
-            {
-                ctx = ldapInitialContextFactory.getDefaultIntialDirContext();
-
-                // Authentication has been successful.
-                // Set the current user, they are now authenticated.
-
-                SearchControls userSearchCtls = new SearchControls();
-                userSearchCtls.setSearchScope(SearchControls.SUBTREE_SCOPE);
-
-                userSearchCtls.setCountLimit(Integer.MAX_VALUE);
-
-                NamingEnumeration searchResults = ctx.search(searchBase, personQuery, userSearchCtls);
-                RESULT_LOOP: while (searchResults.hasMoreElements())
-                {
-                    SearchResult result = (SearchResult) searchResults.next();
-                    Attributes attributes = result.getAttributes();
-                    Attribute uidAttribute = attributes.get(userIdAttributeName);
-                    if (uidAttribute == null)
-                    {
-                        if (errorOnMissingUID)
-                        {
-                            throw new ExportSourceImporterException(
-                                    "User returned by user search does not have mandatory user id attribute "
-                                            + attributes);
-                        }
-                        else
-                        {
-                            s_logger.warn("User returned by user search does not have mandatory user id attribute "
-                                    + attributes);
-                            continue RESULT_LOOP;
-                        }
-                    }
-                    String uid = (String) uidAttribute.get(0);
-
-                    if (uids.contains(uid))
-                    {
-                        s_logger.warn("Duplicate uid found - there will be more than one person object for this user - "
-                                + uid);
-                    }
-
-                    uids.add(uid);
-
-                    if (s_logger.isDebugEnabled())
-                    {
-                        s_logger.debug("Adding user for " + uid);
-                    }
-
-                    AttributesImpl attrs = new AttributesImpl();
-                    attrs.addAttribute(NamespaceService.REPOSITORY_VIEW_1_0_URI, childQName.getLocalName(), childQName
-                            .toPrefixString(), null, QName.createQName("cm", uid, namespaceService).toPrefixString(namespaceService));
-
-                    writer.startElement(ContentModel.TYPE_PERSON.getNamespaceURI(), ContentModel.TYPE_PERSON
-                            .getLocalName(), ContentModel.TYPE_PERSON.toPrefixString(namespaceService), attrs);
-
-                    for (String key : attributeMapping.keySet())
-                    {
-                        QName keyQName = QName.createQName(key, namespaceService);
-
-                        writer.startElement(keyQName.getNamespaceURI(), keyQName.getLocalName(), keyQName
-                                .toPrefixString(namespaceService), new AttributesImpl());
-
-                        // cater for null
-                        String attributeName = attributeMapping.get(key);
-                        if (attributeName != null)
-                        {
-                            Attribute attribute = attributes.get(attributeName);
-                            if (attribute != null)
-                            {
-                                String value = (String) attribute.get(0);
-                                if (value != null)
-                                {
-                                    writer.characters(value.toCharArray(), 0, value.length());
-                                }
-                            }
-                            else
-                            {
-                                String defaultValue = attributeDefaults.get(key);
-                                if (defaultValue != null)
-                                {
-                                    writer.characters(defaultValue.toCharArray(), 0, defaultValue.length());
-                                }
-                            }
-                        }
-                        else
-                        {
-                            String defaultValue = attributeDefaults.get(key);
-                            if (defaultValue != null)
-                            {
-                                writer.characters(defaultValue.toCharArray(), 0, defaultValue.length());
-                            }
-                        }
-
-                        writer.endElement(keyQName.getNamespaceURI(), keyQName.getLocalName(), keyQName
-                                .toPrefixString(namespaceService));
-                    }
-
-                    if (personService.personExists(uid))
-                    {
-                        String uguid = personService.getPerson(uid).getId();
-
-                        writer.startElement(nodeUUID.getNamespaceURI(), nodeUUID.getLocalName(), nodeUUID
-                                .toPrefixString(namespaceService), new AttributesImpl());
-
-                        writer.characters(uguid.toCharArray(), 0, uguid.length());
-
-                        writer.endElement(nodeUUID.getNamespaceURI(), nodeUUID.getLocalName(), nodeUUID
-                                .toPrefixString(namespaceService));
-                    }
-                    writer.endElement(ContentModel.TYPE_PERSON.getNamespaceURI(), ContentModel.TYPE_PERSON
-                            .getLocalName(), ContentModel.TYPE_PERSON.toPrefixString(namespaceService));
-
-                }
-
-            }
-            catch (NamingException e)
-            {
-                throw new ExportSourceImporterException("Failed to import people.", e);
-            }
-            finally
-            {
-                if (ctx != null)
-                {
-                    try
-                    {
-                        ctx.close();
-                    }
-                    catch (NamingException e)
-                    {
-                        throw new ExportSourceImporterException("Failed to import people.", e);
-                    }
-                }
-            }
-
-            for (String prefix : prefixes)
-            {
-                if (!prefix.equals("xml"))
-                {
-                    writer.endPrefixMapping(prefix);
-                }
-            }
-
-            writer.endElement(NamespaceService.REPOSITORY_VIEW_PREFIX, "view", NamespaceService.REPOSITORY_VIEW_PREFIX
-                    + ":" + "view");
-
-            writer.endDocument();
-        }
-        catch (SAXException e)
-        {
-            throw new ExportSourceImporterException("Failed to create file for import.", e);
-        }
-    }
-
-    public static void main(String[] args) throws Exception
-    {
-        ApplicationContext ctx = ApplicationContextHelper.getApplicationContext();
-        ExportSource source = (ExportSource) ctx.getBean("ldapPeopleExportSource");
-        TransactionService txs = (TransactionService) ctx.getBean("transactionComponent");
-        UserTransaction tx = txs.getUserTransaction();
-        tx.begin();
-
-        File file = new File(args[0]);
-        Writer writer = new BufferedWriter(new FileWriter(file));
-        XMLWriter xmlWriter = createXMLExporter(writer);
-        source.generateExport(xmlWriter);
-        xmlWriter.close();
-
-        tx.commit();
-    }
-
-    private static XMLWriter createXMLExporter(Writer writer)
-    {
-        // Define output format
-        OutputFormat format = OutputFormat.createPrettyPrint();
-        format.setNewLineAfterDeclaration(false);
-        format.setIndentSize(3);
-        format.setEncoding("UTF-8");
-
-        // Construct an XML Exporter
-
-        XMLWriter xmlWriter = new XMLWriter(writer, format);
-        return xmlWriter;
-    }
-}
\ No newline at end of file
diff --git a/source/java/org/alfresco/repo/security/authentication/subsystems/SubsystemChainingAuthenticationComponent.java b/source/java/org/alfresco/repo/security/authentication/subsystems/SubsystemChainingAuthenticationComponent.java
index a9a2284757..23c0c4c34e 100644
--- a/source/java/org/alfresco/repo/security/authentication/subsystems/SubsystemChainingAuthenticationComponent.java
+++ b/source/java/org/alfresco/repo/security/authentication/subsystems/SubsystemChainingAuthenticationComponent.java
@@ -28,6 +28,7 @@ import java.util.Collection;
 import java.util.LinkedList;
 import java.util.List;
 
+import org.alfresco.repo.management.subsystems.ActivateableBean;
 import org.alfresco.repo.management.subsystems.ChildApplicationContextManager;
 import org.alfresco.repo.security.authentication.AbstractChainingAuthenticationComponent;
 import org.alfresco.repo.security.authentication.AuthenticationComponent;
@@ -80,7 +81,15 @@ public class SubsystemChainingAuthenticationComponent extends AbstractChainingAu
             ApplicationContext context = this.applicationContextManager.getApplicationContext(instance);
             try
             {
-                result.add((AuthenticationComponent) context.getBean(sourceBeanName));
+                AuthenticationComponent authenticationComponent = (AuthenticationComponent) context
+                        .getBean(sourceBeanName);
+                // Only add active authentication components. E.g. we might have an ldap context that is only used for
+                // synchronizing
+                if (!(authenticationComponent instanceof ActivateableBean)
+                        || ((ActivateableBean) authenticationComponent).isActive())
+                {
+                    result.add(authenticationComponent);
+                }
             }
             catch (NoSuchBeanDefinitionException e)
             {
diff --git a/source/java/org/alfresco/repo/security/authentication/subsystems/SubsystemChainingAuthenticationService.java b/source/java/org/alfresco/repo/security/authentication/subsystems/SubsystemChainingAuthenticationService.java
index cbf5b05949..e808d1a22a 100644
--- a/source/java/org/alfresco/repo/security/authentication/subsystems/SubsystemChainingAuthenticationService.java
+++ b/source/java/org/alfresco/repo/security/authentication/subsystems/SubsystemChainingAuthenticationService.java
@@ -27,6 +27,7 @@ package org.alfresco.repo.security.authentication.subsystems;
 import java.util.LinkedList;
 import java.util.List;
 
+import org.alfresco.repo.management.subsystems.ActivateableBean;
 import org.alfresco.repo.management.subsystems.ChildApplicationContextManager;
 import org.alfresco.repo.security.authentication.AbstractChainingAuthenticationService;
 import org.alfresco.service.cmr.security.AuthenticationService;
@@ -83,7 +84,15 @@ public class SubsystemChainingAuthenticationService extends AbstractChainingAuth
             ApplicationContext context = this.applicationContextManager.getApplicationContext(instance);
             try
             {
-                return (AuthenticationService) context.getBean(sourceBeanName);
+                AuthenticationService authenticationService = (AuthenticationService) context.getBean(sourceBeanName);
+                // Only add active authentication services. E.g. we might have an ldap context that is only used for
+                // synchronizing
+                if (!(authenticationService instanceof ActivateableBean)
+                        || ((ActivateableBean) authenticationService).isActive())
+                {
+
+                    return authenticationService;
+                }
             }
             catch (NoSuchBeanDefinitionException e)
             {
@@ -107,7 +116,15 @@ public class SubsystemChainingAuthenticationService extends AbstractChainingAuth
             ApplicationContext context = this.applicationContextManager.getApplicationContext(instance);
             try
             {
-                result.add((AuthenticationService) context.getBean(sourceBeanName));
+                AuthenticationService authenticationService = (AuthenticationService) context.getBean(sourceBeanName);
+                // Only add active authentication components. E.g. we might have an ldap context that is only used for
+                // synchronizing
+                if (!(authenticationService instanceof ActivateableBean)
+                        || ((ActivateableBean) authenticationService).isActive())
+                {
+
+                    result.add(authenticationService);
+                }
             }
             catch (NoSuchBeanDefinitionException e)
             {
diff --git a/source/java/org/alfresco/repo/security/authentication/userModel.xml b/source/java/org/alfresco/repo/security/authentication/userModel.xml
index a80e02439c..12b622be3a 100644
--- a/source/java/org/alfresco/repo/security/authentication/userModel.xml
+++ b/source/java/org/alfresco/repo/security/authentication/userModel.xml
@@ -2,8 +2,8 @@
 
    Alfresco User Model
    Alfresco
-   2005-08-16
-   0.1
+   2009-06-04
+   0.2
 
    
       
@@ -16,7 +16,6 @@
 
 	
 	  
-      
    
    
    
@@ -75,42 +74,6 @@
          
       
    
-      
-         Alfresco Authority Type
-         usr:authority
-         
-			
-			
-            
-               d:text
-			   
-                  
-               
-            
-            
-               d:text
-               true   
-            
-			
-               d:text
-            
-         
-         
-            
-               
-                  false
-                  true
-               
-               
-                  usr:authority
-                  false
-                  true
-               
-               false
-            
-         
-        
-
    
    
    
diff --git a/source/java/org/alfresco/repo/security/authority/AuthorityDAO.java b/source/java/org/alfresco/repo/security/authority/AuthorityDAO.java
index a7c1595b04..500c1b236e 100644
--- a/source/java/org/alfresco/repo/security/authority/AuthorityDAO.java
+++ b/source/java/org/alfresco/repo/security/authority/AuthorityDAO.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2005-2007 Alfresco Software Limited.
+ * Copyright (C) 2005-2009 Alfresco Software Limited.
  *
  * This program is free software; you can redistribute it and/or
  * modify it under the terms of the GNU General Public License
@@ -18,7 +18,7 @@
  * As a special exception to the terms and conditions of version 2.0 of 
  * the GPL, you may redistribute this Program in connection with Free/Libre 
  * and Open Source Software ("FLOSS") applications as described in Alfresco's 
- * FLOSS exception.  You should have recieved a copy of the text describing 
+ * FLOSS exception.  You should have received a copy of the text describing 
  * the FLOSS exception, and it is also available here: 
  * http://www.alfresco.com/legal/licensing"
  */
@@ -27,6 +27,7 @@ package org.alfresco.repo.security.authority;
 import java.util.Set;
 
 import org.alfresco.service.cmr.repository.NodeRef;
+import org.alfresco.service.cmr.security.AuthorityService;
 import org.alfresco.service.cmr.security.AuthorityType;
 
 public interface AuthorityDAO
@@ -42,11 +43,11 @@ public interface AuthorityDAO
     /**
      * Create an authority.
      * 
-     * @param parentName
      * @param name
-     * @param authorityDisplayName 
+     * @param authorityDisplayName
+     * @param authorityZone
      */
-    void createAuthority(String parentName, String name, String authorityDisplayName);
+    void createAuthority(String name, String authorityDisplayName, String authorityZone);
 
     /**
      * Delete an authority.
@@ -148,4 +149,33 @@ public interface AuthorityDAO
      */
     public Set findAuthorities(AuthorityType type, String namePattern);
 
+    /**
+     * Gets or creates an authority zone node with the specified name
+     * 
+     * @param zoneName
+     *            the zone name
+     * @return reference to the zone node
+     */
+    public NodeRef getOrCreateZone(String zoneName);
+    
+    /**
+     * Gets the name of the zone containing the specified authority.
+     * 
+     * @param name
+     *            the authority long name
+     * @return the the name of the zone containing the specified authority, {@link AuthorityService#DEFAULT_ZONE} if the
+     *         authority exists but has no zone, or null if the authority does not exist.
+     */
+    public String getAuthorityZone(String name);
+    
+    /**
+     * Gets the names of all authorities in a zone, optionally filtered by type.
+     * 
+     * @param zoneName
+     *            the zone name
+     * @param type
+     *            the authority type to filter by or null for all authority types
+     * @return the names of all authorities in a zone, optionally filtered by type
+     */
+    public Set getAllAuthoritiesInZone(String zoneName, AuthorityType type);
 }
diff --git a/source/java/org/alfresco/repo/security/authority/AuthorityDAOImpl.java b/source/java/org/alfresco/repo/security/authority/AuthorityDAOImpl.java
index 691e429cd4..4fee1ef74b 100644
--- a/source/java/org/alfresco/repo/security/authority/AuthorityDAOImpl.java
+++ b/source/java/org/alfresco/repo/security/authority/AuthorityDAOImpl.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2005-2007 Alfresco Software Limited.
+ * Copyright (C) 2005-2009 Alfresco Software Limited.
  *
  * This program is free software; you can redistribute it and/or
  * modify it under the terms of the GNU General Public License
@@ -15,18 +15,16 @@
  * along with this program; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 
- * As a special exception to the terms and conditions of version 2.0 of
- * the GPL, you may redistribute this Program in connection with Free/Libre
- * and Open Source Software ("FLOSS") applications as described in Alfresco's
- * FLOSS exception.  You should have recieved a copy of the text describing
- * the FLOSS exception, and it is also available here:
+ * As a special exception to the terms and conditions of version 2.0 of 
+ * the GPL, you may redistribute this Program in connection with Free/Libre 
+ * and Open Source Software ("FLOSS") applications as described in Alfresco's 
+ * FLOSS exception.  You should have received a copy of the text describing 
+ * the FLOSS exception, and it is also available here: 
  * http://www.alfresco.com/legal/licensing"
  */
 package org.alfresco.repo.security.authority;
 
 import java.io.Serializable;
-import java.util.ArrayList;
-import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -38,27 +36,23 @@ import java.util.regex.Pattern;
 import org.alfresco.error.AlfrescoRuntimeException;
 import org.alfresco.model.ContentModel;
 import org.alfresco.repo.cache.SimpleCache;
-import org.alfresco.repo.search.impl.lucene.LuceneQueryParser;
 import org.alfresco.service.cmr.dictionary.DictionaryService;
 import org.alfresco.service.cmr.repository.ChildAssociationRef;
 import org.alfresco.service.cmr.repository.NodeRef;
 import org.alfresco.service.cmr.repository.NodeService;
 import org.alfresco.service.cmr.repository.StoreRef;
 import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter;
-import org.alfresco.service.cmr.search.ResultSet;
-import org.alfresco.service.cmr.search.ResultSetRow;
-import org.alfresco.service.cmr.search.SearchParameters;
-import org.alfresco.service.cmr.search.SearchService;
+import org.alfresco.service.cmr.security.AuthorityService;
 import org.alfresco.service.cmr.security.AuthorityType;
+import org.alfresco.service.cmr.security.PersonService;
 import org.alfresco.service.namespace.NamespacePrefixResolver;
 import org.alfresco.service.namespace.QName;
 import org.alfresco.service.namespace.RegexQNamePattern;
-import org.alfresco.util.ISO9075;
 import org.alfresco.util.SearchLanguageConversion;
 
 public class AuthorityDAOImpl implements AuthorityDAO
 {
-    public static final StoreRef STOREREF_USERS = new StoreRef("user", "alfrescoUserStore");
+    private StoreRef storeRef;
 
     private NodeService nodeService;
 
@@ -68,10 +62,12 @@ public class AuthorityDAOImpl implements AuthorityDAO
 
     private QName qnameAssocAuthorities;
 
-    private SearchService searchService;
+    private QName qnameAssocZones;
 
     private DictionaryService dictionaryService;
 
+    private PersonService personService;
+
     private SimpleCache> authorityLookupCache;
 
     public AuthorityDAOImpl()
@@ -79,6 +75,11 @@ public class AuthorityDAOImpl implements AuthorityDAO
         super();
     }
 
+    public void setStoreUrl(String storeUrl)
+    {
+        this.storeRef = new StoreRef(storeUrl);
+    }
+
     public void setDictionaryService(DictionaryService dictionaryService)
     {
         this.dictionaryService = dictionaryService;
@@ -89,6 +90,7 @@ public class AuthorityDAOImpl implements AuthorityDAO
         this.namespacePrefixResolver = namespacePrefixResolver;
         qnameAssocSystem = QName.createQName("sys", "system", namespacePrefixResolver);
         qnameAssocAuthorities = QName.createQName("sys", "authorities", namespacePrefixResolver);
+        qnameAssocZones = QName.createQName("sys", "zones", namespacePrefixResolver);
     }
 
     public void setNodeService(NodeService nodeService)
@@ -96,16 +98,16 @@ public class AuthorityDAOImpl implements AuthorityDAO
         this.nodeService = nodeService;
     }
 
-    public void setSearchService(SearchService searchService)
-    {
-        this.searchService = searchService;
-    }
-
     public void setUserToAuthorityCache(SimpleCache> userToAuthorityCache)
     {
         this.authorityLookupCache = userToAuthorityCache;
     }
 
+    public void setPersonService(PersonService personService)
+    {
+        this.personService = personService;
+    }
+
     public boolean authorityExists(String name)
     {
         NodeRef ref = getAuthorityOrNull(name);
@@ -119,51 +121,36 @@ public class AuthorityDAOImpl implements AuthorityDAO
         {
             throw new UnknownAuthorityException("An authority was not found for " + parentName);
         }
-        if (AuthorityType.getAuthorityType(childName).equals(AuthorityType.USER))
+        AuthorityType authorityType = AuthorityType.getAuthorityType(childName);
+        if (!authorityType.equals(AuthorityType.USER) && !authorityType.equals(AuthorityType.GROUP))
         {
-            Collection memberCollection = DefaultTypeConverter.INSTANCE.getCollection(String.class, nodeService.getProperty(parentRef, ContentModel.PROP_MEMBERS));
-            HashSet members = new HashSet();
-            members.addAll(memberCollection);
-            members.add(childName);
-            nodeService.setProperty(parentRef, ContentModel.PROP_MEMBERS, members);
-            // userToAuthorityCache.remove(childName);
-            authorityLookupCache.clear();
+            throw new AlfrescoRuntimeException("Authorities of the type " + AuthorityType.getAuthorityType(childName)
+                    + " may not be added to other authorities");
         }
-        else if (AuthorityType.getAuthorityType(childName).equals(AuthorityType.GROUP))
+        NodeRef childRef = getAuthorityOrNull(childName);
+        if (childRef == null)
         {
-            NodeRef childRef = getAuthorityOrNull(childName);
-            if (childRef == null)
-            {
-                throw new UnknownAuthorityException("An authority was not found for " + childName);
-            }
-            nodeService.addChild(parentRef, childRef, ContentModel.ASSOC_MEMBER, QName.createQName("usr", childName, namespacePrefixResolver));
-            authorityLookupCache.clear();
-        }
-        else
-        {
-            throw new AlfrescoRuntimeException("Authorities of the type " + AuthorityType.getAuthorityType(childName) + " may not be added to other authorities");
+            throw new UnknownAuthorityException("An authority was not found for " + childName);
         }
+        nodeService.addChild(parentRef, childRef, ContentModel.ASSOC_MEMBER, QName.createQName("cm", childName,
+                namespacePrefixResolver));
+        authorityLookupCache.clear();
     }
 
-    public void createAuthority(String parentName, String name, String authorityDisplayName)
+    public void createAuthority(String name, String authorityDisplayName, String authorityZone)
     {
         HashMap props = new HashMap();
         props.put(ContentModel.PROP_AUTHORITY_NAME, name);
         props.put(ContentModel.PROP_AUTHORITY_DISPLAY_NAME, authorityDisplayName);
-        if (parentName != null)
+        NodeRef childRef;
+        NodeRef authorityContainerRef = getAuthorityContainer();
+        childRef = nodeService.createNode(authorityContainerRef, ContentModel.ASSOC_CHILDREN,
+                QName.createQName("cm", name, namespacePrefixResolver), ContentModel.TYPE_AUTHORITY_CONTAINER, props)
+                .getChildRef();
+        if (authorityZone != null)
         {
-            NodeRef parentRef = getAuthorityOrNull(parentName);
-            if (parentRef == null)
-            {
-                throw new UnknownAuthorityException("An authority was not found for " + parentName);
-            }
-            nodeService.createNode(parentRef, ContentModel.ASSOC_MEMBER, QName.createQName("usr", name, namespacePrefixResolver), ContentModel.TYPE_AUTHORITY_CONTAINER, props);
-        }
-        else
-        {
-            NodeRef authorityContainerRef = getAuthorityContainer();
-            nodeService.createNode(authorityContainerRef, ContentModel.ASSOC_CHILDREN, QName.createQName("usr", name, namespacePrefixResolver),
-                    ContentModel.TYPE_AUTHORITY_CONTAINER, props);
+            nodeService.addChild(getOrCreateZone(authorityZone), childRef, ContentModel.ASSOC_IN_ZONE, QName
+                    .createQName("cm", name, namespacePrefixResolver));
         }
         authorityLookupCache.clear();
     }
@@ -181,77 +168,58 @@ public class AuthorityDAOImpl implements AuthorityDAO
 
     public Set getAllRootAuthorities(AuthorityType type)
     {
-        HashSet authorities = new HashSet();
-        NodeRef container = getAuthorityContainer();
-        if (container != null)
+        if (type != null && type.equals(AuthorityType.USER))
         {
-            findAuthorities(type, null, container, authorities, false, false, false);
+            return Collections. emptySet();
+        }
+        Set authorities = new HashSet();
+        for (NodeRef nodeRef : nodeService.getNodesWithoutParentAssocsOfType(this.storeRef,
+                ContentModel.TYPE_AUTHORITY_CONTAINER, ContentModel.ASSOC_MEMBER))
+        {
+            addAuthorityNameIfMatches(authorities, nodeRef, type, null);
         }
         return authorities;
     }
-
+    
     public Set getAllAuthorities(AuthorityType type)
     {
-        HashSet authorities = new HashSet();
-        NodeRef container = getAuthorityContainer();
-        if (container != null)
-        {
-            findAuthorities(type, null, container, authorities, false, true, false);
-        }
-        return authorities;
+        return findAuthorities(type, null);
     }
 
     public Set findAuthorities(AuthorityType type, String namePattern)
     {
-        String regExpString = SearchLanguageConversion.convert(SearchLanguageConversion.DEF_LUCENE, SearchLanguageConversion.DEF_REGEX, namePattern);
-        Pattern pattern = Pattern.compile(regExpString, Pattern.CASE_INSENSITIVE);
-        if ((type != null) && (type == AuthorityType.GROUP))
+        Pattern pattern = null;
+        if (namePattern != null)
         {
-            return findGroupsByLuceneQuery(namePattern, pattern);
+            String regExpString = SearchLanguageConversion.convert(SearchLanguageConversion.DEF_LUCENE,
+                    SearchLanguageConversion.DEF_REGEX, namePattern);
+            pattern = Pattern.compile(regExpString, Pattern.CASE_INSENSITIVE);
         }
         HashSet authorities = new HashSet();
-        NodeRef container = getAuthorityContainer();
-        if (container != null)
-        {
-            findAuthorities(type, pattern, container, authorities, false, true, false);
-        }
-        return authorities;
-    }
 
-    private Set findGroupsByLuceneQuery(String namePattern, Pattern pattern)
-    {
-        HashSet authorities = new HashSet();
-        SearchParameters sp = new SearchParameters();
-        sp.addStore(STOREREF_USERS);
-        sp.setLanguage("lucene");
-        sp.setQuery("+TYPE:\""
-                + ContentModel.TYPE_AUTHORITY_CONTAINER + "\"" + " +@"
-                + LuceneQueryParser.escape("{" + ContentModel.PROP_AUTHORITY_NAME.getNamespaceURI() + "}" + ISO9075.encode(ContentModel.PROP_AUTHORITY_NAME.getLocalName()))
-                + ":\"" + namePattern + "\"");
-        ResultSet rs = null;
-        try
+        // If users are included, we use the person service to determine the complete set of names
+        if (type == null || type.equals(AuthorityType.USER))
         {
-            rs = searchService.query(sp);
-
-            for (ResultSetRow row : rs)
+            for (NodeRef nodeRef : personService.getAllPeople())
             {
-                String test = DefaultTypeConverter.INSTANCE.convert(String.class, nodeService.getProperty(row.getNodeRef(), ContentModel.PROP_AUTHORITY_NAME));
-                Matcher m = pattern.matcher(test);
-                if (m.matches())
+                addAuthorityNameIfMatches(authorities, DefaultTypeConverter.INSTANCE.convert(String.class, nodeService.getProperty(nodeRef,
+                        ContentModel.PROP_USERNAME)), type, pattern);
+            }
+        }
+        
+        // For other types, we just look directly under the authority container
+        if (type == null || !type.equals(AuthorityType.USER))
+        {
+            NodeRef container = getAuthorityContainer();
+            if (container != null)
+            {
+                for (ChildAssociationRef childRef : nodeService.getChildAssocs(container))
                 {
-                    authorities.add(test);
+                    addAuthorityNameIfMatches(authorities, childRef.getQName().getLocalName(), type, pattern);
                 }
             }
-            return authorities;
         }
-        finally
-        {
-            if (rs != null)
-            {
-                rs.close();
-            }
-        }
-
+        return authorities;
     }
 
     public Set getContainedAuthorities(AuthorityType type, String name, boolean immediate)
@@ -267,7 +235,7 @@ public class AuthorityDAOImpl implements AuthorityDAO
             {
                 throw new UnknownAuthorityException("An authority was not found for " + name);
             }
-           
+
             CacheKey key = new CacheKey(type, name, false, !immediate);
 
             HashSet authorities = authorityLookupCache.get(key);
@@ -288,26 +256,13 @@ public class AuthorityDAOImpl implements AuthorityDAO
         {
             throw new UnknownAuthorityException("An authority was not found for " + parentName);
         }
-        if (AuthorityType.getAuthorityType(childName).equals(AuthorityType.USER))
+        NodeRef childRef = getAuthorityOrNull(childName);
+        if (childRef == null)
         {
-            Collection memberCollection = DefaultTypeConverter.INSTANCE.getCollection(String.class, nodeService.getProperty(parentRef, ContentModel.PROP_MEMBERS));
-            HashSet members = new HashSet();
-            members.addAll(memberCollection);
-            members.remove(childName);
-            nodeService.setProperty(parentRef, ContentModel.PROP_MEMBERS, members);
-            // userToAuthorityCache.remove(childName);
-            authorityLookupCache.clear();
-        }
-        else
-        {
-            NodeRef childRef = getAuthorityOrNull(childName);
-            if (childRef == null)
-            {
-                throw new UnknownAuthorityException("An authority was not found for " + childName);
-            }
-            nodeService.removeChild(parentRef, childRef);
-            authorityLookupCache.clear();
+            throw new UnknownAuthorityException("An authority was not found for " + childName);
         }
+        nodeService.removeChild(parentRef, childRef);
+        authorityLookupCache.clear();
     }
 
     public Set getContainingAuthorities(AuthorityType type, String name, boolean immediate)
@@ -325,42 +280,38 @@ public class AuthorityDAOImpl implements AuthorityDAO
 
     }
 
-    private void findAuthorities(AuthorityType type, String name, Set authorities, boolean parents, boolean recursive)
+    private void addAuthorityNameIfMatches(Set authorities, NodeRef nodeRef, AuthorityType type, Pattern pattern)
+    {
+        addAuthorityNameIfMatches(authorities, DefaultTypeConverter.INSTANCE.convert(String.class, nodeService.getProperty(nodeRef,
+                ContentModel.PROP_AUTHORITY_NAME)), type, pattern);
+    }
+
+    private void addAuthorityNameIfMatches(Set authorities, String authorityName, AuthorityType type, Pattern pattern)
+    {
+        if (type == null || AuthorityType.getAuthorityType(authorityName).equals(type))
+        {
+            if (pattern == null)
+            {
+                authorities.add(authorityName);
+            }
+            else
+            {
+                Matcher m = pattern.matcher(authorityName);
+                if (m.matches())
+                {
+                    authorities.add(authorityName);
+                }
+            }
+        }
+    }
+
+    private void findAuthorities(AuthorityType type, String name, Set authorities, boolean parents,
+            boolean recursive)
     {
         if (AuthorityType.getAuthorityType(name).equals(AuthorityType.GUEST))
         {
             // Nothing to do
         }
-        else if (AuthorityType.getAuthorityType(name).equals(AuthorityType.USER))
-        {
-            if (parents)
-            {
-                for (NodeRef ref : getUserContainers(name))
-                {
-                    if (recursive)
-                    {
-                        findAuthorities(type, null, ref, authorities, parents, recursive, true);
-                    }
-                    else
-                    {
-                        String authorityName = DefaultTypeConverter.INSTANCE.convert(String.class, nodeService.getProperty(ref, ContentModel.PROP_AUTHORITY_NAME));
-                        if (type == null)
-                        {
-                            authorities.add(authorityName);
-                        }
-                        else
-                        {
-                            AuthorityType authorityType = AuthorityType.getAuthorityType(authorityName);
-                            if (authorityType.equals(type))
-                            {
-                                authorities.add(authorityName);
-                            }
-                        }
-                    }
-                }
-            }
-        }
-
         else
         {
             NodeRef ref = getAuthorityOrNull(name);
@@ -375,229 +326,52 @@ public class AuthorityDAOImpl implements AuthorityDAO
         }
     }
 
-    private ArrayList getUserContainers(String name)
+    private void findAuthorities(AuthorityType type, Pattern pattern, NodeRef nodeRef, Set authorities,
+            boolean parents, boolean recursive, boolean includeNode)
     {
-        ArrayList containers = findUserContainers(name);
-        return containers;
-    }
+        QName currentType = nodeService.getType(nodeRef);
+        boolean isAuthority = dictionaryService.isSubClass(currentType, ContentModel.TYPE_AUTHORITY);
 
-    private ArrayList findUserContainers(String name)
-    {
-        SearchParameters sp = new SearchParameters();
-        sp.addStore(STOREREF_USERS);
-        sp.setLanguage("lucene");
-        sp.setQuery("+TYPE:\""
-                + ContentModel.TYPE_AUTHORITY_CONTAINER + "\"" + " +@"
-                + LuceneQueryParser.escape("{" + ContentModel.PROP_MEMBERS.getNamespaceURI() + "}" + ISO9075.encode(ContentModel.PROP_MEMBERS.getLocalName())) + ":\"" + name
-                + "\"");
-        ResultSet rs = null;
-        try
+        if (includeNode && isAuthority)
         {
-            rs = searchService.query(sp);
-            ArrayList answer = new ArrayList(rs.length());
-            for (ResultSetRow row : rs)
-            {
-                answer.add(row.getNodeRef());
-            }
-            return answer;
-        }
-        finally
-        {
-            if (rs != null)
-            {
-                rs.close();
-            }
+            String authorityName = DefaultTypeConverter.INSTANCE.convert(String.class, nodeService
+                    .getProperty(nodeRef, dictionaryService.isSubClass(currentType,
+                            ContentModel.TYPE_AUTHORITY_CONTAINER) ? ContentModel.PROP_AUTHORITY_NAME
+                            : ContentModel.PROP_USERNAME));
+            addAuthorityNameIfMatches(authorities, authorityName, type, pattern);
         }
 
-    }
-
-    private void findAuthorities(AuthorityType type, Pattern pattern, NodeRef nodeRef, Set authorities, boolean parents, boolean recursive, boolean includeNode)
-    {
-        List cars = parents ? nodeService.getParentAssocs(nodeRef) : nodeService.getChildAssocs(nodeRef);
-
-        if (includeNode)
+        // Loop over children if we want immediate children or are in recursive mode
+        if (!includeNode || (recursive && isAuthority))
         {
-            String authorityName = DefaultTypeConverter.INSTANCE.convert(String.class, nodeService.getProperty(nodeRef, ContentModel.PROP_AUTHORITY_NAME));
-            if (type == null)
-            {
-                if (pattern == null)
-                {
-                    authorities.add(authorityName);
-                }
-                else
-                {
-                    Matcher m = pattern.matcher(authorityName);
-                    if (m.matches())
-                    {
-                        authorities.add(authorityName);
-                    }
-                }
-            }
-            else
-            {
-                AuthorityType authorityType = AuthorityType.getAuthorityType(authorityName);
-                if (authorityType.equals(type))
-                {
-                    if (pattern == null)
-                    {
-                        authorities.add(authorityName);
-                    }
-                    else
-                    {
-                        Matcher m = pattern.matcher(authorityName);
-                        if (m.matches())
-                        {
-                            authorities.add(authorityName);
-                        }
-                    }
-                }
-            }
-        }
+            List cars = parents ? nodeService.getParentAssocs(nodeRef, ContentModel.ASSOC_MEMBER,
+                    RegexQNamePattern.MATCH_ALL) : nodeService.getChildAssocs(nodeRef);
 
-        // Loop over children
-        for (ChildAssociationRef car : cars)
-        {
-            NodeRef current = parents ? car.getParentRef() : car.getChildRef();
-            QName currentType = nodeService.getType(current);
-            if (dictionaryService.isSubClass(currentType, ContentModel.TYPE_AUTHORITY))
+            for (ChildAssociationRef car : cars)
             {
-
-                String authorityName = DefaultTypeConverter.INSTANCE.convert(String.class, nodeService.getProperty(current, ContentModel.PROP_AUTHORITY_NAME));
-
-                if (type == null)
-                {
-                    if (pattern == null)
-                    {
-                        authorities.add(authorityName);
-                    }
-                    else
-                    {
-                        Matcher m = pattern.matcher(authorityName);
-                        if (m.matches())
-                        {
-                            authorities.add(authorityName);
-                        }
-                    }
-                    if (recursive)
-                    {
-                        findAuthorities(type, pattern, current, authorities, parents, recursive, false);
-                    }
-                }
-                else
-                {
-                    AuthorityType authorityType = AuthorityType.getAuthorityType(authorityName);
-                    if (authorityType.equals(type))
-                    {
-                        if (pattern == null)
-                        {
-                            authorities.add(authorityName);
-                        }
-                        else
-                        {
-                            Matcher m = pattern.matcher(authorityName);
-                            if (m.matches())
-                            {
-                                authorities.add(authorityName);
-                            }
-                        }
-                    }
-                    if (recursive)
-                    {
-                        findAuthorities(type, pattern, current, authorities, parents, recursive, false);
-                    }
-                }
-            }
-        }
-        // loop over properties
-        if (!parents)
-        {
-            Collection members = DefaultTypeConverter.INSTANCE.getCollection(String.class, nodeService.getProperty(nodeRef, ContentModel.PROP_MEMBERS));
-            if (members != null)
-            {
-                for (String user : members)
-                {
-                    if (user != null)
-                    {
-                        if (type == null)
-                        {
-                            if (pattern == null)
-                            {
-                                authorities.add(user);
-                            }
-                            else
-                            {
-                                Matcher m = pattern.matcher(user);
-                                if (m.matches())
-                                {
-                                    authorities.add(user);
-                                    ;
-                                }
-                            }
-                        }
-                        else
-                        {
-                            AuthorityType authorityType = AuthorityType.getAuthorityType(user);
-                            if (authorityType.equals(type))
-                            {
-                                if (pattern == null)
-                                {
-                                    authorities.add(user);
-                                }
-                                else
-                                {
-                                    Matcher m = pattern.matcher(user);
-                                    if (m.matches())
-                                    {
-                                        authorities.add(user);
-                                        ;
-                                    }
-                                }
-                            }
-                        }
-                    }
-                }
+                NodeRef current = parents ? car.getParentRef() : car.getChildRef();
+                findAuthorities(type, pattern, current, authorities, parents, recursive, true);
             }
         }
     }
 
+
     private NodeRef getAuthorityOrNull(String name)
     {
-        SearchParameters sp = new SearchParameters();
-        sp.addStore(STOREREF_USERS);
-        sp.setLanguage("lucene");
-        sp.setQuery("+TYPE:\""
-                + ContentModel.TYPE_AUTHORITY_CONTAINER + "\"" + " +@"
-                + LuceneQueryParser.escape("{" + ContentModel.PROP_AUTHORITY_NAME.getNamespaceURI() + "}" + ISO9075.encode(ContentModel.PROP_AUTHORITY_NAME.getLocalName()))
-                + ":\"" + name + "\"");
-        ResultSet rs = null;
-        try
+        if (AuthorityType.getAuthorityType(name).equals(AuthorityType.USER))
         {
-            rs = searchService.query(sp);
-            if (rs.length() == 0)
+            if (!personService.personExists(name))
             {
                 return null;
             }
-            else
-            {
-                for (ResultSetRow row : rs)
-                {
-                    String test = DefaultTypeConverter.INSTANCE.convert(String.class, nodeService.getProperty(row.getNodeRef(), ContentModel.PROP_AUTHORITY_NAME));
-                    if (test.equals(name))
-                    {
-                        return row.getNodeRef();
-                    }
-                }
-            }
-            return null;
+            return personService.getPerson(name);
         }
-        finally
+        else
         {
-            if (rs != null)
-            {
-                rs.close();
-            }
+            List results = nodeService.getChildAssocs(getAuthorityContainer(),
+                    ContentModel.ASSOC_CHILDREN, QName.createQName("cm", name, namespacePrefixResolver));
+            return results.isEmpty() ? null : results.get(0).getChildRef();
         }
-
     }
 
     /**
@@ -605,22 +379,36 @@ public class AuthorityDAOImpl implements AuthorityDAO
      */
     private NodeRef getAuthorityContainer()
     {
-        NodeRef rootNodeRef = nodeService.getRootNode(STOREREF_USERS);
-        List results = nodeService.getChildAssocs(rootNodeRef, RegexQNamePattern.MATCH_ALL, qnameAssocSystem);
+        return getSystemContainer(qnameAssocAuthorities);
+    }
+
+    /**
+     * @return Returns the zone container, which must exist
+     */
+    private NodeRef getZoneContainer()
+    {
+        return getSystemContainer(qnameAssocZones);
+    }
+
+    private NodeRef getSystemContainer(QName assocQName)
+    {
+        NodeRef rootNodeRef = nodeService.getRootNode(this.storeRef);
+        List results = nodeService.getChildAssocs(rootNodeRef, RegexQNamePattern.MATCH_ALL,
+                qnameAssocSystem);
         NodeRef sysNodeRef = null;
         if (results.size() == 0)
         {
-            throw new AlfrescoRuntimeException("Required authority system path not found: " + qnameAssocSystem);
+            throw new AlfrescoRuntimeException("Required system path not found: " + qnameAssocSystem);
         }
         else
         {
             sysNodeRef = results.get(0).getChildRef();
         }
-        results = nodeService.getChildAssocs(sysNodeRef, RegexQNamePattern.MATCH_ALL, qnameAssocAuthorities);
+        results = nodeService.getChildAssocs(sysNodeRef, RegexQNamePattern.MATCH_ALL, assocQName);
         NodeRef authNodeRef = null;
         if (results.size() == 0)
         {
-            throw new AlfrescoRuntimeException("Required authority path not found: " + qnameAssocAuthorities);
+            throw new AlfrescoRuntimeException("Required path not found: " + assocQName);
         }
         else
         {
@@ -644,9 +432,9 @@ public class AuthorityDAOImpl implements AuthorityDAO
             {
                 name = (String) nodeService.getProperty(authorityRef, ContentModel.PROP_AUTHORITY_NAME);
             }
-            else if (type.equals(ContentModel.TYPE_AUTHORITY))
+            else if (type.equals(ContentModel.TYPE_PERSON))
             {
-                name = (String) nodeService.getProperty(authorityRef, ContentModel.PROP_USER_USERNAME);
+                name = (String) nodeService.getProperty(authorityRef, ContentModel.PROP_USERNAME);
             }
         }
         return name;
@@ -678,6 +466,59 @@ public class AuthorityDAOImpl implements AuthorityDAO
 
     }
 
+    public NodeRef getOrCreateZone(String zoneName)
+    {
+        NodeRef zoneContainerRef = getZoneContainer();
+        QName zoneQName = QName.createQName("cm", zoneName, namespacePrefixResolver);
+        List results = nodeService.getChildAssocs(zoneContainerRef, ContentModel.ASSOC_CHILDREN,
+                zoneQName);
+        if (results.isEmpty())
+        {
+            HashMap props = new HashMap();
+            props.put(ContentModel.PROP_NAME, zoneName);
+            return nodeService.createNode(zoneContainerRef, ContentModel.ASSOC_CHILDREN, zoneQName,
+                    ContentModel.TYPE_ZONE, props).getChildRef();
+        }
+        else
+        {
+            return results.get(0).getChildRef();
+        }
+    }
+
+    public String getAuthorityZone(String name)
+    {
+        NodeRef childRef = getAuthorityOrNull(name);
+        if (childRef == null)
+        {
+            return null;
+        }
+        List results = nodeService.getParentAssocs(childRef, ContentModel.ASSOC_IN_ZONE,
+                RegexQNamePattern.MATCH_ALL);
+        if (results.isEmpty())
+        {
+    
+            return AuthorityService.DEFAULT_ZONE;
+        }
+        NodeRef zoneRef = results.get(0).getParentRef();
+        Serializable value = nodeService.getProperty(zoneRef, ContentModel.PROP_NAME);
+        if (value == null)
+        {
+            return null;
+        }
+        return DefaultTypeConverter.INSTANCE.convert(String.class, value);
+    }
+    
+    public Set getAllAuthoritiesInZone(String zoneName, AuthorityType type)
+    {
+        HashSet authorities = new HashSet();
+        NodeRef zoneRef = getOrCreateZone(zoneName);
+        for (ChildAssociationRef childRef : nodeService.getChildAssocs(zoneRef))
+        {
+            addAuthorityNameIfMatches(authorities, childRef.getQName().getLocalName(), type, null);
+        }
+        return authorities;
+    }
+
     private static class CacheKey implements Serializable
     {
         /**
diff --git a/source/java/org/alfresco/repo/security/authority/AuthorityServiceImpl.java b/source/java/org/alfresco/repo/security/authority/AuthorityServiceImpl.java
index 5a4250ecae..5f6bc3b724 100644
--- a/source/java/org/alfresco/repo/security/authority/AuthorityServiceImpl.java
+++ b/source/java/org/alfresco/repo/security/authority/AuthorityServiceImpl.java
@@ -288,9 +288,9 @@ public class AuthorityServiceImpl implements AuthorityService, InitializingBean
         }
     }
     
-    public String createAuthority(AuthorityType type, String parentName, String shortName)
+    public String createAuthority(AuthorityType type, String shortName)
     {
-        return createAuthority(type, parentName, shortName, shortName);
+        return createAuthority(type, shortName, shortName, null);
     }
 
     public void deleteAuthority(String name)
@@ -360,11 +360,12 @@ public class AuthorityServiceImpl implements AuthorityService, InitializingBean
        return authorityDAO.authorityExists(name);
     }
 
-    public String createAuthority(AuthorityType type, String parentName, String shortName, String authorityDisplayName)
+    public String createAuthority(AuthorityType type, String shortName, String authorityDisplayName,
+            String authorityZone)
     {
         checkTypeIsMutable(type);
         String name = getName(type, shortName);
-        authorityDAO.createAuthority(parentName, name, authorityDisplayName);
+        authorityDAO.createAuthority(name, authorityDisplayName, authorityZone);
         return name;
     }
 
@@ -385,4 +386,18 @@ public class AuthorityServiceImpl implements AuthorityService, InitializingBean
         authorityDAO.setAuthorityDisplayName(authorityName, authorityDisplayName);
     }
 
+    public String getAuthorityZone(String name)
+    {
+        return authorityDAO.getAuthorityZone(name);
+    }
+
+    public NodeRef getOrCreateZone(String zoneName)
+    {
+        return authorityDAO.getOrCreateZone(zoneName);
+    }
+
+    public Set getAllAuthoritiesInZone(String zoneName, AuthorityType type)
+    {
+        return authorityDAO.getAllAuthoritiesInZone(zoneName, type);
+    }
 }
diff --git a/source/java/org/alfresco/repo/security/authority/AuthorityServiceTest.java b/source/java/org/alfresco/repo/security/authority/AuthorityServiceTest.java
index b650a01fc7..701b248f38 100644
--- a/source/java/org/alfresco/repo/security/authority/AuthorityServiceTest.java
+++ b/source/java/org/alfresco/repo/security/authority/AuthorityServiceTest.java
@@ -171,7 +171,7 @@ public class AuthorityServiceTest extends TestCase
                 {
                     StringBuilder name = new StringBuilder();
                     name.append("__").append(i).append(j).append(k);
-                    pubAuthorityService.createAuthority(AuthorityType.GROUP, null, name.toString());
+                    pubAuthorityService.createAuthority(AuthorityType.GROUP, name.toString());
                 }
             }
         }
@@ -239,7 +239,7 @@ public class AuthorityServiceTest extends TestCase
     {
         try
         {
-            pubAuthorityService.createAuthority(AuthorityType.ADMIN, null, "woof");
+            pubAuthorityService.createAuthority(AuthorityType.ADMIN, "woof");
             fail("Should not be able to create an admin authority");
         }
         catch (AuthorityException ae)
@@ -252,7 +252,7 @@ public class AuthorityServiceTest extends TestCase
     {
         try
         {
-            pubAuthorityService.createAuthority(AuthorityType.EVERYONE, null, "woof");
+            pubAuthorityService.createAuthority(AuthorityType.EVERYONE, "woof");
             fail("Should not be able to create an everyone authority");
         }
         catch (AuthorityException ae)
@@ -265,7 +265,7 @@ public class AuthorityServiceTest extends TestCase
     {
         try
         {
-            pubAuthorityService.createAuthority(AuthorityType.GUEST, null, "woof");
+            pubAuthorityService.createAuthority(AuthorityType.GUEST, "woof");
             fail("Should not be able to create an guest authority");
         }
         catch (AuthorityException ae)
@@ -278,7 +278,7 @@ public class AuthorityServiceTest extends TestCase
     {
         try
         {
-            pubAuthorityService.createAuthority(AuthorityType.OWNER, null, "woof");
+            pubAuthorityService.createAuthority(AuthorityType.OWNER, "woof");
             fail("Should not be able to create an owner authority");
         }
         catch (AuthorityException ae)
@@ -291,7 +291,7 @@ public class AuthorityServiceTest extends TestCase
     {
         try
         {
-            pubAuthorityService.createAuthority(AuthorityType.USER, null, "woof");
+            pubAuthorityService.createAuthority(AuthorityType.USER, "woof");
             fail("Should not be able to create an user authority");
         }
         catch (AuthorityException ae)
@@ -306,7 +306,7 @@ public class AuthorityServiceTest extends TestCase
 
         assertEquals(2, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size());
         assertEquals(2, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size());
-        auth = pubAuthorityService.createAuthority(AuthorityType.GROUP, null, "woof");
+        auth = pubAuthorityService.createAuthority(AuthorityType.GROUP, "woof");
         assertEquals(3, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size());
         assertEquals(3, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size());
         pubAuthorityService.deleteAuthority(auth);
@@ -315,7 +315,7 @@ public class AuthorityServiceTest extends TestCase
 
         assertEquals(0, pubAuthorityService.getAllAuthorities(AuthorityType.ROLE).size());
         assertEquals(0, pubAuthorityService.getAllRootAuthorities(AuthorityType.ROLE).size());
-        auth = pubAuthorityService.createAuthority(AuthorityType.ROLE, null, "woof");
+        auth = pubAuthorityService.createAuthority(AuthorityType.ROLE, "woof");
         assertEquals(1, pubAuthorityService.getAllAuthorities(AuthorityType.ROLE).size());
         assertEquals(1, pubAuthorityService.getAllRootAuthorities(AuthorityType.ROLE).size());
         pubAuthorityService.deleteAuthority(auth);
@@ -334,20 +334,23 @@ public class AuthorityServiceTest extends TestCase
         assertFalse(pubAuthorityService.authorityExists(pubAuthorityService.getName(AuthorityType.GROUP, "one")));
         assertEquals(2, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size());
         assertEquals(2, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size());
-        auth1 = pubAuthorityService.createAuthority(AuthorityType.GROUP, null, "one");
+        auth1 = pubAuthorityService.createAuthority(AuthorityType.GROUP, "one");
         assertTrue(pubAuthorityService.authorityExists(auth1));
         assertEquals(3, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size());
         assertEquals(3, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size());
-        auth2 = pubAuthorityService.createAuthority(AuthorityType.GROUP, null, "two");
+        auth2 = pubAuthorityService.createAuthority(AuthorityType.GROUP, "two");
         assertEquals(4, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size());
         assertEquals(4, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size());
-        auth3 = pubAuthorityService.createAuthority(AuthorityType.GROUP, auth1, "three");
+        auth3 = pubAuthorityService.createAuthority(AuthorityType.GROUP, "three");
+        pubAuthorityService.addAuthority(auth1, auth3);
         assertEquals(5, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size());
         assertEquals(4, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size());
-        auth4 = pubAuthorityService.createAuthority(AuthorityType.GROUP, auth1, "four");
+        auth4 = pubAuthorityService.createAuthority(AuthorityType.GROUP, "four");
+        pubAuthorityService.addAuthority(auth1, auth4);
         assertEquals(6, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size());
         assertEquals(4, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size());
-        auth5 = pubAuthorityService.createAuthority(AuthorityType.GROUP, auth2, "five");
+        auth5 = pubAuthorityService.createAuthority(AuthorityType.GROUP, "five");
+        pubAuthorityService.addAuthority(auth2, auth5);
         assertEquals(7, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size());
         assertEquals(4, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size());
 
@@ -369,19 +372,22 @@ public class AuthorityServiceTest extends TestCase
 
         assertEquals(0, pubAuthorityService.getAllAuthorities(AuthorityType.ROLE).size());
         assertEquals(0, pubAuthorityService.getAllRootAuthorities(AuthorityType.ROLE).size());
-        auth1 = pubAuthorityService.createAuthority(AuthorityType.ROLE, null, "one");
+        auth1 = pubAuthorityService.createAuthority(AuthorityType.ROLE, "one");
         assertEquals(1, pubAuthorityService.getAllAuthorities(AuthorityType.ROLE).size());
         assertEquals(1, pubAuthorityService.getAllRootAuthorities(AuthorityType.ROLE).size());
-        auth2 = pubAuthorityService.createAuthority(AuthorityType.ROLE, null, "two");
+        auth2 = pubAuthorityService.createAuthority(AuthorityType.ROLE, "two");
         assertEquals(2, pubAuthorityService.getAllAuthorities(AuthorityType.ROLE).size());
         assertEquals(2, pubAuthorityService.getAllRootAuthorities(AuthorityType.ROLE).size());
-        auth3 = pubAuthorityService.createAuthority(AuthorityType.ROLE, auth1, "three");
+        auth3 = pubAuthorityService.createAuthority(AuthorityType.ROLE, "three");
+        pubAuthorityService.addAuthority(auth1, auth3);
         assertEquals(3, pubAuthorityService.getAllAuthorities(AuthorityType.ROLE).size());
         assertEquals(2, pubAuthorityService.getAllRootAuthorities(AuthorityType.ROLE).size());
-        auth4 = pubAuthorityService.createAuthority(AuthorityType.ROLE, auth1, "four");
+        auth4 = pubAuthorityService.createAuthority(AuthorityType.ROLE, "four");
+        pubAuthorityService.addAuthority(auth1, auth4);
         assertEquals(4, pubAuthorityService.getAllAuthorities(AuthorityType.ROLE).size());
         assertEquals(2, pubAuthorityService.getAllRootAuthorities(AuthorityType.ROLE).size());
-        auth5 = pubAuthorityService.createAuthority(AuthorityType.ROLE, auth2, "five");
+        auth5 = pubAuthorityService.createAuthority(AuthorityType.ROLE, "five");
+        pubAuthorityService.addAuthority(auth2, auth5);
         assertEquals(5, pubAuthorityService.getAllAuthorities(AuthorityType.ROLE).size());
         assertEquals(2, pubAuthorityService.getAllRootAuthorities(AuthorityType.ROLE).size());
 
@@ -424,23 +430,26 @@ public class AuthorityServiceTest extends TestCase
 
         assertEquals(2, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size());
         assertEquals(2, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size());
-        auth1 = pubAuthorityService.createAuthority(AuthorityType.GROUP, null, "one");
+        auth1 = pubAuthorityService.createAuthority(AuthorityType.GROUP, "one");
         assertEquals("GROUP_one", auth1);
         assertEquals(3, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size());
         assertEquals(3, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size());
-        auth2 = pubAuthorityService.createAuthority(AuthorityType.GROUP, null, "two");
+        auth2 = pubAuthorityService.createAuthority(AuthorityType.GROUP, "two");
         assertEquals("GROUP_two", auth2);
         assertEquals(4, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size());
         assertEquals(4, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size());
-        auth3 = pubAuthorityService.createAuthority(AuthorityType.GROUP, auth1, "three");
+        auth3 = pubAuthorityService.createAuthority(AuthorityType.GROUP, "three");
+        pubAuthorityService.addAuthority(auth1, auth3);
         assertEquals("GROUP_three", auth3);
         assertEquals(5, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size());
         assertEquals(4, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size());
-        auth4 = pubAuthorityService.createAuthority(AuthorityType.GROUP, auth1, "four");
+        auth4 = pubAuthorityService.createAuthority(AuthorityType.GROUP, "four");
+        pubAuthorityService.addAuthority(auth1, auth4);
         assertEquals("GROUP_four", auth4);
         assertEquals(6, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size());
         assertEquals(4, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size());
-        auth5 = pubAuthorityService.createAuthority(AuthorityType.GROUP, auth2, "five");
+        auth5 = pubAuthorityService.createAuthority(AuthorityType.GROUP, "five");
+        pubAuthorityService.addAuthority(auth2, auth5);
         assertEquals("GROUP_five", auth5);
         assertEquals(7, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size());
         assertEquals(4, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size());
@@ -491,19 +500,22 @@ public class AuthorityServiceTest extends TestCase
 
         assertEquals(2, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size());
         assertEquals(2, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size());
-        auth1 = pubAuthorityService.createAuthority(AuthorityType.GROUP, null, "one");
+        auth1 = pubAuthorityService.createAuthority(AuthorityType.GROUP, "one");
         assertEquals(3, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size());
         assertEquals(3, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size());
-        auth2 = pubAuthorityService.createAuthority(AuthorityType.GROUP, null, "two");
+        auth2 = pubAuthorityService.createAuthority(AuthorityType.GROUP, "two");
         assertEquals(4, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size());
         assertEquals(4, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size());
-        auth3 = pubAuthorityService.createAuthority(AuthorityType.GROUP, auth1, "three");
+        auth3 = pubAuthorityService.createAuthority(AuthorityType.GROUP, "three");
+        pubAuthorityService.addAuthority(auth1, auth3);
         assertEquals(5, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size());
         assertEquals(4, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size());
-        auth4 = pubAuthorityService.createAuthority(AuthorityType.GROUP, auth1, "four");
+        auth4 = pubAuthorityService.createAuthority(AuthorityType.GROUP, "four");
+        pubAuthorityService.addAuthority(auth1, auth4);
         assertEquals(6, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size());
         assertEquals(4, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size());
-        auth5 = pubAuthorityService.createAuthority(AuthorityType.GROUP, auth2, "five");
+        auth5 = pubAuthorityService.createAuthority(AuthorityType.GROUP, "five");
+        pubAuthorityService.addAuthority(auth2, auth5);
         assertEquals(7, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size());
         assertEquals(4, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size());
 
@@ -558,19 +570,22 @@ public class AuthorityServiceTest extends TestCase
 
         assertEquals(2, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size());
         assertEquals(2, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size());
-        auth1 = pubAuthorityService.createAuthority(AuthorityType.GROUP, null, "one");
+        auth1 = pubAuthorityService.createAuthority(AuthorityType.GROUP, "one");
         assertEquals(3, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size());
         assertEquals(3, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size());
-        auth2 = pubAuthorityService.createAuthority(AuthorityType.GROUP, null, "two");
+        auth2 = pubAuthorityService.createAuthority(AuthorityType.GROUP, "two");
         assertEquals(4, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size());
         assertEquals(4, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size());
-        auth3 = pubAuthorityService.createAuthority(AuthorityType.GROUP, auth1, "three");
+        auth3 = pubAuthorityService.createAuthority(AuthorityType.GROUP, "three");
+        pubAuthorityService.addAuthority(auth1, auth3);
         assertEquals(5, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size());
         assertEquals(4, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size());
-        auth4 = pubAuthorityService.createAuthority(AuthorityType.GROUP, auth1, "four");
+        auth4 = pubAuthorityService.createAuthority(AuthorityType.GROUP, "four");
+        pubAuthorityService.addAuthority(auth1, auth4);
         assertEquals(6, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size());
         assertEquals(4, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size());
-        auth5 = pubAuthorityService.createAuthority(AuthorityType.GROUP, auth2, "five");
+        auth5 = pubAuthorityService.createAuthority(AuthorityType.GROUP, "five");
+        pubAuthorityService.addAuthority(auth2, auth5);
         assertEquals(7, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size());
         assertEquals(4, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size());
 
@@ -629,20 +644,25 @@ public class AuthorityServiceTest extends TestCase
         personService.getPerson("andy6");
         assertEquals(2, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size());
         assertEquals(2, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size());
-        String auth1 = pubAuthorityService.createAuthority(AuthorityType.GROUP, null, "one");
+        String auth1 = pubAuthorityService.createAuthority(AuthorityType.GROUP, "one");
         pubAuthorityService.addAuthority(auth1, "andy1");
-        String auth2 = pubAuthorityService.createAuthority(AuthorityType.GROUP, auth1, "two");
+        String auth2 = pubAuthorityService.createAuthority(AuthorityType.GROUP, "two");
+        pubAuthorityService.addAuthority(auth1, auth2);
         pubAuthorityService.addAuthority(auth2, "andy1");
         pubAuthorityService.addAuthority(auth2, "andy2");
-        String auth3 = pubAuthorityService.createAuthority(AuthorityType.GROUP, auth2, "three");
+        String auth3 = pubAuthorityService.createAuthority(AuthorityType.GROUP, "three");
+        pubAuthorityService.addAuthority(auth2, auth3);
         pubAuthorityService.addAuthority(auth3, "andy3");
-        String auth4 = pubAuthorityService.createAuthority(AuthorityType.GROUP, auth3, "four");
+        String auth4 = pubAuthorityService.createAuthority(AuthorityType.GROUP, "four");
+        pubAuthorityService.addAuthority(auth3, auth4);
         pubAuthorityService.addAuthority(auth4, "andy1");
         pubAuthorityService.addAuthority(auth4, "andy4");
-        String auth5 = pubAuthorityService.createAuthority(AuthorityType.GROUP, auth4, "five");
+        String auth5 = pubAuthorityService.createAuthority(AuthorityType.GROUP, "five");
+        pubAuthorityService.addAuthority(auth4, auth5);
         pubAuthorityService.addAuthority(auth5, "andy1");
         pubAuthorityService.addAuthority(auth5, "andy5");
-        String auth6 = pubAuthorityService.createAuthority(AuthorityType.GROUP, auth3, "six");
+        String auth6 = pubAuthorityService.createAuthority(AuthorityType.GROUP, "six");
+        pubAuthorityService.addAuthority(auth3, auth6);
         pubAuthorityService.addAuthority(auth6, "andy1");
         pubAuthorityService.addAuthority(auth6, "andy5");
         pubAuthorityService.addAuthority(auth6, "andy6");
@@ -802,17 +822,17 @@ public class AuthorityServiceTest extends TestCase
 
         assertEquals(2, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size());
         assertEquals(2, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size());
-        String auth1 = pubAuthorityService.createAuthority(AuthorityType.GROUP, null, "one");
+        String auth1 = pubAuthorityService.createAuthority(AuthorityType.GROUP, "one");
         pubAuthorityService.addAuthority(auth1, "1234");
-        String auth2 = pubAuthorityService.createAuthority(AuthorityType.GROUP, null, "two");
+        String auth2 = pubAuthorityService.createAuthority(AuthorityType.GROUP, "two");
         pubAuthorityService.addAuthority(auth2, "andy");
-        String auth3 = pubAuthorityService.createAuthority(AuthorityType.GROUP, null, "three");
+        String auth3 = pubAuthorityService.createAuthority(AuthorityType.GROUP, "three");
         pubAuthorityService.addAuthority(auth3, "Novalike");
-        String auth4 = pubAuthorityService.createAuthority(AuthorityType.GROUP, null, "four");
+        String auth4 = pubAuthorityService.createAuthority(AuthorityType.GROUP, "four");
         pubAuthorityService.addAuthority(auth4, "1andy");
-        String auth5 = pubAuthorityService.createAuthority(AuthorityType.GROUP, null, "five");
+        String auth5 = pubAuthorityService.createAuthority(AuthorityType.GROUP, "five");
         pubAuthorityService.addAuthority(auth5, "andy2");
-        String auth6 = pubAuthorityService.createAuthority(AuthorityType.GROUP, null, "six");
+        String auth6 = pubAuthorityService.createAuthority(AuthorityType.GROUP, "six");
         pubAuthorityService.addAuthority(auth6, "an3dy");
 
         assertEquals(1, pubAuthorityService.getContainedAuthorities(null, auth1, true).size());
@@ -848,21 +868,25 @@ public class AuthorityServiceTest extends TestCase
         assertEquals(2, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size());
         assertEquals(2, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size());
 
-        String auth1234 = pubAuthorityService.createAuthority(AuthorityType.GROUP, null, "1234");
+        String auth1234 = pubAuthorityService.createAuthority(AuthorityType.GROUP, "1234");
         assertEquals(0, pubAuthorityService.getContainedAuthorities(AuthorityType.GROUP, auth1234, false).size());
-        String authC1 = pubAuthorityService.createAuthority(AuthorityType.GROUP, auth1234, "circle");
+        String authC1 = pubAuthorityService.createAuthority(AuthorityType.GROUP, "circle");
+        pubAuthorityService.addAuthority(auth1234, authC1);
         assertEquals(1, pubAuthorityService.getContainedAuthorities(AuthorityType.GROUP, auth1234, false).size());
         assertEquals(0, pubAuthorityService.getContainedAuthorities(AuthorityType.GROUP, authC1, false).size());
-        String authC2 = pubAuthorityService.createAuthority(AuthorityType.GROUP, authC1, "bigCircle");
+        String authC2 = pubAuthorityService.createAuthority(AuthorityType.GROUP, "bigCircle");
+        pubAuthorityService.addAuthority(authC1, authC2);
         assertEquals(2, pubAuthorityService.getContainedAuthorities(AuthorityType.GROUP, auth1234, false).size());
         assertEquals(1, pubAuthorityService.getContainedAuthorities(AuthorityType.GROUP, authC1, false).size());
         assertEquals(0, pubAuthorityService.getContainedAuthorities(AuthorityType.GROUP, authC2, false).size());
-        String authStuff = pubAuthorityService.createAuthority(AuthorityType.GROUP, authC2, "|<>?~@:}{+_)(*&^%$£!¬`,./#';][=-0987654321 1234556678 '");
+        String authStuff = pubAuthorityService.createAuthority(AuthorityType.GROUP, "|<>?~@:}{+_)(*&^%$£!¬`,./#';][=-0987654321 1234556678 '");
+        pubAuthorityService.addAuthority(authC2, authStuff);
         assertEquals(3, pubAuthorityService.getContainedAuthorities(AuthorityType.GROUP, auth1234, false).size());
         assertEquals(2, pubAuthorityService.getContainedAuthorities(AuthorityType.GROUP, authC1, false).size());
         assertEquals(1, pubAuthorityService.getContainedAuthorities(AuthorityType.GROUP, authC2, false).size());
         assertEquals(0, pubAuthorityService.getContainedAuthorities(AuthorityType.GROUP, authStuff, false).size());
-        String authSpace = pubAuthorityService.createAuthority(AuthorityType.GROUP, authStuff, "  Circles     ");
+        String authSpace = pubAuthorityService.createAuthority(AuthorityType.GROUP, "  Circles     ");
+        pubAuthorityService.addAuthority(authStuff, authSpace);
         assertEquals(4, pubAuthorityService.getContainedAuthorities(AuthorityType.GROUP, auth1234, false).size());
         assertEquals(3, pubAuthorityService.getContainedAuthorities(AuthorityType.GROUP, authC1, false).size());
         assertEquals(2, pubAuthorityService.getContainedAuthorities(AuthorityType.GROUP, authC2, false).size());
@@ -905,12 +929,12 @@ public class AuthorityServiceTest extends TestCase
 
     public void testAuthorityDisplayNames()
     {
-        String authOne = pubAuthorityService.createAuthority(AuthorityType.GROUP, null, "One");
+        String authOne = pubAuthorityService.createAuthority(AuthorityType.GROUP, "One");
         assertEquals(pubAuthorityService.getAuthorityDisplayName(authOne), "One");
         pubAuthorityService.setAuthorityDisplayName(authOne, "Selfish Crocodile");
         assertEquals(pubAuthorityService.getAuthorityDisplayName(authOne), "Selfish Crocodile");
 
-        String authTwo = pubAuthorityService.createAuthority(AuthorityType.GROUP, null, "Two", "Lamp posts");
+        String authTwo = pubAuthorityService.createAuthority(AuthorityType.GROUP, "Two", "Lamp posts", null);
         assertEquals(pubAuthorityService.getAuthorityDisplayName(authTwo), "Lamp posts");
         pubAuthorityService.setAuthorityDisplayName(authTwo, "Happy Hippos");
         assertEquals(pubAuthorityService.getAuthorityDisplayName(authTwo), "Happy Hippos");
diff --git a/source/java/org/alfresco/repo/security/authority/ExtendedPermissionServiceTest.java b/source/java/org/alfresco/repo/security/authority/ExtendedPermissionServiceTest.java
index 1ed749f2e6..29575c04e7 100644
--- a/source/java/org/alfresco/repo/security/authority/ExtendedPermissionServiceTest.java
+++ b/source/java/org/alfresco/repo/security/authority/ExtendedPermissionServiceTest.java
@@ -41,7 +41,7 @@ public class ExtendedPermissionServiceTest extends AbstractPermissionTest
         permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, getPermission(PermissionService.READ),
                 "GROUP_test", AccessStatus.ALLOWED));
         assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED);
-        authorityService.createAuthority(AuthorityType.GROUP, null, "test");
+        authorityService.createAuthority(AuthorityType.GROUP, "test");
         authorityService.addAuthority("GROUP_test", "andy");
         assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED);
         authorityService.removeAuthority("GROUP_test", "andy");
@@ -58,7 +58,7 @@ public class ExtendedPermissionServiceTest extends AbstractPermissionTest
         permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, getPermission(PermissionService.READ),
                 "GROUP_test", AccessStatus.ALLOWED));
         assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED);
-        authorityService.createAuthority(AuthorityType.GROUP, null, "test");
+        authorityService.createAuthority(AuthorityType.GROUP, "test");
         authorityService.addAuthority("GROUP_test", "andy");
         assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED);
         permissionService.deletePermissions("GROUP_test");
diff --git a/source/java/org/alfresco/repo/security/authority/SimpleAuthorityServiceImpl.java b/source/java/org/alfresco/repo/security/authority/SimpleAuthorityServiceImpl.java
index 21b080506d..b20501acb3 100644
--- a/source/java/org/alfresco/repo/security/authority/SimpleAuthorityServiceImpl.java
+++ b/source/java/org/alfresco/repo/security/authority/SimpleAuthorityServiceImpl.java
@@ -184,13 +184,18 @@ public class SimpleAuthorityServiceImpl implements AuthorityService
     {
         
     }
-
     
-    public String createAuthority(AuthorityType type, String parentName, String shortName)
+    public String createAuthority(AuthorityType type, String shortName)
     {
        return "";
     }
 
+    public String createAuthority(AuthorityType type, String shortName, String authorityDisplayName,
+            String authorityZone)
+    {
+        return "";
+    }
+    
     public void deleteAuthority(String name)
     {
       
@@ -269,11 +274,6 @@ public class SimpleAuthorityServiceImpl implements AuthorityService
         return authorities;
     }
 
-    public String createAuthority(AuthorityType type, String parentName, String shortName, String authorityDisplayName)
-    {
-        return "";
-    }
-
     public String getAuthorityDisplayName(String name)
     {
         return "";
@@ -291,4 +291,18 @@ public class SimpleAuthorityServiceImpl implements AuthorityService
     	return findAuthorities(type, fullNamePattern);
 	}
 
+    public Set getAllAuthoritiesInZone(String zoneName, AuthorityType type)
+    {
+        return Collections.emptySet();
+    }
+
+    public String getAuthorityZone(String name)
+    {
+        return AuthorityService.DEFAULT_ZONE;
+    }
+
+    public NodeRef getOrCreateZone(String zoneName)
+    {
+        return null;
+    }
 }
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 ec51de03b6..b49be3cb65 100644
--- a/source/java/org/alfresco/repo/security/authority/script/ScriptAuthorityService.java
+++ b/source/java/org/alfresco/repo/security/authority/script/ScriptAuthorityService.java
@@ -128,7 +128,7 @@ public class ScriptAuthorityService extends BaseScopableProcessorExtension
 	 */
 	public ScriptGroup createRootGroup(String shortName, String displayName)
 	{
-		authorityService.createAuthority(AuthorityType.GROUP, null, shortName, displayName);
+		authorityService.createAuthority(AuthorityType.GROUP, shortName, displayName, null);
 		return getGroup(shortName);
 	}
 	
diff --git a/source/java/org/alfresco/repo/security/authority/script/ScriptGroup.java b/source/java/org/alfresco/repo/security/authority/script/ScriptGroup.java
index ab581ef1c3..090bf477d7 100644
--- a/source/java/org/alfresco/repo/security/authority/script/ScriptGroup.java
+++ b/source/java/org/alfresco/repo/security/authority/script/ScriptGroup.java
@@ -297,7 +297,8 @@ public class ScriptGroup implements Authority, Serializable
 	 */
 	public ScriptGroup createGroup(String shortName, String displayName)
 	{
-		String authorityName = authorityService.createAuthority(AuthorityType.GROUP, fullName, shortName, displayName);
+		String authorityName = authorityService.createAuthority(AuthorityType.GROUP, shortName, displayName, null);
+		authorityService.addAuthority(fullName, authorityName);
 		ScriptGroup childGroup = new ScriptGroup(authorityName, authorityService);
 		clearCaches();
 		return childGroup;
diff --git a/source/java/org/alfresco/repo/security/permissions/impl/PermissionServiceTest.java b/source/java/org/alfresco/repo/security/permissions/impl/PermissionServiceTest.java
index 20dd7144d2..1869de7f6b 100644
--- a/source/java/org/alfresco/repo/security/permissions/impl/PermissionServiceTest.java
+++ b/source/java/org/alfresco/repo/security/permissions/impl/PermissionServiceTest.java
@@ -2407,7 +2407,7 @@ public class PermissionServiceTest extends AbstractPermissionTest
         NodeRef n10 = nodeService.createNode(n1, ContentModel.ASSOC_CHILDREN, QName.createQName("{namespace}ten"), ContentModel.TYPE_FOLDER).getChildRef();
 
         personService.getPerson("andy");
-        String groupAuth = authorityService.createAuthority(AuthorityType.GROUP, null, "G");
+        String groupAuth = authorityService.createAuthority(AuthorityType.GROUP, "G");
         authorityService.addAuthority(groupAuth, "andy");
 
         // assertEquals(0, filterForStore(permissionService.findNodesByAssignedPermissionForCurrentUser("Consumer",
diff --git a/source/java/org/alfresco/repo/security/person/PersonServiceImpl.java b/source/java/org/alfresco/repo/security/person/PersonServiceImpl.java
index 66e70819f0..cc65139f7e 100644
--- a/source/java/org/alfresco/repo/security/person/PersonServiceImpl.java
+++ b/source/java/org/alfresco/repo/security/person/PersonServiceImpl.java
@@ -582,6 +582,11 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per
     }
 
     public NodeRef createPerson(Map properties)
+    {
+        return createPerson(properties, null);
+    }
+
+    public NodeRef createPerson(Map properties, String zone)
     {
         String userName = DefaultTypeConverter.INSTANCE.convert(String.class, properties.get(ContentModel.PROP_USERNAME));
         AuthorityType authorityType = AuthorityType.getAuthorityType(userName);
@@ -595,12 +600,19 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per
         properties.put(ContentModel.PROP_USERNAME, userName);
         properties.put(ContentModel.PROP_SIZE_CURRENT, 0L);
 
-        NodeRef personRef =  nodeService.createNode(
+        NodeRef personRef = nodeService.createNode(
                 getPeopleContainer(),
                 ContentModel.ASSOC_CHILDREN,
                 QName.createQName("cm", userName.toLowerCase(), namespacePrefixResolver),       // Lowercase: ETHREEOH-1431
                 ContentModel.TYPE_PERSON,
                 properties).getChildRef();
+        
+        if (zone != null)
+        {
+            // Add the person to an authentication zone (corresponding to an external user registry)
+            // Let's preserve case on this child association
+            nodeService.addChild(authorityService.getOrCreateZone(zone), personRef, ContentModel.ASSOC_IN_ZONE, QName.createQName("cm", userName, namespacePrefixResolver));
+        }
         return personRef;
     }
 
@@ -630,14 +642,6 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per
 
     public void deletePerson(String userName)
     {
-        NodeRef personNodeRef = getPersonOrNull(userName);
-
-        // delete the person
-        if (personNodeRef != null)
-        {
-            nodeService.deleteNode(personNodeRef);
-        }
-
         // remove user from any containing authorities
         Set containerAuthorities = authorityService.getContainingAuthorities(null, userName, true);
         for (String containerAuthority : containerAuthorities)
@@ -647,6 +651,13 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per
 
         // remove any user permissions
         permissionServiceSPI.deletePermissions(userName);
+
+        // delete the person
+        NodeRef personNodeRef = getPersonOrNull(userName);
+        if (personNodeRef != null)
+        {
+            nodeService.deleteNode(personNodeRef);
+        }
     }
 
     public Set getAllPeople()
diff --git a/source/java/org/alfresco/repo/security/sync/ChainingUserRegistrySynchronizer.java b/source/java/org/alfresco/repo/security/sync/ChainingUserRegistrySynchronizer.java
new file mode 100644
index 0000000000..52e28d4023
--- /dev/null
+++ b/source/java/org/alfresco/repo/security/sync/ChainingUserRegistrySynchronizer.java
@@ -0,0 +1,479 @@
+/*
+ * Copyright (C) 2005-2009 Alfresco Software Limited.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program 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 General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+ * As a special exception to the terms and conditions of version 2.0 of 
+ * the GPL, you may redistribute this Program in connection with Free/Libre 
+ * and Open Source Software ("FLOSS") applications as described in Alfresco's 
+ * FLOSS exception.  You should have received a copy of the text describing 
+ * the FLOSS exception, and it is also available here: 
+ * http://www.alfresco.com/legal/licensing"
+ */
+package org.alfresco.repo.security.sync;
+
+import java.text.DateFormat;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.TreeSet;
+
+import org.alfresco.model.ContentModel;
+import org.alfresco.repo.attributes.Attribute;
+import org.alfresco.repo.attributes.LongAttributeValue;
+import org.alfresco.repo.attributes.MapAttributeValue;
+import org.alfresco.repo.management.subsystems.ActivateableBean;
+import org.alfresco.repo.management.subsystems.ChildApplicationContextManager;
+import org.alfresco.service.cmr.attributes.AttributeService;
+import org.alfresco.service.cmr.security.AuthorityService;
+import org.alfresco.service.cmr.security.AuthorityType;
+import org.alfresco.service.cmr.security.PersonService;
+import org.alfresco.util.PropertyMap;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.beans.factory.NoSuchBeanDefinitionException;
+import org.springframework.context.ApplicationContext;
+
+/**
+ * A ChainingUserRegistrySynchronizer is responsible for synchronizing Alfresco's local user (person) and
+ * group (authority) information with the external subsystems in the authentication chain (most typically LDAP
+ * directories). When the {@link #synchronize(boolean)} method is called, it visits each {@link UserRegistry} bean in
+ * the 'chain' of application contexts, managed by a {@link ChildApplicationContextManager}, and compares its
+ * timestamped user and group information with the local users and groups last retrieved from the same source. Any
+ * updates and additions made to those users and groups are applied to the local copies. The ordering of each
+ * {@link UserRegistry} in the chain determines its precedence when it comes to user and group name collisions.
+ * 

+ * The force argument determines whether a complete or partial set of information is queried from the + * {@link UserRegistry}. When true then all users and groups are queried. With this complete set of + * information, the synchronizer is able to identify which users and groups have been deleted, so it will delete users + * and groups as well as update and create them. Since processing all users and groups may be fairly time consuming, it + * is recommended this mode is only used by a background scheduled synchronization job. When the argument is + * false then only those users and groups modified since the most recent modification date of all the + * objects last queried from the same {@link UserRegistry} are retrieved. In this mode, local users and groups are + * created and updated, but not deleted (except where a name collision with a lower priority {@link UserRegistry} is + * detected). This 'differential' mode is much faster, and by default is triggered when a user is successfully + * authenticated who doesn't yet have a local person object in Alfresco. This should mean that new users and their group + * information are pulled over from LDAP servers as and when required. + * + * @author dward + */ +public class ChainingUserRegistrySynchronizer implements UserRegistrySynchronizer +{ + /** The logger. */ + private static final Log logger = LogFactory.getLog(ChainingUserRegistrySynchronizer.class); + + /** The path in the attribute service below which we persist attributes. */ + private static final String ROOT_ATTRIBUTE_PATH = ".ChainingUserRegistrySynchronizer"; + + /** The label under which the last group modification timestamp is stored for each zone. */ + private static final String GROUP_LAST_MODIFIED_ATTRIBUTE = "GROUP"; + + /** The label under which the last user modification timestamp is stored for each zone. */ + private static final String PERSON_LAST_MODIFIED_ATTRIBUTE = "PERSON"; + + /** The manager for the autentication chain to be traversed. */ + private ChildApplicationContextManager applicationContextManager; + + /** The name used to look up a {@link UserRegistry} bean in each child application context. */ + private String sourceBeanName; + + /** The authority service. */ + private AuthorityService authorityService; + + /** The person service. */ + private PersonService personService; + + /** The attribute service. */ + private AttributeService attributeService; + + /** + * Sets the application context manager. + * + * @param applicationContextManager + * the applicationContextManager to set + */ + public void setApplicationContextManager(ChildApplicationContextManager applicationContextManager) + { + this.applicationContextManager = applicationContextManager; + } + + /** + * Sets the name used to look up a {@link UserRegistry} bean in each child application context. + * + * @param sourceBeanName + * the bean name + */ + public void setSourceBeanName(String sourceBeanName) + { + this.sourceBeanName = sourceBeanName; + } + + /** + * Sets the authority service. + * + * @param authorityService + * the new authority service + */ + public void setAuthorityService(AuthorityService authorityService) + { + this.authorityService = authorityService; + } + + /** + * Sets the person service. + * + * @param personService + * the new person service + */ + public void setPersonService(PersonService personService) + { + this.personService = personService; + } + + /** + * Sets the attribute service. + * + * @param attributeService + * the new attribute service + */ + public void setAttributeService(AttributeService attributeService) + { + this.attributeService = attributeService; + } + + /* + * (non-Javadoc) + * @see org.alfresco.repo.security.sync.UserRegistrySynchronizer#synchronize(boolean) + */ + public void synchronize(boolean force) + { + Set visitedZoneIds = new TreeSet(); + for (String zoneId : this.applicationContextManager.getInstanceIds()) + { + ApplicationContext context = this.applicationContextManager.getApplicationContext(zoneId); + try + { + UserRegistry plugin = (UserRegistry) context.getBean(this.sourceBeanName); + if (!(plugin instanceof ActivateableBean) || ((ActivateableBean) plugin).isActive()) + { + ChainingUserRegistrySynchronizer.logger.info("Synchronizing users and groups with user registry '" + + zoneId + "'"); + if (force) + { + ChainingUserRegistrySynchronizer.logger + .warn("Forced synchronization with user registry '" + + zoneId + + "'; some users and groups previously created by synchronization with this user registry may be removed."); + } + int personsProcessed = syncPersonsWithPlugin(zoneId, plugin, force, visitedZoneIds); + int groupsProcessed = syncGroupsWithPlugin(zoneId, plugin, force, visitedZoneIds); + ChainingUserRegistrySynchronizer.logger + .info("Finished synchronizing users and groups with user registry '" + zoneId + "'"); + logger.info(personsProcessed + " user(s) and " + groupsProcessed + " group(s) processed"); + } + } + catch (NoSuchBeanDefinitionException e) + { + // Ignore and continue + } + visitedZoneIds.add(zoneId); + } + } + + /** + * Synchronizes local users (persons) with a {@link UserRegistry} for a particular zone. + * + * @param zoneId + * the zone id. This identifier is used to tag all created users, so that in the future we can tell those + * that have been deleted from the registry. + * @param userRegistry + * the user registry for the zone. + * @param force + * true if all persons are to be queried. false if only those changed since the + * most recent queried user should be queried. + * @param visitedZoneIds + * the set of zone ids already processed. These zones have precedence over the current zone when it comes + * to user name 'collisions'. If a user is queried that already exists locally but is tagged with one of + * the zones in this set, then it will be ignored as this zone has lower priority. + * @return the number of users processed + */ + private int syncPersonsWithPlugin(String zoneId, UserRegistry userRegistry, boolean force, + Set visitedZoneIds) + { + int processedCount = 0; + long lastModifiedMillis = force ? -1L : getMostRecentUpdateTime( + ChainingUserRegistrySynchronizer.PERSON_LAST_MODIFIED_ATTRIBUTE, zoneId); + Date lastModified = lastModifiedMillis == -1 ? null : new Date(lastModifiedMillis); + if (lastModified == null) + { + ChainingUserRegistrySynchronizer.logger.info("Retrieving all users from user registry '" + zoneId + "'"); + } + else + { + ChainingUserRegistrySynchronizer.logger.info("Retrieving users changed since " + + DateFormat.getDateTimeInstance().format(lastModified) + " from user registry '" + zoneId + "'"); + } + Iterator persons = userRegistry.getPersons(lastModified); + Set personsToDelete = this.authorityService.getAllAuthoritiesInZone(zoneId, AuthorityType.USER); + while (persons.hasNext()) + { + NodeDescription person = persons.next(); + PropertyMap personProperties = person.getProperties(); + String personName = (String) personProperties.get(ContentModel.PROP_USERNAME); + if (personsToDelete.remove(personName)) + { + // The person already existed in this zone: update the person + ChainingUserRegistrySynchronizer.logger.info("Updating user '" + personName + "'"); + this.personService.setPersonProperties(personName, personProperties); + } + else + { + // The person does not exist in this zone, but may exist in another zone + String zone = this.authorityService.getAuthorityZone(personName); + if (zone != null) + { + if (visitedZoneIds.contains(zone)) + { + // A person that exists in a different zone with higher precedence + continue; + } + // The person existed, but in a zone with lower precedence + ChainingUserRegistrySynchronizer.logger + .warn("Recreating occluded user '" + + personName + + "'. This user was previously created manually or through synchronization with a lower priority user registry."); + this.personService.deletePerson(personName); + } + else + { + // The person did not exist at all + ChainingUserRegistrySynchronizer.logger.info("Creating user '" + personName + "'"); + } + this.personService.createPerson(personProperties, zoneId); + } + // Increment the count of processed people + processedCount++; + + // Maintain the last modified date + Date personLastModified = person.getLastModified(); + if (personLastModified != null) + { + lastModifiedMillis = Math.max(lastModifiedMillis, personLastModified.getTime()); + } + } + + if (force && !personsToDelete.isEmpty()) + { + for (String personName : personsToDelete) + { + ChainingUserRegistrySynchronizer.logger.warn("Deleting user '" + personName + "'"); + this.personService.deletePerson(personName); + processedCount++; + } + } + + if (lastModifiedMillis != -1) + { + setMostRecentUpdateTime(ChainingUserRegistrySynchronizer.PERSON_LAST_MODIFIED_ATTRIBUTE, zoneId, + lastModifiedMillis); + } + + return processedCount; + } + + /** + * Synchronizes local groups (authorities) with a {@link UserRegistry} for a particular zone. + * + * @param zoneId + * the zone id. This identifier is used to tag all created groups, so that in the future we can tell + * those that have been deleted from the registry. + * @param userRegistry + * the user registry for the zone. + * @param force + * true if all groups are to be queried. false if only those changed since the + * most recent queried group should be queried. + * @param visitedZoneIds + * the set of zone ids already processed. These zones have precedence over the current zone when it comes + * to group name 'collisions'. If a group is queried that already exists locally but is tagged with one + * of the zones in this set, then it will be ignored as this zone has lower priority. + * @return the number of groups processed + */ + private int syncGroupsWithPlugin(String zoneId, UserRegistry plugin, boolean force, Set visitedZoneIds) + { + int processedCount = 0; + long lastModifiedMillis = force ? -1L : getMostRecentUpdateTime( + ChainingUserRegistrySynchronizer.GROUP_LAST_MODIFIED_ATTRIBUTE, zoneId); + Date lastModified = lastModifiedMillis == -1 ? null : new Date(lastModifiedMillis); + if (lastModified == null) + { + ChainingUserRegistrySynchronizer.logger.info("Retrieving all groups from user registry '" + zoneId + "'"); + } + else + { + ChainingUserRegistrySynchronizer.logger.info("Retrieving groups changed since " + + DateFormat.getDateTimeInstance().format(lastModified) + " from user registry '" + zoneId + "'"); + } + + Iterator groups = plugin.getGroups(lastModified); + Map> groupAssocsToCreate = new TreeMap>(); + Set groupsToDelete = this.authorityService.getAllAuthoritiesInZone(zoneId, AuthorityType.GROUP); + while (groups.hasNext()) + { + NodeDescription group = groups.next(); + PropertyMap groupProperties = group.getProperties(); + String groupName = (String) groupProperties.get(ContentModel.PROP_AUTHORITY_NAME); + if (groupsToDelete.remove(groupName)) + { + // update an existing group in the same zone + Set oldChildren = this.authorityService.getContainedAuthorities(null, groupName, true); + Set newChildren = group.getChildAssociations(); + Set toDelete = new TreeSet(oldChildren); + Set toAdd = new TreeSet(newChildren); + toDelete.removeAll(newChildren); + toAdd.removeAll(oldChildren); + if (!toAdd.isEmpty()) + { + groupAssocsToCreate.put(groupName, toAdd); + } + for (String child : toDelete) + { + ChainingUserRegistrySynchronizer.logger.info("Removing '" + + this.authorityService.getShortName(child) + "' from group '" + + this.authorityService.getShortName(groupName) + "'"); + this.authorityService.removeAuthority(groupName, child); + } + } + else + { + String groupShortName = this.authorityService.getShortName(groupName); + String groupZone = this.authorityService.getAuthorityZone(groupName); + if (groupZone != null) + { + if (visitedZoneIds.contains(groupZone)) + { + // A group that exists in a different zone with higher precedence + continue; + } + // The group existed, but in a zone with lower precedence + ChainingUserRegistrySynchronizer.logger + .warn("Recreating occluded group '" + + groupShortName + + "'. This group was previously created manually or through synchronization with a lower priority user registry."); + this.authorityService.deleteAuthority(groupName); + } + else + { + ChainingUserRegistrySynchronizer.logger.info("Creating group '" + groupShortName + "'"); + } + + // create the group + this.authorityService.createAuthority(AuthorityType.getAuthorityType(groupName), groupShortName, + (String) groupProperties.get(ContentModel.PROP_AUTHORITY_DISPLAY_NAME), zoneId); + Set children = group.getChildAssociations(); + if (!children.isEmpty()) + { + groupAssocsToCreate.put(groupName, children); + } + } + + // Increment the count of processed groups + processedCount++; + + // Maintain the last modified date + Date groupLastModified = group.getLastModified(); + if (groupLastModified != null) + { + lastModifiedMillis = Math.max(lastModifiedMillis, groupLastModified.getTime()); + } + } + + // Add the new associations, now that we have created everything + for (Map.Entry> entry : groupAssocsToCreate.entrySet()) + { + for (String child : entry.getValue()) + { + String groupName = entry.getKey(); + ChainingUserRegistrySynchronizer.logger.info("Adding '" + this.authorityService.getShortName(child) + + "' to group '" + this.authorityService.getShortName(groupName) + "'"); + this.authorityService.addAuthority(groupName, child); + } + + } + + // Delete groups if we have complete information for the zone + if (force && !groupsToDelete.isEmpty()) + { + for (String group : groupsToDelete) + { + ChainingUserRegistrySynchronizer.logger.warn("Deleting group '" + + this.authorityService.getShortName(group) + "'"); + this.authorityService.deleteAuthority(group); + processedCount++; + } + } + + if (lastModifiedMillis != -1) + { + setMostRecentUpdateTime(ChainingUserRegistrySynchronizer.GROUP_LAST_MODIFIED_ATTRIBUTE, zoneId, + lastModifiedMillis); + } + + return processedCount; + } + + /** + * Gets the persisted most recent update time for a label and zone. + * + * @param label + * the label + * @param zoneId + * the zone id + * @return the most recent update time in milliseconds + */ + private long getMostRecentUpdateTime(String label, String zoneId) + { + Attribute attribute = this.attributeService.getAttribute(ChainingUserRegistrySynchronizer.ROOT_ATTRIBUTE_PATH + + '/' + label + '/' + zoneId); + return attribute == null ? -1 : attribute.getLongValue(); + } + + /** + * Persists the most recent update time for a label and zone. + * + * @param label + * the label + * @param zoneId + * the zone id + * @param lastModifiedMillis + * the update time in milliseconds + */ + private void setMostRecentUpdateTime(String label, String zoneId, long lastModifiedMillis) + { + String path = ChainingUserRegistrySynchronizer.ROOT_ATTRIBUTE_PATH + '/' + label; + if (!this.attributeService.exists(path)) + { + if (!this.attributeService.exists(ChainingUserRegistrySynchronizer.ROOT_ATTRIBUTE_PATH)) + { + this.attributeService.setAttribute("", ChainingUserRegistrySynchronizer.ROOT_ATTRIBUTE_PATH, + new MapAttributeValue()); + } + this.attributeService.setAttribute(ChainingUserRegistrySynchronizer.ROOT_ATTRIBUTE_PATH, label, + new MapAttributeValue()); + } + this.attributeService.setAttribute(path, zoneId, new LongAttributeValue(lastModifiedMillis)); + } +} diff --git a/source/java/org/alfresco/repo/security/sync/ChainingUserRegistrySynchronizerTest.java b/source/java/org/alfresco/repo/security/sync/ChainingUserRegistrySynchronizerTest.java new file mode 100644 index 0000000000..54f1c3be5f --- /dev/null +++ b/source/java/org/alfresco/repo/security/sync/ChainingUserRegistrySynchronizerTest.java @@ -0,0 +1,524 @@ +/* + * Copyright (C) 2005-2009 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program 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 General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have received a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.repo.security.sync; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Date; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.management.subsystems.ChildApplicationContextManager; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.security.AuthorityService; +import org.alfresco.service.cmr.security.AuthorityType; +import org.alfresco.service.cmr.security.PersonService; +import org.alfresco.util.BaseSpringTest; +import org.alfresco.util.PropertyMap; +import org.hibernate.SessionFactory; +import org.hibernate.engine.SessionFactoryImplementor; +import org.springframework.context.ApplicationContext; +import org.springframework.context.support.StaticApplicationContext; + +/** + * Tests the {@link ChainingUserRegistrySynchronizer} using a simulated {@link UserRegistry}. + * + * @author dward + */ +public class ChainingUserRegistrySynchronizerTest extends BaseSpringTest +{ + + /** The context locations, in reverse priority order. */ + private static final String[] CONFIG_LOCATIONS = + { + "classpath:alfresco/application-context.xml", "classpath:sync-test-context.xml" + }; + + /** The synchronizer we are testing. */ + private UserRegistrySynchronizer synchronizer; + + /** The application context manager. */ + private MockApplicationContextManager applicationContextManager; + + /** The person service. */ + private PersonService personService; + + /** The authority service. */ + private AuthorityService authorityService; + + /** The node service. */ + private NodeService nodeService; + + /* + * (non-Javadoc) + * @see org.springframework.test.AbstractTransactionalSpringContextTests#onSetUpInTransaction() + */ + @Override + protected void onSetUpInTransaction() throws Exception + { + ApplicationContext context = getApplicationContext(); + this.synchronizer = (UserRegistrySynchronizer) context.getBean("testUserRegistrySynchronizer"); + this.applicationContextManager = (MockApplicationContextManager) context + .getBean("testApplicationContextManager"); + this.personService = (PersonService) context.getBean("personService"); + this.authorityService = (AuthorityService) context.getBean("authorityService"); + this.nodeService = (NodeService) context.getBean("nodeService"); + } + + /* + * (non-Javadoc) + * @see org.springframework.test.AbstractTransactionalSpringContextTests#onTearDownInTransaction() + */ + protected void onTearDownInTransaction() throws Exception + { + flushAndClear(); + + // Try to clear the Hibernate L2 cache so we have consistency after a rollback + SessionFactory sessionFactory = getSession().getSessionFactory(); + String[] persistentClasses = ((SessionFactoryImplementor) sessionFactory).getImplementors("java.lang.Object"); + for (String persistentClass : persistentClasses) + { + sessionFactory.evictEntity(persistentClass); + } + } + + /* + * (non-Javadoc) + * @see org.alfresco.util.BaseSpringTest#getConfigLocations() + */ + @Override + protected String[] getConfigLocations() + { + return ChainingUserRegistrySynchronizerTest.CONFIG_LOCATIONS; + } + + /** + * Sets up the test users and groups in two zones, "Z1" and "Z2", by doing a forced synchronize with a Mock user + * registry. Note that the zones have some overlapping entries. The layout is as follows + * + *

+     * Z1
+     * G1
+     * G2 - U1, G3 - U2, G4, G5
+     * 
+     * Z2
+     * G2 - U1, U3, U4
+     * G6 - U3, U4, G7 - U5
+     * 
+ * + * @throws Exception + * the exception + */ + public void setUpTestUsersAndGroups() throws Exception + { + this.applicationContextManager.setUserRegistries(new MockUserRegistry("Z1", new NodeDescription[] + { + newPerson("U1"), newPerson("U2") + }, new NodeDescription[] + { + newGroup("G1"), newGroup("G2", "U1", "G3"), newGroup("G3", "U2", "G4", "G5"), newGroup("G4"), + newGroup("G5") + }), new MockUserRegistry("Z2", new NodeDescription[] + { + newPerson("U1"), newPerson("U3"), newPerson("U4"), newPerson("U5") + }, new NodeDescription[] + { + newGroup("G2", "U1", "U3", "U4"), newGroup("G6", "U3", "U4", "G7"), newGroup("G7", "U5") + })); + this.synchronizer.synchronize(true); + assertExists("Z1", "U1"); + assertExists("Z1", "U2"); + assertExists("Z1", "G1"); + assertExists("Z1", "G2", "U1", "G3"); + assertExists("Z1", "G3", "U2", "G4", "G5"); + assertExists("Z1", "G4"); + assertExists("Z1", "G5"); + assertExists("Z2", "U3"); + assertExists("Z2", "U4"); + assertExists("Z2", "U5"); + assertExists("Z2", "G6", "U3", "U4", "G7"); + assertExists("Z2", "G7", "U5"); + } + + /** + * Tests a differential update of the test users and groups. The layout is as follows + * + *
+     * Z1
+     * G1 - U1, U6
+     * G2 - U1
+     * G3 - U2, G4, G5 - U6
+     * 
+     * Z2
+     * G2 - U1, U3, U4, U6
+     * G6 - U3, U4, G7
+     * 
+ * + * @throws Exception + * the exception + */ + public void testDifferentialUpdate() throws Exception + { + setUpTestUsersAndGroups(); + this.applicationContextManager.setUserRegistries(new MockUserRegistry("Z1", new NodeDescription[] + { + newPerson("U1", "changeofemail@alfresco.com"), newPerson("U6") + }, new NodeDescription[] + { + newGroup("G1", "U1", "U6"), newGroup("G2", "U1"), newGroup("G5", "U6") + }), new MockUserRegistry("Z2", new NodeDescription[] + { + newPerson("U1", "shouldbeignored@alfresco.com"), newPerson("U5", "u5email@alfresco.com"), newPerson("U6") + }, new NodeDescription[] + { + newGroup("G2", "U1", "U3", "U4", "U6"), newGroup("G7") + })); + this.synchronizer.synchronize(false); + assertExists("Z1", "U1"); + assertEmailEquals("U1", "changeofemail@alfresco.com"); + assertExists("Z1", "U2"); + assertExists("Z1", "U6"); + assertExists("Z1", "G1", "U1", "U6"); + assertExists("Z1", "G2", "U1"); + assertExists("Z1", "G3", "U2", "G4", "G5"); + assertExists("Z1", "G4"); + assertExists("Z1", "G5", "U6"); + assertExists("Z2", "U3"); + assertExists("Z2", "U4"); + assertExists("Z2", "U5"); + assertEmailEquals("U5", "u5email@alfresco.com"); + assertExists("Z2", "G6", "U3", "U4", "G7"); + assertExists("Z2", "G7"); + } + + /** + * Tests a forced update of the test users and groups. Also tests that groups and users that previously existed in + * Z2 get moved when they appear in Z1. The layout is as follows + * + *
+     * Z1
+     * G1 - U6
+     * G2 - 
+     * G3 - U2, G5 - U6
+     * G6 - U3
+     * 
+     * Z2
+     * G2 - U1, U3, U6
+     * G6 - U3, G7
+     * 
+ * + * @throws Exception + * the exception + */ + public void testForcedUpdate() throws Exception + { + setUpTestUsersAndGroups(); + this.applicationContextManager.setUserRegistries(new MockUserRegistry("Z1", new NodeDescription[] + { + newPerson("U2"), newPerson("U3"), newPerson("U6") + }, new NodeDescription[] + { + newGroup("G1", "U6"), newGroup("G2"), newGroup("G3", "U2", "G5"), newGroup("G5", "U6"), + newGroup("G6", "U3") + }), new MockUserRegistry("Z2", new NodeDescription[] + { + newPerson("U1", "somenewemail@alfresco.com"), newPerson("U3"), newPerson("U6") + }, new NodeDescription[] + { + newGroup("G2", "U1", "U3", "U6"), newGroup("G6", "U3", "G7"), newGroup("G7") + })); + this.synchronizer.synchronize(true); + assertExists("Z1", "U2"); + assertExists("Z1", "U3"); + assertExists("Z1", "U6"); + assertExists("Z1", "G1", "U6"); + assertExists("Z1", "G2"); + assertExists("Z1", "G3", "U2", "G5"); + assertNotExists("G4"); + assertExists("Z1", "G5", "U6"); + assertExists("Z1", "G6", "U3"); + assertExists("Z2", "U1"); + assertEmailEquals("U1", "somenewemail@alfresco.com"); + assertNotExists("U4"); + assertNotExists("U5"); + assertExists("Z2", "G7"); + } + + /** + * Constructs a description of a test group + * + * @param name + * the name + * @param members + * the members + * @return the node description + */ + private NodeDescription newGroup(String name, String... members) + { + NodeDescription group = new NodeDescription(); + PropertyMap properties = group.getProperties(); + properties.put(ContentModel.PROP_AUTHORITY_NAME, longName(name)); + if (members.length > 0) + { + Set assocs = group.getChildAssociations(); + for (String member : members) + { + assocs.add(longName(member)); + } + } + group.setLastModified(new Date()); + return group; + } + + /** + * Constructs a description of a test person with default email (userName@alfresco.com) + * + * @param userName + * the user name + * @return the node description + */ + private NodeDescription newPerson(String userName) + { + return newPerson(userName, userName + "@alfresco.com"); + } + + /** + * Constructs a description of a test person with a given email. + * + * @param userName + * the user name + * @param email + * the email + * @return the node description + */ + private NodeDescription newPerson(String userName, String email) + { + NodeDescription person = new NodeDescription(); + PropertyMap properties = person.getProperties(); + properties.put(ContentModel.PROP_USERNAME, userName); + properties.put(ContentModel.PROP_FIRSTNAME, userName + "F"); + properties.put(ContentModel.PROP_LASTNAME, userName + "L"); + properties.put(ContentModel.PROP_EMAIL, email); + person.setLastModified(new Date()); + return person; + } + + /** + * Perform all the necessary assertions to ensure that an authority and its members exist in the correct zone. + * + * @param zone + * the zone + * @param name + * the name + * @param members + * the members + */ + private void assertExists(String zone, String name, String... members) + { + String longName = longName(name); + // Check authority exists + assertTrue(this.authorityService.authorityExists(longName)); + + // Check in correct zone + assertEquals(zone, this.authorityService.getAuthorityZone(longName)); + if (AuthorityType.getAuthorityType(longName).equals(AuthorityType.GROUP)) + { + // Check groups have expected members + Set memberSet = new HashSet(members.length * 2); + for (String member : members) + { + memberSet.add(longName(member)); + } + assertEquals(memberSet, this.authorityService.getContainedAuthorities(null, longName, true)); + } + else + { + // Check users exist as persons + assertTrue(this.personService.personExists(name)); + } + } + + /** + * Perform all the necessary assertions to ensure that an authority does not exist. + * + * @param name + * the name + */ + private void assertNotExists(String name) + { + String longName = longName(name); + // Check authority does not exist + assertFalse(this.authorityService.authorityExists(longName)); + + // Check there is no zone + assertNull(this.authorityService.getAuthorityZone(longName)); + if (!AuthorityType.getAuthorityType(longName).equals(AuthorityType.GROUP)) + { + // Check person does not exist + assertFalse(this.personService.personExists(name)); + } + } + + /** + * Asserts that a person's email has the expected value + * + * @param personName + * the person name + * @param email + * the email + */ + private void assertEmailEquals(String personName, String email) + { + NodeRef personRef = this.personService.getPerson(personName); + assertEquals(email, this.nodeService.getProperty(personRef, ContentModel.PROP_EMAIL)); + } + + /** + * Converts the given short name to a full authority name, assuming that those short names beginning with 'G' + * correspond to groups and all others correspond to users. + * + * @param shortName + * the short name + * @return the full authority name + */ + private String longName(String shortName) + { + return this.authorityService.getName(shortName.startsWith("G") ? AuthorityType.GROUP : AuthorityType.USER, + shortName); + } + + /** + * A Mock {@link UserRegistry} that returns a fixed set of users and groups. + */ + public static class MockUserRegistry implements UserRegistry + { + + /** The zone id. */ + private String zoneId; + + /** The persons. */ + private NodeDescription[] persons; + + /** The groups. */ + private NodeDescription[] groups; + + /** + * Instantiates a new mock user registry. + * + * @param zoneId + * the zone id + * @param persons + * the persons + * @param groups + * the groups + */ + public MockUserRegistry(String zoneId, NodeDescription[] persons, NodeDescription[] groups) + { + this.zoneId = zoneId; + this.persons = persons; + this.groups = groups; + } + + /** + * Gets the zone id. + * + * @return the zoneId + */ + public String getZoneId() + { + return this.zoneId; + } + + /* + * (non-Javadoc) + * @see org.alfresco.repo.security.sync.UserRegistry#getGroups(java.util.Date) + */ + public Iterator getGroups(Date modifiedSince) + { + return Arrays.asList(this.groups).iterator(); + } + + /* + * (non-Javadoc) + * @see org.alfresco.repo.security.sync.UserRegistry#getPersons(java.util.Date) + */ + public Iterator getPersons(Date modifiedSince) + { + return Arrays.asList(this.persons).iterator(); + } + } + + /** + * An {@link ChildApplicationContextManager} for a chain of application contexts containing mock user registries. + */ + public static class MockApplicationContextManager implements ChildApplicationContextManager + { + + /** The contexts. */ + private Map contexts; + + /** + * Sets the user registries. + * + * @param registries + * the new user registries + */ + public void setUserRegistries(MockUserRegistry... registries) + { + this.contexts = new LinkedHashMap(registries.length * 2); + for (MockUserRegistry registry : registries) + { + StaticApplicationContext context = new StaticApplicationContext(); + context.getDefaultListableBeanFactory().registerSingleton("userRegistry", registry); + this.contexts.put(registry.getZoneId(), context); + } + } + + /* + * (non-Javadoc) + * @see + * org.alfresco.repo.management.subsystems.ChildApplicationContextManager#getApplicationContext(java.lang.String + * ) + */ + public ApplicationContext getApplicationContext(String id) + { + return this.contexts.get(id); + } + + /* + * (non-Javadoc) + * @see org.alfresco.repo.management.subsystems.ChildApplicationContextManager#getInstanceIds() + */ + public Collection getInstanceIds() + { + return this.contexts.keySet(); + } + } +} diff --git a/source/java/org/alfresco/repo/security/sync/NodeDescription.java b/source/java/org/alfresco/repo/security/sync/NodeDescription.java new file mode 100644 index 0000000000..a36b2608f5 --- /dev/null +++ b/source/java/org/alfresco/repo/security/sync/NodeDescription.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2005-2009 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program 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 General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have received a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.repo.security.sync; + +import java.util.Date; +import java.util.Set; +import java.util.TreeSet; + +import org.alfresco.util.PropertyMap; + +/** + * An 'off-line' description of an Alfresco node. + * + * @author dward + */ +public class NodeDescription +{ + /** The properties. */ + private final PropertyMap properties = new PropertyMap(19); + + /** The child associations. */ + private final Set childAssociations = new TreeSet(); + + /** The last modification date. */ + private Date lastModified; + + /** + * Gets the last modification date. + * + * @return the last modification date + */ + public Date getLastModified() + { + return lastModified; + } + + /** + * Sets the last modification date. + * + * @param lastModified + * the last modification date + */ + public void setLastModified(Date lastModified) + { + this.lastModified = lastModified; + } + + /** + * Gets the properties. + * + * @return the properties + */ + public PropertyMap getProperties() + { + return properties; + } + + /** + * Gets the child associations. + * + * @return the child associations + */ + public Set getChildAssociations() + { + return childAssociations; + } +} diff --git a/source/java/org/alfresco/repo/security/sync/UserRegistry.java b/source/java/org/alfresco/repo/security/sync/UserRegistry.java new file mode 100644 index 0000000000..c5bb0a358a --- /dev/null +++ b/source/java/org/alfresco/repo/security/sync/UserRegistry.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2005-2009 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program 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 General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have received a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.repo.security.sync; + +import java.util.Date; +import java.util.Iterator; + +/** + * A UserRegistry is an encapsulation of an external registry from which user and group information can be + * queried (typically an LDAP directory). Implementations may optional support the ability to query only those users and + * groups modified since a certain time. + * + * @author dward + */ +public interface UserRegistry +{ + /** + * Gets descriptions of all the persons (users) in the user registry or all those changed since a certain date. + * + * @param modifiedSince + * if non-null, then only descriptions of users modified since this date should be returned; if + * null then descriptions of all users should be returned. + * @return a {@link Iterator} over {@link NodeDescription}s of all the persons (users) in the user registry or all + * those changed since a certain date. The description properties should correspond to those of an Alfresco + * person node. + */ + public Iterator getPersons(Date modifiedSince); + + /** + * Gets descriptions of all the groups in the user registry or all those changed since a certain date. + * + * @param modifiedSince + * if non-null, then only descriptions of groups modified since this date should be returned; if + * null then descriptions of all groups should be returned. + * @return a {@link Iterator} over {@link NodeDescription}s of all the groups in the user registry or all those + * changed since a certain date. The description properties should correspond to those of an Alfresco + * authority node. + */ + public Iterator getGroups(Date modifiedSince); +} diff --git a/source/java/org/alfresco/repo/security/sync/UserRegistrySynchronizer.java b/source/java/org/alfresco/repo/security/sync/UserRegistrySynchronizer.java new file mode 100644 index 0000000000..02fe5a0796 --- /dev/null +++ b/source/java/org/alfresco/repo/security/sync/UserRegistrySynchronizer.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2005-2009 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program 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 General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have received a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.repo.security.sync; + +/** + * A UserRegistrySynchronizer is responsible for synchronizing Alfresco's local user (person) and group + * (authority) information with one or more external sources (most typically LDAP directories). + * + * @author dward + */ +public interface UserRegistrySynchronizer +{ + /** + * Retrieves timestamped user and group information from configured external sources and compares it with the local + * users and groups last retrieved from the same sources. Any updates and additions made to those users and groups + * are applied to the local Alfresco copies. + * + * @param force + * Should a complete or partial set of information be queried from the external sources? When + * true then all users and groups are queried. With this complete set of information, + * the implementation is able to identify which users and groups have been deleted, so it will delete + * users and groups as well as update and create them. When false then each source is only + * queried for those users and groups modified since the most recent modification date of all the objects + * last queried from that same source. In this mode, local users and groups are created and updated, but + * not deleted. + */ + public void synchronize(boolean force); +} \ No newline at end of file diff --git a/source/java/org/alfresco/repo/security/sync/UserRegistrySynchronizerJob.java b/source/java/org/alfresco/repo/security/sync/UserRegistrySynchronizerJob.java new file mode 100644 index 0000000000..60ad687133 --- /dev/null +++ b/source/java/org/alfresco/repo/security/sync/UserRegistrySynchronizerJob.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2005-2009 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program 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 General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have received a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.repo.security.sync; + +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; +import org.quartz.Job; +import org.quartz.JobExecutionContext; +import org.quartz.JobExecutionException; + +/** + * A scheduled job that regularly invokes a {@link UserRegistrySynchronizer}. Supports a + * synchronizeChangesOnly string parameter. When "true" means that the + * {@link UserRegistrySynchronizer#synchronize(boolean)} method will be called with a false argument rather + * than the default true. + * + * @author dward + */ +public class UserRegistrySynchronizerJob implements Job +{ + /* + * (non-Javadoc) + * @see org.quartz.Job#execute(org.quartz.JobExecutionContext) + */ + public void execute(JobExecutionContext executionContext) throws JobExecutionException + { + final UserRegistrySynchronizer userRegistrySynchronizer = (UserRegistrySynchronizer) executionContext + .getJobDetail().getJobDataMap().get("userRegistrySynchronizer"); + final String synchronizeChangesOnly = (String) executionContext.getJobDetail().getJobDataMap().get( + "synchronizeChangesOnly"); + AuthenticationUtil.runAs(new RunAsWork() + { + public Object doWork() throws Exception + { + userRegistrySynchronizer.synchronize(synchronizeChangesOnly == null + || !Boolean.parseBoolean(synchronizeChangesOnly)); + return null; + } + }, AuthenticationUtil.getSystemUserName()); + } + +} diff --git a/source/java/org/alfresco/repo/security/sync/ldap/LDAPUserRegistry.java b/source/java/org/alfresco/repo/security/sync/ldap/LDAPUserRegistry.java new file mode 100644 index 0000000000..8f0b7cf7e2 --- /dev/null +++ b/source/java/org/alfresco/repo/security/sync/ldap/LDAPUserRegistry.java @@ -0,0 +1,857 @@ +/* + * Copyright (C) 2005-2009 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program 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 General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have received a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.repo.security.sync.ldap; + +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; +import java.util.TimeZone; +import java.util.TreeMap; +import java.util.TreeSet; + +import javax.naming.NamingEnumeration; +import javax.naming.NamingException; +import javax.naming.directory.Attribute; +import javax.naming.directory.Attributes; +import javax.naming.directory.InitialDirContext; +import javax.naming.directory.SearchControls; +import javax.naming.directory.SearchResult; +import javax.naming.ldap.LdapName; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.model.ContentModel; +import org.alfresco.repo.management.subsystems.ActivateableBean; +import org.alfresco.repo.security.authentication.ldap.LDAPInitialDirContextFactory; +import org.alfresco.repo.security.sync.NodeDescription; +import org.alfresco.repo.security.sync.UserRegistry; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.alfresco.util.PropertyMap; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.factory.InitializingBean; + +/** + * A {@link UserRegistry} implementation with the ability to query Alfresco-like descriptions of users and groups from + * an LDAP directory, optionally restricted to those modified since a certain time. + * + * @author dward + */ +public class LDAPUserRegistry implements UserRegistry, InitializingBean, ActivateableBean +{ + /** The logger. */ + private static Log logger = LogFactory.getLog(LDAPUserRegistry.class); + + /** Is this bean active? I.e. should this part of the subsystem be used? */ + private boolean active = true; + + /** The group query. */ + private String groupQuery = "(objectclass=groupOfNames)"; + + /** The group differential query. */ + private String groupDifferentialQuery = "(&(objectclass=groupOfNames)(!(modifyTimestamp<={0})))"; + + /** The person query. */ + private String personQuery = "(objectclass=inetOrgPerson)"; + + /** The person differential query. */ + private String personDifferentialQuery = "(&(objectclass=inetOrgPerson)(!(modifyTimestamp<={0})))"; + + /** The group search base. */ + private String groupSearchBase; + + /** The user search base. */ + private String userSearchBase; + + /** The group id attribute name. */ + private String groupIdAttributeName = "cn"; + + /** The user id attribute name. */ + private String userIdAttributeName = "uid"; + + /** The member attribute name. */ + private String memberAttributeName = "member"; + + /** The modification timestamp attribute name. */ + private String modifyTimestampAttributeName = "modifyTimestamp"; + + /** The group type. */ + private String groupType = "groupOfNames"; + + /** The person type. */ + private String personType = "inetOrgPerson"; + + /** The ldap initial context factory. */ + private LDAPInitialDirContextFactory ldapInitialContextFactory; + + /** The attribute mapping. */ + private Map attributeMapping; + + /** The namespace service. */ + private NamespaceService namespaceService; + + /** The attribute defaults. */ + private Map attributeDefaults; + + /** Should we error on missing group members? */ + private boolean errorOnMissingMembers; + + /** Should we error on duplicate group IDs? */ + private boolean errorOnDuplicateGID; + + /** Should we error on missing group IDs? */ + private boolean errorOnMissingGID = true; + + /** Should we error on missing user IDs? */ + private boolean errorOnMissingUID = true; + + /** An array of all LDAP attributes to be queried from users */ + private String[] userAttributeNames; + + /** An array of all LDAP attributes to be queried from groups */ + private String[] groupAttributeNames; + + /** The LDAP generalized time format. */ + private static DateFormat LDAP_GENERALIZED_TIME_FORMAT; + static + { + LDAPUserRegistry.LDAP_GENERALIZED_TIME_FORMAT = new SimpleDateFormat("yyyyMMddHHmmss'Z'"); + LDAPUserRegistry.LDAP_GENERALIZED_TIME_FORMAT.setTimeZone(TimeZone.getTimeZone("GMT")); + } + + /** + * Indicates whether this bean is active. I.e. should this part of the subsystem be used? + * + * @param active + * true if this bean is active + */ + public void setActive(boolean active) + { + this.active = active; + } + + /** + * Sets the group id attribute name. + * + * @param groupIdAttributeName + * the group id attribute name + */ + public void setGroupIdAttributeName(String groupIdAttributeName) + { + this.groupIdAttributeName = groupIdAttributeName; + } + + /** + * Sets the group query. + * + * @param groupQuery + * the group query + */ + public void setGroupQuery(String groupQuery) + { + this.groupQuery = groupQuery; + } + + /** + * Sets the group differential query. + * + * @param groupDifferentialQuery + * the group differential query + */ + public void setGroupDifferentialQuery(String groupDifferentialQuery) + { + this.groupDifferentialQuery = groupDifferentialQuery; + } + + /** + * Sets the person query. + * + * @param personQuery + * the person query + */ + public void setPersonQuery(String personQuery) + { + this.personQuery = personQuery; + } + + /** + * Sets the person differential query. + * + * @param personDifferentialQuery + * the person differential query + */ + public void setPersonDifferentialQuery(String personDifferentialQuery) + { + this.personDifferentialQuery = personDifferentialQuery; + } + + /** + * Sets the group type. + * + * @param groupType + * the group type + */ + public void setGroupType(String groupType) + { + this.groupType = groupType; + } + + /** + * Sets the member attribute name. + * + * @param memberAttribute + * the member attribute name + */ + public void setMemberAttribute(String memberAttribute) + { + this.memberAttributeName = memberAttribute; + } + + /** + * Sets the person type. + * + * @param personType + * the person type + */ + public void setPersonType(String personType) + { + this.personType = personType; + } + + /** + * Sets the group search base. + * + * @param groupSearchBase + * the group search base + */ + public void setGroupSearchBase(String groupSearchBase) + { + this.groupSearchBase = groupSearchBase; + } + + /** + * Sets the user search base. + * + * @param userSearchBase + * the user search base + */ + public void setUserSearchBase(String userSearchBase) + { + this.userSearchBase = userSearchBase; + } + + /** + * Sets the user id attribute name. + * + * @param userIdAttributeName + * the user id attribute name + */ + public void setUserIdAttributeName(String userIdAttributeName) + { + this.userIdAttributeName = userIdAttributeName; + } + + /** + * Sets the modification timestamp attribute name. + * + * @param modifyTimestampAttributeName + * the modification timestamp attribute name + */ + public void setModifyTimestampAttributeName(String modifyTimestampAttributeName) + { + this.modifyTimestampAttributeName = modifyTimestampAttributeName; + } + + /** + * Decides whether to error on missing group members. + * + * @param errorOnMissingMembers + * true if we should error on missing group members + */ + public void setErrorOnMissingMembers(boolean errorOnMissingMembers) + { + this.errorOnMissingMembers = errorOnMissingMembers; + } + + /** + * Decides whether to error on missing group IDs. + * + * @param errorOnMissingGID + * true if we should error on missing group IDs + */ + public void setErrorOnMissingGID(boolean errorOnMissingGID) + { + this.errorOnMissingGID = errorOnMissingGID; + } + + /** + * Decides whether to error on missing user IDs. + * + * @param errorOnMissingUID + * true if we should error on missing user IDs + */ + public void setErrorOnMissingUID(boolean errorOnMissingUID) + { + this.errorOnMissingUID = errorOnMissingUID; + } + + /** + * Decides whether to error on duplicate group IDs. + * + * @param errorOnDuplicateGID + * true if we should error on duplicate group IDs + */ + public void setErrorOnDuplicateGID(boolean errorOnDuplicateGID) + { + this.errorOnDuplicateGID = errorOnDuplicateGID; + } + + /** + * Sets the LDAP initial dir context factory. + * + * @param ldapInitialDirContextFactory + * the new LDAP initial dir context factory + */ + public void setLDAPInitialDirContextFactory(LDAPInitialDirContextFactory ldapInitialDirContextFactory) + { + this.ldapInitialContextFactory = ldapInitialDirContextFactory; + } + + /** + * Sets the attribute defaults. + * + * @param attributeDefaults + * the attribute defaults + */ + public void setAttributeDefaults(Map attributeDefaults) + { + this.attributeDefaults = attributeDefaults; + } + + /** + * Sets the namespace service. + * + * @param namespaceService + * the namespace service + */ + public void setNamespaceService(NamespaceService namespaceService) + { + this.namespaceService = namespaceService; + } + + /** + * Sets the attribute mapping. + * + * @param attributeMapping + * the attribute mapping + */ + public void setAttributeMapping(Map attributeMapping) + { + this.attributeMapping = attributeMapping; + } + + /* + * (non-Javadoc) + * @see org.alfresco.repo.management.subsystems.ActivateableBean#isActive() + */ + public boolean isActive() + { + return this.active; + } + + /* + * (non-Javadoc) + * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet() + */ + public void afterPropertiesSet() throws Exception + { + Set userAttributeSet = new TreeSet(); + userAttributeSet.add(this.userIdAttributeName); + userAttributeSet.add(this.modifyTimestampAttributeName); + for (String attribute : this.attributeMapping.values()) + { + if (attribute != null) + { + userAttributeSet.add(attribute); + } + } + this.userAttributeNames = new String[userAttributeSet.size()]; + userAttributeSet.toArray(this.userAttributeNames); + this.groupAttributeNames = new String[] + { + this.groupIdAttributeName, this.modifyTimestampAttributeName, this.memberAttributeName + }; + } + + /* + * (non-Javadoc) + * @see org.alfresco.repo.security.sync.UserRegistry#getPersons(java.util.Date) + */ + public Iterator getPersons(Date modifiedSince) + { + return new PersonIterator(modifiedSince); + } + + /* + * (non-Javadoc) + * @see org.alfresco.repo.security.sync.UserRegistry#getGroups(java.util.Date) + */ + public Iterator getGroups(Date modifiedSince) + { + Map lookup = new TreeMap(); + InitialDirContext ctx = null; + try + { + ctx = this.ldapInitialContextFactory.getDefaultIntialDirContext(); + + SearchControls userSearchCtls = new SearchControls(); + userSearchCtls.setSearchScope(SearchControls.SUBTREE_SCOPE); + userSearchCtls.setReturningAttributes(this.groupAttributeNames); + + NamingEnumeration searchResults; + + if (modifiedSince == null) + { + searchResults = ctx.search(this.groupSearchBase, this.groupQuery, userSearchCtls); + } + else + { + searchResults = ctx.search(this.groupSearchBase, this.groupDifferentialQuery, new Object[] + { + LDAPUserRegistry.LDAP_GENERALIZED_TIME_FORMAT.format(modifiedSince) + }, userSearchCtls); + } + + LdapName groupDistinguishedNamePrefix = new LdapName(this.groupSearchBase); + LdapName userDistinguishedNamePrefix = new LdapName(this.userSearchBase); + + while (searchResults.hasMoreElements()) + { + SearchResult result = searchResults.next(); + Attributes attributes = result.getAttributes(); + Attribute gidAttribute = attributes.get(this.groupIdAttributeName); + if (gidAttribute == null) + { + if (this.errorOnMissingGID) + { + throw new AlfrescoRuntimeException( + "NodeDescription returned by group search does not have mandatory group id attribute " + + attributes); + } + else + { + LDAPUserRegistry.logger.warn("Missing GID on " + attributes); + continue; + } + } + String gid = "GROUP_" + gidAttribute.get(0); + + NodeDescription group = lookup.get(gid); + if (group == null) + { + group = new NodeDescription(); + group.getProperties().put(ContentModel.PROP_AUTHORITY_NAME, gid); + lookup.put(gid, group); + } + else if (this.errorOnDuplicateGID) + { + throw new AlfrescoRuntimeException("Duplicate group id found for " + gid); + } + else + { + LDAPUserRegistry.logger.warn("Duplicate gid found for " + gid + " -> merging definitions"); + } + + Attribute modifyTimestamp = attributes.get(this.modifyTimestampAttributeName); + if (modifyTimestamp != null) + { + group.setLastModified(LDAPUserRegistry.LDAP_GENERALIZED_TIME_FORMAT.parse(modifyTimestamp.get() + .toString())); + } + Set childAssocs = group.getChildAssociations(); + + Attribute memAttribute = attributes.get(this.memberAttributeName); + // check for null + if (memAttribute != null) + { + for (int i = 0; i < memAttribute.size(); i++) + { + String attribute = (String) memAttribute.get(i); + if (attribute != null) + { + LdapName distinguishedName = new LdapName(attribute); + Attribute nameAttribute; + + // If the user and group search bases are different we may be able to recognise user and + // group DNs without a secondary lookup + if (!this.userSearchBase.equals(this.groupSearchBase)) + { + Attributes nameAttributes = distinguishedName.getRdn(distinguishedName.size() - 1) + .toAttributes(); + + // Recognise user DNs + if (distinguishedName.startsWith(userDistinguishedNamePrefix) + && (nameAttribute = nameAttributes.get(this.userIdAttributeName)) != null) + { + childAssocs.add((String) nameAttribute.get()); + continue; + } + + // Recognise group DNs + if (distinguishedName.startsWith(groupDistinguishedNamePrefix) + && (nameAttribute = nameAttributes.get(this.groupIdAttributeName)) != null) + { + childAssocs.add("GROUP_" + nameAttribute.get()); + continue; + } + } + + // If we can't determine the name and type from the DN alone, try a directory lookup + if (distinguishedName.startsWith(userDistinguishedNamePrefix) + || distinguishedName.startsWith(groupDistinguishedNamePrefix)) + { + try + { + + Attributes childAttributes = ctx.getAttributes(attribute, new String[] + { + "objectclass", this.groupIdAttributeName, this.userIdAttributeName + }); + String objectclass = (String) childAttributes.get("objectclass").get(); + if (objectclass.equals(this.personType)) + { + nameAttribute = childAttributes.get(this.userIdAttributeName); + if (nameAttribute == null) + { + if (this.errorOnMissingUID) + { + throw new AlfrescoRuntimeException( + "User missing user id attribute DN =" + attribute + " att = " + + this.userIdAttributeName); + } + else + { + LDAPUserRegistry.logger.warn("User missing user id attribute DN =" + + attribute + " att = " + this.userIdAttributeName); + continue; + } + } + + childAssocs.add((String) nameAttribute.get()); + continue; + } + else if (objectclass.equals(this.groupType)) + { + + nameAttribute = childAttributes.get(this.groupIdAttributeName); + if (nameAttribute == null) + { + if (this.errorOnMissingGID) + { + throw new AlfrescoRuntimeException( + "Group returned by group search does not have mandatory group id attribute " + + attributes); + } + else + { + LDAPUserRegistry.logger.warn("Missing GID on " + childAttributes); + continue; + } + } + childAssocs.add("GROUP_" + nameAttribute.get()); + continue; + } + } + catch (NamingException e) + { + // Unresolvable name + } + } + if (this.errorOnMissingMembers) + { + throw new AlfrescoRuntimeException("Failed to resolve distinguished name: " + attribute); + } + LDAPUserRegistry.logger.warn("Failed to resolve distinguished name: " + attribute); + } + } + } + } + + if (LDAPUserRegistry.logger.isDebugEnabled()) + { + LDAPUserRegistry.logger.debug("Found " + lookup.size()); + } + + return lookup.values().iterator(); + } + catch (NamingException e) + { + throw new AlfrescoRuntimeException("User and group import failed", e); + } + catch (ParseException e) + { + throw new AlfrescoRuntimeException("User and group import failed", e); + } + finally + { + if (ctx != null) + { + try + { + ctx.close(); + } + catch (NamingException e) + { + } + } + } + } + + /** + * Wraps the LDAP user query as an {@link Iterator}. + */ + public class PersonIterator implements Iterator + { + + /** The directory context. */ + private InitialDirContext ctx; + + /** The search results. */ + private NamingEnumeration searchResults; + + /** The uids. */ + private HashSet uids = new HashSet(); + + /** The next node description to return. */ + private NodeDescription next; + + /** + * Instantiates a new person iterator. + * + * @param modifiedSince + * if non-null, then only descriptions of users modified since this date should be returned; if + * null then descriptions of all users should be returned. + */ + public PersonIterator(Date modifiedSince) + { + try + { + this.ctx = LDAPUserRegistry.this.ldapInitialContextFactory.getDefaultIntialDirContext(); + + // Authentication has been successful. + // Set the current user, they are now authenticated. + + SearchControls userSearchCtls = new SearchControls(); + userSearchCtls.setSearchScope(SearchControls.SUBTREE_SCOPE); + userSearchCtls.setCountLimit(Integer.MAX_VALUE); + userSearchCtls.setReturningAttributes(LDAPUserRegistry.this.userAttributeNames); + + if (modifiedSince == null) + { + this.searchResults = this.ctx.search(LDAPUserRegistry.this.userSearchBase, + LDAPUserRegistry.this.personQuery, userSearchCtls); + } + else + { + this.searchResults = this.ctx.search(LDAPUserRegistry.this.userSearchBase, + LDAPUserRegistry.this.personDifferentialQuery, new Object[] + { + LDAPUserRegistry.LDAP_GENERALIZED_TIME_FORMAT.format(modifiedSince) + }, userSearchCtls); + } + this.next = fetchNext(); + } + catch (NamingException e) + { + throw new AlfrescoRuntimeException("Failed to import people.", e); + } + finally + { + if (this.searchResults == null) + { + try + { + this.ctx.close(); + } + catch (Exception e) + { + } + this.ctx = null; + } + } + } + + /* + * (non-Javadoc) + * @see java.util.Iterator#hasNext() + */ + public boolean hasNext() + { + return this.next != null; + } + + /* + * (non-Javadoc) + * @see java.util.Iterator#next() + */ + public NodeDescription next() + { + if (this.next == null) + { + throw new IllegalStateException(); + } + NodeDescription current = this.next; + try + { + this.next = fetchNext(); + } + catch (NamingException e) + { + throw new AlfrescoRuntimeException("Failed to import people.", e); + } + return current; + } + + /** + * Pre-fetches the next node description to be returned. + * + * @return the node description + * @throws NamingException + * on a naming exception + */ + private NodeDescription fetchNext() throws NamingException + { + while (this.searchResults.hasMoreElements()) + { + SearchResult result = this.searchResults.next(); + Attributes attributes = result.getAttributes(); + Attribute uidAttribute = attributes.get(LDAPUserRegistry.this.userIdAttributeName); + if (uidAttribute == null) + { + if (LDAPUserRegistry.this.errorOnMissingUID) + { + throw new AlfrescoRuntimeException( + "User returned by user search does not have mandatory user id attribute " + attributes); + } + else + { + LDAPUserRegistry.logger + .warn("User returned by user search does not have mandatory user id attribute " + + attributes); + continue; + } + } + String uid = (String) uidAttribute.get(0); + + if (this.uids.contains(uid)) + { + LDAPUserRegistry.logger + .warn("Duplicate uid found - there will be more than one person object for this user - " + + uid); + } + + this.uids.add(uid); + + if (LDAPUserRegistry.logger.isDebugEnabled()) + { + LDAPUserRegistry.logger.debug("Adding user for " + uid); + } + + NodeDescription person = new NodeDescription(); + + Attribute modifyTimestamp = attributes.get(LDAPUserRegistry.this.modifyTimestampAttributeName); + if (modifyTimestamp != null) + { + try + { + person.setLastModified(LDAPUserRegistry.LDAP_GENERALIZED_TIME_FORMAT.parse(modifyTimestamp + .get().toString())); + } + catch (ParseException e) + { + throw new AlfrescoRuntimeException("Failed to import people.", e); + } + } + + PropertyMap properties = person.getProperties(); + for (String key : LDAPUserRegistry.this.attributeMapping.keySet()) + { + QName keyQName = QName.createQName(key, LDAPUserRegistry.this.namespaceService); + + // cater for null + String attributeName = LDAPUserRegistry.this.attributeMapping.get(key); + if (attributeName != null) + { + Attribute attribute = attributes.get(attributeName); + if (attribute != null) + { + String value = (String) attribute.get(0); + if (value != null) + { + properties.put(keyQName, value); + } + } + else + { + String defaultValue = LDAPUserRegistry.this.attributeDefaults.get(key); + if (defaultValue != null) + { + properties.put(keyQName, defaultValue); + } + } + } + else + { + String defaultValue = LDAPUserRegistry.this.attributeDefaults.get(key); + if (defaultValue != null) + { + properties.put(keyQName, defaultValue); + } + } + } + return person; + } + this.searchResults.close(); + this.searchResults = null; + this.ctx.close(); + this.ctx = null; + return null; + } + + /* + * (non-Javadoc) + * @see java.util.Iterator#remove() + */ + public void remove() + { + throw new UnsupportedOperationException(); + } + } +} diff --git a/source/java/org/alfresco/repo/site/SiteServiceImpl.java b/source/java/org/alfresco/repo/site/SiteServiceImpl.java index 58825483b7..f481cf1b7d 100644 --- a/source/java/org/alfresco/repo/site/SiteServiceImpl.java +++ b/source/java/org/alfresco/repo/site/SiteServiceImpl.java @@ -359,16 +359,15 @@ public class SiteServiceImpl implements SiteService, SiteModel public String doWork() throws Exception { // Create the site's groups - String siteGroup = authorityService.createAuthority( - AuthorityType.GROUP, null, getSiteGroup(shortName, - false)); + String siteGroup = authorityService + .createAuthority(AuthorityType.GROUP, getSiteGroup(shortName, false)); Set permissions = permissionService.getSettablePermissions(SiteModel.TYPE_SITE); for (String permission : permissions) { // Create a group for the permission - String permissionGroup = authorityService.createAuthority( - AuthorityType.GROUP, siteGroup, getSiteRoleGroup( - shortName, permission, false)); + String permissionGroup = authorityService.createAuthority(AuthorityType.GROUP, getSiteRoleGroup( + shortName, permission, false)); + authorityService.addAuthority(siteGroup, permissionGroup); // Assign the group the relevant permission on the site permissionService.setPermission(siteNodeRef, permissionGroup, permission, true); diff --git a/source/java/org/alfresco/repo/site/SiteServiceImplTest.java b/source/java/org/alfresco/repo/site/SiteServiceImplTest.java index f31ab61fb0..1c861c800d 100644 --- a/source/java/org/alfresco/repo/site/SiteServiceImplTest.java +++ b/source/java/org/alfresco/repo/site/SiteServiceImplTest.java @@ -112,18 +112,19 @@ public class SiteServiceImplTest extends BaseAlfrescoSpringTest createUser(USER_FOUR); // Create the test groups - this.groupOne = this.authorityService.createAuthority(AuthorityType.GROUP, null, GROUP_ONE); + this.groupOne = this.authorityService.createAuthority(AuthorityType.GROUP, GROUP_ONE); this.authorityService.addAuthority(this.groupOne, USER_TWO); - this.groupTwo = this.authorityService.createAuthority(AuthorityType.GROUP, null, GROUP_TWO); + this.groupTwo = this.authorityService.createAuthority(AuthorityType.GROUP, GROUP_TWO); this.authorityService.addAuthority(this.groupTwo, USER_TWO); this.authorityService.addAuthority(this.groupTwo, USER_THREE); - this.groupThree = this.authorityService.createAuthority(AuthorityType.GROUP, null, GROUP_THREE); + this.groupThree = this.authorityService.createAuthority(AuthorityType.GROUP, GROUP_THREE); this.authorityService.addAuthority(this.groupThree, USER_TWO); this.authorityService.addAuthority(this.groupThree, USER_THREE); - this.groupFour = this.authorityService.createAuthority(AuthorityType.GROUP, this.groupThree, GROUP_FOUR); + this.groupFour = this.authorityService.createAuthority(AuthorityType.GROUP, GROUP_FOUR); + this.authorityService.addAuthority(this.groupThree, this.groupFour); this.authorityService.addAuthority(this.groupFour, USER_FOUR); diff --git a/source/java/org/alfresco/repo/tenant/MultiTDemoTest.java b/source/java/org/alfresco/repo/tenant/MultiTDemoTest.java index 3b01d1fb4f..e82a145419 100644 --- a/source/java/org/alfresco/repo/tenant/MultiTDemoTest.java +++ b/source/java/org/alfresco/repo/tenant/MultiTDemoTest.java @@ -881,7 +881,12 @@ public class MultiTDemoTest extends TestCase } } - this.authorityService.createAuthority(AuthorityType.GROUP, parentGroupName, shortName); + this.authorityService.createAuthority(AuthorityType.GROUP, shortName); + + if (parentGroupName != null) + { + this.authorityService.addAuthority(parentGroupName, groupName); + } } else diff --git a/source/java/org/alfresco/repo/version/NodeServiceImpl.java b/source/java/org/alfresco/repo/version/NodeServiceImpl.java index ad5b2b7222..c130c1f3bb 100644 --- a/source/java/org/alfresco/repo/version/NodeServiceImpl.java +++ b/source/java/org/alfresco/repo/version/NodeServiceImpl.java @@ -650,4 +650,13 @@ public class NodeServiceImpl implements NodeService, VersionModel { throw new UnsupportedOperationException(MSG_UNSUPPORTED); } + + /** + * @throws UnsupportedOperationException always + */ + public Collection getNodesWithoutParentAssocsOfType(StoreRef storeRef, QName nodeTypeQName, + QName assocTypeQName) + { + throw new UnsupportedOperationException(MSG_UNSUPPORTED); + } } diff --git a/source/java/org/alfresco/service/cmr/repository/NodeService.java b/source/java/org/alfresco/service/cmr/repository/NodeService.java index a97bcece32..05dd045add 100644 --- a/source/java/org/alfresco/service/cmr/repository/NodeService.java +++ b/source/java/org/alfresco/service/cmr/repository/NodeService.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2007 Alfresco Software Limited. + * Copyright (C) 2005-2009 Alfresco Software Limited. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -18,7 +18,7 @@ * As a special exception to the terms and conditions of version 2.0 of * the GPL, you may redistribute this Program in connection with Free/Libre * and Open Source Software ("FLOSS") applications as described in Alfresco's - * FLOSS exception. You should have recieved a copy of the text describing + * FLOSS exception. You should have received a copy of the text describing * the FLOSS exception, and it is also available here: * http://www.alfresco.com/legal/licensing" */ @@ -559,6 +559,22 @@ public interface NodeService @Auditable(key = Auditable.Key.ARG_0 ,parameters = {"nodeRef"}) public ChildAssociationRef getPrimaryParent(NodeRef nodeRef) throws InvalidNodeRefException; + /** + * Gets the set of nodes of a certain type without parent associations of a certain type. In effect the 'orphans' + * with respect to a certain association type. + * + * @param storeRef + * the store reference + * @param nodeTypeQName + * the node type QName + * @param assocTypeQName + * the association type QName + * @return the set of nodes of the required type without parent associations of the required type + */ + @Auditable(key = Auditable.Key.ARG_0 ,parameters = {"storeRef", "nodeTypeQName", "assocTypeQName"}) + public Collection getNodesWithoutParentAssocsOfType(final StoreRef storeRef, final QName nodeTypeQName, + final QName assocTypeQName); + /** * * @param sourceRef a reference to a real node diff --git a/source/java/org/alfresco/service/cmr/security/AuthorityService.java b/source/java/org/alfresco/service/cmr/security/AuthorityService.java index d5831e191a..9de5fed709 100644 --- a/source/java/org/alfresco/service/cmr/security/AuthorityService.java +++ b/source/java/org/alfresco/service/cmr/security/AuthorityService.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2007 Alfresco Software Limited. + * Copyright (C) 2005-2009 Alfresco Software Limited. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -18,7 +18,7 @@ * As a special exception to the terms and conditions of version 2.0 of * the GPL, you may redistribute this Program in connection with Free/Libre * and Open Source Software ("FLOSS") applications as described in Alfresco's - * FLOSS exception. You should have recieved a copy of the text describing + * FLOSS exception. You should have received a copy of the text describing * the FLOSS exception, and it is also available here: * http://www.alfresco.com/legal/licensing" */ @@ -28,6 +28,8 @@ import java.util.Set; import org.alfresco.service.Auditable; import org.alfresco.service.PublicService; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.namespace.QName; /** * The service that encapsulates authorities granted to users. @@ -46,6 +48,13 @@ import org.alfresco.service.PublicService; @PublicService public interface AuthorityService { + + /** + * The default zone that owns all authorities for which a zone is not explicitly specified in the authorityZone + * property + */ + public static final String DEFAULT_ZONE = ""; + /** * Check of the current user has admin authority. * @@ -105,7 +114,7 @@ public interface AuthorityService * Find authorities by pattern matching (* and ?) against the full authority name. * @param type - the authority type * @param namePattern - the pattern which will be matched against the full authority name. - * @return the names of the authorites matching the pattern and type. + * @return the names of the authorities matching the pattern and type. */ @Auditable(parameters = {"type"}) public Set findAuthorities(AuthorityType type, String namePattern); @@ -123,14 +132,10 @@ public interface AuthorityService public Set getAllRootAuthorities(AuthorityType type); /** - * Create an authority. If the parent is null thisw method creates a root - * authority. + * Create an authority. * * @param type - * the type of the authority - * @param parentName - - * the full name of the parent authority. If this is null then a root - * authority is created. * @param shortName - * the short name of the authority to create * this will also be set as the default display name for the authority @@ -138,28 +143,25 @@ public interface AuthorityService * @return the name of the authority (this will be the prefix, if any * associated with the type appended with the short name) */ - @Auditable(parameters = {"type", "parentName", "shortName"}) - public String createAuthority(AuthorityType type, String parentName, String shortName); + @Auditable(parameters = {"type", "shortName"}) + public String createAuthority(AuthorityType type, String shortName); /** - * Create an authority. If the parent is null this method creates a root - * authority. + * Create an authority with a display name and zone. * - * @param type - + * @param type * the type of the authority - * @param parentName - - * the full name of the parent authority. If this is null then a root - * authority is created. - * @param shortName - + * @param shortName * the short name of the authority to create - * @param authorityDisplayName - * the display name for the authority - * - * @return the full name of the authority (this will be the prefix, if any - * associated with the type appended with the short name) + * @param authorityDisplayName + * the display name for the authority + * @param authorityZone + * identifier for external user registry owning the authority or null if not applicable + * @return the full name of the authority (this will be the prefix, if any associated with the type appended with + * the short name) */ - @Auditable(parameters = {"type", "parentName", "shortName", "authorityDisplayName"}) - public String createAuthority(AuthorityType type, String parentName, String shortName, String authorityDisplayName); + @Auditable(parameters = {"type", "shortName", "authorityDisplayName", "authorityZone"}) + public String createAuthority(AuthorityType type, String shortName, String authorityDisplayName, String authorityZone); /** * Set an authority to include another authority. For example, adding a @@ -279,4 +281,36 @@ public interface AuthorityService @Auditable(parameters = {"authorityName", "authorityDisplayName"}) public void setAuthorityDisplayName(String authorityName, String authorityDisplayName); + /** + * Gets or creates an authority zone node with the specified name + * + * @param zoneName + * the zone name + * @return reference to the zone node + */ + @Auditable(parameters = {"zoneName"}) + public NodeRef getOrCreateZone(String zoneName); + + /** + * Gets the name of the zone containing the specified authority. + * + * @param name + * the authority long name + * @return the the name of the zone containing the specified authority, {@link AuthorityService#DEFAULT_ZONE} if the + * authority exists but has no zone, or null if the authority does not exist. + */ + @Auditable(parameters = {"name"}) + public String getAuthorityZone(String name); + + /** + * Gets the names of all authorities in a zone, optionally filtered by type. + * + * @param zoneName + * the zone name + * @param type + * the authority type to filter by or null for all authority types + * @return the names of all authorities in a zone, optionally filtered by type + */ + @Auditable(parameters = {"zoneName", "type"}) + public Set getAllAuthoritiesInZone(String zoneName, AuthorityType type); } diff --git a/source/java/org/alfresco/service/cmr/security/PersonService.java b/source/java/org/alfresco/service/cmr/security/PersonService.java index 33b841200c..0328ef5b1f 100644 --- a/source/java/org/alfresco/service/cmr/security/PersonService.java +++ b/source/java/org/alfresco/service/cmr/security/PersonService.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2007 Alfresco Software Limited. + * Copyright (C) 2005-2009 Alfresco Software Limited. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -18,7 +18,7 @@ * As a special exception to the terms and conditions of version 2.0 of * the GPL, you may redistribute this Program in connection with Free/Libre * and Open Source Software ("FLOSS") applications as described in Alfresco's - * FLOSS exception. You should have recieved a copy of the text describing + * FLOSS exception. You should have received a copy of the text describing * the FLOSS exception, and it is also available here: * http://www.alfresco.com/legal/licensing" */ @@ -139,6 +139,21 @@ public interface PersonService @Auditable(parameters = {"properties"}) public NodeRef createPerson(Map properties); + /** + * Create a new person with the given properties, recording them against the given zone name (usually identifying an + * external user registry from which the details were obtained). The userName is one of the properties. Users with + * duplicate userNames are not allowed. + * + * @param properties + * the properties + * @param zone + * an identifier for the external user registry owning the person information, or null if + * not applicable. + * @return the node ref + */ + @Auditable(parameters = {"properties", "zone"}) + public NodeRef createPerson(Map properties, String zone); + /** * Delete the person identified by the given user name. * diff --git a/source/java/org/alfresco/wcm/sandbox/SandboxFactory.java b/source/java/org/alfresco/wcm/sandbox/SandboxFactory.java index 92c53bf7c6..c1c943ab91 100644 --- a/source/java/org/alfresco/wcm/sandbox/SandboxFactory.java +++ b/source/java/org/alfresco/wcm/sandbox/SandboxFactory.java @@ -365,7 +365,7 @@ public final class SandboxFactory extends WCMUtil String group = authorityService.getName(AuthorityType.GROUP, shortName); if (!authorityService.authorityExists(group)) { - authorityService.createAuthority(AuthorityType.GROUP, null, shortName); + authorityService.createAuthority(AuthorityType.GROUP, shortName); } if (!isPermissionSet(dirRef, group, permission)) { @@ -399,7 +399,7 @@ public final class SandboxFactory extends WCMUtil String group = authorityService.getName(AuthorityType.GROUP, shortName); if (!authorityService.authorityExists(group)) { - authorityService.createAuthority(AuthorityType.GROUP, null, shortName); + authorityService.createAuthority(AuthorityType.GROUP, shortName); } Set members = authorityService.getContainedAuthorities(AuthorityType.USER, group, true); if (!members.contains(user)) diff --git a/source/java/org/alfresco/wcm/webproject/WebProjectServiceImplTest.java b/source/java/org/alfresco/wcm/webproject/WebProjectServiceImplTest.java index 1c860d663b..52ce6473d6 100644 --- a/source/java/org/alfresco/wcm/webproject/WebProjectServiceImplTest.java +++ b/source/java/org/alfresco/wcm/webproject/WebProjectServiceImplTest.java @@ -138,7 +138,7 @@ public class WebProjectServiceImplTest extends AbstractWCMServiceImplTest String groupName = authorityService.getName(AuthorityType.GROUP, shortName); if (authorityService.authorityExists(groupName) == false) { - authorityService.createAuthority(AuthorityType.GROUP, null, shortName); + authorityService.createAuthority(AuthorityType.GROUP, shortName); for (String userName : userNames) { diff --git a/source/test-resources/sync-test-context.xml b/source/test-resources/sync-test-context.xml new file mode 100644 index 0000000000..9e0e967ec8 --- /dev/null +++ b/source/test-resources/sync-test-context.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + userRegistry + + + + + + + \ No newline at end of file