diff --git a/config/alfresco/messages/site-service.properties b/config/alfresco/messages/site-service.properties index 18e64db3d4..5b3939588a 100644 --- a/config/alfresco/messages/site-service.properties +++ b/config/alfresco/messages/site-service.properties @@ -1,6 +1,7 @@ # Site service externalised display strings site_service.unable_to_create=Unable to create site because the site short name {0} is already in use. Site short names must be unique. +site_service.visibility_group_missing=Unable to create site because the visibility group {0} does not exist. site_service.can_not_update=Can not update site {0} because it does not exist. site_service.can_not_delete=Can not delete site {0} because it does not exist. site_service.site_no_exist=Site {0} does not exist. diff --git a/config/alfresco/site-services-context.xml b/config/alfresco/site-services-context.xml index b4aa1ab2e2..73ea4326e2 100644 --- a/config/alfresco/site-services-context.xml +++ b/config/alfresco/site-services-context.xml @@ -90,8 +90,7 @@ - - + diff --git a/config/alfresco/subsystems/sysAdmin/default/sysadmin-parameter-context.xml b/config/alfresco/subsystems/sysAdmin/default/sysadmin-parameter-context.xml index c8b9fd53ff..68d8596dca 100644 --- a/config/alfresco/subsystems/sysAdmin/default/sysadmin-parameter-context.xml +++ b/config/alfresco/subsystems/sysAdmin/default/sysadmin-parameter-context.xml @@ -38,6 +38,8 @@ ${share.protocol} + + diff --git a/config/alfresco/subsystems/sysAdmin/default/sysadmin-parameter.properties b/config/alfresco/subsystems/sysAdmin/default/sysadmin-parameter.properties index 92ddefa30f..fecf5e867d 100644 --- a/config/alfresco/subsystems/sysAdmin/default/sysadmin-parameter.properties +++ b/config/alfresco/subsystems/sysAdmin/default/sysadmin-parameter.properties @@ -10,3 +10,8 @@ share.context=share share.host=${localname} share.port=8080 share.protocol=http + +# Share Site configuration. +# +# This property controls who has visibility of created share sites. +site.public.group=GROUP_EVERYONE diff --git a/source/java/org/alfresco/repo/admin/SysAdminParams.java b/source/java/org/alfresco/repo/admin/SysAdminParams.java index fda30a2b5a..5a37bce23d 100644 --- a/source/java/org/alfresco/repo/admin/SysAdminParams.java +++ b/source/java/org/alfresco/repo/admin/SysAdminParams.java @@ -105,6 +105,15 @@ public interface SysAdminParams */ public String getShareProtocol(); + /** + * Gets the group name used for public site visibility. + * Only members of this group will have SiteConsumer access to 'public' share sites. + * + * @return the name of the public site group. + * @since 3.4 + */ + public String getSitePublicGroup(); + /** * Expands the special ${localname} token within a host name using the resolved DNS name for the local host. * diff --git a/source/java/org/alfresco/repo/admin/SysAdminParamsImpl.java b/source/java/org/alfresco/repo/admin/SysAdminParamsImpl.java index d07641dd63..cc83c22a79 100644 --- a/source/java/org/alfresco/repo/admin/SysAdminParamsImpl.java +++ b/source/java/org/alfresco/repo/admin/SysAdminParamsImpl.java @@ -23,6 +23,7 @@ import java.util.ArrayList; import java.util.List; import java.util.StringTokenizer; +import org.alfresco.service.cmr.security.PermissionService; import org.alfresco.service.license.LicenseService; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.NoSuchBeanDefinitionException; @@ -75,6 +76,9 @@ public class SysAdminParamsImpl implements SysAdminParams, ApplicationContextAwa /** Share protocol. */ private String shareProtocol = "http"; + + // The default is GROUP_EVERYONE, although this will likely be overridden by an injected value from spring. + private String sitePublicGroup = PermissionService.ALL_AUTHORITIES; public SysAdminParamsImpl() { @@ -283,4 +287,19 @@ public class SysAdminParamsImpl implements SysAdminParams, ApplicationContextAwa { return hostName.replace(TOKEN_LOCAL_NAME, localName); } + + /* + * (non-Javadoc) + * @see org.alfresco.repo.admin.SysAdminParams#getSitePublicGroup() + */ + public String getSitePublicGroup() + { + return this.sitePublicGroup; + } + + public void setSitePublicGroup(String sitePublicGroup) + { + this.sitePublicGroup = sitePublicGroup; + } + } diff --git a/source/java/org/alfresco/repo/site/SiteServiceImpl.java b/source/java/org/alfresco/repo/site/SiteServiceImpl.java index f91c163319..aeaa309df5 100644 --- a/source/java/org/alfresco/repo/site/SiteServiceImpl.java +++ b/source/java/org/alfresco/repo/site/SiteServiceImpl.java @@ -33,6 +33,7 @@ import java.util.concurrent.ConcurrentHashMap; import org.alfresco.model.ContentModel; import org.alfresco.repo.activities.ActivityType; +import org.alfresco.repo.admin.SysAdminParams; import org.alfresco.repo.search.QueryParameterDefImpl; import org.alfresco.repo.search.impl.lucene.LuceneQueryParser; import org.alfresco.repo.security.authentication.AuthenticationContext; @@ -70,13 +71,13 @@ import org.alfresco.service.cmr.tagging.TaggingService; import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; import org.alfresco.service.namespace.RegexQNamePattern; +import org.alfresco.util.PropertyCheck; import org.alfresco.util.PropertyMap; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.json.JSONException; import org.json.JSONObject; import org.springframework.extensions.surf.util.ParameterCheck; -import org.alfresco.util.PropertyCheck; /** * Site Service Implementation. Also bootstraps the site AVM and DM stores. @@ -109,6 +110,7 @@ public class SiteServiceImpl implements SiteService, SiteModel /** Messages */ private static final String MSG_UNABLE_TO_CREATE = "site_service.unable_to_create"; + private static final String MSG_VISIBILITY_GROUP_MISSING = "site_service.visibility_group_missing"; private static final String MSG_CAN_NOT_UPDATE = "site_service.can_not_update"; private static final String MSG_CAN_NOT_DELETE = "site_service.can_not_delete"; private static final String MSG_SITE_NO_EXIST = "site_service.site_no_exist"; @@ -132,7 +134,8 @@ public class SiteServiceImpl implements SiteService, SiteModel private TenantService tenantService; private TenantAdminService tenantAdminService; private RetryingTransactionHelper retryingTransactionHelper; - private Comparator roleComparator ; + private Comparator roleComparator; + private SysAdminParams sysAdminParams; /** @@ -268,6 +271,11 @@ public class SiteServiceImpl implements SiteService, SiteModel { this.roleComparator = roleComparator; } + + public void setSysAdminParams(SysAdminParams sysAdminParams) + { + this.sysAdminParams = sysAdminParams; + } public Comparator getRoleComparator() { @@ -398,7 +406,21 @@ public class SiteServiceImpl implements SiteService, SiteModel // - add the current user to the site manager group if (SiteVisibility.PUBLIC.equals(visibility) == true) { - permissionService.setPermission(siteNodeRef, PermissionService.ALL_AUTHORITIES, SITE_CONSUMER, true); + // From Alfresco 3.4 the 'site public' group is configurable. Out of the box it is + // GROUP_EVERYONE so unconfigured behaviour is unchanged. But from 3.4 admins + // can change the value of property site.public.group via JMX/properties files + // to be another group of their choosing. + // This then is the group that is given SiteConsumer access to newly created sites. + final String sitePublicGroup = sysAdminParams.getSitePublicGroup(); + + boolean groupExists = authorityService.authorityExists(sitePublicGroup); + if (!PermissionService.ALL_AUTHORITIES.equals(sitePublicGroup) && !groupExists) + { + // If the group does not exist, we cannot create the site. + throw new SiteServiceException(MSG_VISIBILITY_GROUP_MISSING, new Object[]{sitePublicGroup}); + } + + permissionService.setPermission(siteNodeRef, sitePublicGroup, SITE_CONSUMER, true); } else if (SiteVisibility.MODERATED.equals(visibility) == true) { diff --git a/source/java/org/alfresco/repo/site/SiteServiceImplTest.java b/source/java/org/alfresco/repo/site/SiteServiceImplTest.java index cb8d6561be..1e6fee6d06 100644 --- a/source/java/org/alfresco/repo/site/SiteServiceImplTest.java +++ b/source/java/org/alfresco/repo/site/SiteServiceImplTest.java @@ -28,6 +28,7 @@ import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.model.ContentModel; import org.alfresco.model.ForumModel; import org.alfresco.repo.jscript.ClasspathScriptLocation; +import org.alfresco.repo.management.subsystems.ChildApplicationContextFactory; import org.alfresco.repo.security.authentication.AuthenticationComponent; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.service.cmr.model.FileFolderService; @@ -37,6 +38,7 @@ import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.repository.ScriptLocation; import org.alfresco.service.cmr.repository.ScriptService; +import org.alfresco.service.cmr.security.AccessPermission; import org.alfresco.service.cmr.security.AuthorityService; import org.alfresco.service.cmr.security.AuthorityType; import org.alfresco.service.cmr.security.PermissionService; @@ -148,6 +150,10 @@ public class SiteServiceImplTest extends BaseAlfrescoSpringTest } } + /** + * This test method ensures that public sites can be created and that their site info is correct. + * It also tests that a duplicate site cannot be created. + */ public void testCreateSite() throws Exception { // Create a public site @@ -203,10 +209,108 @@ public class SiteServiceImplTest extends BaseAlfrescoSpringTest { // Expected } - - } - + + /** + * This method tests https://issues.alfresco.com/jira/browse/ALF-3785 which allows 'public' sites + * to be only visible to members of a configured group, by default EVERYONE. + * + * @author Neil McErlean + * @since 3.4 + */ + @SuppressWarnings("deprecation") + public void testConfigurableSitePublicGroup() throws Exception + { + AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getAdminUserName()); + + // We'll be configuring a JMX managed bean (in this test method only). + ChildApplicationContextFactory sysAdminSubsystem = (ChildApplicationContextFactory) applicationContext.getBean("sysAdmin"); + final String sitePublicGroupPropName = "site.public.group"; + final String originalSitePublicGroup = "GROUP_EVERYONE"; + + try + { + // Firstly we'll ensure that the site.public.group has the correct (pristine) value. + String groupName = sysAdminSubsystem.getProperty(sitePublicGroupPropName); + assertEquals(sitePublicGroupPropName + " was not the pristine value", + originalSitePublicGroup, groupName); + + // Create a 'normal', unconfigured site. + SiteInfo unconfiguredSite = siteService.createSite(TEST_SITE_PRESET, "unconfigured", + TEST_TITLE, TEST_DESCRIPTION, SiteVisibility.PUBLIC); + assertTrue(containsConsumerPermission(originalSitePublicGroup, unconfiguredSite)); + + + // Now set the managed bean's visibility group to something other than GROUP_EVERYONE. + // This is the group that will have visibility of subsequently created sites. + // + // We'll intentionally set it to a group that DOES NOT EXIST YET. + String newGroupName = this.getClass().getSimpleName() + System.currentTimeMillis(); + String prefixedNewGroupName = PermissionService.GROUP_PREFIX + newGroupName; + + sysAdminSubsystem.stop(); + sysAdminSubsystem.setProperty(sitePublicGroupPropName, prefixedNewGroupName); + sysAdminSubsystem.start(); + + // Now create a site as before. It should fail as we're using a group that doesn't exist. + boolean expectedExceptionThrown = false; + try + { + siteService.createSite(TEST_SITE_PRESET, "thisShouldFail", + TEST_TITLE, TEST_DESCRIPTION, SiteVisibility.PUBLIC); + } + catch (SiteServiceException expected) + { + expectedExceptionThrown = true; + } + if (!expectedExceptionThrown) + { + fail("Expected exception on createSite with non-existent group was not thrown."); + } + + + // Now we'll create the group used above. + authorityService.createAuthority(AuthorityType.GROUP, newGroupName); + + + // And create the site as before. This time it should succeed. + SiteInfo configuredSite = siteService.createSite(TEST_SITE_PRESET, "configured", + TEST_TITLE, TEST_DESCRIPTION, SiteVisibility.PUBLIC); + + // And check the permissions on the site. + assertTrue("The configured site should not have " + originalSitePublicGroup + " as SiteContributor", + !containsConsumerPermission(originalSitePublicGroup, configuredSite)); + assertTrue("The configured site should have (newGroupName) as SiteContributor", + containsConsumerPermission(prefixedNewGroupName, configuredSite)); + } + finally + { + // Reset the JMX bean to its out-of-the-box values. + sysAdminSubsystem.stop(); + sysAdminSubsystem.setProperty(sitePublicGroupPropName, originalSitePublicGroup); + sysAdminSubsystem.start(); + } + } + + private boolean containsConsumerPermission(final String groupName, + SiteInfo unconfiguredSite) + { + boolean result = false; + Set perms = permissionService.getAllSetPermissions(unconfiguredSite.getNodeRef()); + for (AccessPermission p : perms) + { + if (p.getAuthority().equals(groupName) && + p.getPermission().equals(SiteModel.SITE_CONSUMER)) + { + result = true; + } + } + return result; + } + + /** + * This method tests that admin and system users can set site membership for a site of which they are not SiteManagers. + */ public void testETHREEOH_15() throws Exception { SiteInfo siteInfo = this.siteService.createSite(TEST_SITE_PRESET, "mySiteTest", TEST_TITLE, TEST_DESCRIPTION, SiteVisibility.PUBLIC);