mirror of
				https://github.com/Alfresco/alfresco-community-repo.git
				synced 2025-10-29 15:21:53 +00:00 
			
		
		
		
	Compare commits
	
		
			43 Commits
		
	
	
		
			feature/AC
			...
			15.13
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 817839f292 | ||
|  | a6833a5956 | ||
|  | b4289884b0 | ||
|  | 3c5af30501 | ||
|  | 197966b35a | ||
|  | 49e546f0c9 | ||
|  | 4ef772560a | ||
|  | 2fa3aa5638 | ||
|  | a63a232da2 | ||
|  | 8d1aeece29 | ||
|  | c177a5ffee | ||
|  | d28d4873be | ||
|  | 44d7c2328c | ||
|  | 073338afa7 | ||
|  | 3e53467ac8 | ||
|  | 0d5ffdac2e | ||
|  | ac03eb7642 | ||
|  | 2ff5b7dd0a | ||
|  | b0d7e6dfba | ||
|  | 3304a62a35 | ||
|  | 763591c1a3 | ||
|  | 6de5a507fe | ||
|  | 3990bc9db4 | ||
|  | f2207fe43e | ||
|  | f48db84334 | ||
|  | 98d73b7200 | ||
|  | acd4b1efcb | ||
|  | 4433dd009a | ||
|  | d1a9794ec8 | ||
|  | bf848ff882 | ||
|  | 69ebccfc20 | ||
|  | 600b50fce1 | ||
|  | 37e8586658 | ||
|  | 4b12ed5a51 | ||
|  | e379b7704d | ||
|  | 297be122a6 | ||
|  | ca28024ad8 | ||
|  | cfd3255aa7 | ||
|  | 4e436160cc | ||
|  | bf0ca4ca83 | ||
|  | e23a97960f | ||
|  | 2d2371a792 | ||
|  | 0ea69dd4ef | 
| @@ -157,10 +157,10 @@ jobs: | ||||
|         - docker run -d -p 61616:61616 -p 5672:5672 alfresco/alfresco-activemq:5.16.1 | ||||
|       script: travis_wait 20 mvn -B test -pl repository -Dtest=AllDBTestsTestSuite -Ddb.name=alfresco -Ddb.url=jdbc:mariadb://localhost:3307/alfresco?useUnicode=yes\&characterEncoding=UTF-8 -Ddb.username=alfresco -Ddb.password=alfresco -Ddb.driver=org.mariadb.jdbc.Driver | ||||
|  | ||||
|     - name: "Repository - MySQL 5.7.23 tests" | ||||
|     - name: "Repository - MySQL 5.7.28 tests" | ||||
|       if: (branch =~ /(release\/.*$|master)/ AND commit_message !~ /\[skip db\]/ AND type != pull_request) OR commit_message =~ /\[db\]/ | ||||
|       before_script: | ||||
|         - docker run -d -p 3307:3306 -e MYSQL_ROOT_PASSWORD=alfresco -e MYSQL_USER=alfresco -e MYSQL_DATABASE=alfresco -e MYSQL_PASSWORD=alfresco  mysql:5.7.23 --transaction-isolation='READ-COMMITTED' --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci | ||||
|         - docker run -d -p 3307:3306 -e MYSQL_ROOT_PASSWORD=alfresco -e MYSQL_USER=alfresco -e MYSQL_DATABASE=alfresco -e MYSQL_PASSWORD=alfresco  mysql:5.7.28 --transaction-isolation='READ-COMMITTED' --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci | ||||
|         - docker run -d -p 61616:61616 -p 5672:5672 alfresco/alfresco-activemq:5.16.1 | ||||
|       script: travis_wait 20 mvn -B test -pl repository -Dtest=AllDBTestsTestSuite -Ddb.driver=com.mysql.jdbc.Driver -Ddb.name=alfresco -Ddb.url=jdbc:mysql://localhost:3307/alfresco -Ddb.username=alfresco -Ddb.password=alfresco | ||||
|  | ||||
|   | ||||
| @@ -7,7 +7,7 @@ | ||||
|    <parent> | ||||
|       <groupId>org.alfresco</groupId> | ||||
|       <artifactId>alfresco-community-repo-amps</artifactId> | ||||
|       <version>14.146-SNAPSHOT</version> | ||||
|       <version>15.13</version> | ||||
|    </parent> | ||||
|  | ||||
|    <modules> | ||||
|   | ||||
| @@ -7,7 +7,7 @@ | ||||
|    <parent> | ||||
|       <groupId>org.alfresco</groupId> | ||||
|       <artifactId>alfresco-governance-services-community-parent</artifactId> | ||||
|       <version>14.146-SNAPSHOT</version> | ||||
|       <version>15.13</version> | ||||
|    </parent> | ||||
|  | ||||
|    <modules> | ||||
|   | ||||
| @@ -7,7 +7,7 @@ | ||||
|    <parent> | ||||
|       <groupId>org.alfresco</groupId> | ||||
|       <artifactId>alfresco-governance-services-automation-community-repo</artifactId> | ||||
|       <version>14.146-SNAPSHOT</version> | ||||
|       <version>15.13</version> | ||||
|    </parent> | ||||
|  | ||||
|    <build> | ||||
|   | ||||
| @@ -102,7 +102,7 @@ public class DeleteRecordTests extends BaseRMRestTest | ||||
|         testSite = dataSite.usingAdmin().createPublicRandomSite(); | ||||
|         recordFolder = createCategoryFolderInFilePlan(); | ||||
|         unfiledRecordFolder = createUnfiledContainerChild(UNFILED_RECORDS_CONTAINER_ALIAS, getRandomName("Unfiled Folder "), | ||||
|                 UNFILED_RECORD_FOLDER_TYPE); | ||||
|             UNFILED_RECORD_FOLDER_TYPE); | ||||
|     } | ||||
|  | ||||
|     /** Data provider with electronic and non-electronic records to be deleted */ | ||||
| @@ -133,10 +133,10 @@ public class DeleteRecordTests extends BaseRMRestTest | ||||
|      * </pre> | ||||
|      */ | ||||
|     @Test | ||||
|     ( | ||||
|         dataProvider = "recordsToBeDeleted", | ||||
|         description = "Admin user can delete records" | ||||
|     ) | ||||
|         ( | ||||
|             dataProvider = "recordsToBeDeleted", | ||||
|             description = "Admin user can delete records" | ||||
|         ) | ||||
|     @AlfrescoTest(jira="RM-4363") | ||||
|     public void adminCanDeleteRecords(String recordId) | ||||
|     { | ||||
| @@ -154,17 +154,17 @@ public class DeleteRecordTests extends BaseRMRestTest | ||||
|      * </pre> | ||||
|      */ | ||||
|     @Test | ||||
|     ( | ||||
|         description = "User without write permissions can't delete a record" | ||||
|     ) | ||||
|         ( | ||||
|             description = "User without write permissions can't delete a record" | ||||
|         ) | ||||
|     @AlfrescoTest(jira="RM-4363") | ||||
|     public void userWithoutWritePermissionsCantDeleteRecord() | ||||
|     { | ||||
|         // Create a non-electronic record in unfiled records | ||||
|         UnfiledContainerChild nonElectronicRecord = UnfiledContainerChild.builder() | ||||
|                 .name("Record " + RandomData.getRandomAlphanumeric()) | ||||
|                 .nodeType(NON_ELECTRONIC_RECORD_TYPE) | ||||
|                 .build(); | ||||
|             .name("Record " + RandomData.getRandomAlphanumeric()) | ||||
|             .nodeType(NON_ELECTRONIC_RECORD_TYPE) | ||||
|             .build(); | ||||
|         UnfiledContainerChild newRecord = getRestAPIFactory().getUnfiledContainersAPI().createUnfiledContainerChild(nonElectronicRecord, UNFILED_RECORDS_CONTAINER_ALIAS); | ||||
|  | ||||
|         assertStatusCode(CREATED); | ||||
| @@ -187,9 +187,9 @@ public class DeleteRecordTests extends BaseRMRestTest | ||||
|      * </pre> | ||||
|      */ | ||||
|     @Test | ||||
|     ( | ||||
|         description = "User without delete records capability can't delete a record" | ||||
|     ) | ||||
|         ( | ||||
|             description = "User without delete records capability can't delete a record" | ||||
|         ) | ||||
|     @AlfrescoTest(jira="RM-4363") | ||||
|     public void userWithoutDeleteRecordsCapabilityCantDeleteRecord() | ||||
|     { | ||||
| @@ -234,7 +234,7 @@ public class DeleteRecordTests extends BaseRMRestTest | ||||
|  | ||||
|         STEP("Create a record in first folder and copy it into second folder."); | ||||
|         String recordId = getRestAPIFactory().getRecordFolderAPI() | ||||
|                     .createRecord(createElectronicRecordModel(), recordFolder.getId(), getFile(IMAGE_FILE)).getId(); | ||||
|             .createRecord(createElectronicRecordModel(), recordFolder.getId(), getFile(IMAGE_FILE)).getId(); | ||||
|         String copyId = copyNode(recordId, recordFolderB.getId()).getId(); | ||||
|         assertStatusCode(CREATED); | ||||
|  | ||||
| @@ -290,6 +290,7 @@ public class DeleteRecordTests extends BaseRMRestTest | ||||
|      * Then it is still possible to view the content of the copy | ||||
|      * </pre> | ||||
|      */ | ||||
|     /* | ||||
|     @Test (description = "Destroying record doesn't delete the content for the associated copy") | ||||
|     @AlfrescoTest (jira = "MNT-20145") | ||||
|     public void destroyOfRecord() | ||||
| @@ -323,14 +324,15 @@ public class DeleteRecordTests extends BaseRMRestTest | ||||
|  | ||||
|         STEP("Execute the disposition schedule steps."); | ||||
|         rmRolesAndActionsAPI.executeAction(getAdminUser().getUsername(), getAdminUser().getUsername(), recordFiled.getName(), | ||||
|                 RM_ACTIONS.CUT_OFF); | ||||
|             RM_ACTIONS.CUT_OFF); | ||||
|         rmRolesAndActionsAPI.executeAction(getAdminUser().getUsername(), getAdminUser().getUsername(), recordFiled.getName(), | ||||
|                 RM_ACTIONS.DESTROY); | ||||
|             RM_ACTIONS.DESTROY); | ||||
|  | ||||
|         STEP("Check that it's possible to load the copy content."); | ||||
|         getNodeContent(copy.getId()); | ||||
|         assertStatusCode(OK); | ||||
|     } | ||||
|     */ | ||||
|  | ||||
|     /** | ||||
|      * <pre> | ||||
| @@ -348,14 +350,14 @@ public class DeleteRecordTests extends BaseRMRestTest | ||||
|  | ||||
|         STEP("Declare file version as record."); | ||||
|         recordsAPI.declareDocumentVersionAsRecord(getAdminUser().getUsername(), getAdminUser().getPassword(), testSite.getId(), | ||||
|                 testFile.getName()); | ||||
|             testFile.getName()); | ||||
|         UnfiledContainerChild unfiledContainerChild = getRestAPIFactory().getUnfiledContainersAPI() | ||||
|                                                                        .getUnfiledContainerChildren(UNFILED_RECORDS_CONTAINER_ALIAS) | ||||
|                                                                        .getEntries().stream() | ||||
|                                                                        .filter(child -> child.getEntry().getName() | ||||
|                                                                                              .startsWith(testFile.getName().substring(0, testFile.getName().indexOf(".")))) | ||||
|                                                                        .findFirst() | ||||
|                                                                        .get().getEntry(); | ||||
|             .getUnfiledContainerChildren(UNFILED_RECORDS_CONTAINER_ALIAS) | ||||
|             .getEntries().stream() | ||||
|             .filter(child -> child.getEntry().getName() | ||||
|                 .startsWith(testFile.getName().substring(0, testFile.getName().indexOf(".")))) | ||||
|             .findFirst() | ||||
|             .get().getEntry(); | ||||
|  | ||||
|         STEP("Delete the record."); | ||||
|         deleteAndVerify(unfiledContainerChild.getId()); | ||||
|   | ||||
| @@ -7,7 +7,7 @@ | ||||
|    <parent> | ||||
|       <groupId>org.alfresco</groupId> | ||||
|       <artifactId>alfresco-governance-services-community-parent</artifactId> | ||||
|       <version>14.146-SNAPSHOT</version> | ||||
|       <version>15.13</version> | ||||
|    </parent> | ||||
|  | ||||
|    <modules> | ||||
|   | ||||
| @@ -109,6 +109,10 @@ rm.completerecord.mandatorypropertiescheck.enabled=true | ||||
| # | ||||
| rm.patch.v22.convertToStandardFilePlan=false | ||||
|  | ||||
| # | ||||
| # Max Batch size for adding the associations between the frozen nodes and the hold | ||||
| rm.patch.v35.holdNewChildAssocPatch.batchSize=1000 | ||||
|  | ||||
| # Permission mapping | ||||
| # these take a comma separated string of permissions from org.alfresco.service.cmr.security.PermissionService | ||||
| # read maps to ReadRecords and write to FileRecords | ||||
|   | ||||
| @@ -17,5 +17,6 @@ | ||||
|       <property name="filePlanService" ref="filePlanService" /> | ||||
|       <property name="holdService" ref="holdService" /> | ||||
|       <property name="nodeService" ref="nodeService" /> | ||||
|       <property name="batchSize" value="${rm.patch.v35.holdNewChildAssocPatch.batchSize}" /> | ||||
|    </bean> | ||||
| </beans> | ||||
|   | ||||
| @@ -8,7 +8,7 @@ | ||||
|    <parent> | ||||
|       <groupId>org.alfresco</groupId> | ||||
|       <artifactId>alfresco-governance-services-community-repo-parent</artifactId> | ||||
|       <version>14.146-SNAPSHOT</version> | ||||
|       <version>15.13</version> | ||||
|    </parent> | ||||
|  | ||||
|    <properties> | ||||
|   | ||||
| @@ -30,6 +30,9 @@ import static org.alfresco.model.ContentModel.ASSOC_CONTAINS; | ||||
| import static org.alfresco.module.org_alfresco_module_rm.model.RecordsManagementCustomModel.RM_CUSTOM_URI; | ||||
| import static org.alfresco.module.org_alfresco_module_rm.model.RecordsManagementModel.ASSOC_FROZEN_CONTENT; | ||||
|  | ||||
| import java.util.ArrayList; | ||||
| import java.util.Collection; | ||||
| import java.util.Iterator; | ||||
| import java.util.List; | ||||
|  | ||||
| import org.alfresco.model.ContentModel; | ||||
| @@ -37,11 +40,14 @@ import org.alfresco.module.org_alfresco_module_rm.fileplan.FilePlanService; | ||||
| import org.alfresco.module.org_alfresco_module_rm.hold.HoldService; | ||||
| import org.alfresco.module.org_alfresco_module_rm.patch.AbstractModulePatch; | ||||
| import org.alfresco.repo.policy.BehaviourFilter; | ||||
| import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; | ||||
| 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.namespace.QName; | ||||
| import org.alfresco.service.namespace.RegexQNamePattern; | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
|  | ||||
| /** | ||||
|  * Patch to create new hold child association to link the record to the hold | ||||
| @@ -52,8 +58,15 @@ import org.alfresco.service.namespace.RegexQNamePattern; | ||||
|  */ | ||||
| public class RMv35HoldNewChildAssocPatch extends AbstractModulePatch | ||||
| { | ||||
|     /** logger */ | ||||
|     protected static final Logger LOGGER = LoggerFactory.getLogger(RMv35HoldNewChildAssocPatch.class); | ||||
|  | ||||
|     /** A name for the associations created by this patch. */ | ||||
|     protected static final QName PATCH_ASSOC_NAME = QName.createQName(RM_CUSTOM_URI, RMv35HoldNewChildAssocPatch.class.getSimpleName()); | ||||
|     protected static final QName PATCH_ASSOC_NAME = QName.createQName(RM_CUSTOM_URI, | ||||
|             RMv35HoldNewChildAssocPatch.class.getSimpleName()); | ||||
|  | ||||
|     /** The batch size for processing frozen nodes. */ | ||||
|     private int batchSize = 1000; | ||||
|  | ||||
|     /** | ||||
|      * File plan service interface | ||||
| @@ -75,7 +88,8 @@ public class RMv35HoldNewChildAssocPatch extends AbstractModulePatch | ||||
|     /** | ||||
|      * Setter for fileplanservice | ||||
|      * | ||||
|      * @param filePlanService File plan service interface | ||||
|      * @param filePlanService | ||||
|      *            File plan service interface | ||||
|      */ | ||||
|     public void setFilePlanService(FilePlanService filePlanService) | ||||
|     { | ||||
| @@ -85,7 +99,8 @@ public class RMv35HoldNewChildAssocPatch extends AbstractModulePatch | ||||
|     /** | ||||
|      * Setter for hold service | ||||
|      * | ||||
|      * @param holdService Hold service interface. | ||||
|      * @param holdService | ||||
|      *            Hold service interface. | ||||
|      */ | ||||
|     public void setHoldService(HoldService holdService) | ||||
|     { | ||||
| @@ -95,7 +110,8 @@ public class RMv35HoldNewChildAssocPatch extends AbstractModulePatch | ||||
|     /** | ||||
|      * Setter for node service | ||||
|      * | ||||
|      * @param nodeService Interface for public and internal node and store operations. | ||||
|      * @param nodeService | ||||
|      *            Interface for public and internal node and store operations. | ||||
|      */ | ||||
|     public void setNodeService(NodeService nodeService) | ||||
|     { | ||||
| @@ -112,33 +128,49 @@ public class RMv35HoldNewChildAssocPatch extends AbstractModulePatch | ||||
|         this.behaviourFilter = behaviourFilter; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Setter for maximum batch size | ||||
|      * | ||||
|      * @param maxBatchSize | ||||
|      *            The max amount of associations to be created between the frozen nodes and the hold in a transaction | ||||
|      */ | ||||
|     public void setBatchSize(int batchSize) | ||||
|     { | ||||
|         this.batchSize = batchSize; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void applyInternal() | ||||
|     { | ||||
|         behaviourFilter.disableBehaviour(ContentModel.ASPECT_AUDITABLE); | ||||
|         behaviourFilter.disableBehaviour(ContentModel.ASPECT_VERSIONABLE); | ||||
|  | ||||
|         try | ||||
|         { | ||||
|             int patchedNodesCounter = 0; | ||||
|  | ||||
|             for (NodeRef filePlan : filePlanService.getFilePlans()) | ||||
|             { | ||||
|                 for (NodeRef hold : holdService.getHolds(filePlan)) | ||||
|                 { | ||||
|                     List<ChildAssociationRef> frozenAssoc = nodeService.getChildAssocs(hold, ASSOC_FROZEN_CONTENT, RegexQNamePattern.MATCH_ALL); | ||||
|                     for (ChildAssociationRef ref : frozenAssoc) | ||||
|                     LOGGER.debug("Analyzing hold {}", hold.getId()); | ||||
|  | ||||
|                     BatchWorker batchWorker = new BatchWorker(hold); | ||||
|  | ||||
|                     LOGGER.debug("Hold has {} items to be analyzed", batchWorker.getWorkSize()); | ||||
|  | ||||
|                     while (batchWorker.hasMoreResults()) | ||||
|                     { | ||||
|                         NodeRef childNodeRef = ref.getChildRef(); | ||||
|                         // In testing we found that this was returning more than just "contains" associations. | ||||
|                         // Possibly this is due to the code in Node2ServiceImpl.getParentAssocs not using the second parameter. | ||||
|                         List<ChildAssociationRef> parentAssocs = nodeService.getParentAssocs(childNodeRef, ASSOC_CONTAINS, RegexQNamePattern.MATCH_ALL); | ||||
|                         boolean childContainedByHold = | ||||
|                                 parentAssocs.stream().anyMatch(entry -> entry.getParentRef().equals(hold) && entry.getTypeQName().equals(ASSOC_CONTAINS)); | ||||
|                         if (!childContainedByHold) | ||||
|                         { | ||||
|                             nodeService.addChild(hold, childNodeRef, ASSOC_CONTAINS, PATCH_ASSOC_NAME); | ||||
|                         } | ||||
|                         processBatch(hold, batchWorker); | ||||
|                     } | ||||
|  | ||||
|                     LOGGER.debug("Patched {} items in hold", batchWorker.getTotalPatchedNodes()); | ||||
|  | ||||
|                     patchedNodesCounter += batchWorker.getTotalPatchedNodes(); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             LOGGER.debug("Patch applied to {} children across all holds", patchedNodesCounter); | ||||
|         } | ||||
|         finally | ||||
|         { | ||||
| @@ -146,4 +178,92 @@ public class RMv35HoldNewChildAssocPatch extends AbstractModulePatch | ||||
|             behaviourFilter.enableBehaviour(ContentModel.ASPECT_VERSIONABLE); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private void processBatch(NodeRef hold, BatchWorker batch) | ||||
|     { | ||||
|         transactionService.getRetryingTransactionHelper().doInTransaction(() -> { | ||||
|  | ||||
|             Collection<ChildAssociationRef> childRefs = batch.getNextWork(); | ||||
|  | ||||
|             LOGGER.debug("Processing batch of {} children in hold", childRefs.size()); | ||||
|  | ||||
|             for (ChildAssociationRef child : childRefs) | ||||
|             { | ||||
|                 NodeRef childNodeRef = child.getChildRef(); | ||||
|  | ||||
|                 if (!isChildContainedByHold(hold, childNodeRef)) | ||||
|                 { | ||||
|                     nodeService.addChild(hold, childNodeRef, ASSOC_CONTAINS, PATCH_ASSOC_NAME); | ||||
|                     batch.countPatchedNode(); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             return null; | ||||
|         }, false, true); | ||||
|     } | ||||
|  | ||||
|     private boolean isChildContainedByHold(NodeRef hold, NodeRef child) | ||||
|     { | ||||
|         // In testing we found that this was returning more than just "contains" associations. | ||||
|         // Possibly this is due to the code in Node2ServiceImpl.getParentAssocs not using the second | ||||
|         // parameter. | ||||
|         List<ChildAssociationRef> parentAssocs = nodeService.getParentAssocs(child, ASSOC_CONTAINS, RegexQNamePattern.MATCH_ALL); | ||||
|         return parentAssocs.stream() | ||||
|                 .anyMatch(entry -> entry.getParentRef().equals(hold) && entry.getTypeQName().equals(ASSOC_CONTAINS)); | ||||
|     } | ||||
|  | ||||
|     private class BatchWorker | ||||
|     { | ||||
|         NodeRef hold; | ||||
|         int totalPatchedNodes = 0; | ||||
|         int workSize; | ||||
|         Iterator<ChildAssociationRef> iterator; | ||||
|  | ||||
|         public BatchWorker(NodeRef hold) | ||||
|         { | ||||
|             this.hold = hold; | ||||
|             setupHold(); | ||||
|         } | ||||
|  | ||||
|         public boolean hasMoreResults() | ||||
|         { | ||||
|             return iterator == null ? true : iterator.hasNext(); | ||||
|         } | ||||
|  | ||||
|         public void countPatchedNode() | ||||
|         { | ||||
|             this.totalPatchedNodes += 1; | ||||
|         } | ||||
|  | ||||
|         public int getTotalPatchedNodes() | ||||
|         { | ||||
|             return totalPatchedNodes; | ||||
|         } | ||||
|  | ||||
|         public int getWorkSize() | ||||
|         { | ||||
|             return workSize; | ||||
|         } | ||||
|  | ||||
|         public void setupHold() | ||||
|         { | ||||
|             // Get child assocs without preloading | ||||
|             List<ChildAssociationRef> holdChildren = nodeService.getChildAssocs(hold, ASSOC_FROZEN_CONTENT, | ||||
|                     RegexQNamePattern.MATCH_ALL, Integer.MAX_VALUE, false); | ||||
|             this.iterator = holdChildren.listIterator(); | ||||
|             this.workSize = holdChildren.size(); | ||||
|         } | ||||
|  | ||||
|         public Collection<ChildAssociationRef> getNextWork() | ||||
|         { | ||||
|             List<ChildAssociationRef> frozenNodes = new ArrayList<ChildAssociationRef>(batchSize); | ||||
|             while (iterator.hasNext() && frozenNodes.size() < batchSize) | ||||
|             { | ||||
|                 frozenNodes.add(iterator.next()); | ||||
|             } | ||||
|             return frozenNodes; | ||||
|         } | ||||
|  | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -4,8 +4,8 @@ | ||||
|  | ||||
| # Version label | ||||
| version.major=7 | ||||
| version.minor=0 | ||||
| version.revision=0 | ||||
| version.minor=2 | ||||
| version.revision=1 | ||||
| version.label= | ||||
|  | ||||
| # Edition label | ||||
| @@ -15,4 +15,4 @@ version.edition=Community | ||||
| version.scmrevision=@scm-path@-r@scm-revision@ | ||||
|  | ||||
| # Build number | ||||
| version.build=r@scm-revision@-b@build-number@ | ||||
| version.build=r@scm-revision@-b@build-number@ | ||||
|   | ||||
| @@ -33,8 +33,10 @@ import static java.util.Collections.emptyList; | ||||
| import static org.alfresco.model.ContentModel.ASSOC_CONTAINS; | ||||
| import static org.alfresco.module.org_alfresco_module_rm.model.RecordsManagementModel.ASSOC_FROZEN_CONTENT; | ||||
| import static org.alfresco.module.org_alfresco_module_rm.patch.v35.RMv35HoldNewChildAssocPatch.PATCH_ASSOC_NAME; | ||||
| import static org.mockito.Matchers.any; | ||||
| import static org.mockito.Matchers.anyMap; | ||||
| import static org.mockito.ArgumentMatchers.any; | ||||
| import static org.mockito.ArgumentMatchers.anyBoolean; | ||||
| import static org.mockito.ArgumentMatchers.anyMap; | ||||
| import static org.mockito.Mockito.doAnswer; | ||||
| import static org.mockito.Mockito.doReturn; | ||||
| import static org.mockito.Mockito.never; | ||||
| import static org.mockito.Mockito.times; | ||||
| @@ -51,16 +53,21 @@ import java.util.Set; | ||||
| import org.alfresco.module.org_alfresco_module_rm.fileplan.FilePlanService; | ||||
| import org.alfresco.module.org_alfresco_module_rm.hold.HoldService; | ||||
| import org.alfresco.repo.policy.BehaviourFilter; | ||||
| import org.alfresco.repo.transaction.RetryingTransactionHelper; | ||||
| import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; | ||||
| 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.namespace.QName; | ||||
| import org.alfresco.service.namespace.RegexQNamePattern; | ||||
| import org.alfresco.service.transaction.TransactionService; | ||||
| import org.junit.Before; | ||||
| import org.junit.Test; | ||||
| import org.mockito.InjectMocks; | ||||
| import org.mockito.Mock; | ||||
| import org.mockito.MockitoAnnotations; | ||||
| import org.mockito.invocation.InvocationOnMock; | ||||
| import org.mockito.stubbing.Answer; | ||||
|  | ||||
| /** | ||||
|  * RM V3.5  Create new hold child association to link the record to the hold | ||||
| @@ -81,6 +88,12 @@ public class RMv35HoldNewChildAssocPatchUnitTest | ||||
|     @Mock | ||||
|     private BehaviourFilter mockBehaviourFilter; | ||||
|  | ||||
|     @Mock | ||||
|     private TransactionService mockTransactionService; | ||||
|  | ||||
|     @Mock | ||||
|     private RetryingTransactionHelper mockRetryingTransactionHelper; | ||||
|  | ||||
|     @InjectMocks | ||||
|     private RMv35HoldNewChildAssocPatch patch; | ||||
|  | ||||
| @@ -112,25 +125,63 @@ public class RMv35HoldNewChildAssocPatchUnitTest | ||||
|     /** | ||||
|      * Test secondary associations are created for held items so that they are "contained" in the hold. | ||||
|      */ | ||||
|     @SuppressWarnings("unchecked") | ||||
|     @Test | ||||
|     public void testAddChildDuringUpgrade() | ||||
|     { | ||||
|         when(mockFilePlanService.getFilePlans()).thenReturn(fileplans); | ||||
|         when(mockHoldService.getHolds(filePlanRef)).thenReturn(holds); | ||||
|         when(mockNodeService.getChildAssocs(holdRef, ASSOC_FROZEN_CONTENT, RegexQNamePattern.MATCH_ALL)).thenReturn(childAssocs); | ||||
|         when(mockNodeService.getChildAssocs(holdRef, ASSOC_FROZEN_CONTENT, RegexQNamePattern.MATCH_ALL, Integer.MAX_VALUE, false)) | ||||
|                 .thenReturn(childAssocs); | ||||
|         when(childAssociationRef.getChildRef()).thenReturn(heldItemRef); | ||||
|  | ||||
|         // setup retrying transaction helper | ||||
|         Answer<Object> doInTransactionAnswer = new Answer<Object>() | ||||
|         { | ||||
|             @SuppressWarnings("rawtypes") | ||||
|             @Override | ||||
|             public Object answer(InvocationOnMock invocation) throws Throwable | ||||
|             { | ||||
|                 RetryingTransactionCallback callback = (RetryingTransactionCallback) invocation.getArguments()[0]; | ||||
|                 // when(childAssociationRef.getChildRef()).thenReturn(heldItemRef); | ||||
|                 return callback.execute(); | ||||
|             } | ||||
|         }; | ||||
|         doAnswer(doInTransactionAnswer).when(mockRetryingTransactionHelper) | ||||
|                 .<Object> doInTransaction(any(RetryingTransactionCallback.class), anyBoolean(), anyBoolean()); | ||||
|  | ||||
|         when(mockTransactionService.getRetryingTransactionHelper()).thenReturn(mockRetryingTransactionHelper); | ||||
|  | ||||
|         patch.applyInternal(); | ||||
|  | ||||
|         verify(mockNodeService, times(1)).addChild(holdRef, heldItemRef, ASSOC_CONTAINS, PATCH_ASSOC_NAME); | ||||
|     } | ||||
|  | ||||
|     @SuppressWarnings("unchecked") | ||||
|     @Test | ||||
|     public void patchRunWithSuccessWhenNoHeldChildren() | ||||
|     { | ||||
|         when(mockFilePlanService.getFilePlans()).thenReturn(fileplans); | ||||
|         when(mockHoldService.getHolds(filePlanRef)).thenReturn(holds); | ||||
|         when(mockNodeService.getChildAssocs(holdRef, ASSOC_FROZEN_CONTENT, RegexQNamePattern.MATCH_ALL)).thenReturn(emptyList()); | ||||
|         when(mockNodeService.getChildAssocs(holdRef, ASSOC_FROZEN_CONTENT, RegexQNamePattern.MATCH_ALL, Integer.MAX_VALUE, false)) | ||||
|                 .thenReturn(emptyList()); | ||||
|  | ||||
|         // setup retrying transaction helper | ||||
|         Answer<Object> doInTransactionAnswer = new Answer<Object>() | ||||
|         { | ||||
|             @SuppressWarnings("rawtypes") | ||||
|             @Override | ||||
|             public Object answer(InvocationOnMock invocation) throws Throwable | ||||
|             { | ||||
|                 RetryingTransactionCallback callback = (RetryingTransactionCallback) invocation.getArguments()[0]; | ||||
|                 when(childAssociationRef.getChildRef()).thenReturn(heldItemRef); | ||||
|                 return callback.execute(); | ||||
|             } | ||||
|         }; | ||||
|         doAnswer(doInTransactionAnswer).when(mockRetryingTransactionHelper) | ||||
|                 .<Object> doInTransaction(any(RetryingTransactionCallback.class), anyBoolean(), anyBoolean()); | ||||
|  | ||||
|         when(mockTransactionService.getRetryingTransactionHelper()).thenReturn(mockRetryingTransactionHelper); | ||||
|  | ||||
|         patch.applyInternal(); | ||||
|  | ||||
|   | ||||
| @@ -7,7 +7,7 @@ | ||||
|     <parent> | ||||
|         <groupId>org.alfresco</groupId> | ||||
|         <artifactId>alfresco-governance-services-community-repo-parent</artifactId> | ||||
|         <version>14.146-SNAPSHOT</version> | ||||
|         <version>15.13</version> | ||||
|     </parent> | ||||
|  | ||||
|     <build> | ||||
|   | ||||
| @@ -7,7 +7,7 @@ | ||||
|     <parent> | ||||
|         <groupId>org.alfresco</groupId> | ||||
|         <artifactId>alfresco-community-repo</artifactId> | ||||
|         <version>14.146-SNAPSHOT</version> | ||||
|         <version>15.13</version> | ||||
|     </parent> | ||||
|  | ||||
|     <modules> | ||||
|   | ||||
| @@ -8,7 +8,7 @@ | ||||
|     <parent> | ||||
|         <groupId>org.alfresco</groupId> | ||||
|         <artifactId>alfresco-community-repo-amps</artifactId> | ||||
|         <version>14.146-SNAPSHOT</version> | ||||
|         <version>15.13</version> | ||||
|     </parent> | ||||
|  | ||||
|     <properties> | ||||
|   | ||||
| @@ -176,7 +176,6 @@ public class NodeBrowserScript extends NodeBrowserPost implements Serializable | ||||
| 			{ | ||||
| 				status.setCode(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); | ||||
| 				status.setMessage(e.getMessage()); | ||||
| 				status.setException(e); | ||||
| 				status.setRedirect(true); | ||||
| 			} | ||||
|     		return tmplMap; | ||||
|   | ||||
| @@ -7,7 +7,7 @@ | ||||
|    <parent> | ||||
|       <groupId>org.alfresco</groupId> | ||||
|       <artifactId>alfresco-community-repo</artifactId> | ||||
|       <version>14.146-SNAPSHOT</version> | ||||
|       <version>15.13</version> | ||||
|    </parent> | ||||
|  | ||||
|    <dependencies> | ||||
|   | ||||
| @@ -7,7 +7,7 @@ | ||||
|     <parent> | ||||
|         <groupId>org.alfresco</groupId> | ||||
|         <artifactId>alfresco-community-repo</artifactId> | ||||
|         <version>14.146-SNAPSHOT</version> | ||||
|         <version>15.13</version> | ||||
|     </parent> | ||||
|  | ||||
|     <properties> | ||||
|   | ||||
| @@ -7,7 +7,7 @@ | ||||
|     <parent> | ||||
|         <groupId>org.alfresco</groupId> | ||||
|         <artifactId>alfresco-community-repo</artifactId> | ||||
|         <version>14.146-SNAPSHOT</version> | ||||
|         <version>15.13</version> | ||||
|     </parent> | ||||
|  | ||||
|     <dependencies> | ||||
|   | ||||
| @@ -9,6 +9,6 @@ | ||||
|     <parent> | ||||
|         <groupId>org.alfresco</groupId> | ||||
|         <artifactId>alfresco-community-repo-packaging</artifactId> | ||||
|         <version>14.146-SNAPSHOT</version> | ||||
|         <version>15.13</version> | ||||
|     </parent> | ||||
| </project> | ||||
|   | ||||
| @@ -7,7 +7,7 @@ | ||||
|     <parent> | ||||
|         <groupId>org.alfresco</groupId> | ||||
|         <artifactId>alfresco-community-repo-packaging</artifactId> | ||||
|         <version>14.146-SNAPSHOT</version> | ||||
|         <version>15.13</version> | ||||
|     </parent> | ||||
|  | ||||
|     <properties> | ||||
|   | ||||
| @@ -7,7 +7,7 @@ | ||||
|     <parent> | ||||
|         <groupId>org.alfresco</groupId> | ||||
|         <artifactId>alfresco-community-repo</artifactId> | ||||
|         <version>14.146-SNAPSHOT</version> | ||||
|         <version>15.13</version> | ||||
|     </parent> | ||||
|  | ||||
|     <modules> | ||||
|   | ||||
| @@ -6,7 +6,7 @@ | ||||
|     <parent> | ||||
|         <groupId>org.alfresco</groupId> | ||||
|         <artifactId>alfresco-community-repo-packaging</artifactId> | ||||
|         <version>14.146-SNAPSHOT</version> | ||||
|         <version>15.13</version> | ||||
|     </parent> | ||||
|  | ||||
|     <modules> | ||||
|   | ||||
| @@ -9,7 +9,7 @@ | ||||
|     <parent> | ||||
|         <groupId>org.alfresco</groupId> | ||||
|         <artifactId>alfresco-community-repo-tests</artifactId> | ||||
|         <version>14.146-SNAPSHOT</version> | ||||
|         <version>15.13</version> | ||||
|     </parent> | ||||
|  | ||||
|     <developers> | ||||
|   | ||||
| @@ -9,7 +9,7 @@ | ||||
|     <parent> | ||||
|         <groupId>org.alfresco</groupId> | ||||
|         <artifactId>alfresco-community-repo-tests</artifactId> | ||||
|         <version>14.146-SNAPSHOT</version> | ||||
|         <version>15.13</version> | ||||
|     </parent> | ||||
|  | ||||
|     <developers> | ||||
|   | ||||
| @@ -9,7 +9,7 @@ | ||||
|     <parent> | ||||
|         <groupId>org.alfresco</groupId> | ||||
|         <artifactId>alfresco-community-repo-tests</artifactId> | ||||
|         <version>14.146-SNAPSHOT</version> | ||||
|         <version>15.13</version> | ||||
|     </parent> | ||||
|  | ||||
|     <developers> | ||||
|   | ||||
| @@ -9,7 +9,7 @@ | ||||
|     <parent> | ||||
|         <groupId>org.alfresco</groupId> | ||||
|         <artifactId>alfresco-community-repo-tests</artifactId> | ||||
|         <version>14.146-SNAPSHOT</version> | ||||
|         <version>15.13</version> | ||||
|     </parent> | ||||
|  | ||||
|     <developers> | ||||
|   | ||||
| @@ -16,7 +16,7 @@ import org.testng.annotations.Test; | ||||
|  */ | ||||
| public class GetProcessSanityTests extends RestTest | ||||
| { | ||||
|     private UserModel userWhoStartsProcess, assignee; | ||||
|     private UserModel userWhoStartsProcess, assignee, user; | ||||
|     private RestProcessModel addedProcess, process; | ||||
|  | ||||
|     @BeforeClass(alwaysRun = true) | ||||
| @@ -24,6 +24,7 @@ public class GetProcessSanityTests extends RestTest | ||||
|     { | ||||
|         userWhoStartsProcess = dataUser.createRandomTestUser(); | ||||
|         assignee = dataUser.createRandomTestUser(); | ||||
|         user = dataUser.createRandomTestUser(); | ||||
|         addedProcess = restClient.authenticateUser(userWhoStartsProcess).withWorkflowAPI().addProcess("activitiAdhoc", assignee, false, CMISUtil.Priority.High); | ||||
|     } | ||||
|  | ||||
| @@ -59,4 +60,13 @@ public class GetProcessSanityTests extends RestTest | ||||
|         process.assertThat().field("id").is(addedProcess.getId()) | ||||
|                 .and().field("startUserId").is(addedProcess.getStartUserId()); | ||||
|     } | ||||
|  | ||||
|     @TestRail(section = { TestGroup.REST_API, TestGroup.PROCESSES }, executionType = ExecutionType.SANITY, | ||||
|             description = "Verify User that is not involved in a process cannot get that process using REST API and status code is FORBIDDEN (403)") | ||||
|     @Test(groups = { TestGroup.REST_API, TestGroup.WORKFLOW, TestGroup.PROCESSES, TestGroup.SANITY }) | ||||
|     public void shouldNotGetProcessesByNotInvolvedUser() throws Exception | ||||
|     { | ||||
|         process = restClient.authenticateUser(user).withWorkflowAPI().usingProcess(addedProcess).getProcess(); | ||||
|         restClient.assertStatusCodeIs(HttpStatus.FORBIDDEN); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -9,7 +9,7 @@ | ||||
|     <parent> | ||||
|         <groupId>org.alfresco</groupId> | ||||
|         <artifactId>alfresco-community-repo-tests</artifactId> | ||||
|         <version>14.146-SNAPSHOT</version> | ||||
|         <version>15.13</version> | ||||
|     </parent> | ||||
|  | ||||
|     <developers> | ||||
|   | ||||
| @@ -7,7 +7,7 @@ | ||||
|     <parent> | ||||
|         <groupId>org.alfresco</groupId> | ||||
|         <artifactId>alfresco-community-repo-packaging</artifactId> | ||||
|         <version>14.146-SNAPSHOT</version> | ||||
|         <version>15.13</version> | ||||
|     </parent> | ||||
|  | ||||
|     <properties> | ||||
|   | ||||
							
								
								
									
										18
									
								
								pom.xml
									
									
									
									
									
								
							
							
						
						
									
										18
									
								
								pom.xml
									
									
									
									
									
								
							| @@ -2,7 +2,7 @@ | ||||
| <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | ||||
|     <modelVersion>4.0.0</modelVersion> | ||||
|     <artifactId>alfresco-community-repo</artifactId> | ||||
|     <version>14.146-SNAPSHOT</version>  | ||||
|     <version>15.13</version> | ||||
|     <packaging>pom</packaging> | ||||
|     <name>Alfresco Community Repo Parent</name> | ||||
|  | ||||
| @@ -25,7 +25,7 @@ | ||||
|     <properties> | ||||
|         <acs.version.major>7</acs.version.major> | ||||
|         <acs.version.minor>2</acs.version.minor> | ||||
|         <acs.version.revision>0</acs.version.revision> | ||||
|         <acs.version.revision>1</acs.version.revision> | ||||
|         <acs.version.label /> | ||||
|         <amp.min.version>${acs.version.major}.0.0</amp.min.version> | ||||
|  | ||||
| @@ -55,19 +55,19 @@ | ||||
|         <dependency.alfresco-greenmail.version>6.2</dependency.alfresco-greenmail.version> | ||||
|         <dependency.acs-event-model.version>0.0.13</dependency.acs-event-model.version> | ||||
|  | ||||
|         <dependency.spring.version>5.3.15</dependency.spring.version> | ||||
|         <dependency.spring.version>5.3.18</dependency.spring.version> | ||||
|         <dependency.antlr.version>3.5.2</dependency.antlr.version> | ||||
|         <dependency.jackson.version>2.13.1</dependency.jackson.version> | ||||
|         <dependency.jackson-databind.version>2.13.1</dependency.jackson-databind.version> | ||||
|         <dependency.jackson.version>2.13.3</dependency.jackson.version> | ||||
|         <dependency.jackson-databind.version>2.13.3</dependency.jackson-databind.version> | ||||
|         <dependency.cxf.version>3.5.0</dependency.cxf.version> | ||||
|         <dependency.opencmis.version>1.0.0</dependency.opencmis.version> | ||||
|         <dependency.webscripts.version>8.28</dependency.webscripts.version> | ||||
|         <dependency.webscripts.version>8.31</dependency.webscripts.version> | ||||
|         <dependency.bouncycastle.version>1.70</dependency.bouncycastle.version> | ||||
|         <dependency.mockito-core.version>3.11.2</dependency.mockito-core.version> | ||||
|         <dependency.org-json.version>20211205</dependency.org-json.version> | ||||
|         <dependency.commons-dbcp.version>2.9.0</dependency.commons-dbcp.version> | ||||
|         <dependency.commons-io.version>2.11.0</dependency.commons-io.version> | ||||
|         <dependency.gson.version>2.8.5</dependency.gson.version> | ||||
|         <dependency.gson.version>2.8.9</dependency.gson.version> | ||||
|         <dependency.httpclient.version>4.5.13</dependency.httpclient.version> | ||||
|         <dependency.httpcore.version>4.4.15</dependency.httpcore.version> | ||||
|         <dependency.commons-httpclient.version>3.1-HTTPCLIENT-1265</dependency.commons-httpclient.version> | ||||
| @@ -107,7 +107,7 @@ | ||||
|  | ||||
|         <alfresco.googledrive.version>3.2.1.3</alfresco.googledrive.version> | ||||
|         <alfresco.aos-module.version>1.4.1</alfresco.aos-module.version>  | ||||
|         <alfresco.api-explorer.version>7.2.0</alfresco.api-explorer.version> <!-- Also in alfresco-enterprise-share --> | ||||
|         <alfresco.api-explorer.version>7.2.1</alfresco.api-explorer.version> <!-- Also in alfresco-enterprise-share --> | ||||
|  | ||||
|         <alfresco.maven-plugin.version>2.2.0</alfresco.maven-plugin.version> | ||||
|         <license-maven-plugin.version>2.0.1.alfresco-2</license-maven-plugin.version> | ||||
| @@ -146,7 +146,7 @@ | ||||
|         <connection>scm:git:https://github.com/Alfresco/alfresco-community-repo.git</connection> | ||||
|         <developerConnection>scm:git:https://github.com/Alfresco/alfresco-community-repo.git</developerConnection> | ||||
|         <url>https://github.com/Alfresco/alfresco-community-repo</url> | ||||
|         <tag>HEAD</tag> | ||||
|         <tag>15.13</tag> | ||||
|     </scm> | ||||
|  | ||||
|     <distributionManagement> | ||||
|   | ||||
| @@ -7,7 +7,7 @@ | ||||
|     <parent> | ||||
|         <groupId>org.alfresco</groupId> | ||||
|         <artifactId>alfresco-community-repo</artifactId> | ||||
|         <version>14.146-SNAPSHOT</version> | ||||
|         <version>15.13</version> | ||||
|     </parent> | ||||
|  | ||||
|     <dependencies> | ||||
|   | ||||
| @@ -511,7 +511,9 @@ public class ProcessesImpl extends WorkflowRestImpl implements Processes | ||||
|         { | ||||
|             throw new InvalidArgumentException("processId is required to get the process info"); | ||||
|         } | ||||
|          | ||||
|  | ||||
|         validateIfUserAllowedToWorkWithProcess(processId); | ||||
|  | ||||
|         HistoricProcessInstance processInstance = activitiProcessEngine | ||||
|                 .getHistoryService() | ||||
|                 .createHistoricProcessInstanceQuery() | ||||
|   | ||||
| @@ -3,6 +3,7 @@ function main() | ||||
|  | ||||
| // Get the args | ||||
| var filter = args["filter"]; | ||||
| if (filter!==null && !filter.includes(":")) {filter += " [hint:useCQ]";} | ||||
| var maxResults = args["maxResults"]; | ||||
| var skipCountStr = args["skipCount"]; | ||||
| var skipCount = skipCountStr != null ? parseInt(skipCountStr) : -1; | ||||
|   | ||||
| @@ -3,7 +3,7 @@ function main() | ||||
|    // Get the args | ||||
|    var siteShortName = url.templateArgs.shortname, | ||||
|       site = siteService.getSite(siteShortName), | ||||
|       filter = (args.filter != null) ? args.filter : (args.shortNameFilter != null) ? args.shortNameFilter : "", | ||||
|       filter = ((args.filter != null) ? args.filter : (args.shortNameFilter != null) ? args.shortNameFilter : "" )+ " [hint:useCQ]", | ||||
|       maxResults = (args.maxResults == null) ? 10 : parseInt(args.maxResults, 10), | ||||
|       authorityType = args.authorityType, | ||||
|       zone = args.zone, | ||||
|   | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -7,7 +7,7 @@ | ||||
|     <parent> | ||||
|         <groupId>org.alfresco</groupId> | ||||
|         <artifactId>alfresco-community-repo</artifactId> | ||||
|         <version>14.146-SNAPSHOT</version> | ||||
|         <version>15.13</version> | ||||
|     </parent> | ||||
|  | ||||
|     <dependencies> | ||||
| @@ -236,7 +236,7 @@ | ||||
|         <dependency> | ||||
|             <groupId>org.freemarker</groupId> | ||||
|             <artifactId>freemarker</artifactId> | ||||
|             <version>2.3.20-alfresco-patched-20200421</version> | ||||
|             <version>2.3.20-alfresco-patched-20220413</version> | ||||
|         </dependency> | ||||
|         <dependency> | ||||
|             <groupId>org.apache.xmlbeans</groupId> | ||||
|   | ||||
| @@ -220,6 +220,7 @@ public class DeleteNotExistsExecutor implements StatementExecutor | ||||
|                 { | ||||
|                     // Process batch | ||||
|                     primaryId = processPrimaryTableResultSet(primaryPrepStmt, secondaryPrepStmts, deletePrepStmt, deleteIds, primaryTableName, primaryColumnName, tableColumn); | ||||
|                     connection.commit(); | ||||
|  | ||||
|                     if (primaryId == null) | ||||
|                     { | ||||
| @@ -298,7 +299,6 @@ public class DeleteNotExistsExecutor implements StatementExecutor | ||||
|                     if (deleteIds.size() == deleteBatchSize) | ||||
|                     { | ||||
|                         deleteFromPrimaryTable(deletePrepStmt, deleteIds, primaryTableName); | ||||
|                         connection.commit(); | ||||
|                     } | ||||
|  | ||||
|                     if (!resultSet.next()) | ||||
|   | ||||
| @@ -117,6 +117,7 @@ public class MySQLDeleteNotExistsExecutor extends DeleteNotExistsExecutor | ||||
|                 { | ||||
|                     // Process batch | ||||
|                     primaryId = processPrimaryTableResultSet(primaryPrepStmt, secondaryPrepStmts, deletePrepStmt, deleteIds, primaryTableName, primaryColumnName, tableColumn); | ||||
|                     connection.commit(); | ||||
|  | ||||
|                     if (primaryId == null) | ||||
|                     { | ||||
|   | ||||
| @@ -0,0 +1,201 @@ | ||||
| /* | ||||
|  * #%L | ||||
|  * Alfresco Repository | ||||
|  * %% | ||||
|  * Copyright (C) 2005 - 2022 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 <http://www.gnu.org/licenses/>. | ||||
|  * #L% | ||||
|  */ | ||||
| package org.alfresco.repo.jscript; | ||||
|  | ||||
| import org.apache.commons.logging.Log; | ||||
| import org.apache.commons.logging.LogFactory; | ||||
| import org.mozilla.javascript.Callable; | ||||
| import org.mozilla.javascript.Context; | ||||
| import org.mozilla.javascript.ContextFactory; | ||||
| import org.mozilla.javascript.Scriptable; | ||||
|  | ||||
| /** | ||||
|  * Custom factory that allows to apply configured limits during script executions | ||||
|  *  | ||||
|  * @see ContextFactory | ||||
|  */ | ||||
| public class AlfrescoContextFactory extends ContextFactory | ||||
| { | ||||
|     private static final Log LOGGER = LogFactory.getLog(AlfrescoContextFactory.class); | ||||
|  | ||||
|     private int optimizationLevel = -1; | ||||
|     private int maxScriptExecutionSeconds = -1; | ||||
|     private int maxStackDepth = -1; | ||||
|     private long maxMemoryUsedInBytes = -1L; | ||||
|     private int observeInstructionCount = -1; | ||||
|  | ||||
|     private AlfrescoScriptThreadMxBeanWrapper threadMxBeanWrapper; | ||||
|  | ||||
|     private final int INTERPRETIVE_MODE = -1; | ||||
|  | ||||
|     @Override | ||||
|     protected Context makeContext() | ||||
|     { | ||||
|         AlfrescoScriptContext context = new AlfrescoScriptContext(); | ||||
|  | ||||
|         context.setOptimizationLevel(optimizationLevel); | ||||
|  | ||||
|         // Needed for both time and memory measurement | ||||
|         if (maxScriptExecutionSeconds > 0 || maxMemoryUsedInBytes > 0L) | ||||
|         { | ||||
|             if (observeInstructionCount > 0) | ||||
|             { | ||||
|                 LOGGER.info("Enabling observer count..."); | ||||
|                 context.setGenerateObserverCount(true); | ||||
|                 context.setInstructionObserverThreshold(observeInstructionCount); | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 LOGGER.info("Disabling observer count..."); | ||||
|                 context.setGenerateObserverCount(false); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         // Memory limit | ||||
|         if (maxMemoryUsedInBytes > 0) | ||||
|         { | ||||
|             context.setThreadId(Thread.currentThread().getId()); | ||||
|         } | ||||
|  | ||||
|         // Max stack depth | ||||
|         if (maxStackDepth > 0) | ||||
|         { | ||||
|             if (optimizationLevel != INTERPRETIVE_MODE) | ||||
|             { | ||||
|                 LOGGER.warn("Changing optimization level from " + optimizationLevel + " to " + INTERPRETIVE_MODE); | ||||
|             } | ||||
|             // stack depth can only be set when no optimizations are applied | ||||
|             context.setOptimizationLevel(INTERPRETIVE_MODE); | ||||
|             context.setMaximumInterpreterStackDepth(maxStackDepth); | ||||
|         } | ||||
|  | ||||
|         return context; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     protected void observeInstructionCount(Context cx, int instructionCount) | ||||
|     { | ||||
|         AlfrescoScriptContext acx = (AlfrescoScriptContext) cx; | ||||
|  | ||||
|         if (acx.isLimitsEnabled()) | ||||
|         { | ||||
|             // Time limit | ||||
|             if (maxScriptExecutionSeconds > 0) | ||||
|             { | ||||
|                 long currentTime = System.currentTimeMillis(); | ||||
|                 if (currentTime - acx.getStartTime() > maxScriptExecutionSeconds * 1000) | ||||
|                 { | ||||
|                     throw new Error("Maximum script time of " + maxScriptExecutionSeconds + " seconds exceeded"); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             // Memory | ||||
|             if (maxMemoryUsedInBytes > 0 && threadMxBeanWrapper != null && threadMxBeanWrapper.isThreadAllocatedMemorySupported()) | ||||
|             { | ||||
|  | ||||
|                 if (acx.getStartMemory() <= 0) | ||||
|                 { | ||||
|                     acx.setStartMemory(threadMxBeanWrapper.getThreadAllocatedBytes(acx.getThreadId())); | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     long currentAllocatedBytes = threadMxBeanWrapper.getThreadAllocatedBytes(acx.getThreadId()); | ||||
|                     if (currentAllocatedBytes - acx.getStartMemory() >= maxMemoryUsedInBytes) | ||||
|                     { | ||||
|                         throw new Error("Memory limit of " + maxMemoryUsedInBytes + " bytes reached"); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     protected Object doTopCall(Callable callable, Context cx, Scriptable scope, Scriptable thisObj, Object[] args) | ||||
|     { | ||||
|         AlfrescoScriptContext acx = (AlfrescoScriptContext) cx; | ||||
|         acx.setStartTime(System.currentTimeMillis()); | ||||
|         return super.doTopCall(callable, cx, scope, thisObj, args); | ||||
|     } | ||||
|  | ||||
|     public int getOptimizationLevel() | ||||
|     { | ||||
|         return optimizationLevel; | ||||
|     } | ||||
|  | ||||
|     public void setOptimizationLevel(int optimizationLevel) | ||||
|     { | ||||
|         this.optimizationLevel = optimizationLevel; | ||||
|     } | ||||
|  | ||||
|     public int getMaxScriptExecutionSeconds() | ||||
|     { | ||||
|         return maxScriptExecutionSeconds; | ||||
|     } | ||||
|  | ||||
|     public void setMaxScriptExecutionSeconds(int maxScriptExecutionSeconds) | ||||
|     { | ||||
|         this.maxScriptExecutionSeconds = maxScriptExecutionSeconds; | ||||
|     } | ||||
|  | ||||
|     public int getMaxStackDepth() | ||||
|     { | ||||
|         return maxStackDepth; | ||||
|     } | ||||
|  | ||||
|     public void setMaxStackDepth(int maxStackDepth) | ||||
|     { | ||||
|         this.maxStackDepth = maxStackDepth; | ||||
|     } | ||||
|  | ||||
|     public long getMaxMemoryUsedInBytes() | ||||
|     { | ||||
|         return maxMemoryUsedInBytes; | ||||
|     } | ||||
|  | ||||
|     public void setMaxMemoryUsedInBytes(long maxMemoryUsedInBytes) | ||||
|     { | ||||
|         this.maxMemoryUsedInBytes = maxMemoryUsedInBytes; | ||||
|         if (maxMemoryUsedInBytes > 0) | ||||
|         { | ||||
|             this.threadMxBeanWrapper = new AlfrescoScriptThreadMxBeanWrapper(); | ||||
|             if (!threadMxBeanWrapper.isThreadAllocatedMemorySupported()) | ||||
|             { | ||||
|                 LOGGER.warn("com.sun.management.ThreadMXBean was not found on the classpath. " | ||||
|                         + "This means that the limiting the memory usage for a script will NOT work."); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public int getObserveInstructionCount() | ||||
|     { | ||||
|         return observeInstructionCount; | ||||
|     } | ||||
|  | ||||
|     public void setObserveInstructionCount(int observeInstructionCount) | ||||
|     { | ||||
|         this.observeInstructionCount = observeInstructionCount; | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,81 @@ | ||||
| /* | ||||
|  * #%L | ||||
|  * Alfresco Repository | ||||
|  * %% | ||||
|  * Copyright (C) 2005 - 2022 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 <http://www.gnu.org/licenses/>. | ||||
|  * #L% | ||||
|  */ | ||||
| package org.alfresco.repo.jscript; | ||||
|  | ||||
| import org.mozilla.javascript.Context; | ||||
|  | ||||
| /** | ||||
|  * Custom Rhino context that holds data as start time and memory | ||||
|  *  | ||||
|  * @see Context | ||||
|  */ | ||||
| public class AlfrescoScriptContext extends Context | ||||
| { | ||||
|     private long startTime; | ||||
|     private long threadId; | ||||
|     private long startMemory; | ||||
|     private boolean limitsEnabled = false; | ||||
|  | ||||
|     public long getStartTime() | ||||
|     { | ||||
|         return startTime; | ||||
|     } | ||||
|  | ||||
|     public void setStartTime(long startTime) | ||||
|     { | ||||
|         this.startTime = startTime; | ||||
|     } | ||||
|  | ||||
|     public long getThreadId() | ||||
|     { | ||||
|         return threadId; | ||||
|     } | ||||
|  | ||||
|     public void setThreadId(long threadId) | ||||
|     { | ||||
|         this.threadId = threadId; | ||||
|     } | ||||
|  | ||||
|     public long getStartMemory() | ||||
|     { | ||||
|         return startMemory; | ||||
|     } | ||||
|  | ||||
|     public void setStartMemory(long startMemory) | ||||
|     { | ||||
|         this.startMemory = startMemory; | ||||
|     } | ||||
|  | ||||
|     public boolean isLimitsEnabled() | ||||
|     { | ||||
|         return limitsEnabled; | ||||
|     } | ||||
|  | ||||
|     public void setLimitsEnabled(boolean limitsEnabled) | ||||
|     { | ||||
|         this.limitsEnabled = limitsEnabled; | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,78 @@ | ||||
| /* | ||||
|  * #%L | ||||
|  * Alfresco Repository | ||||
|  * %% | ||||
|  * Copyright (C) 2005 - 2022 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 <http://www.gnu.org/licenses/>. | ||||
|  * #L% | ||||
|  */ | ||||
| package org.alfresco.repo.jscript; | ||||
|  | ||||
| import java.lang.management.ManagementFactory; | ||||
| import java.lang.management.ThreadMXBean; | ||||
|  | ||||
| /** | ||||
|  * Allows to monitor memory usage | ||||
|  */ | ||||
| public class AlfrescoScriptThreadMxBeanWrapper | ||||
| { | ||||
|  | ||||
|     private ThreadMXBean threadMXBean = null; | ||||
|     private boolean threadAllocatedMemorySupported = false; | ||||
|  | ||||
|     private final String THREAD_MX_BEAN_SUN = "com.sun.management.ThreadMXBean"; | ||||
|  | ||||
|     public AlfrescoScriptThreadMxBeanWrapper() | ||||
|     { | ||||
|         checkThreadAllocatedMemory(); | ||||
|     } | ||||
|  | ||||
|     public long getThreadAllocatedBytes(long threadId) | ||||
|     { | ||||
|         if (threadMXBean != null && threadAllocatedMemorySupported) | ||||
|         { | ||||
|             return ((com.sun.management.ThreadMXBean) threadMXBean).getThreadAllocatedBytes(threadId); | ||||
|         } | ||||
|  | ||||
|         return -1; | ||||
|     } | ||||
|  | ||||
|     public void checkThreadAllocatedMemory() | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             Class<?> clazz = Class.forName(THREAD_MX_BEAN_SUN); | ||||
|             if (clazz != null) | ||||
|             { | ||||
|                 this.threadAllocatedMemorySupported = true; | ||||
|                 this.threadMXBean = (com.sun.management.ThreadMXBean) ManagementFactory.getThreadMXBean(); | ||||
|             } | ||||
|         } | ||||
|         catch (Exception e) | ||||
|         { | ||||
|             this.threadAllocatedMemorySupported = false; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public boolean isThreadAllocatedMemorySupported() | ||||
|     { | ||||
|         return threadAllocatedMemorySupported; | ||||
|     } | ||||
| } | ||||
| @@ -57,10 +57,12 @@ import org.alfresco.service.namespace.QName; | ||||
| import org.apache.commons.logging.Log; | ||||
| import org.apache.commons.logging.LogFactory; | ||||
| import org.mozilla.javascript.Context; | ||||
| import org.mozilla.javascript.ContextFactory; | ||||
| import org.mozilla.javascript.ImporterTopLevel; | ||||
| import org.mozilla.javascript.Script; | ||||
| import org.mozilla.javascript.Scriptable; | ||||
| import org.mozilla.javascript.ScriptableObject; | ||||
| import org.mozilla.javascript.Undefined; | ||||
| import org.mozilla.javascript.WrapFactory; | ||||
| import org.mozilla.javascript.WrappedException; | ||||
| import org.springframework.beans.factory.InitializingBean; | ||||
| @@ -108,6 +110,23 @@ public class RhinoScriptProcessor extends BaseProcessor implements ScriptProcess | ||||
|     /** Cache of runtime compiled script instances */ | ||||
|     private final Map<String, Script> scriptCache = new ConcurrentHashMap<String, Script>(256); | ||||
|      | ||||
|     /** Rhino optimization level */ | ||||
|     private int optimizationLevel = -1; | ||||
|  | ||||
|     /** Maximum seconds a script is allowed to run */ | ||||
|     private int maxScriptExecutionSeconds = -1; | ||||
|  | ||||
|     /** Maximum of call stack depth (in terms of number of call frames) */ | ||||
|     private int maxStackDepth = -1; | ||||
|  | ||||
|     /** Maximum memory (bytes) a script can use */ | ||||
|     private long maxMemoryUsedInBytes = -1L; | ||||
|  | ||||
|     /** Number of (bytecode) instructions that will trigger the observer */ | ||||
|     private int observerInstructionCount = 100; | ||||
|  | ||||
|     /** Custom context factory */ | ||||
|     public static AlfrescoContextFactory contextFactory; | ||||
|      | ||||
|     /** | ||||
|      * Set the default store reference | ||||
| @@ -144,6 +163,51 @@ public class RhinoScriptProcessor extends BaseProcessor implements ScriptProcess | ||||
|         this.shareSealedScopes = shareSealedScopes; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @param optimizationLevel | ||||
|      *            -1 interpretive mode, 0 no optimizations, 1-9 optimizations performed | ||||
|      */ | ||||
|     public void setOptimizationLevel(int optimizationLevel) | ||||
|     { | ||||
|         this.optimizationLevel = optimizationLevel; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @param maxScriptExecutionSeconds | ||||
|      *            the number of seconds a script is allowed to run | ||||
|      */ | ||||
|     public void setMaxScriptExecutionSeconds(int maxScriptExecutionSeconds) | ||||
|     { | ||||
|         this.maxScriptExecutionSeconds = maxScriptExecutionSeconds; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @param maxStackDepth | ||||
|      *            the number of call stack depth allowed | ||||
|      */ | ||||
|     public void setMaxStackDepth(int maxStackDepth) | ||||
|     { | ||||
|         this.maxStackDepth = maxStackDepth; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @param maxMemoryUsedInBytes | ||||
|      *            the number of memory a script can use | ||||
|      */ | ||||
|     public void setMaxMemoryUsedInBytes(long maxMemoryUsedInBytes) | ||||
|     { | ||||
|         this.maxMemoryUsedInBytes = maxMemoryUsedInBytes; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @param observerInstructionCount | ||||
|      *            the number of instructions that will trigger {@link ContextFactory#observeInstructionCount} | ||||
|      */ | ||||
|     public void setObserverInstructionCount(int observerInstructionCount) | ||||
|     { | ||||
|         this.observerInstructionCount = observerInstructionCount; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @see org.alfresco.service.cmr.repository.ScriptProcessor#reset() | ||||
|      */ | ||||
| @@ -441,6 +505,8 @@ public class RhinoScriptProcessor extends BaseProcessor implements ScriptProcess | ||||
|     private Object executeScriptImpl(Script script, Map<String, Object> model, boolean secure, String debugScriptName) | ||||
|         throws AlfrescoRuntimeException | ||||
|     { | ||||
|         Scriptable scope = null; | ||||
|  | ||||
|         long startTime = 0; | ||||
|         if (callLogger.isDebugEnabled()) | ||||
|         { | ||||
| @@ -457,14 +523,16 @@ public class RhinoScriptProcessor extends BaseProcessor implements ScriptProcess | ||||
|             // Create a thread-specific scope from one of the shared scopes. | ||||
|             // See http://www.mozilla.org/rhino/scopes.html | ||||
|             cx.setWrapFactory(secure ? wrapFactory : sandboxFactory); | ||||
|             Scriptable scope; | ||||
|  | ||||
|             // Enables or disables execution limits based on secure flag | ||||
|             enableLimits(cx, secure); | ||||
|  | ||||
|             if (this.shareSealedScopes) | ||||
|             { | ||||
|                 Scriptable sharedScope = secure ? this.nonSecureScope : this.secureScope; | ||||
|                 scope = cx.newObject(sharedScope); | ||||
|                 scope.setPrototype(sharedScope); | ||||
|                 scope.setParentScope(null); | ||||
|  | ||||
|             } | ||||
|             else | ||||
|             { | ||||
| @@ -538,6 +606,7 @@ public class RhinoScriptProcessor extends BaseProcessor implements ScriptProcess | ||||
|         } | ||||
|         finally | ||||
|         { | ||||
|             unsetScope(model, scope); | ||||
|             Context.exit(); | ||||
|              | ||||
|             if (callLogger.isDebugEnabled()) | ||||
| @@ -630,6 +699,9 @@ public class RhinoScriptProcessor extends BaseProcessor implements ScriptProcess | ||||
|      */ | ||||
|     public void afterPropertiesSet() throws Exception | ||||
|     { | ||||
|         // Initialize context factory | ||||
|         initContextFactory(); | ||||
|  | ||||
|         // Initialize the secure scope | ||||
|         Context cx = Context.enter(); | ||||
|         try | ||||
| @@ -687,4 +759,129 @@ public class RhinoScriptProcessor extends BaseProcessor implements ScriptProcess | ||||
|         } | ||||
|         return scope; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Clean supplied scope and unset it from any model instance where it has been injected before | ||||
|      * | ||||
|      * @param model | ||||
|      *            Data model containing objects from where scope will be unset | ||||
|      * @param scope | ||||
|      *            The scope to clean | ||||
|      */ | ||||
|     private void unsetScope(Map<String, Object> model, Scriptable scope) | ||||
|     { | ||||
|         if (scope != null) | ||||
|         { | ||||
|             Object[] ids = scope.getIds(); | ||||
|             if (ids != null) | ||||
|             { | ||||
|                 for (Object id : ids) | ||||
|                 { | ||||
|                     try | ||||
|                     { | ||||
|                         deleteProperty(scope, id.toString()); | ||||
|                     } | ||||
|                     catch (Exception e) | ||||
|                     { | ||||
|                         logger.info("Unable to delete id: " + id, e); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if (model != null) | ||||
|         { | ||||
|             for (String key : model.keySet()) | ||||
|             { | ||||
|                 try | ||||
|                 { | ||||
|                     deleteProperty(scope, key); | ||||
|  | ||||
|                     Object obj = model.get(key); | ||||
|                     if (obj instanceof Scopeable) | ||||
|                     { | ||||
|                         ((Scopeable) obj).setScope(null); | ||||
|                     } | ||||
|                 } | ||||
|                 catch (Exception e) | ||||
|                 { | ||||
|                     logger.info("Unable to unset model object " + key + " : ", e); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Deletes a property from the supplied scope, if property is not removable, then is set to null | ||||
|      * | ||||
|      * @param scope | ||||
|      *            the scope object from where property will be removed | ||||
|      * @param name | ||||
|      *            the property name to delete | ||||
|      */ | ||||
|     private void deleteProperty(Scriptable scope, String name) | ||||
|     { | ||||
|         if (scope != null && name != null) | ||||
|         { | ||||
|             if (!ScriptableObject.deleteProperty(scope, name)) | ||||
|             { | ||||
|                 ScriptableObject.putProperty(scope, name, null); | ||||
|             } | ||||
|             scope.delete(name); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Initializes the context factory with limits configuration | ||||
|      */ | ||||
|     private synchronized void initContextFactory() | ||||
|     { | ||||
|         if (contextFactory == null) | ||||
|         { | ||||
|             contextFactory = new AlfrescoContextFactory(); | ||||
|             contextFactory.setOptimizationLevel(optimizationLevel); | ||||
|  | ||||
|             if (maxScriptExecutionSeconds > 0) | ||||
|             { | ||||
|                 contextFactory.setMaxScriptExecutionSeconds(maxScriptExecutionSeconds); | ||||
|             } | ||||
|  | ||||
|             if (maxMemoryUsedInBytes > 0L) | ||||
|             { | ||||
|                 contextFactory.setMaxMemoryUsedInBytes(maxMemoryUsedInBytes); | ||||
|             } | ||||
|  | ||||
|             if (maxStackDepth > 0) | ||||
|             { | ||||
|                 contextFactory.setMaxStackDepth(maxStackDepth); | ||||
|             } | ||||
|  | ||||
|             if (maxScriptExecutionSeconds > 0 || maxMemoryUsedInBytes > 0L) | ||||
|             { | ||||
|                 contextFactory.setObserveInstructionCount(observerInstructionCount); | ||||
|             } | ||||
|  | ||||
|             ContextFactory.initGlobal(contextFactory); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * If script is considered secure no limits will be applied, otherwise, the limits are enabled and the script can be | ||||
|      * interrupted in case a limit has been reached. | ||||
|      * | ||||
|      * @param cx | ||||
|      *            the Rhino scope | ||||
|      * @param secure | ||||
|      *            true if script execution is considered secure (e.g, deployed at classpath level) | ||||
|      */ | ||||
|     private void enableLimits(Context cx, boolean secure) | ||||
|     { | ||||
|         if (cx != null) | ||||
|         { | ||||
|             if (cx instanceof AlfrescoScriptContext) | ||||
|             { | ||||
|                 ((AlfrescoScriptContext) cx).setLimitsEnabled(!secure); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -3,7 +3,7 @@ | ||||
| repository.name=Main Repository | ||||
|  | ||||
| # Schema number | ||||
| version.schema=16000 | ||||
| version.schema=16100 | ||||
|  | ||||
| # Directory configuration | ||||
|  | ||||
| @@ -1336,3 +1336,18 @@ allow.unsecure.callback.jsonp=false | ||||
|  | ||||
| # pre-configured allow list of media/mime types to allow inline instead of attachment (via Content-Disposition response header) | ||||
| content.nonAttach.mimetypes=application/pdf,image/jpeg,image/gif,image/png,image/tiff,image/bmp | ||||
|  | ||||
| # Rhino optimization level | ||||
| scripts.execution.optimizationLevel=0 | ||||
|  | ||||
| # Max seconds a script is allowed to run | ||||
| scripts.execution.maxScriptExecutionSeconds=-1 | ||||
|  | ||||
| # Max call stack depth | ||||
| scripts.execution.maxStackDepth=-1 | ||||
|  | ||||
| # Max memory (bytes) a script can use | ||||
| scripts.execution.maxMemoryUsedInBytes=-1 | ||||
|  | ||||
| # Number of instructions that will trigger the observer | ||||
| scripts.execution.observerInstructionCount=-1 | ||||
| @@ -45,6 +45,21 @@ | ||||
|         <property name="storePath"> | ||||
|             <value>${spaces.company_home.childname}</value> | ||||
|         </property> | ||||
|         <property name="optimizationLevel"> | ||||
|             <value>${scripts.execution.optimizationLevel}</value> | ||||
|         </property> | ||||
|         <property name="maxScriptExecutionSeconds"> | ||||
|             <value>${scripts.execution.maxScriptExecutionSeconds}</value> | ||||
|         </property> | ||||
|         <property name="maxStackDepth"> | ||||
|             <value>${scripts.execution.maxStackDepth}</value> | ||||
|         </property> | ||||
|         <property name="maxMemoryUsedInBytes"> | ||||
|             <value>${scripts.execution.maxMemoryUsedInBytes}</value> | ||||
|         </property> | ||||
|         <property name="observerInstructionCount"> | ||||
|             <value>${scripts.execution.observerInstructionCount}</value> | ||||
|         </property> | ||||
|     </bean> | ||||
|  | ||||
|     <!-- base config implementation that script extension beans extend from - for auto registration | ||||
|   | ||||
| @@ -1,48 +1,48 @@ | ||||
| /* | ||||
|  * #%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 <http://www.gnu.org/licenses/>. | ||||
|  * #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 <http://www.gnu.org/licenses/>. | ||||
|  * #L% | ||||
|  */ | ||||
| package org.alfresco.repo.jscript; | ||||
|  | ||||
| import static org.junit.Assert.fail; | ||||
|  | ||||
| import java.io.IOException; | ||||
| import static org.junit.Assert.fail; | ||||
|  | ||||
| import java.io.IOException; | ||||
| import java.io.InputStream; | ||||
| import java.util.HashMap; | ||||
| import java.util.List; | ||||
| import java.util.Map; | ||||
|  | ||||
| import junit.framework.TestCase; | ||||
|  | ||||
| import org.alfresco.error.AlfrescoRuntimeException; | ||||
| import junit.framework.TestCase; | ||||
|  | ||||
| import org.alfresco.error.AlfrescoRuntimeException; | ||||
| import org.alfresco.model.ContentModel; | ||||
| import org.alfresco.repo.dictionary.DictionaryComponent; | ||||
| import org.alfresco.repo.dictionary.DictionaryDAO; | ||||
| import org.alfresco.repo.dictionary.M2Model; | ||||
| import org.alfresco.repo.node.BaseNodeServiceTest; | ||||
| import org.alfresco.repo.security.authentication.AuthenticationComponent; | ||||
| import org.alfresco.repo.security.permissions.AccessDeniedException; | ||||
| import org.alfresco.repo.node.BaseNodeServiceTest; | ||||
| import org.alfresco.repo.security.authentication.AuthenticationComponent; | ||||
| import org.alfresco.repo.security.permissions.AccessDeniedException; | ||||
| import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; | ||||
| import org.alfresco.service.ServiceRegistry; | ||||
| import org.alfresco.service.cmr.repository.ChildAssociationRef; | ||||
| @@ -54,13 +54,16 @@ import org.alfresco.service.cmr.repository.ScriptService; | ||||
| import org.alfresco.service.cmr.repository.StoreRef; | ||||
| import org.alfresco.service.namespace.QName; | ||||
| import org.alfresco.service.transaction.TransactionService; | ||||
| import org.alfresco.test_category.OwnJVMTestsCategory; | ||||
| import org.alfresco.util.ApplicationContextHelper; | ||||
| import org.alfresco.test_category.OwnJVMTestsCategory; | ||||
| import org.alfresco.util.ApplicationContextHelper; | ||||
| import org.junit.experimental.categories.Category; | ||||
| import org.mozilla.javascript.Context; | ||||
| import org.mozilla.javascript.ImporterTopLevel; | ||||
| import org.mozilla.javascript.Scriptable; | ||||
| import org.mozilla.javascript.ScriptableObject; | ||||
| import org.springframework.context.ApplicationContext; | ||||
| import org.mozilla.javascript.Undefined; | ||||
| import org.mozilla.javascript.UniqueTag; | ||||
| import org.springframework.context.ApplicationContext; | ||||
|  | ||||
|  | ||||
| /** | ||||
| @@ -364,77 +367,173 @@ public class RhinoScriptTest extends TestCase | ||||
|                     return null; | ||||
|                 }                 | ||||
|             }); | ||||
|     } | ||||
|      | ||||
|     // MNT-21009 | ||||
|     public void testUnsecureScriptAddedOnRepoNode() | ||||
|     { | ||||
|  | ||||
|         transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback<Object>() | ||||
|         { | ||||
|             public Object execute() throws Exception | ||||
|             { | ||||
|                 StoreRef store = nodeService.createStore(StoreRef.PROTOCOL_WORKSPACE, "rhino_" + System.currentTimeMillis()); | ||||
|                 NodeRef root = nodeService.getRootNode(store); | ||||
|                 BaseNodeServiceTest.buildNodeGraph(nodeService, root); | ||||
|  | ||||
|                 try | ||||
|                 { | ||||
|                     Map<String, Object> model = new HashMap<String, Object>(); | ||||
|                     model.put("out", System.out); | ||||
|  | ||||
|                     // create an Alfresco scriptable Node object | ||||
|                     // the Node object is a wrapper similar to the TemplateNode | ||||
|                     // concept | ||||
|                     ScriptNode rootNode = new ScriptNode(root, serviceRegistry, null); | ||||
|                     model.put("root", rootNode); | ||||
|  | ||||
|                     // test executing a script embedded inside Node content | ||||
|                     ChildAssociationRef childRef = nodeService.createNode(root, BaseNodeServiceTest.ASSOC_TYPE_QNAME_TEST_CHILDREN, | ||||
|                             QName.createQName(BaseNodeServiceTest.NAMESPACE, "script_content"), BaseNodeServiceTest.TYPE_QNAME_TEST_CONTENT, null); | ||||
|                     NodeRef contentNodeRef = childRef.getChildRef(); | ||||
|                     ContentWriter writer = contentService.getWriter(contentNodeRef, BaseNodeServiceTest.PROP_QNAME_TEST_CONTENT, true); | ||||
|                     writer.setMimetype("application/x-javascript"); | ||||
|                     writer.putContent(BASIC_JAVA); | ||||
|  | ||||
|                     try | ||||
|                     { | ||||
|                         scriptService.executeScript(contentNodeRef, BaseNodeServiceTest.PROP_QNAME_TEST_CONTENT, model); | ||||
|                         fail("execution of nonsecure script on nodeRef is not allowed."); | ||||
|                     } | ||||
|                     catch (AlfrescoRuntimeException ex) | ||||
|                     { | ||||
|                         // expected | ||||
|                     } | ||||
|                      | ||||
|                      | ||||
|                     ChildAssociationRef childRef1 = nodeService.createNode(root, BaseNodeServiceTest.ASSOC_TYPE_QNAME_TEST_CHILDREN, | ||||
|                             QName.createQName(BaseNodeServiceTest.NAMESPACE, "script_content"), BaseNodeServiceTest.TYPE_QNAME_TEST_CONTENT, null); | ||||
|                     NodeRef contentNodeRef1 = childRef1.getChildRef(); | ||||
|                     ContentWriter writer1 = contentService.getWriter(contentNodeRef1, BaseNodeServiceTest.PROP_QNAME_TEST_CONTENT, true); | ||||
|                     writer1.setMimetype("application/x-javascript"); | ||||
|                     writer1.putContent(REFLECTION_GET_CLASS); | ||||
|                      | ||||
|                     try | ||||
|                     { | ||||
|                         scriptService.executeScript(contentNodeRef1, BaseNodeServiceTest.PROP_QNAME_TEST_CONTENT, model); | ||||
|                         fail("execution of nonsecure script on nodeRef is not allowed."); | ||||
|                     } | ||||
|                     catch (AlfrescoRuntimeException ex) | ||||
|                     { | ||||
|                         // expected | ||||
|                     } | ||||
|                 } | ||||
|                 finally | ||||
|                 { | ||||
|                 } | ||||
|  | ||||
|                 return null; | ||||
|             } | ||||
|         }); | ||||
|  | ||||
|     } | ||||
|      | ||||
|     // MNT-21009 | ||||
|     public void testUnsecureScriptAddedOnRepoNode() | ||||
|     { | ||||
|  | ||||
|         transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback<Object>() | ||||
|         { | ||||
|             public Object execute() throws Exception | ||||
|             { | ||||
|                 StoreRef store = nodeService.createStore(StoreRef.PROTOCOL_WORKSPACE, "rhino_" + System.currentTimeMillis()); | ||||
|                 NodeRef root = nodeService.getRootNode(store); | ||||
|                 BaseNodeServiceTest.buildNodeGraph(nodeService, root); | ||||
|  | ||||
|                 try | ||||
|                 { | ||||
|                     Map<String, Object> model = new HashMap<String, Object>(); | ||||
|                     model.put("out", System.out); | ||||
|  | ||||
|                     // create an Alfresco scriptable Node object | ||||
|                     // the Node object is a wrapper similar to the TemplateNode | ||||
|                     // concept | ||||
|                     ScriptNode rootNode = new ScriptNode(root, serviceRegistry, null); | ||||
|                     model.put("root", rootNode); | ||||
|  | ||||
|                     // test executing a script embedded inside Node content | ||||
|                     ChildAssociationRef childRef = nodeService.createNode(root, BaseNodeServiceTest.ASSOC_TYPE_QNAME_TEST_CHILDREN, | ||||
|                             QName.createQName(BaseNodeServiceTest.NAMESPACE, "script_content"), BaseNodeServiceTest.TYPE_QNAME_TEST_CONTENT, null); | ||||
|                     NodeRef contentNodeRef = childRef.getChildRef(); | ||||
|                     ContentWriter writer = contentService.getWriter(contentNodeRef, BaseNodeServiceTest.PROP_QNAME_TEST_CONTENT, true); | ||||
|                     writer.setMimetype("application/x-javascript"); | ||||
|                     writer.putContent(BASIC_JAVA); | ||||
|  | ||||
|                     try | ||||
|                     { | ||||
|                         scriptService.executeScript(contentNodeRef, BaseNodeServiceTest.PROP_QNAME_TEST_CONTENT, model); | ||||
|                         fail("execution of nonsecure script on nodeRef is not allowed."); | ||||
|                     } | ||||
|                     catch (AlfrescoRuntimeException ex) | ||||
|                     { | ||||
|                         // expected | ||||
|                     } | ||||
|                      | ||||
|                      | ||||
|                     ChildAssociationRef childRef1 = nodeService.createNode(root, BaseNodeServiceTest.ASSOC_TYPE_QNAME_TEST_CHILDREN, | ||||
|                             QName.createQName(BaseNodeServiceTest.NAMESPACE, "script_content"), BaseNodeServiceTest.TYPE_QNAME_TEST_CONTENT, null); | ||||
|                     NodeRef contentNodeRef1 = childRef1.getChildRef(); | ||||
|                     ContentWriter writer1 = contentService.getWriter(contentNodeRef1, BaseNodeServiceTest.PROP_QNAME_TEST_CONTENT, true); | ||||
|                     writer1.setMimetype("application/x-javascript"); | ||||
|                     writer1.putContent(REFLECTION_GET_CLASS); | ||||
|                      | ||||
|                     try | ||||
|                     { | ||||
|                         scriptService.executeScript(contentNodeRef1, BaseNodeServiceTest.PROP_QNAME_TEST_CONTENT, model); | ||||
|                         fail("execution of nonsecure script on nodeRef is not allowed."); | ||||
|                     } | ||||
|                     catch (AlfrescoRuntimeException ex) | ||||
|                     { | ||||
|                         // expected | ||||
|                     } | ||||
|                 } | ||||
|                 finally | ||||
|                 { | ||||
|                 } | ||||
|  | ||||
|                 return null; | ||||
|             } | ||||
|         }); | ||||
|  | ||||
|     } | ||||
|  | ||||
|     // MNT-23158 | ||||
|     public void testScopeData() | ||||
|     { | ||||
|         transactionService.getRetryingTransactionHelper().doInTransaction( | ||||
|             new RetryingTransactionCallback<Object>() | ||||
|             { | ||||
|                 public Object execute() throws Exception | ||||
|                 { | ||||
|                     Context cx = Context.enter(); | ||||
|                     try | ||||
|                     { | ||||
|                         Scriptable sharedScope = new ImporterTopLevel(cx, true); | ||||
|                         Scriptable scope = cx.newObject(sharedScope); | ||||
|                         scope.setPrototype(sharedScope); | ||||
|                         scope.setParentScope(null); | ||||
|  | ||||
|                         // Executes a first script | ||||
|                         Object result = cx.evaluateString(scope, "var a = 10; var b = 20; var sum = a+b;", "TestJS1", 1, null); | ||||
|                         assertTrue(Undefined.isUndefined(result)); | ||||
|  | ||||
|                         // Test sum value | ||||
|                         Object sum = scope.get("sum", scope); | ||||
|                         assertEquals(30.0, Context.toNumber(sum)); | ||||
|  | ||||
|                         // No 'sum' property should be found in the shared scope | ||||
|                         sum = sharedScope.get("sum", sharedScope); | ||||
|                         assertEquals(sum, UniqueTag.NOT_FOUND); | ||||
|  | ||||
|                         // No 'b' property should be found in the shared scope | ||||
|                         Object b = ScriptableObject.getProperty(sharedScope, "b"); | ||||
|                         assertEquals(b, UniqueTag.NOT_FOUND); | ||||
|  | ||||
|                         // Cleans scope | ||||
|                         unsetScope(scope); | ||||
|  | ||||
|                         // Executes a second script using the same scope | ||||
|                         result = cx.evaluateString(scope, "var test = 'test';", "TestJS2", 1, null); | ||||
|  | ||||
|                         // 'sum' property should be null | ||||
|                         sum = scope.get("sum", scope); | ||||
|                         assertNull(sum); | ||||
|  | ||||
|                         // New scope initialization | ||||
|                         scope = cx.newObject(sharedScope); | ||||
|                         scope.setPrototype(sharedScope); | ||||
|                         scope.setParentScope(null); | ||||
|  | ||||
|                         // check 'test' property | ||||
|                         Object test = scope.get("test", scope); | ||||
|                         assertEquals(test, UniqueTag.NOT_FOUND); | ||||
|                     } | ||||
|                     finally | ||||
|                     { | ||||
|                         Context.exit(); | ||||
|                     } | ||||
|  | ||||
|                     return null; | ||||
|                 } | ||||
|             }); | ||||
|     } | ||||
|  | ||||
|     private void unsetScope(Scriptable scope) | ||||
|     { | ||||
|         if (scope != null) | ||||
|         { | ||||
|             Object[] ids = scope.getIds(); | ||||
|  | ||||
|             if (ids != null) | ||||
|             { | ||||
|                 for (Object id : ids) | ||||
|                 { | ||||
|                     try | ||||
|                     { | ||||
|                         deleteProperty(scope, id.toString()); | ||||
|                     } | ||||
|                     catch (Exception e) | ||||
|                     { | ||||
|                         // Do nothing | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private void deleteProperty(Scriptable scope, String name) | ||||
|     { | ||||
|         if (scope != null && name != null) | ||||
|         { | ||||
|             if (!ScriptableObject.deleteProperty(scope, name)) | ||||
|             { | ||||
|                 ScriptableObject.putProperty(scope, name, null); | ||||
|             } | ||||
|             scope.delete(name); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private static final String TESTSCRIPT_CLASSPATH1 = "org/alfresco/repo/jscript/test_script1.js"; | ||||
|     private static final String TESTSCRIPT_CLASSPATH2 = "org/alfresco/repo/jscript/test_script2.js"; | ||||
|     private static final String TESTSCRIPT_CLASSPATH3 = "org/alfresco/repo/jscript/test_script3.js"; | ||||
| @@ -452,14 +551,14 @@ public class RhinoScriptTest extends TestCase | ||||
|             "var childByNameNode = root.childByNamePath(\"/\" + childList[0].name);\r\n" + | ||||
|             "logger.log(\"child by name path: \" + childByNameNode.name);\r\n" + | ||||
|             "var xpathResults = root.childrenByXPath(\"/*\");\r\n" + | ||||
|             "logger.log(\"children of root from xpath: \" + xpathResults.length);\r\n"; | ||||
|      | ||||
|     private static final String BASIC_JAVA =  | ||||
|             "var list = com.google.common.collect.Lists.newArrayList();\n" +  | ||||
|             "root.nodeRef.getClass().forName(\"java.lang.ProcessBuilder\")"; | ||||
|      | ||||
|     private static final String REFLECTION_GET_CLASS = | ||||
|           "root.nodeRef.getClass().forName(\"java.lang.ProcessBuilder\")"; | ||||
|              | ||||
|             "logger.log(\"children of root from xpath: \" + xpathResults.length);\r\n"; | ||||
|      | ||||
|     private static final String BASIC_JAVA =  | ||||
|             "var list = com.google.common.collect.Lists.newArrayList();\n" +  | ||||
|             "root.nodeRef.getClass().forName(\"java.lang.ProcessBuilder\")"; | ||||
|      | ||||
|     private static final String REFLECTION_GET_CLASS = | ||||
|           "root.nodeRef.getClass().forName(\"java.lang.ProcessBuilder\")"; | ||||
|              | ||||
|  | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user