From e147c90c9d1997f4858d5558e0e346ab5e930f5a Mon Sep 17 00:00:00 2001 From: Alan Davis Date: Wed, 20 Sep 2017 15:14:42 +0100 Subject: [PATCH 1/6] REPO-2921 Put a .gitbugtraq file in all develop Branches --- .gitbugtraq | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 .gitbugtraq diff --git a/.gitbugtraq b/.gitbugtraq new file mode 100644 index 0000000000..bacffb702d --- /dev/null +++ b/.gitbugtraq @@ -0,0 +1,4 @@ +# For SmartGit +[bugtraq "jira"] + url = https://issues.alfresco.com/jira/browse/%BUGID% + logRegex = ([A-Z]+-\\d+) From 344952540b75b85260bddc144fccd901ef092847 Mon Sep 17 00:00:00 2001 From: Ancuta Morarasu Date: Wed, 20 Sep 2017 18:02:25 +0300 Subject: [PATCH 2/6] REPO-2626: Update alfresco-heartbeat-data-sender to 1.0.1 --- pom.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 804c0f5cd5..c1e9441a58 100644 --- a/pom.xml +++ b/pom.xml @@ -43,6 +43,7 @@ 6.18 6.3 1.0 + 1.0.1 3.2.17.RELEASE @@ -99,7 +100,7 @@ org.alfresco alfresco-heartbeat-data-sender - 1.0.0 + ${dependency.alfresco-hb-data-sender.version} com.sun.mail From 2f16270e6f374474f46d6dd51464212bdf609b0a Mon Sep 17 00:00:00 2001 From: Matt Ward Date: Wed, 20 Sep 2017 16:28:49 +0100 Subject: [PATCH 3/6] MNT-18340: no longer ignores property updates for "unrelated" aspect removal https://issues.alfresco.com/jira/browse/MNT-18340 --- .../org/alfresco/opencmis/CMISConnector.java | 167 +++++++++--------- 1 file changed, 80 insertions(+), 87 deletions(-) diff --git a/src/main/java/org/alfresco/opencmis/CMISConnector.java b/src/main/java/org/alfresco/opencmis/CMISConnector.java index 18773e976c..c5f6d47b90 100644 --- a/src/main/java/org/alfresco/opencmis/CMISConnector.java +++ b/src/main/java/org/alfresco/opencmis/CMISConnector.java @@ -1,28 +1,28 @@ -/* - * #%L - * Alfresco Repository - * %% - * Copyright (C) 2005 - 2016 Alfresco Software Limited - * %% - * This file is part of the Alfresco software. - * If the software was purchased under a paid Alfresco license, the terms of - * the paid license agreement will prevail. Otherwise, the software is - * provided under the following open source license terms: - * - * Alfresco is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Alfresco 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 Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Alfresco. If not, see . - * #L% - */ +/* + * #%L + * Alfresco Repository + * %% + * Copyright (C) 2005 - 2016 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + * #L% + */ package org.alfresco.opencmis; import java.io.BufferedOutputStream; @@ -50,17 +50,17 @@ import java.util.Map.Entry; import java.util.Set; import java.util.TimeZone; import java.util.TreeSet; - + import javax.xml.datatype.DatatypeConfigurationException; import javax.xml.datatype.DatatypeFactory; - + import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.events.types.ContentEvent; import org.alfresco.events.types.ContentEventImpl; import org.alfresco.events.types.ContentReadRangeEvent; import org.alfresco.events.types.Event; import org.alfresco.model.ContentModel; -import org.alfresco.service.cmr.activities.ActivityInfo; +import org.alfresco.service.cmr.activities.ActivityInfo; import org.alfresco.opencmis.dictionary.CMISActionEvaluator; import org.alfresco.opencmis.dictionary.CMISAllowedActionEnum; import org.alfresco.opencmis.dictionary.CMISDictionaryService; @@ -81,8 +81,8 @@ import org.alfresco.opencmis.search.CMISResultSetRow; import org.alfresco.repo.Client; import org.alfresco.repo.Client.ClientType; import org.alfresco.repo.action.executer.ContentMetadataExtracter; -import org.alfresco.repo.cache.SimpleCache; -import org.alfresco.repo.coci.CheckOutCheckInServiceImpl; +import org.alfresco.repo.cache.SimpleCache; +import org.alfresco.repo.coci.CheckOutCheckInServiceImpl; import org.alfresco.repo.events.EventPreparator; import org.alfresco.repo.events.EventPublisher; import org.alfresco.repo.model.filefolder.GetChildrenCannedQuery; @@ -462,22 +462,22 @@ public class CMISConnector implements ApplicationContextAware, ApplicationListen this.objectsDefaultDepth = objectsDefaultDepth; } - /** - * Set the default number of content changes to return if nothing is specified - */ - public void setContentChangesDefaultMaxItems(int contentChangesDefaultMaxItems) - { - if (contentChangesDefaultMaxItems < 1) - { - throw new IllegalArgumentException("The default maximum number of content changes to retrieve must be greater than zero."); - } - else if (contentChangesDefaultMaxItems == Integer.MAX_VALUE) - { - throw new IllegalArgumentException("The server cannot return " + Integer.MAX_VALUE + " content changes in a request!"); - } - this.contentChangesDefaultMaxItems = contentChangesDefaultMaxItems; - } - + /** + * Set the default number of content changes to return if nothing is specified + */ + public void setContentChangesDefaultMaxItems(int contentChangesDefaultMaxItems) + { + if (contentChangesDefaultMaxItems < 1) + { + throw new IllegalArgumentException("The default maximum number of content changes to retrieve must be greater than zero."); + } + else if (contentChangesDefaultMaxItems == Integer.MAX_VALUE) + { + throw new IllegalArgumentException("The server cannot return " + Integer.MAX_VALUE + " content changes in a request!"); + } + this.contentChangesDefaultMaxItems = contentChangesDefaultMaxItems; + } + /** * Set rendition kind mapping. */ @@ -504,14 +504,14 @@ public class CMISConnector implements ApplicationContextAware, ApplicationListen public void setServiceRegistry(ServiceRegistry serviceRegistry) { this.serviceRegistry = serviceRegistry; - } - - /** - * Return the service registry - */ - public final ServiceRegistry getServiceRegistry() - { - return this.serviceRegistry; + } + + /** + * Return the service registry + */ + public final ServiceRegistry getServiceRegistry() + { + return this.serviceRegistry; } /** @@ -3185,8 +3185,8 @@ public class CMISConnector implements ApplicationContextAware, ApplicationListen Set ignore = new HashSet(); ignore.add(ContentModel.ASPECT_REFERENCEABLE); - ignore.add(ContentModel.ASPECT_LOCALIZED); - ignore.add(ContentModel.ASPECT_WORKING_COPY); + ignore.add(ContentModel.ASPECT_LOCALIZED); + ignore.add(ContentModel.ASPECT_WORKING_COPY); // aspects to add == the list of secondary types - existing aspects - ignored aspects Set toAdd = new HashSet(secondaryTypeAspects); @@ -3213,13 +3213,6 @@ public class CMISConnector implements ApplicationContextAware, ApplicationListen for(QName aspectQName : aspectsToRemove) { nodeService.removeAspect(nodeRef, aspectQName); - // aspect is being removed so remove all of its properties from the propsToAdd map - TypeDefinitionWrapper w = getOpenCMISDictionaryService().findNodeType(aspectQName); - for(PropertyDefinitionWrapper wr : w.getProperties()) - { - String propertyId = wr.getPropertyId(); - propsToAdd.remove(propertyId); - } } // add aspects and properties @@ -3607,21 +3600,21 @@ public class CMISConnector implements ApplicationContextAware, ApplicationListen } try - { - String newName = value.toString(); - // If the node is checked out and the name property is set on the working copy, make sure the new name has the working copy format - if (checkOutCheckInService.isWorkingCopy(nodeRef)) - { - String wcLabel = (String)this.nodeService.getProperty(nodeRef, ContentModel.PROP_WORKING_COPY_LABEL); - if (wcLabel == null) - { - wcLabel = CheckOutCheckInServiceImpl.getWorkingCopyLabel(); - } - if (!newName.contains(wcLabel)) - { - newName = CheckOutCheckInServiceImpl.createWorkingCopyName(newName, wcLabel); - } - } + { + String newName = value.toString(); + // If the node is checked out and the name property is set on the working copy, make sure the new name has the working copy format + if (checkOutCheckInService.isWorkingCopy(nodeRef)) + { + String wcLabel = (String)this.nodeService.getProperty(nodeRef, ContentModel.PROP_WORKING_COPY_LABEL); + if (wcLabel == null) + { + wcLabel = CheckOutCheckInServiceImpl.getWorkingCopyLabel(); + } + if (!newName.contains(wcLabel)) + { + newName = CheckOutCheckInServiceImpl.createWorkingCopyName(newName, wcLabel); + } + } fileFolderService.rename(nodeRef, newName); } @@ -3709,30 +3702,30 @@ public class CMISConnector implements ApplicationContextAware, ApplicationListen params.setApplicationName(CMIS_CHANGELOG_AUDIT_APPLICATION); params.setForward(true); params.setFromId(from); - - // So we have a BigInteger. We need to ensure that we cut it down to an integer smaller than Integer.MAX_VALUE + + // So we have a BigInteger. We need to ensure that we cut it down to an integer smaller than Integer.MAX_VALUE - int maxResults = (maxItems == null ? contentChangesDefaultMaxItems : maxItems.intValue()); - maxResults = maxResults < 1 ? contentChangesDefaultMaxItems : maxResults; // Just a double check of the unbundled contents - maxResults = maxResults > contentChangesDefaultMaxItems ? contentChangesDefaultMaxItems : maxResults; // cut it down + int maxResults = (maxItems == null ? contentChangesDefaultMaxItems : maxItems.intValue()); + maxResults = maxResults < 1 ? contentChangesDefaultMaxItems : maxResults; // Just a double check of the unbundled contents + maxResults = maxResults > contentChangesDefaultMaxItems ? contentChangesDefaultMaxItems : maxResults; // cut it down int queryFor = maxResults + 1; // Query for 1 more so that we know if there are more results auditService.auditQuery(changeLogCollectingCallback, params, queryFor); - String newChangeLogToken = null; + String newChangeLogToken = null; // Check if we got more than the client requested if (result.getObjects().size() >= maxResults) { // Build the change log token from the last item StringBuilder clt = new StringBuilder(); - newChangeLogToken = (from == null ? clt.append(maxItems.intValue() + 1).toString() : clt.append(from.longValue() + maxItems.intValue()).toString()); // TODO: Make this readable + newChangeLogToken = (from == null ? clt.append(maxItems.intValue() + 1).toString() : clt.append(from.longValue() + maxItems.intValue()).toString()); // TODO: Make this readable // Remove extra item that was not actually requested - result.getObjects().remove(result.getObjects().size() - 1).getId(); + result.getObjects().remove(result.getObjects().size() - 1).getId(); // Note to client that there are more items result.setHasMoreItems(true); } else - { + { // We got the same or fewer than the number requested, so there are no more items result.setHasMoreItems(false); } From bb53033ce5cea60ca2c9ea3a6f72057c93457d9d Mon Sep 17 00:00:00 2001 From: Matt Ward Date: Wed, 20 Sep 2017 17:45:05 +0100 Subject: [PATCH 4/6] MNT-18340: test to prove prop updates work when removing aspects --- .../java/org/alfresco/opencmis/CMISTest.java | 800 +++++++++--------- 1 file changed, 405 insertions(+), 395 deletions(-) diff --git a/src/test/java/org/alfresco/opencmis/CMISTest.java b/src/test/java/org/alfresco/opencmis/CMISTest.java index b0a69bb7cb..3518b87e76 100644 --- a/src/test/java/org/alfresco/opencmis/CMISTest.java +++ b/src/test/java/org/alfresco/opencmis/CMISTest.java @@ -1,172 +1,173 @@ - -/* - * #%L - * Alfresco Repository - * %% - * Copyright (C) 2005 - 2016 Alfresco Software Limited - * %% - * This file is part of the Alfresco software. - * If the software was purchased under a paid Alfresco license, the terms of - * the paid license agreement will prevail. Otherwise, the software is - * provided under the following open source license terms: - * - * Alfresco is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Alfresco 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 Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Alfresco. If not, see . - * #L% - */ + +/* + * #%L + * Alfresco Repository + * %% + * Copyright (C) 2005 - 2016 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + * #L% + */ package org.alfresco.opencmis; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; - -import java.io.ByteArrayInputStream; -import java.io.File; -import java.io.Serializable; -import java.io.UnsupportedEncodingException; -import java.math.BigDecimal; -import java.math.BigInteger; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.GregorianCalendar; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import org.alfresco.model.ContentModel; -import org.alfresco.opencmis.dictionary.CMISDictionaryService; -import org.alfresco.opencmis.dictionary.PropertyDefinitionWrapper; -import org.alfresco.opencmis.dictionary.TypeDefinitionWrapper; -import org.alfresco.opencmis.search.CMISQueryOptions; -import org.alfresco.opencmis.search.CMISQueryOptions.CMISQueryMode; -import org.alfresco.repo.action.evaluator.ComparePropertyValueEvaluator; -import org.alfresco.repo.action.executer.AddFeaturesActionExecuter; -import org.alfresco.repo.audit.AuditComponent; -import org.alfresco.repo.audit.AuditComponentImpl; -import org.alfresco.repo.audit.AuditServiceImpl; -import org.alfresco.repo.audit.UserAuditFilter; -import org.alfresco.repo.audit.model.AuditModelRegistryImpl; -import org.alfresco.repo.content.MimetypeMap; -import org.alfresco.repo.dictionary.DictionaryDAO; -import org.alfresco.repo.dictionary.M2Model; -import org.alfresco.repo.domain.audit.AuditDAO; -import org.alfresco.repo.domain.node.ContentDataWithId; -import org.alfresco.repo.domain.node.NodeDAO; -import org.alfresco.repo.model.Repository; -import org.alfresco.repo.node.archive.NodeArchiveService; -import org.alfresco.repo.security.authentication.AuthenticationComponent; -import org.alfresco.repo.security.authentication.AuthenticationContext; -import org.alfresco.repo.security.authentication.AuthenticationUtil; -import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; -import org.alfresco.repo.tenant.TenantAdminService; -import org.alfresco.repo.tenant.TenantService; -import org.alfresco.repo.tenant.TenantUtil; -import org.alfresco.repo.tenant.TenantUtil.TenantRunAsWork; -import org.alfresco.repo.transaction.RetryingTransactionHelper; -import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; -import org.alfresco.repo.version.VersionableAspectTest; -import org.alfresco.repo.workflow.WorkflowDeployer; -import org.alfresco.service.ServiceRegistry; -import org.alfresco.service.cmr.action.ActionCondition; -import org.alfresco.service.cmr.action.ActionService; -import org.alfresco.service.cmr.dictionary.AspectDefinition; -import org.alfresco.service.cmr.dictionary.DictionaryService; -import org.alfresco.service.cmr.lock.LockService; -import org.alfresco.service.cmr.lock.LockType; -import org.alfresco.service.cmr.model.FileFolderService; -import org.alfresco.service.cmr.model.FileInfo; -import org.alfresco.service.cmr.repository.ChildAssociationRef; -import org.alfresco.service.cmr.repository.ContentService; -import org.alfresco.service.cmr.repository.ContentWriter; -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.rule.Rule; -import org.alfresco.service.cmr.rule.RuleService; -import org.alfresco.service.cmr.rule.RuleType; -import org.alfresco.service.cmr.search.SearchService; -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; -import org.alfresco.service.cmr.tagging.TaggingService; -import org.alfresco.service.cmr.version.Version; -import org.alfresco.service.cmr.version.VersionService; -import org.alfresco.service.cmr.version.VersionType; -import org.alfresco.service.cmr.workflow.WorkflowAdminService; -import org.alfresco.service.cmr.workflow.WorkflowService; -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.Pair; -import org.apache.chemistry.opencmis.commons.PropertyIds; -import org.apache.chemistry.opencmis.commons.data.Ace; -import org.apache.chemistry.opencmis.commons.data.AllowableActions; -import org.apache.chemistry.opencmis.commons.data.CmisExtensionElement; -import org.apache.chemistry.opencmis.commons.data.ContentStream; -import org.apache.chemistry.opencmis.commons.data.FailedToDeleteData; -import org.apache.chemistry.opencmis.commons.data.ObjectData; -import org.apache.chemistry.opencmis.commons.data.ObjectInFolderData; -import org.apache.chemistry.opencmis.commons.data.ObjectInFolderList; -import org.apache.chemistry.opencmis.commons.data.ObjectList; -import org.apache.chemistry.opencmis.commons.data.ObjectParentData; -import org.apache.chemistry.opencmis.commons.data.Properties; -import org.apache.chemistry.opencmis.commons.data.PropertyData; -import org.apache.chemistry.opencmis.commons.data.RepositoryInfo; -import org.apache.chemistry.opencmis.commons.definitions.PropertyDefinition; -import org.apache.chemistry.opencmis.commons.definitions.TypeDefinition; -import org.apache.chemistry.opencmis.commons.definitions.TypeDefinitionContainer; -import org.apache.chemistry.opencmis.commons.enums.AclPropagation; -import org.apache.chemistry.opencmis.commons.enums.Action; -import org.apache.chemistry.opencmis.commons.enums.ChangeType; -import org.apache.chemistry.opencmis.commons.enums.CmisVersion; -import org.apache.chemistry.opencmis.commons.enums.IncludeRelationships; -import org.apache.chemistry.opencmis.commons.enums.UnfileObject; -import org.apache.chemistry.opencmis.commons.enums.VersioningState; -import org.apache.chemistry.opencmis.commons.exceptions.CmisConstraintException; -import org.apache.chemistry.opencmis.commons.exceptions.CmisInvalidArgumentException; -import org.apache.chemistry.opencmis.commons.exceptions.CmisRuntimeException; -import org.apache.chemistry.opencmis.commons.exceptions.CmisUpdateConflictException; -import org.apache.chemistry.opencmis.commons.impl.dataobjects.AccessControlListImpl; -import org.apache.chemistry.opencmis.commons.impl.dataobjects.CmisExtensionElementImpl; -import org.apache.chemistry.opencmis.commons.impl.dataobjects.ContentStreamImpl; -import org.apache.chemistry.opencmis.commons.impl.dataobjects.ExtensionDataImpl; -import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertiesImpl; -import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyDecimalDefinitionImpl; -import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyIdImpl; -import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyIntegerDefinitionImpl; -import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyIntegerImpl; -import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyStringImpl; -import org.apache.chemistry.opencmis.commons.impl.server.AbstractServiceFactory; -import org.apache.chemistry.opencmis.commons.server.CallContext; -import org.apache.chemistry.opencmis.commons.server.CmisService; -import org.apache.chemistry.opencmis.commons.spi.Holder; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.springframework.context.ApplicationContext; -import org.springframework.extensions.webscripts.GUID; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.Serializable; +import java.io.UnsupportedEncodingException; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.GregorianCalendar; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +import org.alfresco.model.ContentModel; +import org.alfresco.opencmis.dictionary.CMISDictionaryService; +import org.alfresco.opencmis.dictionary.PropertyDefinitionWrapper; +import org.alfresco.opencmis.dictionary.TypeDefinitionWrapper; +import org.alfresco.opencmis.search.CMISQueryOptions; +import org.alfresco.opencmis.search.CMISQueryOptions.CMISQueryMode; +import org.alfresco.repo.action.evaluator.ComparePropertyValueEvaluator; +import org.alfresco.repo.action.executer.AddFeaturesActionExecuter; +import org.alfresco.repo.audit.AuditComponent; +import org.alfresco.repo.audit.AuditComponentImpl; +import org.alfresco.repo.audit.AuditServiceImpl; +import org.alfresco.repo.audit.UserAuditFilter; +import org.alfresco.repo.audit.model.AuditModelRegistryImpl; +import org.alfresco.repo.content.MimetypeMap; +import org.alfresco.repo.dictionary.DictionaryDAO; +import org.alfresco.repo.dictionary.M2Model; +import org.alfresco.repo.domain.audit.AuditDAO; +import org.alfresco.repo.domain.node.ContentDataWithId; +import org.alfresco.repo.domain.node.NodeDAO; +import org.alfresco.repo.model.Repository; +import org.alfresco.repo.node.archive.NodeArchiveService; +import org.alfresco.repo.security.authentication.AuthenticationComponent; +import org.alfresco.repo.security.authentication.AuthenticationContext; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; +import org.alfresco.repo.tenant.TenantAdminService; +import org.alfresco.repo.tenant.TenantService; +import org.alfresco.repo.tenant.TenantUtil; +import org.alfresco.repo.tenant.TenantUtil.TenantRunAsWork; +import org.alfresco.repo.transaction.RetryingTransactionHelper; +import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; +import org.alfresco.repo.version.VersionableAspectTest; +import org.alfresco.repo.workflow.WorkflowDeployer; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.action.ActionCondition; +import org.alfresco.service.cmr.action.ActionService; +import org.alfresco.service.cmr.dictionary.AspectDefinition; +import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.cmr.lock.LockService; +import org.alfresco.service.cmr.lock.LockType; +import org.alfresco.service.cmr.model.FileFolderService; +import org.alfresco.service.cmr.model.FileInfo; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.ContentService; +import org.alfresco.service.cmr.repository.ContentWriter; +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.rule.Rule; +import org.alfresco.service.cmr.rule.RuleService; +import org.alfresco.service.cmr.rule.RuleType; +import org.alfresco.service.cmr.search.SearchService; +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; +import org.alfresco.service.cmr.tagging.TaggingService; +import org.alfresco.service.cmr.version.Version; +import org.alfresco.service.cmr.version.VersionService; +import org.alfresco.service.cmr.version.VersionType; +import org.alfresco.service.cmr.workflow.WorkflowAdminService; +import org.alfresco.service.cmr.workflow.WorkflowService; +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.Pair; +import org.apache.chemistry.opencmis.commons.PropertyIds; +import org.apache.chemistry.opencmis.commons.data.Ace; +import org.apache.chemistry.opencmis.commons.data.AllowableActions; +import org.apache.chemistry.opencmis.commons.data.CmisExtensionElement; +import org.apache.chemistry.opencmis.commons.data.ContentStream; +import org.apache.chemistry.opencmis.commons.data.FailedToDeleteData; +import org.apache.chemistry.opencmis.commons.data.ObjectData; +import org.apache.chemistry.opencmis.commons.data.ObjectInFolderData; +import org.apache.chemistry.opencmis.commons.data.ObjectInFolderList; +import org.apache.chemistry.opencmis.commons.data.ObjectList; +import org.apache.chemistry.opencmis.commons.data.ObjectParentData; +import org.apache.chemistry.opencmis.commons.data.Properties; +import org.apache.chemistry.opencmis.commons.data.PropertyData; +import org.apache.chemistry.opencmis.commons.data.RepositoryInfo; +import org.apache.chemistry.opencmis.commons.definitions.PropertyDefinition; +import org.apache.chemistry.opencmis.commons.definitions.TypeDefinition; +import org.apache.chemistry.opencmis.commons.definitions.TypeDefinitionContainer; +import org.apache.chemistry.opencmis.commons.enums.AclPropagation; +import org.apache.chemistry.opencmis.commons.enums.Action; +import org.apache.chemistry.opencmis.commons.enums.ChangeType; +import org.apache.chemistry.opencmis.commons.enums.CmisVersion; +import org.apache.chemistry.opencmis.commons.enums.IncludeRelationships; +import org.apache.chemistry.opencmis.commons.enums.UnfileObject; +import org.apache.chemistry.opencmis.commons.enums.VersioningState; +import org.apache.chemistry.opencmis.commons.exceptions.CmisConstraintException; +import org.apache.chemistry.opencmis.commons.exceptions.CmisInvalidArgumentException; +import org.apache.chemistry.opencmis.commons.exceptions.CmisRuntimeException; +import org.apache.chemistry.opencmis.commons.exceptions.CmisUpdateConflictException; +import org.apache.chemistry.opencmis.commons.impl.dataobjects.AccessControlListImpl; +import org.apache.chemistry.opencmis.commons.impl.dataobjects.CmisExtensionElementImpl; +import org.apache.chemistry.opencmis.commons.impl.dataobjects.ContentStreamImpl; +import org.apache.chemistry.opencmis.commons.impl.dataobjects.ExtensionDataImpl; +import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertiesImpl; +import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyDecimalDefinitionImpl; +import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyIdImpl; +import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyIntegerDefinitionImpl; +import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyIntegerImpl; +import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyStringImpl; +import org.apache.chemistry.opencmis.commons.impl.server.AbstractServiceFactory; +import org.apache.chemistry.opencmis.commons.server.CallContext; +import org.apache.chemistry.opencmis.commons.server.CmisService; +import org.apache.chemistry.opencmis.commons.spi.Holder; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.springframework.context.ApplicationContext; +import org.springframework.extensions.webscripts.GUID; /** * OpenCMIS tests. @@ -175,11 +176,11 @@ import org.springframework.extensions.webscripts.GUID; * */ public class CMISTest -{ - private static Log logger = LogFactory.getLog(CMISTest.class); +{ + private static Log logger = LogFactory.getLog(CMISTest.class); - private static final QName TEST_START_TASK = QName.createQName("http://www.alfresco.org/model/workflow/test/1.0", "startTaskVarScriptAssign"); - private static final QName TEST_WORKFLOW_TASK = QName.createQName("http://www.alfresco.org/model/workflow/test/1.0", "assignVarTask"); + private static final QName TEST_START_TASK = QName.createQName("http://www.alfresco.org/model/workflow/test/1.0", "startTaskVarScriptAssign"); + private static final QName TEST_WORKFLOW_TASK = QName.createQName("http://www.alfresco.org/model/workflow/test/1.0", "assignVarTask"); private static ApplicationContext ctx = ApplicationContextHelper.getApplicationContext(new String[]{ApplicationContextHelper.CONFIG_LOCATIONS[0],"classpath:test-cmisinteger_modell-context.xml"}); @@ -209,7 +210,7 @@ public class CMISTest private TenantService tenantService; private SearchService searchService; private java.util.Properties globalProperties; - private AuditComponentImpl auditComponent; + private AuditComponentImpl auditComponent; private AlfrescoCmisServiceFactory factory; @@ -388,7 +389,7 @@ public class CMISTest this.tenantAdminService = (TenantAdminService) ctx.getBean("tenantAdminService"); this.tenantService = (TenantService) ctx.getBean("tenantService"); this.searchService = (SearchService) ctx.getBean("SearchService"); - this.auditComponent = (AuditComponentImpl) ctx.getBean("auditComponent"); + this.auditComponent = (AuditComponentImpl) ctx.getBean("auditComponent"); this.globalProperties = (java.util.Properties) ctx.getBean("global-properties"); this.globalProperties.setProperty(VersionableAspectTest.AUTO_VERSION_PROPS_KEY, "true"); @@ -658,9 +659,9 @@ public class CMISTest assertNotNull(startTaskTypeDefinition); assertNotNull(workflowTaskTypeDefinition); - // caches are refreshed asynchronously - Thread.sleep(5000); - + // caches are refreshed asynchronously + Thread.sleep(5000); + // check that loaded model is available via CMIS API CallContext context = new SimpleCallContext("admin", "admin", CmisVersion.CMIS_1_1); CmisService service = factory.getService(context); @@ -799,12 +800,12 @@ public class CMISTest */ @Test public void testContentMimeTypeDetection() - { - ServiceRegistry serviceRegistry = (ServiceRegistry) ctx.getBean(ServiceRegistry.SERVICE_REGISTRY); - FileFolderService ffs = serviceRegistry.getFileFolderService(); - AuthenticationComponent authenticationComponent = (AuthenticationComponent) ctx.getBean("authenticationComponent"); - final String isoEncoding = "ISO-8859-1"; - final String utfEncoding = "UTF-8"; + { + ServiceRegistry serviceRegistry = (ServiceRegistry) ctx.getBean(ServiceRegistry.SERVICE_REGISTRY); + FileFolderService ffs = serviceRegistry.getFileFolderService(); + AuthenticationComponent authenticationComponent = (AuthenticationComponent) ctx.getBean("authenticationComponent"); + final String isoEncoding = "ISO-8859-1"; + final String utfEncoding = "UTF-8"; // get repository id List repositories = withCmisService(new CmisServiceCallback>() @@ -875,16 +876,16 @@ public class CMISTest return contentType; } }); - assertEquals("Mimetype is not defined correctly.", MimetypeMap.MIMETYPE_HTML, contentType); - - // check that the encoding is detected correctly + assertEquals("Mimetype is not defined correctly.", MimetypeMap.MIMETYPE_HTML, contentType); + + // check that the encoding is detected correctly checkEncoding(ffs, authenticationComponent, objectData, utfEncoding); } // create content stream with mimetype and encoding as UTF-8 { - String mimeType = MimetypeMap.MIMETYPE_TEXT_PLAIN + "; charset="+isoEncoding; - // NOTE that we intentionally specify the wrong charset here. + String mimeType = MimetypeMap.MIMETYPE_TEXT_PLAIN + "; charset="+isoEncoding; + // NOTE that we intentionally specify the wrong charset here. // Alfresco will detect the encoding (as UTF-8 - given by the ContentStreamImpl constructor) final ContentStreamImpl contentStreamHTML = new ContentStreamImpl(null, mimeType, " Hello

Test html

"); withCmisService(new CmisServiceCallback() @@ -917,68 +918,68 @@ public class CMISTest return contentType; } }); - assertEquals("Mimetype is not defined correctly.", MimetypeMap.MIMETYPE_TEXT_PLAIN, contentType); - - // check that the encoding is detected correctly + assertEquals("Mimetype is not defined correctly.", MimetypeMap.MIMETYPE_TEXT_PLAIN, contentType); + + // check that the encoding is detected correctly checkEncoding(ffs, authenticationComponent, objectData, utfEncoding); } - - // create content stream with mimetype and encoding as ISO-8859-1 - { - String mimeType = MimetypeMap.MIMETYPE_TEXT_PLAIN + "; charset=" + utfEncoding; - // NOTE that we intentionally specify the wrong charset here. - // Alfresco will detect the encoding (as ISO-8859-1 - given by the ContentStreamImpl with streams) - String content = "aegif Mind Share Leader Generating New Paradigms by aegif corporation

Test html

"; - byte[] buf = null; - try - { - buf = content.getBytes(isoEncoding); // set the encoding here for the content stream - } - catch (UnsupportedEncodingException e) - { - e.printStackTrace(); - } - - ByteArrayInputStream input = new ByteArrayInputStream(buf); - - final ContentStream contentStreamHTML = new ContentStreamImpl(null, BigInteger.valueOf(buf.length), mimeType, input); - withCmisService(new CmisServiceCallback() - { - @Override - public Void execute(CmisService cmisService) - { - Holder latestObjectIdHolder = getHolderOfObjectOfLatestVersion(cmisService, repositoryId, - objectIdHolder); - cmisService.setContentStream(repositoryId, latestObjectIdHolder, true, null, contentStreamHTML, null); - return null; - } - }); - - // check mimetype - final ObjectData objectData = withCmisService(new CmisServiceCallback() - { - @Override - public ObjectData execute(CmisService cmisService) - { - ObjectData objectData = cmisService.getObjectByPath(repositoryId, path, null, false, - IncludeRelationships.NONE, null, false, false, null); - return objectData; - } - }); - String contentType = withCmisService(new CmisServiceCallback() - { - @Override - public String execute(CmisService cmisService) - { - String contentType = cmisService.getObjectInfo(repositoryId, objectData.getId()).getContentType(); - return contentType; - } - }); - assertEquals("Mimetype is not defined correctly.", MimetypeMap.MIMETYPE_TEXT_PLAIN, contentType); - - // check that the encoding is detected correctly - checkEncoding(ffs, authenticationComponent, objectData, isoEncoding); - } + + // create content stream with mimetype and encoding as ISO-8859-1 + { + String mimeType = MimetypeMap.MIMETYPE_TEXT_PLAIN + "; charset=" + utfEncoding; + // NOTE that we intentionally specify the wrong charset here. + // Alfresco will detect the encoding (as ISO-8859-1 - given by the ContentStreamImpl with streams) + String content = "aegif Mind Share Leader Generating New Paradigms by aegif corporation

Test html

"; + byte[] buf = null; + try + { + buf = content.getBytes(isoEncoding); // set the encoding here for the content stream + } + catch (UnsupportedEncodingException e) + { + e.printStackTrace(); + } + + ByteArrayInputStream input = new ByteArrayInputStream(buf); + + final ContentStream contentStreamHTML = new ContentStreamImpl(null, BigInteger.valueOf(buf.length), mimeType, input); + withCmisService(new CmisServiceCallback() + { + @Override + public Void execute(CmisService cmisService) + { + Holder latestObjectIdHolder = getHolderOfObjectOfLatestVersion(cmisService, repositoryId, + objectIdHolder); + cmisService.setContentStream(repositoryId, latestObjectIdHolder, true, null, contentStreamHTML, null); + return null; + } + }); + + // check mimetype + final ObjectData objectData = withCmisService(new CmisServiceCallback() + { + @Override + public ObjectData execute(CmisService cmisService) + { + ObjectData objectData = cmisService.getObjectByPath(repositoryId, path, null, false, + IncludeRelationships.NONE, null, false, false, null); + return objectData; + } + }); + String contentType = withCmisService(new CmisServiceCallback() + { + @Override + public String execute(CmisService cmisService) + { + String contentType = cmisService.getObjectInfo(repositoryId, objectData.getId()).getContentType(); + return contentType; + } + }); + assertEquals("Mimetype is not defined correctly.", MimetypeMap.MIMETYPE_TEXT_PLAIN, contentType); + + // check that the encoding is detected correctly + checkEncoding(ffs, authenticationComponent, objectData, isoEncoding); + } // checkout/checkin object with mimetype and encoding { @@ -1033,50 +1034,50 @@ public class CMISTest return contentType; } }); - assertEquals("Mimetype is not defined correctly.", MimetypeMap.MIMETYPE_HTML, contentType); - - checkEncoding(ffs, authenticationComponent, objectData, utfEncoding); + assertEquals("Mimetype is not defined correctly.", MimetypeMap.MIMETYPE_HTML, contentType); + + checkEncoding(ffs, authenticationComponent, objectData, utfEncoding); } - } - - protected void checkEncoding(FileFolderService ffs, AuthenticationComponent authenticationComponent, - final ObjectData objectData, String expectedEncoding) - { - // Authenticate as system to check the properties in alfresco - authenticationComponent.setSystemUserAsCurrentUser(); - try - { - NodeRef doc1NodeRef = cmisIdToNodeRef(objectData.getId()); - doc1NodeRef.getId(); - - FileInfo fileInfo = ffs.getFileInfo(doc1NodeRef); - Map properties2 = fileInfo.getProperties(); - - ContentDataWithId contentData = (ContentDataWithId) properties2 - .get(QName.createQName("{http://www.alfresco.org/model/content/1.0}content")); - String encoding = contentData.getEncoding(); - - assertEquals(expectedEncoding, encoding); - } - finally - { - authenticationComponent.clearCurrentSecurityContext(); - } } - /** - * Turns a CMIS id into a node ref - * @param nodeId - * @return - */ - private NodeRef cmisIdToNodeRef(String nodeId) - { - int idx = nodeId.indexOf(";"); - if(idx != -1) - { - nodeId = nodeId.substring(0, idx); - } - NodeRef nodeRef = new NodeRef(nodeId); - return nodeRef; + + protected void checkEncoding(FileFolderService ffs, AuthenticationComponent authenticationComponent, + final ObjectData objectData, String expectedEncoding) + { + // Authenticate as system to check the properties in alfresco + authenticationComponent.setSystemUserAsCurrentUser(); + try + { + NodeRef doc1NodeRef = cmisIdToNodeRef(objectData.getId()); + doc1NodeRef.getId(); + + FileInfo fileInfo = ffs.getFileInfo(doc1NodeRef); + Map properties2 = fileInfo.getProperties(); + + ContentDataWithId contentData = (ContentDataWithId) properties2 + .get(QName.createQName("{http://www.alfresco.org/model/content/1.0}content")); + String encoding = contentData.getEncoding(); + + assertEquals(expectedEncoding, encoding); + } + finally + { + authenticationComponent.clearCurrentSecurityContext(); + } + } + /** + * Turns a CMIS id into a node ref + * @param nodeId + * @return + */ + private NodeRef cmisIdToNodeRef(String nodeId) + { + int idx = nodeId.indexOf(";"); + if(idx != -1) + { + nodeId = nodeId.substring(0, idx); + } + NodeRef nodeRef = new NodeRef(nodeId); + return nodeRef; } private Holder getHolderOfObjectOfLatestVersion(CmisService cmisService, String repositoryId, Holder currentHolder) { @@ -1755,9 +1756,13 @@ public class CMISTest List secondaryTypeIds = currentProperties.getProperties().get(PropertyIds.SECONDARY_OBJECT_TYPE_IDS).getValues(); + assertTrue(secondaryTypeIds.contains(aspectName)); + secondaryTypeIds.remove(aspectName); final PropertiesImpl newProperties = new PropertiesImpl(); newProperties.addProperty(new PropertyStringImpl(PropertyIds.SECONDARY_OBJECT_TYPE_IDS, secondaryTypeIds)); + final String updatedName = "My_new_name_"+UUID.randomUUID().toString(); + newProperties.replaceProperty(new PropertyStringImpl(PropertyIds.NAME, updatedName)); withCmisService(new CmisServiceCallback() { @@ -1765,6 +1770,8 @@ public class CMISTest public Void execute(CmisService cmisService) { Holder latestObjectIdHolder = getHolderOfObjectOfLatestVersion(cmisService, repositoryId, objectIdHolder); + // This will result in aspectName being removed + // but that shouldn't mean that, for example, a cmis:name prop update gets ignored (MNT-18340) cmisService.updateProperties(repositoryId, latestObjectIdHolder, null, newProperties, null); return null; } @@ -1775,12 +1782,15 @@ public class CMISTest @Override public Properties execute(CmisService cmisService) { - Properties properties = cmisService.getProperties(repositoryId, objectIdHolder.getValue(), null, null); + Holder latestObjectIdHolder = getHolderOfObjectOfLatestVersion(cmisService, repositoryId, objectIdHolder); + Properties properties = cmisService.getProperties(repositoryId, latestObjectIdHolder.getValue(), null, null); return properties; } }, CmisVersion.CMIS_1_1); secondaryTypeIds = currentProperties1.getProperties().get(PropertyIds.SECONDARY_OBJECT_TYPE_IDS).getValues(); + assertFalse(secondaryTypeIds.contains(aspectName)); + assertEquals(updatedName, currentProperties1.getProperties().get(PropertyIds.NAME).getFirstValue()); } /** @@ -2553,32 +2563,32 @@ public class CMISTest { TenantUtil.runAsUserTenant(new TenantRunAsWork() { - @Override - public Void doWork() throws Exception - { - M2Model customModel = M2Model.createModel( - Thread.currentThread().getContextClassLoader(). - getResourceAsStream("dictionary/dictionarydaotest_model1.xml")); - dictionaryDAO.putModel(customModel); - - assertNotNull(cmisDictionaryService.findType("P:cm:dublincore")); - TypeDefinitionWrapper td = cmisDictionaryService.findType("D:daotest1:type1"); - assertNotNull(td); - return null; - } + @Override + public Void doWork() throws Exception + { + M2Model customModel = M2Model.createModel( + Thread.currentThread().getContextClassLoader(). + getResourceAsStream("dictionary/dictionarydaotest_model1.xml")); + dictionaryDAO.putModel(customModel); + + assertNotNull(cmisDictionaryService.findType("P:cm:dublincore")); + TypeDefinitionWrapper td = cmisDictionaryService.findType("D:daotest1:type1"); + assertNotNull(td); + return null; + } }, "user1", "tenant1"); TenantUtil.runAsUserTenant(new TenantRunAsWork() { - @Override - public Void doWork() throws Exception - { - assertNotNull(cmisDictionaryService.findType("P:cm:dublincore")); - TypeDefinitionWrapper td = cmisDictionaryService.findType("D:daotest1:type1"); - assertNull(td); - return null; - } - }, "user2", "tenant2"); + @Override + public Void doWork() throws Exception + { + assertNotNull(cmisDictionaryService.findType("P:cm:dublincore")); + TypeDefinitionWrapper td = cmisDictionaryService.findType("D:daotest1:type1"); + assertNull(td); + return null; + } + }, "user2", "tenant2"); } /** @@ -2706,32 +2716,32 @@ public class CMISTest assertFalse("CMISChangeEvent " + changeType + " should store short form of objectId " + objectId, objectId.toString().contains(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE.toString())); - } + } int expectAtLeast = changes.getObjects().size(); - - // We should also be able to query without passing in any limit - changes = cmisService.getContentChanges(repositoryId, new Holder(changeToken), Boolean.TRUE, null, Boolean.FALSE, Boolean.FALSE, null, null); - assertTrue("Expected to still get changes", changes.getObjects().size() >= expectAtLeast); - // and zero - changes = cmisService.getContentChanges(repositoryId, new Holder(changeToken), Boolean.TRUE, null, Boolean.FALSE, Boolean.FALSE, BigInteger.valueOf(0), null); - assertTrue("Expected to still get changes", changes.getObjects().size() >= expectAtLeast); - // and one - changes = cmisService.getContentChanges(repositoryId, new Holder(changeToken), Boolean.TRUE, null, Boolean.FALSE, Boolean.FALSE, BigInteger.valueOf(1), null); - assertEquals("Expected to still get changes", changes.getObjects().size(), 1); - // Integery.MAX_VALUE must be handled - // This will limit the number to a sane value - changes = cmisService.getContentChanges(repositoryId, new Holder(changeToken), Boolean.TRUE, null, Boolean.FALSE, Boolean.FALSE, BigInteger.valueOf(Integer.MAX_VALUE), null); - assertTrue("Expected to still get changes", changes.getObjects().size() >= expectAtLeast); - // but not negative - try - { - changes = cmisService.getContentChanges(repositoryId, new Holder(changeToken), Boolean.TRUE, null, Boolean.FALSE, Boolean.FALSE, BigInteger.valueOf(-1), null); - fail("Negative maxItems is expected to fail"); - } - catch (CmisInvalidArgumentException e) - { - // Expected - } + + // We should also be able to query without passing in any limit + changes = cmisService.getContentChanges(repositoryId, new Holder(changeToken), Boolean.TRUE, null, Boolean.FALSE, Boolean.FALSE, null, null); + assertTrue("Expected to still get changes", changes.getObjects().size() >= expectAtLeast); + // and zero + changes = cmisService.getContentChanges(repositoryId, new Holder(changeToken), Boolean.TRUE, null, Boolean.FALSE, Boolean.FALSE, BigInteger.valueOf(0), null); + assertTrue("Expected to still get changes", changes.getObjects().size() >= expectAtLeast); + // and one + changes = cmisService.getContentChanges(repositoryId, new Holder(changeToken), Boolean.TRUE, null, Boolean.FALSE, Boolean.FALSE, BigInteger.valueOf(1), null); + assertEquals("Expected to still get changes", changes.getObjects().size(), 1); + // Integery.MAX_VALUE must be handled + // This will limit the number to a sane value + changes = cmisService.getContentChanges(repositoryId, new Holder(changeToken), Boolean.TRUE, null, Boolean.FALSE, Boolean.FALSE, BigInteger.valueOf(Integer.MAX_VALUE), null); + assertTrue("Expected to still get changes", changes.getObjects().size() >= expectAtLeast); + // but not negative + try + { + changes = cmisService.getContentChanges(repositoryId, new Holder(changeToken), Boolean.TRUE, null, Boolean.FALSE, Boolean.FALSE, BigInteger.valueOf(-1), null); + fail("Negative maxItems is expected to fail"); + } + catch (CmisInvalidArgumentException e) + { + // Expected + } return null; } @@ -3668,64 +3678,64 @@ public class CMISTest AuthenticationUtil.popAuthentication(); } } - - @Test - public void testCreateDocWithVersioningStateNone() throws Exception - { - AuthenticationUtil.pushAuthentication(); - AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getAdminUserName()); - - try - { - // get repository id - final String repositoryId = withCmisService(new CmisServiceCallback() - { - @Override - public String execute(CmisService cmisService) - { - List repositories = cmisService.getRepositoryInfos(null); - assertTrue(repositories.size() > 0); - RepositoryInfo repo = repositories.get(0); - final String repositoryId = repo.getId(); - return repositoryId; - } - }, CmisVersion.CMIS_1_1); - - final NodeRef documentNodeRef = withCmisService(new CmisServiceCallback() - { - @Override - public NodeRef execute(CmisService cmisService) - { - final PropertiesImpl properties = new PropertiesImpl(); - String objectTypeId = "cmis:document"; - properties.addProperty(new PropertyIdImpl(PropertyIds.OBJECT_TYPE_ID, objectTypeId)); - String fileName = "textFile" + GUID.generate(); - properties.addProperty(new PropertyStringImpl(PropertyIds.NAME, fileName)); - final ContentStreamImpl contentStream = new ContentStreamImpl(fileName, MimetypeMap.MIMETYPE_TEXT_PLAIN, "Simple text plain document"); - - String nodeId = cmisService.create(repositoryId, properties, repositoryHelper.getCompanyHome().getId(), contentStream, VersioningState.NONE, null, null); - return new NodeRef(nodeId.substring(0, nodeId.indexOf(';'))); - } - }, CmisVersion.CMIS_1_1); - - // check versioning properties - transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback>() - { - @Override - public List execute() throws Throwable - { - assertTrue(nodeService.exists(documentNodeRef)); - assertFalse(nodeService.hasAspect(documentNodeRef, ContentModel.ASPECT_VERSIONABLE)); - - return null; - } - }); - } - finally - { - AuthenticationUtil.popAuthentication(); - } - } + + @Test + public void testCreateDocWithVersioningStateNone() throws Exception + { + AuthenticationUtil.pushAuthentication(); + AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getAdminUserName()); + + try + { + // get repository id + final String repositoryId = withCmisService(new CmisServiceCallback() + { + @Override + public String execute(CmisService cmisService) + { + List repositories = cmisService.getRepositoryInfos(null); + assertTrue(repositories.size() > 0); + RepositoryInfo repo = repositories.get(0); + final String repositoryId = repo.getId(); + return repositoryId; + } + }, CmisVersion.CMIS_1_1); + + final NodeRef documentNodeRef = withCmisService(new CmisServiceCallback() + { + @Override + public NodeRef execute(CmisService cmisService) + { + final PropertiesImpl properties = new PropertiesImpl(); + String objectTypeId = "cmis:document"; + properties.addProperty(new PropertyIdImpl(PropertyIds.OBJECT_TYPE_ID, objectTypeId)); + String fileName = "textFile" + GUID.generate(); + properties.addProperty(new PropertyStringImpl(PropertyIds.NAME, fileName)); + final ContentStreamImpl contentStream = new ContentStreamImpl(fileName, MimetypeMap.MIMETYPE_TEXT_PLAIN, "Simple text plain document"); + + String nodeId = cmisService.create(repositoryId, properties, repositoryHelper.getCompanyHome().getId(), contentStream, VersioningState.NONE, null, null); + return new NodeRef(nodeId.substring(0, nodeId.indexOf(';'))); + } + }, CmisVersion.CMIS_1_1); + + // check versioning properties + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback>() + { + @Override + public List execute() throws Throwable + { + assertTrue(nodeService.exists(documentNodeRef)); + assertFalse(nodeService.hasAspect(documentNodeRef, ContentModel.ASPECT_VERSIONABLE)); + + return null; + } + }); + } + finally + { + AuthenticationUtil.popAuthentication(); + } + } /** * MNT-14951: Test that the list of parents can be retrieved for a folder. @@ -3792,5 +3802,5 @@ public class CMISTest auditSubsystem.destroy(); AuthenticationUtil.popAuthentication(); } - } + } } From 366a0e773ed2fb7dd0d1b6c64a4b43170a5f194a Mon Sep 17 00:00:00 2001 From: Alexandru-Eusebiu Epure Date: Fri, 22 Sep 2017 14:45:14 +0300 Subject: [PATCH 5/6] Fix/mnt 16641 cmis applying aspects including protected properties (REPO-1065) (#20) * REPO-1065 : Service Pack: MNT-16641 CMIS API: Applying aspects including protected properties results in CmisInvalidArgumentException: Property is read-only! Remove code from CMISConnector.processSecondaryTypes, that for each aspect added, it will search all of his properties in repository and add them with a null value(if not included in the propsToAdd). Add an extra check to CMISTest.testUpdatePropertiesSetDeleteContentVersioning now adding the P:cm:lockable aspect to a document (before this fix failing). Added CMISConnector.isUpdatable returns true/false if a property is updatable --- .../org/alfresco/opencmis/CMISConnector.java | 96 +++++++++---------- .../java/org/alfresco/opencmis/CMISTest.java | 14 ++- 2 files changed, 61 insertions(+), 49 deletions(-) diff --git a/src/main/java/org/alfresco/opencmis/CMISConnector.java b/src/main/java/org/alfresco/opencmis/CMISConnector.java index 18773e976c..a188b3e38c 100644 --- a/src/main/java/org/alfresco/opencmis/CMISConnector.java +++ b/src/main/java/org/alfresco/opencmis/CMISConnector.java @@ -3097,13 +3097,14 @@ public class CMISConnector implements ApplicationContextAware, ApplicationListen throw new CmisInvalidArgumentException("Property " + property.getId() + " is unknown!"); } } + + Boolean isOnWorkingCopy = checkOutCheckInService.isWorkingCopy(nodeRef); + Updatability updatability = propDef.getPropertyDefinition().getUpdatability(); + if (!isUpdatable(updatability, isOnWorkingCopy)) + { + throw new CmisInvalidArgumentException("Property " + propertyId + " is read-only!"); + } - Updatability updatability = propDef.getPropertyDefinition().getUpdatability(); - if ((updatability == Updatability.READONLY) - || (updatability == Updatability.WHENCHECKEDOUT && !checkOutCheckInService.isWorkingCopy(nodeRef))) - { - throw new CmisInvalidArgumentException("Property " + property.getId() + " is read-only!"); - } TypeDefinitionWrapper propType = propDef.getOwningType(); Serializable value = getValue(property, propDef.getPropertyDefinition().getCardinality() == Cardinality.MULTI); Pair pair = new Pair(propType, value); @@ -3127,21 +3128,21 @@ public class CMISConnector implements ApplicationContextAware, ApplicationListen } } - for (String propertyId : propsMap.keySet()) - { - if(propertyId.equals(PropertyIds.SECONDARY_OBJECT_TYPE_IDS)) - { - // already handled above - continue; - } - - pair = propsMap.get(propertyId); - TypeDefinitionWrapper propType = pair.getFirst(); - Serializable value = pair.getSecond(); - if (Arrays.binarySearch(exclude, propertyId) < 0) - { - setProperty(nodeRef, propType, propertyId, value); - } + for (String propertyId : propsMap.keySet()) + { + if (propertyId.equals(PropertyIds.SECONDARY_OBJECT_TYPE_IDS)) + { + // already handled above + continue; + } + + pair = propsMap.get(propertyId); + TypeDefinitionWrapper propType = pair.getFirst(); + Serializable value = pair.getSecond(); + if (Arrays.binarySearch(exclude, propertyId) < 0) + { + setProperty(nodeRef, propType, propertyId, value); + } } List extensions = properties.getExtensions(); @@ -3226,26 +3227,6 @@ public class CMISConnector implements ApplicationContextAware, ApplicationListen for(QName aspectQName : toAdd) { nodeService.addAspect(nodeRef, aspectQName, null); - - // get aspect properties - AspectDefinition aspectDef = dictionaryService.getAspect(aspectQName); - Map aspectPropDefs = aspectDef.getProperties(); - TypeDefinitionWrapper w = getOpenCMISDictionaryService().findNodeType(aspectQName); - // for each aspect property... - for(QName propQName : aspectPropDefs.keySet()) - { - // find CMIS property id - PropertyDefinitionWrapper property = w.getPropertyByQName(propQName); - String propertyId = property.getPropertyId(); - if(!propsToAdd.containsKey(propertyId)) - { - TypeDefinitionWrapper propType = property.getOwningType(); - // CMIS 1.1 secondary types specification requires that all secondary type properties are set - // property not included in propsToAdd, add it with null value - Pair pair = new Pair(propType, null); - propsToAdd.put(propertyId, pair); - } - } } } @@ -3579,13 +3560,13 @@ public class CMISConnector implements ApplicationContextAware, ApplicationListen { throw new CmisInvalidArgumentException("Property " + propertyId + " is unknown!"); } - + + Boolean isOnWorkingCopy = checkOutCheckInService.isWorkingCopy(nodeRef); Updatability updatability = propDef.getPropertyDefinition().getUpdatability(); - if ((updatability == Updatability.READONLY) - || (updatability == Updatability.WHENCHECKEDOUT && !checkOutCheckInService.isWorkingCopy(nodeRef))) - { - throw new CmisInvalidArgumentException("Property " + propertyId + " is read-only!"); - } + if (!isUpdatable(updatability, isOnWorkingCopy)) + { + throw new CmisInvalidArgumentException("Property " + propertyId + " is read-only!"); + } if(propDef.getPropertyId().equals(PropertyIds.SECONDARY_OBJECT_TYPE_IDS)) { @@ -3610,7 +3591,7 @@ public class CMISConnector implements ApplicationContextAware, ApplicationListen { String newName = value.toString(); // If the node is checked out and the name property is set on the working copy, make sure the new name has the working copy format - if (checkOutCheckInService.isWorkingCopy(nodeRef)) + if (isOnWorkingCopy) { String wcLabel = (String)this.nodeService.getProperty(nodeRef, ContentModel.PROP_WORKING_COPY_LABEL); if (wcLabel == null) @@ -4109,5 +4090,24 @@ public class CMISConnector implements ApplicationContextAware, ApplicationListen singletonCache.put(KEY_CMIS_RENDITION_MAPPING_NODEREF, renditionMapping); } return renditionMapping; + } + + /** + * Verify if a property is updatable. + * @param updatability + * @param isOnWorkingCopy + * @return + */ + private boolean isUpdatable(Updatability updatability, Boolean isOnWorkingCopy) + { + if ((updatability == Updatability.READONLY) + || (updatability == Updatability.WHENCHECKEDOUT && !isOnWorkingCopy)) + { + return false; + } + else + { + return true; + } } } diff --git a/src/test/java/org/alfresco/opencmis/CMISTest.java b/src/test/java/org/alfresco/opencmis/CMISTest.java index b0a69bb7cb..19071d6309 100644 --- a/src/test/java/org/alfresco/opencmis/CMISTest.java +++ b/src/test/java/org/alfresco/opencmis/CMISTest.java @@ -3352,7 +3352,19 @@ public class CMISTest cmisService.updateProperties(repositoryId, new Holder(fileInfo.getNodeRef().toString()), null, properties, null); } - + //This extra check was added due to MNT-16641. + { + PropertiesImpl properties = new PropertiesImpl(); + properties.addProperty(new PropertyStringImpl(PropertyIds.SECONDARY_OBJECT_TYPE_IDS, "P:cm:lockable")); + + Set existingAspects = nodeService.getAspects(docs.get(0).getNodeRef()); + cmisService.updateProperties(repositoryId,new Holder(docs.get(0).getNodeRef().toString()), null, properties, null); + Set updatedAspects = nodeService.getAspects(docs.get(0).getNodeRef()); + updatedAspects.removeAll(existingAspects); + + assertEquals(ContentModel.ASPECT_LOCKABLE, updatedAspects.iterator().next()); + + } return repositoryId; } }, CmisVersion.CMIS_1_1); From a89dc6c904064fc069b2dac4cc675ddb4cdaf4da Mon Sep 17 00:00:00 2001 From: Cristian Turlica Date: Mon, 25 Sep 2017 08:08:41 +0300 Subject: [PATCH 6/6] Fix REPO-2903 Service Pack: MNT-18285: Brute force account attacks: Accounts not reenabled when using authentication chaining (#22) - Logins are now protected based on a combined key from authentication service system id and user name, this allows us to fix the case when a valid login was denied for subsequent authentication service if the prior authentication services in the chain failed. - Cache is now set to be local, as the new implementation doesn't require it to be fully distributed --- .../AuthenticationServiceImpl.java | 29 ++++-- src/main/resources/alfresco/caches.properties | 2 +- .../AuthenticationServiceImplTest.java | 90 +++++++++++++++++-- 3 files changed, 109 insertions(+), 12 deletions(-) diff --git a/src/main/java/org/alfresco/repo/security/authentication/AuthenticationServiceImpl.java b/src/main/java/org/alfresco/repo/security/authentication/AuthenticationServiceImpl.java index 1d5f307d2c..d3e162593c 100644 --- a/src/main/java/org/alfresco/repo/security/authentication/AuthenticationServiceImpl.java +++ b/src/main/java/org/alfresco/repo/security/authentication/AuthenticationServiceImpl.java @@ -33,6 +33,7 @@ import org.alfresco.repo.management.subsystems.ActivateableBean; import org.alfresco.repo.security.authentication.AuthenticationComponent.UserNameValidationMode; import org.alfresco.repo.tenant.TenantContextHolder; import org.alfresco.service.cmr.security.PersonService; +import org.alfresco.util.GUID; import org.alfresco.util.Pair; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -42,7 +43,10 @@ public class AuthenticationServiceImpl extends AbstractAuthenticationService imp private Log logger = LogFactory.getLog(AuthenticationServiceImpl.class); AuthenticationComponent authenticationComponent; TicketComponent ticketComponent; - + + /** a serviceInstanceId identifying this unique instance */ + private String serviceInstanceId; + private String domain; private boolean allowsUserCreation = true; private boolean allowsUserDeletion = true; @@ -85,6 +89,8 @@ public class AuthenticationServiceImpl extends AbstractAuthenticationService imp public AuthenticationServiceImpl() { super(); + + this.serviceInstanceId = GUID.generate(); } public void setTicketComponent(TicketComponent ticketComponent) @@ -124,9 +130,10 @@ public class AuthenticationServiceImpl extends AbstractAuthenticationService imp TenantContextHolder.setTenantDomain(tenant); if (protectionEnabled) { - if (protectedUsersCache.get(userName) != null) + final String protectedUserKey = getProtectedUserKey(userName); + if (protectedUsersCache.get(protectedUserKey) != null) { - protectedUsersCache.remove(userName); + protectedUsersCache.remove(protectedUserKey); } } } @@ -148,7 +155,8 @@ public class AuthenticationServiceImpl extends AbstractAuthenticationService imp boolean isProtected = false; if (protectionEnabled) { - ProtectedUser protectedUser = protectedUsersCache.get(userName); + final String protectedUserKey = getProtectedUserKey(userName); + ProtectedUser protectedUser = protectedUsersCache.get(protectedUserKey); if (protectedUser != null) { long currentTimeStamp = System.currentTimeMillis(); @@ -168,7 +176,8 @@ public class AuthenticationServiceImpl extends AbstractAuthenticationService imp { if (protectionEnabled) { - ProtectedUser protectedUser = protectedUsersCache.get(userName); + final String protectedUserKey = getProtectedUserKey(userName); + ProtectedUser protectedUser = protectedUsersCache.get(protectedUserKey); if (protectedUser == null) { protectedUser = new ProtectedUser(userName); @@ -186,10 +195,18 @@ public class AuthenticationServiceImpl extends AbstractAuthenticationService imp } } } - protectedUsersCache.put(userName, protectedUser); + protectedUsersCache.put(protectedUserKey, protectedUser); } } + /** + * Creates a key by combining the service instance ID with the username. This are the type of keys maintained by protectedUsersCache map. + */ + public String getProtectedUserKey(String userName) + { + return serviceInstanceId + "@@" + userName; + } + public String getCurrentUserName() throws AuthenticationException { return authenticationComponent.getCurrentUserName(); diff --git a/src/main/resources/alfresco/caches.properties b/src/main/resources/alfresco/caches.properties index c629c244b0..876a8640d6 100644 --- a/src/main/resources/alfresco/caches.properties +++ b/src/main/resources/alfresco/caches.properties @@ -729,7 +729,7 @@ cache.authorizationCache.readBackupData=false cache.protectedUsersCache.maxItems=1000 cache.protectedUsersCache.timeToLiveSeconds=0 cache.protectedUsersCache.maxIdleSeconds=0 -cache.protectedUsersCache.cluster.type=fully-distributed +cache.protectedUsersCache.cluster.type=local cache.protectedUsersCache.backup-count=1 cache.protectedUsersCache.eviction-policy=LRU cache.protectedUsersCache.eviction-percentage=25 diff --git a/src/test/java/org/alfresco/repo/security/authentication/AuthenticationServiceImplTest.java b/src/test/java/org/alfresco/repo/security/authentication/AuthenticationServiceImplTest.java index a7a322287d..366979083f 100644 --- a/src/test/java/org/alfresco/repo/security/authentication/AuthenticationServiceImplTest.java +++ b/src/test/java/org/alfresco/repo/security/authentication/AuthenticationServiceImplTest.java @@ -34,6 +34,7 @@ import java.util.Collection; import java.util.HashMap; import java.util.Map; +import static junit.framework.TestCase.assertNotNull; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; @@ -57,6 +58,7 @@ public class AuthenticationServiceImplTest private SimpleCache cache; private TicketComponent ticketComponent = mock(TicketComponent.class); private AuthenticationServiceImpl authService; + private AuthenticationServiceImpl authService2; private static final String USERNAME = "username"; private static final char[] PASSWORD = "password".toCharArray(); @@ -69,6 +71,11 @@ public class AuthenticationServiceImplTest authService.setTicketComponent(ticketComponent); cache = new MockCache<>(); authService.setProtectedUsersCache(cache); + + authService2 = new AuthenticationServiceImpl(); + authService2.setAuthenticationComponent(authenticationComponent); + authService2.setTicketComponent(ticketComponent); + authService2.setProtectedUsersCache(cache); } @Test @@ -104,7 +111,9 @@ public class AuthenticationServiceImplTest } verify(authenticationComponent, times(limit)).authenticate(USERNAME, PASSWORD); assertTrue("The user should be protected.", authService.isUserProtected(USERNAME)); - assertEquals("The number of recorded logins did not match.", attempts, cache.get(USERNAME).getNumLogins()); + + final String protectedUserKey = authService.getProtectedUserKey(USERNAME); + assertEquals("The number of recorded logins did not match.", attempts, cache.get(protectedUserKey).getNumLogins()); // test that the protection is still in place even if the password is correct doNothing().when(authenticationComponent).authenticate(USERNAME, PASSWORD); @@ -118,7 +127,7 @@ public class AuthenticationServiceImplTest // normal } verify(authenticationComponent, times(limit)).authenticate(USERNAME, PASSWORD); - assertEquals("The number of recorded logins did not match.", attempts + 1, cache.get(USERNAME).getNumLogins()); + assertEquals("The number of recorded logins did not match.", attempts + 1, cache.get(protectedUserKey).getNumLogins()); } @Test @@ -145,11 +154,13 @@ public class AuthenticationServiceImplTest } } assertTrue("The user should be protected.", authService.isUserProtected(USERNAME)); - assertEquals("The number of recorded logins did not match.", attempts, cache.get(USERNAME).getNumLogins()); + + final String protectedUserKey = authService.getProtectedUserKey(USERNAME); + assertEquals("The number of recorded logins did not match.", attempts, cache.get(protectedUserKey).getNumLogins()); Thread.sleep(timeLimit*1000 + 1); assertFalse("The user should not be protected any more.", authService.isUserProtected(USERNAME)); assertEquals("The number of recorded logins should stay the same after protection period ends.", - attempts, cache.get(USERNAME).getNumLogins()); + attempts, cache.get(protectedUserKey).getNumLogins()); doNothing().when(authenticationComponent).authenticate(USERNAME, PASSWORD); try @@ -161,9 +172,78 @@ public class AuthenticationServiceImplTest fail("An " + AuthenticationException.class.getName() + " should not be thrown."); } assertNull("The user should be removed from the cache after successful login.", - cache.get(USERNAME)); + cache.get(protectedUserKey)); } + @Test + public void testAuthChainWorksIfFirstAuthFails() throws Exception + { + int timeLimit = 1; + int attempts = 2; + authService.setProtectionPeriodSeconds(timeLimit); + authService.setProtectionLimit(attempts); + authService.setProtectionEnabled(true); + + authService2.setProtectionPeriodSeconds(timeLimit); + authService2.setProtectionLimit(attempts); + authService2.setProtectionEnabled(true); + + AuthenticationServiceImpl[] authenticationChain = {authService, authService2}; + + doThrow(new AuthenticationException("Bad password")) + .when(authenticationComponent).authenticate(USERNAME, PASSWORD); + + // Authentication fails on first run. + for (int i = 0; i < attempts; i++) { + for (AuthenticationServiceImpl authentication : authenticationChain) { + try { + authentication.authenticate(USERNAME, PASSWORD); + fail("An " + AuthenticationException.class.getName() + " should be thrown."); + } catch (AuthenticationException ae) { + // normal + } + } + } + + for (AuthenticationServiceImpl authentication : authenticationChain) + { + assertTrue("The user should be protected.", authentication.isUserProtected(USERNAME)); + } + + Thread.sleep(timeLimit*1000 + 1); + + for (AuthenticationServiceImpl authentication : authenticationChain) + { + assertFalse("The user should not be protected any more.", authentication.isUserProtected(USERNAME)); + } + + // Authentication always fails on first authentication service in the chain. + try + { + authenticationChain[0].authenticate(USERNAME, PASSWORD); + fail("An " + AuthenticationException.class.getName() + " should be thrown."); + } catch (AuthenticationException ae) { + // normal + } + + // Authentication should succeed on second authentication service in the chain. + doNothing().when(authenticationComponent).authenticate(USERNAME, PASSWORD); + try + { + authenticationChain[1].authenticate(USERNAME, PASSWORD); + } + catch (AuthenticationException ae) + { + fail("An " + AuthenticationException.class.getName() + " should not be thrown."); + } + + assertNotNull("The user should not be removed from the cache for the corresponding authorization service after a failed login.", + cache.get(authenticationChain[0].getProtectedUserKey(USERNAME))); + assertNull("The user should be removed from the cache for the corresponding authorization service after successful login.", + cache.get(authenticationChain[1].getProtectedUserKey(USERNAME))); + } + + @Test public void testProtectionDisabledBadPassword() {