diff --git a/dotCMS/src/main/java/com/dotcms/rest/api/v1/workflow/WorkflowResource.java b/dotCMS/src/main/java/com/dotcms/rest/api/v1/workflow/WorkflowResource.java index 33e1e9eacf95..478f461f2e0f 100644 --- a/dotCMS/src/main/java/com/dotcms/rest/api/v1/workflow/WorkflowResource.java +++ b/dotCMS/src/main/java/com/dotcms/rest/api/v1/workflow/WorkflowResource.java @@ -935,6 +935,7 @@ public EventOutput fireBulkActions(@Context final HttpServletRequest request, eventBuilder.name("failure"); eventBuilder.data(Map.class, Map.of("failure", inode)); + eventBuilder.mediaType(MediaType.APPLICATION_JSON_TYPE); final OutboundEvent event = eventBuilder.build(); try { eventOutput.write(event); diff --git a/dotCMS/src/main/java/com/dotmarketing/business/PermissionBitAPIImpl.java b/dotCMS/src/main/java/com/dotmarketing/business/PermissionBitAPIImpl.java index 42f31becb0f6..0afa1407f670 100644 --- a/dotCMS/src/main/java/com/dotmarketing/business/PermissionBitAPIImpl.java +++ b/dotCMS/src/main/java/com/dotmarketing/business/PermissionBitAPIImpl.java @@ -718,7 +718,7 @@ private void checkCopyPermissions(Permissionable permissionable, User user) thro * @return boolean * @throws DotDataException */ - private boolean checkIfContentletTypeHasEditPermissions(final Permissionable permissionable, final User user) throws DotDataException { + public boolean checkIfContentletTypeHasEditPermissions(final Permissionable permissionable, final User user) throws DotDataException { return permissionable instanceof Contentlet? // we can check if the content type has edit permissions doesUserHavePermission(Contentlet.class.cast(permissionable).getContentType(), PermissionAPI.PERMISSION_EDIT_PERMISSIONS, user):false; diff --git a/dotCMS/src/main/java/com/dotmarketing/portlets/workflows/actionlet/ResetPermissionsActionlet.java b/dotCMS/src/main/java/com/dotmarketing/portlets/workflows/actionlet/ResetPermissionsActionlet.java new file mode 100644 index 000000000000..68de5abd547f --- /dev/null +++ b/dotCMS/src/main/java/com/dotmarketing/portlets/workflows/actionlet/ResetPermissionsActionlet.java @@ -0,0 +1,66 @@ +package com.dotmarketing.portlets.workflows.actionlet; + +import com.dotmarketing.business.APILocator; +import com.dotmarketing.business.PermissionAPI; +import com.dotmarketing.business.PermissionBitAPIImpl; +import com.dotmarketing.business.Permissionable; +import com.dotmarketing.business.ajax.PermissionAjax; +import com.dotmarketing.exception.DotDataException; +import com.dotmarketing.exception.DotSecurityException; +import com.dotmarketing.portlets.workflows.model.WorkflowActionClassParameter; +import com.dotmarketing.portlets.workflows.model.WorkflowActionFailureException; +import com.dotmarketing.portlets.workflows.model.WorkflowActionletParameter; +import com.dotmarketing.portlets.workflows.model.WorkflowProcessor; +import com.dotmarketing.util.Logger; +import com.liferay.portal.model.User; + +import java.util.List; +import java.util.Map; + + +/** + * This Actionlet allows the user to reset the permissions of a contentlet + * The user must have edit permissions to fire this action. + */ +public class ResetPermissionsActionlet extends WorkFlowActionlet{ + @Override + public List getParameters() { + return List.of(); + } + + @Override + public String getName() { + return "Reset Permissions"; + } + + @Override + public String getHowTo() { + return "This actionlet will reset permissions of the selected contentlets. It does not require any parameters."; + } + + /* + * This method will reset the permissions of the contentlet after checking that the user has edit permissions to modify it. + * */ + @Override + public void executeAction(WorkflowProcessor processor, Map params) throws WorkflowActionFailureException { + + try { + + User user = processor.getUser(); + + PermissionAPI permissionAPI = APILocator.getPermissionAPI(); + PermissionBitAPIImpl api = (PermissionBitAPIImpl) APILocator.getPermissionAPI(); + Permissionable asset = processor.getContentlet(); + if (!user.isAdmin() && !api.doesUserHavePermission(asset, PermissionAPI.PERMISSION_EDIT_PERMISSIONS, user) && + !api.checkIfContentletTypeHasEditPermissions(asset, user)) { + + throw new DotSecurityException("User id: " + user.getUserId() + " does not have permission to alter permissions on asset " + asset.getPermissionId()); + } + permissionAPI.removePermissions(asset); + } catch ( Exception e) { + Logger.debug(ResetPermissionsActionlet.class, e.getMessage()); + throw new WorkflowActionFailureException(e.getMessage(), e); + } + + } +} diff --git a/dotCMS/src/main/java/com/dotmarketing/portlets/workflows/business/WorkflowAPIImpl.java b/dotCMS/src/main/java/com/dotmarketing/portlets/workflows/business/WorkflowAPIImpl.java index f7ae608762db..9e946ca3934c 100644 --- a/dotCMS/src/main/java/com/dotmarketing/portlets/workflows/business/WorkflowAPIImpl.java +++ b/dotCMS/src/main/java/com/dotmarketing/portlets/workflows/business/WorkflowAPIImpl.java @@ -71,39 +71,7 @@ import com.dotmarketing.portlets.structure.model.Structure; import com.dotmarketing.portlets.workflows.LargeMessageActionlet; import com.dotmarketing.portlets.workflows.MessageActionlet; -import com.dotmarketing.portlets.workflows.actionlet.Actionlet; -import com.dotmarketing.portlets.workflows.actionlet.ArchiveContentActionlet; -import com.dotmarketing.portlets.workflows.actionlet.AsyncEmailActionlet; -import com.dotmarketing.portlets.workflows.actionlet.BatchAction; -import com.dotmarketing.portlets.workflows.actionlet.CheckURLAccessibilityActionlet; -import com.dotmarketing.portlets.workflows.actionlet.CheckinContentActionlet; -import com.dotmarketing.portlets.workflows.actionlet.CheckoutContentActionlet; -import com.dotmarketing.portlets.workflows.actionlet.CommentOnWorkflowActionlet; -import com.dotmarketing.portlets.workflows.actionlet.CopyActionlet; -import com.dotmarketing.portlets.workflows.actionlet.DeleteContentActionlet; -import com.dotmarketing.portlets.workflows.actionlet.DestroyContentActionlet; -import com.dotmarketing.portlets.workflows.actionlet.EmailActionlet; -import com.dotmarketing.portlets.workflows.actionlet.FourEyeApproverActionlet; -import com.dotmarketing.portlets.workflows.actionlet.MoveContentActionlet; -import com.dotmarketing.portlets.workflows.actionlet.MultipleApproverActionlet; -import com.dotmarketing.portlets.workflows.actionlet.NotifyAssigneeActionlet; -import com.dotmarketing.portlets.workflows.actionlet.NotifyUsersActionlet; -import com.dotmarketing.portlets.workflows.actionlet.PublishContentActionlet; -import com.dotmarketing.portlets.workflows.actionlet.PushNowActionlet; -import com.dotmarketing.portlets.workflows.actionlet.PushPublishActionlet; -import com.dotmarketing.portlets.workflows.actionlet.ReindexContentActionlet; -import com.dotmarketing.portlets.workflows.actionlet.ResetApproversActionlet; -import com.dotmarketing.portlets.workflows.actionlet.ResetTaskActionlet; -import com.dotmarketing.portlets.workflows.actionlet.SaveContentActionlet; -import com.dotmarketing.portlets.workflows.actionlet.SaveContentAsDraftActionlet; -import com.dotmarketing.portlets.workflows.actionlet.SendFormEmailActionlet; -import com.dotmarketing.portlets.workflows.actionlet.SetValueActionlet; -import com.dotmarketing.portlets.workflows.actionlet.TranslationActionlet; -import com.dotmarketing.portlets.workflows.actionlet.TwitterActionlet; -import com.dotmarketing.portlets.workflows.actionlet.UnarchiveContentActionlet; -import com.dotmarketing.portlets.workflows.actionlet.UnpublishContentActionlet; -import com.dotmarketing.portlets.workflows.actionlet.VelocityScriptActionlet; -import com.dotmarketing.portlets.workflows.actionlet.WorkFlowActionlet; +import com.dotmarketing.portlets.workflows.actionlet.*; import com.dotmarketing.portlets.workflows.model.SystemActionWorkflowActionMapping; import com.dotmarketing.portlets.workflows.model.WorkflowAction; import com.dotmarketing.portlets.workflows.model.WorkflowActionClass; @@ -264,6 +232,7 @@ public WorkflowAPIImpl() { NotifyAssigneeActionlet.class, UnarchiveContentActionlet.class, ResetTaskActionlet.class, + ResetPermissionsActionlet.class, MultipleApproverActionlet.class, FourEyeApproverActionlet.class, TwitterActionlet.class, diff --git a/dotcms-integration/src/test/java/com/dotcms/rest/api/v1/workflow/WorkflowResourceIntegrationTest.java b/dotcms-integration/src/test/java/com/dotcms/rest/api/v1/workflow/WorkflowResourceIntegrationTest.java index e45f8d17256b..89a1fa4c5f7a 100644 --- a/dotcms-integration/src/test/java/com/dotcms/rest/api/v1/workflow/WorkflowResourceIntegrationTest.java +++ b/dotcms-integration/src/test/java/com/dotcms/rest/api/v1/workflow/WorkflowResourceIntegrationTest.java @@ -22,17 +22,11 @@ import static com.dotmarketing.portlets.workflows.util.WorkflowImportExportUtil.ACTION_ORDER; import static com.dotmarketing.portlets.workflows.util.WorkflowImportExportUtil.STEP_ID; import static com.dotmarketing.portlets.workflows.util.WorkflowImportExportUtil.getInstance; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; +import static org.junit.Assert.*; import static org.mockito.ArgumentMatchers.nullable; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import static org.mockito.Mockito.*; import com.dotcms.contenttype.business.ContentTypeAPI; import com.dotcms.contenttype.business.FieldAPI; @@ -58,12 +52,9 @@ import com.dotcms.contenttype.model.field.WysiwygField; import com.dotcms.contenttype.model.type.BaseContentType; import com.dotcms.contenttype.model.type.ContentType; -import com.dotcms.datagen.CategoryDataGen; -import com.dotcms.datagen.RoleDataGen; -import com.dotcms.datagen.TestDataUtils; -import com.dotcms.datagen.TestUserUtils; -import com.dotcms.datagen.TestWorkflowUtils; -import com.dotcms.datagen.WorkflowDataGen; +import com.dotcms.contenttype.model.type.ContentTypeBuilder; +import com.dotcms.contenttype.model.type.SimpleContentType; +import com.dotcms.datagen.*; import com.dotcms.mock.request.MockAttributeRequest; import com.dotcms.mock.request.MockHeaderRequest; import com.dotcms.mock.request.MockHttpRequestIntegrationTest; @@ -79,38 +70,31 @@ import com.dotcms.rest.api.v1.authentication.ResponseUtil; import com.dotcms.util.CollectionsUtils; import com.dotcms.util.IntegrationTestInitService; -import com.dotcms.workflow.form.BulkActionForm; -import com.dotcms.workflow.form.FireActionForm; -import com.dotcms.workflow.form.FireBulkActionsForm; -import com.dotcms.workflow.form.WorkflowActionForm; -import com.dotcms.workflow.form.WorkflowActionStepForm; -import com.dotcms.workflow.form.WorkflowSchemeForm; -import com.dotcms.workflow.form.WorkflowSchemeImportObjectForm; -import com.dotcms.workflow.form.WorkflowStepUpdateForm; +import com.dotcms.workflow.form.*; import com.dotcms.workflow.helper.WorkflowHelper; import com.dotmarketing.beans.Host; import com.dotmarketing.beans.Permission; -import com.dotmarketing.business.APILocator; -import com.dotmarketing.business.PermissionAPI; -import com.dotmarketing.business.Role; -import com.dotmarketing.business.RoleAPI; +import com.dotmarketing.business.*; import com.dotmarketing.common.reindex.ReindexQueueAPI; import com.dotmarketing.common.reindex.ReindexThread; +import com.dotmarketing.exception.AlreadyExistException; import com.dotmarketing.exception.DotDataException; +import com.dotmarketing.exception.DotSecurityException; import com.dotmarketing.portlets.categories.model.Category; import com.dotmarketing.portlets.contentlet.business.ContentletAPI; import com.dotmarketing.portlets.contentlet.business.DotContentletValidationException; import com.dotmarketing.portlets.contentlet.business.HostAPI; import com.dotmarketing.portlets.contentlet.model.Contentlet; +import com.dotmarketing.portlets.contentlet.model.ContentletDependencies; import com.dotmarketing.portlets.contentlet.model.IndexPolicy; +import com.dotmarketing.portlets.folders.business.FolderAPI; import com.dotmarketing.portlets.languagesmanager.business.LanguageAPI; +import com.dotmarketing.portlets.workflows.actionlet.MoveContentActionlet; +import com.dotmarketing.portlets.workflows.actionlet.ResetPermissionsActionlet; import com.dotmarketing.portlets.workflows.business.BaseWorkflowIntegrationTest; import com.dotmarketing.portlets.workflows.business.WorkflowAPI; import com.dotmarketing.portlets.workflows.business.WorkflowAPI.SystemAction; -import com.dotmarketing.portlets.workflows.model.WorkflowAction; -import com.dotmarketing.portlets.workflows.model.WorkflowScheme; -import com.dotmarketing.portlets.workflows.model.WorkflowState; -import com.dotmarketing.portlets.workflows.model.WorkflowStep; +import com.dotmarketing.portlets.workflows.model.*; import com.dotmarketing.portlets.workflows.util.WorkflowImportExportUtil; import com.dotmarketing.util.DateUtil; import com.dotmarketing.util.Logger; @@ -147,6 +131,8 @@ import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; + +import com.liferay.portal.util.PortalUtil; import org.apache.commons.io.FilenameUtils; import org.apache.commons.io.input.ReaderInputStream; import org.apache.commons.lang.RandomStringUtils; @@ -155,6 +141,8 @@ import org.glassfish.jersey.media.multipart.ContentDisposition; import org.glassfish.jersey.media.multipart.FormDataBodyPart; import org.glassfish.jersey.media.multipart.FormDataMultiPart; +import org.glassfish.jersey.media.sse.EventOutput; +import org.jetbrains.annotations.NotNull; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; @@ -177,6 +165,7 @@ public class WorkflowResourceIntegrationTest extends BaseWorkflowIntegrationTest private static Role systemRole; static private WorkflowScheme testScheme; + @BeforeClass public static void prepare() throws Exception { @@ -2419,6 +2408,9 @@ public void testFireActionDefault_ContentWithCategories_Success() throws Excepti } } + + + private ContentType createCategoryFieldContentType(final String parentCategoryInode) throws Exception { ContentType contentType; @@ -2605,4 +2597,7 @@ private static HttpServletRequest getHttpRequest() { return request; } + } + + diff --git a/dotcms-integration/src/test/java/com/dotmarketing/portlets/workflows/business/WorkflowAPITest.java b/dotcms-integration/src/test/java/com/dotmarketing/portlets/workflows/business/WorkflowAPITest.java index 74824eeb0d18..7c08e8ab274c 100644 --- a/dotcms-integration/src/test/java/com/dotmarketing/portlets/workflows/business/WorkflowAPITest.java +++ b/dotcms-integration/src/test/java/com/dotmarketing/portlets/workflows/business/WorkflowAPITest.java @@ -13,6 +13,7 @@ import com.dotcms.contenttype.model.type.BaseContentType; import com.dotcms.contenttype.model.type.ContentType; import com.dotcms.contenttype.model.type.ContentTypeBuilder; +import com.dotcms.contenttype.model.type.SimpleContentType; import com.dotcms.contenttype.transform.contenttype.StructureTransformer; import com.dotcms.datagen.ContentTypeDataGen; import com.dotcms.datagen.ContentletDataGen; @@ -30,13 +31,7 @@ import com.dotcms.util.IntegrationTestInitService; import com.dotmarketing.beans.Host; import com.dotmarketing.beans.Permission; -import com.dotmarketing.business.APILocator; -import com.dotmarketing.business.CacheLocator; -import com.dotmarketing.business.FactoryLocator; -import com.dotmarketing.business.PermissionAPI; -import com.dotmarketing.business.Permissionable; -import com.dotmarketing.business.Role; -import com.dotmarketing.business.RoleAPI; +import com.dotmarketing.business.*; import com.dotmarketing.common.db.DotConnect; import com.dotmarketing.exception.AlreadyExistException; import com.dotmarketing.exception.DoesNotExistException; @@ -55,17 +50,7 @@ import com.dotmarketing.portlets.structure.model.ContentletRelationships; import com.dotmarketing.portlets.structure.model.Relationship; import com.dotmarketing.portlets.structure.model.Structure; -import com.dotmarketing.portlets.workflows.actionlet.ArchiveContentActionlet; -import com.dotmarketing.portlets.workflows.actionlet.CheckinContentActionlet; -import com.dotmarketing.portlets.workflows.actionlet.DeleteContentActionlet; -import com.dotmarketing.portlets.workflows.actionlet.MoveContentActionlet; -import com.dotmarketing.portlets.workflows.actionlet.PublishContentActionlet; -import com.dotmarketing.portlets.workflows.actionlet.ResetTaskActionlet; -import com.dotmarketing.portlets.workflows.actionlet.SaveContentActionlet; -import com.dotmarketing.portlets.workflows.actionlet.SaveContentAsDraftActionlet; -import com.dotmarketing.portlets.workflows.actionlet.UnarchiveContentActionlet; -import com.dotmarketing.portlets.workflows.actionlet.UnpublishContentActionlet; -import com.dotmarketing.portlets.workflows.actionlet.WorkFlowActionlet; +import com.dotmarketing.portlets.workflows.actionlet.*; import com.dotmarketing.portlets.workflows.business.WorkflowAPI.SystemAction; import com.dotmarketing.portlets.workflows.model.SystemActionWorkflowActionMapping; import com.dotmarketing.portlets.workflows.model.WorkflowAction; @@ -119,13 +104,7 @@ import static com.dotmarketing.portlets.workflows.model.WorkflowState.PUBLISHED; import static com.dotmarketing.portlets.workflows.model.WorkflowState.UNLOCKED; import static com.dotmarketing.portlets.workflows.model.WorkflowState.UNPUBLISHED; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; +import static org.junit.Assert.*; /** * Test the workflowAPI @@ -133,6 +112,7 @@ public class WorkflowAPITest extends IntegrationTestBase { private static User user; + private static User adminUser; private static Host defaultHost; protected static ContentTypeAPIImpl contentTypeAPI; protected static FieldAPI fieldAPI; @@ -348,6 +328,7 @@ public static void prepare() throws Exception { //Setting the test user user = APILocator.getUserAPI().getSystemUser(); + adminUser = APILocator.getUserAPI().loadByUserByEmail("admin@dotcms.com", user, false); defaultHost = hostAPI.findDefaultHost(user, false); contentTypeAPI = (ContentTypeAPIImpl) APILocator.getContentTypeAPI(user); fieldAPI = APILocator.getContentTypeFieldAPI(); @@ -4898,4 +4879,111 @@ public void testAttachDotAssetToWorkflowTask() } + /** + * Method to test: {@link WorkflowAPI#fireContentWorkflow(Contentlet, ContentletDependencies)} + * Given Scenario: Reset permissions to a contentlet from workflow action + * ExpectedResult: The reset of the permissions should be done successfully when firing workflow action + * + */ + @Test + public void test_reset_permissions_subaction() throws DotDataException, DotSecurityException { + + ContentType cType = createContentType("test"); + + + Contentlet contentlet = new ContentletDataGen(cType.id()) + .host(APILocator.systemHost()) + .nextPersisted(); + + //Get original size permissions + final int originalSizePermissions = APILocator.getPermissionAPI().getPermissions(contentlet).size(); + + Role limitedUserRole = TestUserUtils.getBackendRole(); + + addPermission(limitedUserRole, contentlet, PermissionLevel.READ); + + //Since we're setting permissions individually, should be only 1 permission + Assert.assertEquals(1, APILocator.getPermissionAPI().getPermissions(contentlet).size()); + + + final long time = System.currentTimeMillis(); + final String workflowSchemeName = "WorkflowSchemeTestResetPermissions_" + time; + final String workflowScheme3Step1Name = "WorkflowSchemeResetPermissionsStep1_" + time; + final String workflowScheme3Step1ActionResetPermissions = "Reset" + time; + final WorkflowScheme workflowScheme = addWorkflowScheme(workflowSchemeName); + + final Set schemes = new HashSet<>(); + schemes.add(workflowScheme.getId()); + workflowAPI.saveSchemeIdsForContentType(cType, schemes); + + final WorkflowStep workflowSchemeStep = addWorkflowStep(workflowScheme3Step1Name, 1, false, false, + workflowScheme.getId()); + + /* Generate actions */ + final WorkflowAction workflowSchemeResetPermissionAction = addWorkflowAction(workflowScheme3Step1ActionResetPermissions, 1, + workflowSchemeStep.getId(), workflowSchemeStep.getId(), contributor, + workflowScheme.getId()); + + + addSubActionClass("Reset Permissions", workflowSchemeResetPermissionAction.getId(), ResetPermissionsActionlet.class, 0); + + + + List steps = workflowAPI.findSteps(workflowScheme); + assertNotNull(steps); + assertEquals(1, steps.size()); + + //check available actions for admin user + List actions = workflowAPI.findActions(steps, user); + assertNotNull(actions); + assertEquals(1, actions.size()); + + actions = workflowAPI.findActions(steps, user, null); + assertNotNull(actions); + assertEquals(1, actions.size()); + + final ContentletRelationships contentletRelationships = APILocator.getContentletAPI() + .getAllRelationships(contentlet); + + //Reset permissions + contentlet = fireWorkflowAction(contentlet, contentletRelationships, workflowSchemeResetPermissionAction, + StringPool.BLANK, StringPool.BLANK, user); + + + //After reset permissions, the permissions should be back to their original size + Assert.assertEquals(originalSizePermissions, APILocator.getPermissionAPI().getPermissions(contentlet).size()); + + } + + private void addPermission( + final Role role, + final Permissionable contentType, + final PermissionLevel permissionLevel) + + throws DotDataException, DotSecurityException { + + APILocator.getPermissionAPI().save( + getPermission(role, contentType, permissionLevel.getType()), + contentType, adminUser, false); + } + + + private Permission getPermission( + final Role role, + final Permissionable permissionable, + final int permissionPublish) { + + final Permission publishPermission = new Permission(); + publishPermission.setInode(permissionable.getPermissionId()); + publishPermission.setRoleId(role.getId()); + publishPermission.setPermission(permissionPublish); + return publishPermission; + } + + private ContentType createContentType(final String name) throws DotSecurityException, DotDataException { + return contentTypeAPI.save(ContentTypeBuilder.builder(SimpleContentType.class).folder( + FolderAPI.SYSTEM_FOLDER).host(Host.SYSTEM_HOST).name(name) + .owner(user.getUserId()).build()); + } + }