Compare commits

..

57 Commits

Author SHA1 Message Date
alfresco-build
ba50617181 [maven-release-plugin][skip ci] prepare release 23.3.0.54 2024-06-18 07:43:08 +00:00
Sara
0d621badd7 Merge pull request #2696 from Alfresco/feature/MNT-24205_Bump_ATS_to_4.1.3-A2
MNT-24205 Bump t-core to 5.1.3-A3 and t-service to 4.1.3-A2
2024-06-18 08:07:03 +01:00
alfresco-build
661ddce806 [maven-release-plugin][skip ci] prepare for next development iteration 2024-06-17 14:37:53 +00:00
alfresco-build
aa8b0256e1 [maven-release-plugin][skip ci] prepare release 23.3.0.53 2024-06-17 14:37:50 +00:00
tiagosalvado10
8990581cc1 MNT-24205 Bump t-core to 5.1.3-A3 and t-service to 4.1.3-A2 2024-06-17 14:56:44 +01:00
Kacper Magdziarz
04d76a182a Bump SS/IE to 2.0.11-A6 (#2694) 2024-06-17 15:20:02 +02:00
alfresco-build
27fbf633df [maven-release-plugin][skip ci] prepare for next development iteration 2024-06-17 11:22:54 +00:00
alfresco-build
358ca8dc9e [maven-release-plugin][skip ci] prepare release 23.3.0.52 2024-06-17 11:22:51 +00:00
rrajoria
9a25862cc9 Update Aos alpha version 2024-06-17 16:05:53 +05:30
alfresco-build
ac7e58ddc5 [maven-release-plugin][skip ci] prepare for next development iteration 2024-06-16 00:06:38 +00:00
alfresco-build
6bcaa1be04 [maven-release-plugin][skip ci] prepare release 23.3.0.51 2024-06-16 00:06:36 +00:00
Alfresco CI User
a1bf0f5480 [force] Force release for 2024-06-16. 2024-06-16 00:03:32 +00:00
alfresco-build
2c3d5dd297 [maven-release-plugin][skip ci] prepare for next development iteration 2024-06-09 00:07:41 +00:00
alfresco-build
6b32d96f08 [maven-release-plugin][skip ci] prepare release 23.3.0.50 2024-06-09 00:07:38 +00:00
Alfresco CI User
69dd388133 [force] Force release for 2024-06-09. 2024-06-09 00:04:05 +00:00
alfresco-build
29106d307f [maven-release-plugin][skip ci] prepare for next development iteration 2024-06-07 11:35:11 +00:00
alfresco-build
ef8b3578df [maven-release-plugin][skip ci] prepare release 23.3.0.49 2024-06-07 11:35:08 +00:00
Domenico Sibilio
dc5128b447 ACS-8114 Bump TAS utility to 5.0.1 (#2684) 2024-06-07 12:58:40 +02:00
alfresco-build
741ac97c3b [maven-release-plugin][skip ci] prepare for next development iteration 2024-06-06 12:07:03 +00:00
alfresco-build
3f5cb68250 [maven-release-plugin][skip ci] prepare release 23.3.0.48 2024-06-06 12:07:00 +00:00
dependabot[bot]
fdb4f9e338 Bump org.springframework.security:spring-security-bom from 6.2.2 to 6.3.0 (#2658)
updated-dependencies:
- dependency-name: org.springframework.security:spring-security-bom
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-06 13:29:18 +02:00
alfresco-build
f3f3a1db4b [maven-release-plugin][skip ci] prepare for next development iteration 2024-06-05 16:18:58 +00:00
alfresco-build
eb922e1c95 [maven-release-plugin][skip ci] prepare release 23.3.0.47 2024-06-05 16:18:55 +00:00
Domenico Sibilio
46da34fabd ACS-8071 Bump bouncycastle to 1.78.1 (#2682) 2024-06-05 17:42:17 +02:00
alfresco-build
7dee8314fb [maven-release-plugin][skip ci] prepare for next development iteration 2024-06-04 08:47:09 +00:00
alfresco-build
f888f8244e [maven-release-plugin][skip ci] prepare release 23.3.0.46 2024-06-04 08:47:07 +00:00
Kacper Magdziarz
8e3eeb9dd7 [ACS-5648] Add header 'X-Alfresco-Retry-Needed' indicating that recovery mode is on and client should retry later (#2670)
* [ACS-5648] Add header'X-Alfresco-Retry-Needed' indicating that recovery mode is on and client should retry later

* [ACS-5648] Bump ATS to 4.1.3-A1
2024-06-04 10:11:40 +02:00
alfresco-build
012d2f37c7 [maven-release-plugin][skip ci] prepare for next development iteration 2024-06-03 18:42:01 +00:00
alfresco-build
f5f73162bd [maven-release-plugin][skip ci] prepare release 23.3.0.45 2024-06-03 18:41:58 +00:00
Piotr Żurek
e7a83f2641 ACS-8077 Bump Netty and Camel (#2678) 2024-06-03 19:56:14 +02:00
alfresco-build
2a300f9b7c [maven-release-plugin][skip ci] prepare for next development iteration 2024-06-03 11:00:19 +00:00
alfresco-build
ac0ed124aa [maven-release-plugin][skip ci] prepare release 23.3.0.44 2024-06-03 11:00:17 +00:00
Tom Page
cee2da9700 Merge pull request #2661 from Alfresco/feature/PRODENG-276_implement_service_accounts
PRODENG-276: Implement service accounts
2024-06-03 11:24:12 +01:00
alfresco-build
2bc914b649 [maven-release-plugin][skip ci] prepare for next development iteration 2024-06-02 00:06:43 +00:00
alfresco-build
dcd381923e [maven-release-plugin][skip ci] prepare release 23.3.0.43 2024-06-02 00:06:41 +00:00
Alfresco CI User
bcf5d1b2c0 [force] Force release for 2024-06-02. 2024-06-02 00:03:31 +00:00
Jamal Kaabi-Mofrad
89dd6f7b35 PRODENG-276: Addressed minor comments from peer review. 2024-05-31 14:49:44 +01:00
Jamal Kaabi-Mofrad
c82e8d652a PRODENG-276: Resolved peer review comments. 2024-05-30 17:29:04 +01:00
Jamal Kaabi-Mofrad
a9a911e77f PRODENG-276: Resolved PMD issues. 2024-05-30 17:06:24 +01:00
Jamal Kaabi-Mofrad
e59bb3261c PRODENG-276: Fixed test failures. 2024-05-30 15:57:46 +01:00
alfresco-build
dbcb54369a [maven-release-plugin][skip ci] prepare for next development iteration 2024-05-27 14:44:21 +00:00
alfresco-build
b50a022e48 [maven-release-plugin][skip ci] prepare release 23.3.0.42 2024-05-27 14:44:18 +00:00
Domenico Sibilio
799db5f212 ACS-7569 Test with Keycloak 24.0.3 (#2663) 2024-05-27 15:57:08 +02:00
alfresco-build
975976db18 [maven-release-plugin][skip ci] prepare for next development iteration 2024-05-26 00:06:33 +00:00
alfresco-build
809fa28023 [maven-release-plugin][skip ci] prepare release 23.3.0.41 2024-05-26 00:06:31 +00:00
Alfresco CI User
d28359c168 [force] Force release for 2024-05-26. 2024-05-26 00:03:40 +00:00
Jamal Kaabi-Mofrad
d999558b33 PRODENG-276: Cleanup - Fixed inconsistent spacing issues. 2024-05-24 16:04:49 +01:00
Jamal Kaabi-Mofrad
897bfa8410 PRODENG-276: Implemented three distinct service account roles with varying access levels. 2024-05-24 15:54:10 +01:00
alfresco-build
a3b13ed8de [maven-release-plugin][skip ci] prepare for next development iteration 2024-05-20 08:04:40 +00:00
alfresco-build
ea66b9e4f3 [maven-release-plugin][skip ci] prepare release 23.3.0.40 2024-05-20 08:04:38 +00:00
Kacper Magdziarz
0a5827e37c [ACS-7896] Bump AIS/ATS 5.1.2/4.1.2 (#2655) 2024-05-20 09:17:01 +02:00
alfresco-build
c01d1f8c3c [maven-release-plugin][skip ci] prepare for next development iteration 2024-05-19 00:07:13 +00:00
alfresco-build
3895ce6584 [maven-release-plugin][skip ci] prepare release 23.3.0.39 2024-05-19 00:07:11 +00:00
Alfresco CI User
d01f9eb27b [force] Force release for 2024-05-19. 2024-05-19 00:03:26 +00:00
alfresco-build
325dfd285b [maven-release-plugin][skip ci] prepare for next development iteration 2024-05-17 09:51:21 +00:00
alfresco-build
d907461972 [maven-release-plugin][skip ci] prepare release 23.3.0.38 2024-05-17 09:51:18 +00:00
SathishK-T
01a2587555 [MNT-24195] Added fix for ags capability issue (#2648)
* [MNT-24195] added fix for ags capability issue

* [MNT-24195] Build Issue Resolved

* [MNT-24195] Removed unused import

* [MNT-24195] Build issue changes

* [MNT-24195]  added fix for ags capability issue

* [MNT-24195] Added fix for ags capability issue

* [MNT-24195] Fix for Review Comment

* [MNT-24195] Fix for Review Comment

---------

Co-authored-by: Sathish Kumar <ST28@ford.com>
2024-05-17 14:41:46 +05:30
78 changed files with 1459 additions and 3128 deletions

View File

@@ -7,7 +7,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-community-repo-amps</artifactId>
<version>23.3.0.38-SNAPSHOT</version>
<version>23.3.0.54</version>
</parent>
<modules>

View File

@@ -7,7 +7,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-governance-services-community-parent</artifactId>
<version>23.3.0.38-SNAPSHOT</version>
<version>23.3.0.54</version>
</parent>
<modules>

View File

@@ -7,7 +7,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-governance-services-automation-community-repo</artifactId>
<version>23.3.0.38-SNAPSHOT</version>
<version>23.3.0.54</version>
</parent>
<build>
@@ -84,12 +84,6 @@
<artifactId>okhttp</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.awaitility</groupId>
<artifactId>awaitility</artifactId>
<version>${dependency.awaitility.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>

View File

@@ -1,60 +0,0 @@
/*
* #%L
* Alfresco Records Management Module
* %%
* Copyright (C) 2005 - 2024 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.rest.rm.community.model.hold;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import org.alfresco.rest.search.RestRequestQueryModel;
import org.alfresco.utility.model.TestModel;
/**
* POJO for hold bulk request
*
* @author Damian Ujma
*/
@EqualsAndHashCode(callSuper = true)
@Builder
@Data
@NoArgsConstructor
@AllArgsConstructor
public class HoldBulkOperation extends TestModel
{
public enum HoldBulkOperationType
{
ADD
}
@JsonProperty(required = true)
private RestRequestQueryModel query;
@JsonProperty(required = true)
private HoldBulkOperationType op;
}

View File

@@ -1,50 +0,0 @@
/*
* #%L
* Alfresco Records Management Module
* %%
* Copyright (C) 2005 - 2024 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.rest.rm.community.model.hold;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* POJO for hold bulk request entry
*
* @author Damian Ujma
*/
@Builder
@Data
@NoArgsConstructor
@AllArgsConstructor
public class HoldBulkOperationEntry
{
private String bulkStatusId;
private long totalItems;
}

View File

@@ -1,68 +0,0 @@
/*
* #%L
* Alfresco Records Management Module
* %%
* Copyright (C) 2005 - 2024 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.rest.rm.community.model.hold;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.alfresco.utility.model.TestModel;
/**
* POJO for hold bulk request
*
* @author Damian Ujma
*/
@Builder
@Data
@NoArgsConstructor
@AllArgsConstructor
public class HoldBulkStatus extends TestModel
{
private String bulkStatusId;
private String startTime;
private String endTime;
private long processedItems;
private long errorsCount;
private long totalItems;
private String lastError;
private Status status;
public enum Status
{
PENDING,
IN_PROGRESS,
DONE
}
}

View File

@@ -1,38 +0,0 @@
/*
* #%L
* Alfresco Records Management Module
* %%
* Copyright (C) 2005 - 2024 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.rest.rm.community.model.hold;
import org.alfresco.rest.core.RestModels;
/**
* Handle collection of {@link HoldBulkStatusEntry}
*
* @author Damian Ujma
*/
public class HoldBulkStatusCollection extends RestModels<HoldBulkStatusEntry, HoldBulkStatusCollection>
{
}

View File

@@ -1,46 +0,0 @@
/*
* #%L
* Alfresco Records Management Module
* %%
* Copyright (C) 2005 - 2024 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.rest.rm.community.model.hold;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import org.alfresco.rest.core.RestModels;
@Builder
@Data
@EqualsAndHashCode(callSuper = true)
@NoArgsConstructor
@AllArgsConstructor
public class HoldBulkStatusEntry extends RestModels<HoldBulkStatus, HoldBulkStatusEntry>
{
private HoldBulkStatus entry;
}

View File

@@ -48,5 +48,5 @@ import org.alfresco.rest.core.RestModels;
public class HoldChildEntry extends RestModels<Hold, HoldChildEntry>
{
@JsonProperty
private HoldChild entry;
private HoldChildEntry entry;
}

View File

@@ -39,10 +39,6 @@ import static org.springframework.http.HttpMethod.PUT;
import org.alfresco.rest.core.RMRestWrapper;
import org.alfresco.rest.rm.community.model.hold.Hold;
import org.alfresco.rest.rm.community.model.hold.HoldBulkOperation;
import org.alfresco.rest.rm.community.model.hold.HoldBulkOperationEntry;
import org.alfresco.rest.rm.community.model.hold.HoldBulkStatus;
import org.alfresco.rest.rm.community.model.hold.HoldBulkStatusCollection;
import org.alfresco.rest.rm.community.model.hold.HoldChild;
import org.alfresco.rest.rm.community.model.hold.HoldChildCollection;
import org.alfresco.rest.rm.community.model.hold.HoldDeletionReason;
@@ -291,113 +287,4 @@ public class HoldsAPI extends RMModelRequest
{
deleteHoldChild(holdId, holdChildId, EMPTY);
}
/**
* Starts a bulk process for a hold.
*
* @param holdBulkOperation The bulk operation details
* @param hold The identifier of a hold
* @param parameters The URL parameters to add
* @return The {@link HoldBulkOperationEntry} for the started bulk process
* @throws RuntimeException for the following cases:
* <ul>
* <li>{@code hold} or {@code holdBulkOperation} is invalid</li>
* <li>authentication fails</li>
* <li>current user does not have permission to start a bulk process for {@code hold}</li>
* <li>{@code hold} does not exist</li>
* </ul>
*/
public HoldBulkOperationEntry startBulkProcess(HoldBulkOperation holdBulkOperation, String hold, String parameters)
{
mandatoryObject("holdBulkOperation", holdBulkOperation);
mandatoryString("hold", hold);
return getRmRestWrapper().processModel(HoldBulkOperationEntry.class, requestWithBody(
POST,
toJson(holdBulkOperation),
"holds/{hold}/bulk",
hold,
parameters
));
}
/**
* See {@link #startBulkProcess(HoldBulkOperation, String, String)}
*/
public HoldBulkOperationEntry startBulkProcess(HoldBulkOperation holdBulkOperation, String hold)
{
return startBulkProcess(holdBulkOperation, hold, EMPTY);
}
/**
* Gets the status of a bulk process for a hold.
*
* @param holdId The identifier of a hold
* @param holdBulkStatusId The identifier of a bulk status operation
* @param parameters The URL parameters to add
* @return The {@link HoldBulkStatus} for the given {@code holdId} and {@code holdBulkStatusId}
* @throws RuntimeException for the following cases:
* <ul>
* <li>{@code holdId} or {@code holdBulkStatusId} is invalid</li>
* <li>authentication fails</li>
* <li>current user does not have permission to get the bulk status for {@code holdId}</li>
* <li>{@code holdId} or {@code holdBulkStatusId} does not exist</li>
* </ul>
*/
public HoldBulkStatus getBulkStatus(String holdId, String holdBulkStatusId, String parameters)
{
mandatoryString("holdId", holdId);
mandatoryString("holdBulkStatusId", holdBulkStatusId);
return getRmRestWrapper().processModel(HoldBulkStatus.class, simpleRequest(
GET,
"holds/{holdId}/bulk-statuses/{holdBulkStatusId}",
holdId,
holdBulkStatusId,
parameters
));
}
/**
* See {@link #getBulkStatus(String, String, String)}
*/
public HoldBulkStatus getBulkStatus(String holdId, String holdBulkStatusId)
{
return getBulkStatus(holdId, holdBulkStatusId, EMPTY);
}
/**
* Gets the statuses of all bulk processes for a hold.
*
* @param holdId The identifier of a hold
* @param parameters The URL parameters to add
* @return The {@link HoldBulkStatusCollection} for the given {@code holdId}
* @throws RuntimeException for the following cases:
* <ul>
* <li>{@code holdId} is invalid</li>
* <li>authentication fails</li>
* <li>current user does not have permission to get the bulk statuses for {@code holdId}</li>
* <li>{@code holdId} does not exist</li>
* </ul>
*/
public HoldBulkStatusCollection getBulkStatuses(String holdId, String parameters)
{
mandatoryString("holdId", holdId);
return getRmRestWrapper().processModels(HoldBulkStatusCollection.class, simpleRequest(
GET,
"holds/{holdId}/bulk-statuses",
holdId,
parameters
));
}
/**
* See {@link #getBulkStatuses(String, String)}
*/
public HoldBulkStatusCollection getBulkStatuses(String holdId)
{
return getBulkStatuses(holdId, EMPTY);
}
}

View File

@@ -1,532 +0,0 @@
/*
* #%L
* Alfresco Records Management Module
* %%
* Copyright (C) 2005 - 2024 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.rest.rm.community.hold;
import static org.alfresco.rest.rm.community.base.TestData.HOLD_DESCRIPTION;
import static org.alfresco.rest.rm.community.base.TestData.HOLD_REASON;
import static org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanComponentAlias.FILE_PLAN_ALIAS;
import static org.alfresco.rest.rm.community.model.user.UserPermissions.PERMISSION_FILING;
import static org.alfresco.rest.rm.community.model.user.UserPermissions.PERMISSION_READ_RECORDS;
import static org.alfresco.rest.rm.community.util.CommonTestUtils.generateTestPrefix;
import static org.alfresco.utility.report.log.Step.STEP;
import static org.awaitility.Awaitility.await;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.springframework.http.HttpStatus.ACCEPTED;
import static org.springframework.http.HttpStatus.BAD_REQUEST;
import static org.springframework.http.HttpStatus.FORBIDDEN;
import static org.springframework.http.HttpStatus.NOT_FOUND;
import static org.springframework.http.HttpStatus.UNAUTHORIZED;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.alfresco.dataprep.CMISUtil;
import org.alfresco.dataprep.ContentActions;
import org.alfresco.rest.rm.community.base.BaseRMRestTest;
import org.alfresco.rest.rm.community.model.hold.Hold;
import org.alfresco.rest.rm.community.model.hold.HoldBulkOperation;
import org.alfresco.rest.rm.community.model.hold.HoldBulkOperation.HoldBulkOperationType;
import org.alfresco.rest.rm.community.model.hold.HoldBulkOperationEntry;
import org.alfresco.rest.rm.community.model.hold.HoldBulkStatus;
import org.alfresco.rest.rm.community.model.hold.HoldBulkStatus.Status;
import org.alfresco.rest.rm.community.model.hold.HoldBulkStatusCollection;
import org.alfresco.rest.rm.community.model.hold.HoldBulkStatusEntry;
import org.alfresco.rest.rm.community.model.hold.HoldChild;
import org.alfresco.rest.rm.community.model.hold.HoldChildEntry;
import org.alfresco.rest.rm.community.model.user.UserRoles;
import org.alfresco.rest.search.RestRequestQueryModel;
import org.alfresco.rest.search.SearchRequest;
import org.alfresco.rest.v0.service.RoleService;
import org.alfresco.utility.constants.UserRole;
import org.alfresco.utility.model.FileModel;
import org.alfresco.utility.model.FolderModel;
import org.alfresco.utility.model.UserModel;
import org.springframework.beans.factory.annotation.Autowired;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
/**
* API tests for adding items to holds via the bulk process
*/
public class AddToHoldsBulkV1Tests extends BaseRMRestTest
{
private static final String ACCESS_DENIED_ERROR_MESSAGE = "Access Denied. You do not have the appropriate " +
"permissions to perform this operation.";
private static final int NUMBER_OF_FILES = 5;
private final List<FileModel> addedFiles = new ArrayList<>();
private final List<UserModel> users = new ArrayList<>();
private final List<Hold> holds = new ArrayList<>();
private Hold hold;
private Hold hold2;
private Hold hold3;
private FolderModel rootFolder;
private HoldBulkOperation holdBulkOperation;
@Autowired
private RoleService roleService;
@Autowired
private ContentActions contentActions;
@BeforeClass(alwaysRun = true)
public void preconditionForAddContentToHold()
{
STEP("Create a hold.");
hold = getRestAPIFactory().getFilePlansAPI(getAdminUser()).createHold(
Hold.builder().name("HOLD" + generateTestPrefix(AddToHoldsV1Tests.class)).description(HOLD_DESCRIPTION)
.reason(HOLD_REASON).build(), FILE_PLAN_ALIAS);
holds.add(hold);
STEP("Create test files.");
testSite = dataSite.usingAdmin().createPublicRandomSite();
rootFolder = dataContent.usingAdmin().usingSite(testSite).createFolder();
FolderModel folder1 = dataContent.usingAdmin().usingResource(rootFolder).createFolder();
FolderModel folder2 = dataContent.usingAdmin().usingResource(folder1).createFolder();
// Add files to subfolders in the site
for (int i = 0; i < NUMBER_OF_FILES; i++)
{
FileModel documentHeld = dataContent.usingAdmin()
.usingResource(i % 2 == 0 ? folder1 : folder2)
.createContent(CMISUtil.DocumentType.TEXT_PLAIN);
addedFiles.add(documentHeld);
}
RestRequestQueryModel queryReq = getContentFromSiteQuery(testSite.getId());
SearchRequest searchRequest = new SearchRequest();
searchRequest.setQuery(queryReq);
STEP("Wait until all files are searchable.");
await().atMost(30, TimeUnit.SECONDS)
.until(() -> getRestAPIFactory().getSearchAPI(null).search(searchRequest).getPagination()
.getTotalItems() == NUMBER_OF_FILES);
holdBulkOperation = HoldBulkOperation.builder()
.query(queryReq)
.op(HoldBulkOperationType.ADD).build();
}
/**
* Given a user with the add to hold capability and hold filing permission
* When the user adds content from a site to a hold using the bulk API
* Then the content is added to the hold and the status of the bulk operation is DONE
*/
@Test
public void addContentFromTestSiteToHoldUsingBulkAPI()
{
UserModel userAddHoldPermission = roleService.createUserWithSiteRoleRMRoleAndPermission(testSite,
UserRole.SiteCollaborator, hold.getId(), UserRoles.ROLE_RM_MANAGER, PERMISSION_FILING);
users.add(userAddHoldPermission);
STEP("Add content from the site to the hold using the bulk API.");
HoldBulkOperationEntry bulkOperationEntry = getRestAPIFactory().getHoldsAPI(userAddHoldPermission)
.startBulkProcess(holdBulkOperation, hold.getId());
// Verify the status code
assertStatusCode(ACCEPTED);
assertEquals(NUMBER_OF_FILES, bulkOperationEntry.getTotalItems());
STEP("Wait until all files are added to the hold.");
await().atMost(20, TimeUnit.SECONDS).until(
() -> getRestAPIFactory().getHoldsAPI(getAdminUser()).getChildren(hold.getId()).getEntries().size()
== NUMBER_OF_FILES);
List<String> holdChildrenNodeRefs = getRestAPIFactory().getHoldsAPI(userAddHoldPermission)
.getChildren(hold.getId()).getEntries().stream().map(HoldChildEntry::getEntry).map(
HoldChild::getId).toList();
assertEquals(addedFiles.stream().map(FileModel::getNodeRefWithoutVersion).sorted().toList(),
holdChildrenNodeRefs.stream().sorted().toList());
STEP("Check the bulk status.");
HoldBulkStatus holdBulkStatus = getRestAPIFactory().getHoldsAPI(userAddHoldPermission)
.getBulkStatus(hold.getId(), bulkOperationEntry.getBulkStatusId());
assertBulkProcessStatus(holdBulkStatus, NUMBER_OF_FILES, 0, null);
STEP("Check the bulk statuses.");
HoldBulkStatusCollection holdBulkStatusCollection = getRestAPIFactory().getHoldsAPI(userAddHoldPermission)
.getBulkStatuses(hold.getId());
assertEquals(Arrays.asList(holdBulkStatus), holdBulkStatusCollection.getEntries().stream().map(HoldBulkStatusEntry::getEntry).toList());
}
/**
* Given a user with the add to hold capability and hold filing permission
* When the user adds content from a folder and all subfolders to a hold using the bulk API
* Then the content is added to the hold and the status of the bulk operation is DONE
*/
@Test
public void addContentFromFolderAndAllSubfoldersToHoldUsingBulkAPI()
{
hold3 = getRestAPIFactory().getFilePlansAPI(getAdminUser()).createHold(
Hold.builder().name("HOLD" + generateTestPrefix(AddToHoldsV1Tests.class)).description(HOLD_DESCRIPTION)
.reason(HOLD_REASON).build(), FILE_PLAN_ALIAS);
holds.add(hold3);
UserModel userAddHoldPermission = roleService.createUserWithSiteRoleRMRoleAndPermission(testSite,
UserRole.SiteCollaborator, hold3.getId(), UserRoles.ROLE_RM_MANAGER, PERMISSION_FILING);
users.add(userAddHoldPermission);
STEP("Add content from the site to the hold using the bulk API.");
// Get content from folder and all subfolders of the root folder
HoldBulkOperation bulkOperation = HoldBulkOperation.builder()
.query(getContentFromFolderAndAllSubfoldersQuery(rootFolder.getNodeRefWithoutVersion()))
.op(HoldBulkOperationType.ADD).build();
HoldBulkOperationEntry bulkOperationEntry = getRestAPIFactory().getHoldsAPI(userAddHoldPermission)
.startBulkProcess(bulkOperation, hold3.getId());
// Verify the status code
assertStatusCode(ACCEPTED);
assertEquals(NUMBER_OF_FILES, bulkOperationEntry.getTotalItems());
STEP("Wait until all files are added to the hold.");
await().atMost(20, TimeUnit.SECONDS).until(
() -> getRestAPIFactory().getHoldsAPI(getAdminUser()).getChildren(hold3.getId()).getEntries().size()
== NUMBER_OF_FILES);
List<String> holdChildrenNodeRefs = getRestAPIFactory().getHoldsAPI(userAddHoldPermission)
.getChildren(hold3.getId()).getEntries().stream().map(HoldChildEntry::getEntry).map(
HoldChild::getId).toList();
assertEquals(addedFiles.stream().map(FileModel::getNodeRefWithoutVersion).sorted().toList(),
holdChildrenNodeRefs.stream().sorted().toList());
STEP("Check the bulk status.");
HoldBulkStatus holdBulkStatus = getRestAPIFactory().getHoldsAPI(userAddHoldPermission)
.getBulkStatus(hold3.getId(), bulkOperationEntry.getBulkStatusId());
assertBulkProcessStatus(holdBulkStatus, NUMBER_OF_FILES, 0, null);
STEP("Check the bulk statuses.");
HoldBulkStatusCollection holdBulkStatusCollection = getRestAPIFactory().getHoldsAPI(userAddHoldPermission)
.getBulkStatuses(hold3.getId());
assertEquals(Arrays.asList(holdBulkStatus), holdBulkStatusCollection.getEntries().stream().map(HoldBulkStatusEntry::getEntry).toList());
}
/**
* Given a user without the add to hold capability
* When the user adds content from a site to a hold using the bulk API
* Then the user receives access denied error
*/
@Test
public void testBulkProcessWithUserWithoutAddToHoldCapability()
{
UserModel userWithoutAddToHoldCapability = roleService.createUserWithSiteRoleRMRoleAndPermission(testSite,
UserRole
.SiteCollaborator,
hold.getId(), UserRoles.ROLE_RM_POWER_USER, PERMISSION_FILING);
users.add(userWithoutAddToHoldCapability);
STEP("Add content from the site to the hold using the bulk API.");
getRestAPIFactory().getHoldsAPI(userWithoutAddToHoldCapability)
.startBulkProcess(holdBulkOperation, hold.getId());
STEP("Verify the response status code and the error message.");
assertStatusCode(FORBIDDEN);
getRestAPIFactory().getRmRestWrapper().assertLastError().containsSummary(ACCESS_DENIED_ERROR_MESSAGE);
}
/**
* Given a user without the filing permission on a hold
* When the user adds content from a site to a hold using the bulk API
* Then the user receives access denied error
*/
@Test
public void testBulkProcessWithUserWithoutFilingPermissionOnAHold()
{
// User without filing permission on a hold
UserModel userWithoutPermission = roleService.createUserWithSiteRoleRMRoleAndPermission(testSite,
UserRole.SiteCollaborator, hold.getId(), UserRoles.ROLE_RM_MANAGER, PERMISSION_READ_RECORDS);
users.add(userWithoutPermission);
STEP("Add content from the site to the hold using the bulk API.");
getRestAPIFactory().getHoldsAPI(userWithoutPermission)
.startBulkProcess(holdBulkOperation, hold.getId());
STEP("Verify the response status code and the error message.");
assertStatusCode(FORBIDDEN);
getRestAPIFactory().getRmRestWrapper().assertLastError().containsSummary(ACCESS_DENIED_ERROR_MESSAGE);
}
/**
* Given a user without the write permission on all the content
* When the user adds content from a site to a hold using the bulk API
* Then all processed items are marked as errors and the last error message contains access denied error
*/
@Test
public void testBulkProcessWithUserWithoutWritePermissionOnTheContent()
{
// User without write permission on the content
UserModel userWithoutPermission = roleService.createUserWithSiteRoleRMRoleAndPermission(
testSite, UserRole.SiteConsumer,
hold.getId(), UserRoles.ROLE_RM_MANAGER, PERMISSION_FILING);
users.add(userWithoutPermission);
// Wait until permissions are reverted
SearchRequest searchRequest = new SearchRequest();
searchRequest.setQuery(holdBulkOperation.getQuery());
await().atMost(30, TimeUnit.SECONDS)
.until(() -> getRestAPIFactory().getSearchAPI(userWithoutPermission).search(searchRequest).getPagination()
.getTotalItems() == NUMBER_OF_FILES);
STEP("Add content from the site to the hold using the bulk API.");
HoldBulkOperationEntry bulkOperationEntry = getRestAPIFactory().getHoldsAPI(
userWithoutPermission).startBulkProcess(holdBulkOperation, hold.getId());
STEP("Verify the response.");
assertStatusCode(ACCEPTED);
await().atMost(20, TimeUnit.SECONDS).until(() ->
getRestAPIFactory().getHoldsAPI(userWithoutPermission)
.getBulkStatus(hold.getId(), bulkOperationEntry.getBulkStatusId()).getStatus() == Status.DONE);
HoldBulkStatus holdBulkStatus = getRestAPIFactory().getHoldsAPI(userWithoutPermission)
.getBulkStatus(hold.getId(), bulkOperationEntry.getBulkStatusId());
assertBulkProcessStatus(holdBulkStatus, NUMBER_OF_FILES, NUMBER_OF_FILES, ACCESS_DENIED_ERROR_MESSAGE);
}
/**
* Given a user without the write permission on one file
* When the user adds content from a site to a hold using the bulk API
* Then all processed items are added to the hold except the one that the user does not have write permission
* And the status of the bulk operation is DONE, contains the error message and the number of errors is 1
*/
@Test
public void testBulkProcessWithUserWithoutWritePermissionOnOneFile()
{
hold2 = getRestAPIFactory().getFilePlansAPI(getAdminUser()).createHold(
Hold.builder().name("HOLD" + generateTestPrefix(AddToHoldsV1Tests.class)).description(HOLD_DESCRIPTION)
.reason(HOLD_REASON).build(), FILE_PLAN_ALIAS);
holds.add(hold2);
UserModel userAddHoldPermission = roleService.createUserWithSiteRoleRMRoleAndPermission(testSite,
UserRole.SiteCollaborator, hold2.getId(), UserRoles.ROLE_RM_MANAGER, PERMISSION_FILING);
users.add(userAddHoldPermission);
contentActions.setPermissionForUser(getAdminUser().getUsername(), getAdminUser().getPassword(),
testSite.getId(), addedFiles.get(0).getName(), userAddHoldPermission.getUsername(),
UserRole.SiteConsumer.getRoleId(), false);
STEP("Add content from the site to the hold using the bulk API.");
HoldBulkOperationEntry bulkOperationEntry = getRestAPIFactory().getHoldsAPI(userAddHoldPermission)
.startBulkProcess(holdBulkOperation, hold2.getId());
// Verify the status code
assertStatusCode(ACCEPTED);
assertEquals(NUMBER_OF_FILES, bulkOperationEntry.getTotalItems());
STEP("Wait until all files are added to the hold.");
await().atMost(30, TimeUnit.SECONDS).until(
() -> getRestAPIFactory().getHoldsAPI(getAdminUser()).getChildren(hold2.getId()).getEntries().size()
== NUMBER_OF_FILES - 1);
await().atMost(30, TimeUnit.SECONDS).until(
() -> getRestAPIFactory().getHoldsAPI(userAddHoldPermission)
.getBulkStatus(hold2.getId(), bulkOperationEntry.getBulkStatusId()).getProcessedItems() == NUMBER_OF_FILES);
List<String> holdChildrenNodeRefs = getRestAPIFactory().getHoldsAPI(userAddHoldPermission)
.getChildren(hold2.getId()).getEntries().stream().map(HoldChildEntry::getEntry).map(
HoldChild::getId).toList();
assertEquals(addedFiles.stream().skip(1).map(FileModel::getNodeRefWithoutVersion).sorted().toList(),
holdChildrenNodeRefs.stream().sorted().toList());
STEP("Check the bulk status.");
HoldBulkStatus holdBulkStatus = getRestAPIFactory().getHoldsAPI(userAddHoldPermission)
.getBulkStatus(hold2.getId(), bulkOperationEntry.getBulkStatusId());
assertBulkProcessStatus(holdBulkStatus, NUMBER_OF_FILES, 1, ACCESS_DENIED_ERROR_MESSAGE);
STEP("Check the bulk statuses.");
HoldBulkStatusCollection holdBulkStatusCollection = getRestAPIFactory().getHoldsAPI(userAddHoldPermission)
.getBulkStatuses(hold2.getId());
assertEquals(Arrays.asList(holdBulkStatus), holdBulkStatusCollection.getEntries().stream().map(HoldBulkStatusEntry::getEntry).toList());
// Revert the permissions
contentActions.setPermissionForUser(getAdminUser().getUsername(), getAdminUser().getPassword(),
testSite.getId(), addedFiles.get(0).getName(), userAddHoldPermission.getUsername(),
UserRole.SiteCollaborator.getRoleId(), true);
}
/**
* Given an unauthenticated user
* When the user adds content from a site to a hold using the bulk API
* Then the user receives unauthorized error
*/
@Test
public void testBulkProcessAsUnauthenticatedUser()
{
STEP("Start bulk process as unauthenticated user");
getRestAPIFactory().getHoldsAPI(new UserModel(getAdminUser().getUsername(), "wrongPassword"))
.startBulkProcess(holdBulkOperation, hold.getId());
STEP("Verify the response status code.");
assertStatusCode(UNAUTHORIZED);
}
/**
* Given a user with the add to hold capability and hold filing permission
* When the user adds content from a site to a hold using the bulk API
* And the hold does not exist
* Then the user receives not found error
*/
@Test
public void testBulkProcessForNonExistentHold()
{
STEP("Start bulk process for non existent hold");
getRestAPIFactory().getHoldsAPI(getAdminUser()).startBulkProcess(holdBulkOperation, "nonExistentHoldId");
STEP("Verify the response status code.");
assertStatusCode(NOT_FOUND);
}
/**
* Given a user with the add to hold capability and hold filing permission
* When the user adds content from a site to a hold using the bulk API
* and the bulk operation is invalid
* Then the user receives bad request error
*/
@Test
public void testGetBulkStatusesForInvalidOperation()
{
STEP("Start bulk process for non existent hold");
HoldBulkOperation invalidHoldBulkOperation = HoldBulkOperation.builder().op(null)
.query(holdBulkOperation.getQuery()).build();
getRestAPIFactory().getHoldsAPI(getAdminUser()).startBulkProcess(invalidHoldBulkOperation, hold.getId());
STEP("Verify the response status code.");
assertStatusCode(BAD_REQUEST);
}
/**
* Given a user with the add to hold capability and hold filing permission
* When the user adds content from a site to a hold using the bulk API
* And the hold does not exist
* Then the user receives not found error
*/
@Test
public void testGetBulkStatusForNonExistentHold()
{
STEP("Start bulk process for non existent hold");
getRestAPIFactory().getHoldsAPI(getAdminUser()).getBulkStatus("nonExistentHoldId", "nonExistenBulkStatusId");
STEP("Verify the response status code.");
assertStatusCode(NOT_FOUND);
}
/**
* Given a user with the add to hold capability and hold filing permission
* When the user adds content from a site to a hold using the bulk API
* And the bulk status does not exist
* Then the user receives not found error
*/
@Test
public void testGetBulkStatusForNonExistentBulkStatus()
{
STEP("Start bulk process for non bulk status");
getRestAPIFactory().getHoldsAPI(getAdminUser()).getBulkStatus(hold.getId(), "nonExistenBulkStatusId");
STEP("Verify the response status code.");
assertStatusCode(NOT_FOUND);
}
/**
* Given a user with the add to hold capability and hold filing permission
* When the user adds content from a site to a hold using the bulk API
* And the hold does not exist
* Then the user receives not found error
*/
@Test
public void testGetBulkStatusesForNonExistentHold()
{
STEP("Start bulk process for non existent hold");
getRestAPIFactory().getHoldsAPI(getAdminUser()).getBulkStatuses("nonExistentHoldId");
STEP("Verify the response status code.");
assertStatusCode(NOT_FOUND);
}
/**
* Given a user with the add to hold capability and hold filing permission
* When the user adds content from all sites to a hold using the bulk API to exceed the limit (30 items)
* Then the user receives bad request error
*/
@Test
public void testExceedingBulkOperationLimit()
{
RestRequestQueryModel queryReq = new RestRequestQueryModel();
queryReq.setQuery("TYPE:content");
queryReq.setLanguage("afts");
HoldBulkOperation exceedLimitOp = HoldBulkOperation.builder()
.query(queryReq)
.op(HoldBulkOperationType.ADD).build();
STEP("Start bulk process to exceed the limit");
getRestAPIFactory().getHoldsAPI(getAdminUser()).startBulkProcess(exceedLimitOp, hold.getId());
STEP("Verify the response status code.");
assertStatusCode(BAD_REQUEST);
}
private void assertBulkProcessStatus(HoldBulkStatus holdBulkStatus, long expectedProcessedItems,
int expectedErrorsCount, String expectedErrorMessage)
{
assertEquals(Status.DONE, holdBulkStatus.getStatus());
assertEquals(expectedProcessedItems, holdBulkStatus.getTotalItems());
assertEquals(expectedProcessedItems, holdBulkStatus.getProcessedItems());
assertEquals(expectedErrorsCount, holdBulkStatus.getErrorsCount());
assertNotNull(holdBulkStatus.getStartTime());
assertNotNull(holdBulkStatus.getEndTime());
if (expectedErrorMessage != null)
{
assertTrue(holdBulkStatus.getLastError().contains(expectedErrorMessage));
}
}
private RestRequestQueryModel getContentFromSiteQuery(String siteId)
{
RestRequestQueryModel queryReq = new RestRequestQueryModel();
queryReq.setQuery("SITE:\"" + siteId + "\" and TYPE:content");
queryReq.setLanguage("afts");
return queryReq;
}
private RestRequestQueryModel getContentFromFolderAndAllSubfoldersQuery(String folderId)
{
RestRequestQueryModel queryReq = new RestRequestQueryModel();
queryReq.setQuery("ANCESTOR:\"workspace://SpacesStore/" + folderId + "\" and TYPE:content");
queryReq.setLanguage("afts");
return queryReq;
}
@AfterClass(alwaysRun = true)
public void cleanupAddToHoldsBulkV1Tests()
{
dataSite.usingAdmin().deleteSite(testSite);
users.forEach(user -> getDataUser().usingAdmin().deleteUser(user));
holds.forEach(hold -> getRestAPIFactory().getHoldsAPI(getAdminUser()).deleteHold(hold.getId()));
}
}

View File

@@ -7,7 +7,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-governance-services-community-parent</artifactId>
<version>23.3.0.38-SNAPSHOT</version>
<version>23.3.0.54</version>
</parent>
<modules>

View File

@@ -1,3 +1,3 @@
SOLR6_TAG=2.0.10
SOLR6_TAG=2.0.11-A6
POSTGRES_TAG=15.4
ACTIVEMQ_TAG=5.18.3-jre17-rockylinux8

View File

@@ -139,21 +139,3 @@ content.metadata.async.extract.6.enabled=false
# Max number of entries returned in Record search view
rm.recordSearch.maxItems=500
#
# Hold bulk
#
# The number of worker threads.
rm.hold.bulk.threadCount=2
# The maximum number of total items to process in a single bulk operation.
rm.hold.bulk.maxItems=1000
# The number of entries to be fetched from the Search Service as a next set of work object to process.
rm.hold.bulk.batchSize=100
# The number of entries to process before reporting progress.
rm.hold.bulk.logging.interval=100
# The number of entries we process at a time in a transaction.
rm.hold.bulk.itemsPerTransaction=1
cache.bulkHoldStatusCache.cluster.type=fully-distributed
cache.bulkHoldRegistryCache.cluster.type=fully-distributed

View File

@@ -89,9 +89,6 @@
<!-- Import RM Audit -->
<import resource="classpath:alfresco/module/org_alfresco_module_rm/rm-audit-context.xml"/>
<!-- Import RM Bulk -->
<import resource="classpath:alfresco/module/org_alfresco_module_rm/rm-bulk-context.xml"/>
<!-- Import RM query context -->
<import resource="classpath:alfresco/module/org_alfresco_module_rm/query/rm-query-context.xml" />

View File

@@ -1,51 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<bean id="holdBulkService"
class="org.alfresco.module.org_alfresco_module_rm.bulk.hold.HoldBulkServiceImpl">
<property name="serviceRegistry" ref="ServiceRegistry" />
<property name="transactionService" ref="transactionService" />
<property name="searchMapper" ref="searchapiSearchMapper" />
<property name="bulkMonitor" ref="holdBulkMonitor" />
<property name="holdService" ref="HoldService" />
<property name="capabilityService" ref="CapabilityService" />
<property name="permissionService" ref="PermissionService" />
<property name="nodeService" ref="NodeService" />
<property name="threadCount">
<value>${rm.hold.bulk.threadCount}</value>
</property>
<property name="batchSize">
<value>${rm.hold.bulk.batchSize}</value>
</property>
<property name="maxItems">
<value>${rm.hold.bulk.maxItems}</value>
</property>
<property name="loggingInterval">
<value>${rm.hold.bulk.logging.interval}</value>
</property>
<property name="itemsPerTransaction">
<value>${rm.hold.bulk.itemsPerTransaction}</value>
</property>
</bean>
<bean id="holdBulkMonitor" class="org.alfresco.module.org_alfresco_module_rm.bulk.hold.DefaultHoldBulkMonitor">
<property name="holdProgressCache" ref="holdProgressCache" />
<property name="holdProcessRegistry" ref="holdProcessRegistry" />
</bean>
<bean name="holdProgressCache" factory-bean="cacheFactory" factory-method="createCache">
<constructor-arg value="cache.bulkHoldStatusCache" />
</bean>
<bean name="holdProcessRegistry" factory-bean="cacheFactory" factory-method="createCache">
<constructor-arg value="cache.bulkHoldRegistryCache" />
</bean>
</beans>

View File

@@ -83,13 +83,6 @@
<property name="nodesModelFactory" ref="nodesModelFactory" />
<property name="fileFolderService" ref="FileFolderService" />
<property name="transactionService" ref="transactionService" />
<property name="holdBulkService" ref="holdBulkService" />
</bean>
<bean class="org.alfresco.rm.rest.api.holds.HoldsBulkStatusesRelation" >
<property name="holdBulkMonitor" ref="holdBulkMonitor" />
<property name="apiUtils" ref="apiUtils" />
<property name="permissionService" ref="PermissionService" />
</bean>
<bean class="org.alfresco.rm.rest.api.holds.HoldsChildrenRelation">

View File

@@ -41,8 +41,6 @@ services:
-Daos.baseUrlOverwrite=http://localhost:8080/alfresco/aos
-Dmessaging.broker.url=\"failover:(tcp://activemq:61616)?timeout=3000&jms.useCompression=true\"
-DlocalTransform.core-aio.url=http://transform-core-aio:8090/
-Drm.hold.bulk.maxItems=5
-Drm.hold.bulk.batchSize=2
"
ports:
- 8080:8080

View File

@@ -8,7 +8,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-governance-services-community-repo-parent</artifactId>
<version>23.3.0.38-SNAPSHOT</version>
<version>23.3.0.54</version>
</parent>
<properties>

View File

@@ -1,249 +0,0 @@
/*
* #%L
* Alfresco Records Management Module
* %%
* Copyright (C) 2005 - 2024 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.module.org_alfresco_module_rm.bulk;
import java.util.UUID;
import org.alfresco.repo.batch.BatchProcessWorkProvider;
import org.alfresco.repo.batch.BatchProcessor;
import org.alfresco.repo.batch.BatchProcessor.BatchProcessWorker;
import org.alfresco.rest.api.search.impl.SearchMapper;
import org.alfresco.rest.api.search.model.Query;
import org.alfresco.rest.framework.core.exceptions.InvalidArgumentException;
import org.alfresco.service.ServiceRegistry;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.search.ResultSet;
import org.alfresco.service.cmr.search.SearchParameters;
import org.alfresco.service.cmr.search.SearchService;
import org.alfresco.service.transaction.TransactionService;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean;
/**
* A base class for executing bulk operations on nodes based on search query results
*/
public abstract class BulkBaseService<T> implements InitializingBean
{
private static final Log LOG = LogFactory.getLog(BulkBaseService.class);
protected ServiceRegistry serviceRegistry;
protected SearchService searchService;
protected TransactionService transactionService;
protected SearchMapper searchMapper;
protected BulkMonitor<T> bulkMonitor;
protected int threadCount;
protected int batchSize;
protected int itemsPerTransaction;
protected int maxItems;
protected int loggingInterval;
@Override
public void afterPropertiesSet() throws Exception
{
this.searchService = serviceRegistry.getSearchService();
}
/**
* Execute bulk operation on node based on the search query results
*
* @param nodeRef node reference
* @param bulkOperation bulk operation
* @return bulk status
*/
public T execute(NodeRef nodeRef, BulkOperation bulkOperation)
{
checkPermissions(nodeRef, bulkOperation);
ResultSet resultSet = getTotalItems(bulkOperation.searchQuery(), maxItems);
if (maxItems < resultSet.getNumberFound() || resultSet.hasMore())
{
throw new InvalidArgumentException("Too many items to process. Please refine your query.");
}
long totalItems = resultSet.getNumberFound();
// Generate a random process id
String processId = UUID.randomUUID().toString();
T initBulkStatus = getInitBulkStatus(processId, totalItems);
bulkMonitor.updateBulkStatus(initBulkStatus);
bulkMonitor.registerProcess(nodeRef, processId);
BatchProcessWorker<NodeRef> batchProcessWorker = getWorkerProvider(nodeRef, bulkOperation);
BulkStatusUpdater bulkStatusUpdater = getBulkStatusUpdater();
BatchProcessor<NodeRef> batchProcessor = new BatchProcessor<>(
processId,
transactionService.getRetryingTransactionHelper(),
getWorkProvider(bulkOperation, totalItems, bulkStatusUpdater),
threadCount,
itemsPerTransaction,
bulkStatusUpdater,
LOG,
loggingInterval);
runAsyncBatchProcessor(batchProcessor, batchProcessWorker, bulkStatusUpdater);
return initBulkStatus;
}
/**
* Run batch processor
*/
protected void runAsyncBatchProcessor(BatchProcessor<NodeRef> batchProcessor,
BatchProcessWorker<NodeRef> batchProcessWorker, BulkStatusUpdater bulkStatusUpdater)
{
Runnable backgroundLogic = () -> {
try
{
if (LOG.isDebugEnabled())
{
LOG.debug("Started processing batch with name: " + batchProcessor.getProcessName());
}
batchProcessor.processLong(batchProcessWorker, true);
if (LOG.isDebugEnabled())
{
LOG.debug("Processing batch with name: " + batchProcessor.getProcessName() + " completed");
}
}
catch (Exception exception)
{
LOG.error("Error processing batch with name: " + batchProcessor.getProcessName(), exception);
}
finally
{
bulkStatusUpdater.update();
}
};
Thread backgroundThread = new Thread(backgroundLogic, "BulkBaseService-BackgroundThread");
backgroundThread.start();
}
/**
* Get initial bulk status
*
* @param processId process id
* @param totalItems total items
* @return bulk status
*/
protected abstract T getInitBulkStatus(String processId, long totalItems);
/**
* Get bulk status updater
*
* @return bulk status updater
*/
protected abstract BulkStatusUpdater getBulkStatusUpdater();
/**
* Get work provider
*
* @param bulkOperation bulk operation
* @param totalItems total items
* @param bulkStatusUpdater bulk status updater
* @return work provider
*/
protected abstract BatchProcessWorkProvider<NodeRef> getWorkProvider(BulkOperation bulkOperation, long totalItems,
BulkStatusUpdater bulkStatusUpdater);
/**
* Get worker provider
*
* @param nodeRef node reference
* @param bulkOperation bulk operation
* @return worker provider
*/
protected abstract BatchProcessWorker<NodeRef> getWorkerProvider(NodeRef nodeRef, BulkOperation bulkOperation);
/**
* Check permissions
*
* @param nodeRef node reference
* @param bulkOperation bulk operation
*/
protected abstract void checkPermissions(NodeRef nodeRef, BulkOperation bulkOperation);
protected ResultSet getTotalItems(Query searchQuery, int skipCount)
{
SearchParameters searchParams = new SearchParameters();
searchMapper.setDefaults(searchParams);
searchMapper.fromQuery(searchParams, searchQuery);
searchParams.setSkipCount(skipCount);
searchParams.setMaxItems(1);
return searchService.query(searchParams);
}
public void setServiceRegistry(ServiceRegistry serviceRegistry)
{
this.serviceRegistry = serviceRegistry;
}
public void setSearchService(SearchService searchService)
{
this.searchService = searchService;
}
public void setTransactionService(TransactionService transactionService)
{
this.transactionService = transactionService;
}
public void setSearchMapper(SearchMapper searchMapper)
{
this.searchMapper = searchMapper;
}
public void setBulkMonitor(BulkMonitor<T> bulkMonitor)
{
this.bulkMonitor = bulkMonitor;
}
public void setThreadCount(int threadCount)
{
this.threadCount = threadCount;
}
public void setBatchSize(int batchSize)
{
this.batchSize = batchSize;
}
public void setMaxItems(int maxItems)
{
this.maxItems = maxItems;
}
public void setLoggingInterval(int loggingInterval)
{
this.loggingInterval = loggingInterval;
}
public void setItemsPerTransaction(int itemsPerTransaction)
{
this.itemsPerTransaction = itemsPerTransaction;
}
}

View File

@@ -1,58 +0,0 @@
/*
* #%L
* Alfresco Records Management Module
* %%
* Copyright (C) 2005 - 2024 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.module.org_alfresco_module_rm.bulk;
import org.alfresco.service.cmr.repository.NodeRef;
/**
* An interface for monitoring the progress of a bulk operation
*/
public interface BulkMonitor<T>
{
/**
* Update the bulk status
*
* @param bulkStatus the bulk status
*/
void updateBulkStatus(T bulkStatus);
/**
* Register a process
*
* @param nodeRef the node reference
* @param processId the process id
*/
void registerProcess(NodeRef nodeRef, String processId);
/**
* Get the bulk status
*
* @param bulkStatusId the bulk status id
* @return the bulk status
*/
T getBulkStatus(String bulkStatusId);
}

View File

@@ -1,44 +0,0 @@
/*
* #%L
* Alfresco Records Management Module
* %%
* Copyright (C) 2005 - 2024 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.module.org_alfresco_module_rm.bulk;
import org.alfresco.rest.api.search.model.Query;
/**
* An immutable POJO to represent a bulk operation
*/
public record BulkOperation(Query searchQuery, String operationType)
{
public BulkOperation
{
if (operationType == null || searchQuery == null)
{
throw new IllegalArgumentException("Operation type and search query must not be null");
}
}
}

View File

@@ -1,40 +0,0 @@
/*
* #%L
* Alfresco Records Management Module
* %%
* Copyright (C) 2005 - 2024 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.module.org_alfresco_module_rm.bulk;
import org.springframework.context.ApplicationEventPublisher;
/**
* An interface for updating the status of a bulk operation
*/
public interface BulkStatusUpdater extends ApplicationEventPublisher
{
/**
* Update the bulk status
*/
void update();
}

View File

@@ -1,109 +0,0 @@
/*
* #%L
* Alfresco Records Management Module
* %%
* Copyright (C) 2005 - 2024 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.module.org_alfresco_module_rm.bulk.hold;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import org.alfresco.repo.cache.SimpleCache;
import org.alfresco.rm.rest.api.model.HoldBulkStatus;
import org.alfresco.service.cmr.repository.NodeRef;
import org.springframework.context.ApplicationEvent;
import org.springframework.extensions.surf.util.AbstractLifecycleBean;
/**
* Default hold bulk monitor implementation
*/
public class DefaultHoldBulkMonitor extends AbstractLifecycleBean implements HoldBulkMonitor
{
protected SimpleCache<String, HoldBulkStatus> holdProgressCache;
protected SimpleCache<String, List<HoldBulkProcessDetails>> holdProcessRegistry;
@Override
public void updateBulkStatus(HoldBulkStatus holdBulkStatus)
{
holdProgressCache.put(holdBulkStatus.bulkStatusId(), holdBulkStatus);
}
@Override
public void registerProcess(NodeRef holdRef, String processId)
{
List<HoldBulkProcessDetails> processIds = Optional.ofNullable(holdProcessRegistry.get(holdRef.getId()))
.orElse(new ArrayList<>());
processIds.add(new HoldBulkProcessDetails(processId, null));
holdProcessRegistry.put(holdRef.getId(), processIds);
}
@Override
public HoldBulkStatus getBulkStatus(String bulkStatusId)
{
return holdProgressCache.get(bulkStatusId);
}
@Override
public List<HoldBulkStatus> getBulkStatusesForHold(String holdId)
{
return Optional.ofNullable(holdProcessRegistry.get(holdId))
.map(bulkProcessDetailsList -> bulkProcessDetailsList.stream()
.map(HoldBulkProcessDetails::bulkStatusId)
.map(this::getBulkStatus)
.filter(Objects::nonNull)
.sorted(Comparator.comparing(HoldBulkStatus::endTime, Comparator.nullsLast(Comparator.naturalOrder()))
.thenComparing(HoldBulkStatus::startTime, Comparator.nullsLast(Comparator.naturalOrder()))
.reversed())
.toList())
.orElse(Collections.emptyList());
}
public void setHoldProgressCache(
SimpleCache<String, HoldBulkStatus> holdProgressCache)
{
this.holdProgressCache = holdProgressCache;
}
public void setHoldProcessRegistry(
SimpleCache<String, List<HoldBulkProcessDetails>> holdProcessRegistry)
{
this.holdProcessRegistry = holdProcessRegistry;
}
@Override
protected void onBootstrap(ApplicationEvent applicationEvent)
{
// NOOP
}
@Override
protected void onShutdown(ApplicationEvent applicationEvent)
{
// NOOP
}
}

View File

@@ -1,46 +0,0 @@
/*
* #%L
* Alfresco Records Management Module
* %%
* Copyright (C) 2005 - 2024 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.module.org_alfresco_module_rm.bulk.hold;
import java.util.List;
import org.alfresco.module.org_alfresco_module_rm.bulk.BulkMonitor;
import org.alfresco.rm.rest.api.model.HoldBulkStatus;
/**
* An interface for monitoring the progress of a bulk hold operation
*/
public interface HoldBulkMonitor extends BulkMonitor<HoldBulkStatus>
{
/**
* Get the bulk statuses for a hold
*
* @param holdId the hold id
* @return the bulk statuses
*/
List<HoldBulkStatus> getBulkStatusesForHold(String holdId);
}

View File

@@ -1,36 +0,0 @@
/*
* #%L
* Alfresco Records Management Module
* %%
* Copyright (C) 2005 - 2024 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.module.org_alfresco_module_rm.bulk.hold;
import java.io.Serializable;
/**
* A simple immutable POJO to hold the details of a bulk hold process
*/
public record HoldBulkProcessDetails(String bulkStatusId, String creatorInstance) implements Serializable
{
}

View File

@@ -1,45 +0,0 @@
/*
* #%L
* Alfresco Records Management Module
* %%
* Copyright (C) 2005 - 2024 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.module.org_alfresco_module_rm.bulk.hold;
import org.alfresco.module.org_alfresco_module_rm.bulk.BulkOperation;
import org.alfresco.rm.rest.api.model.HoldBulkStatus;
import org.alfresco.service.cmr.repository.NodeRef;
/**
* Interface defining a hold bulk service.
*/
public interface HoldBulkService
{
/**
* Initiates a bulk operation on a hold.
*
* @param holdRef The hold reference
* @param bulkOperation The bulk operation
*/
HoldBulkStatus execute(NodeRef holdRef, BulkOperation bulkOperation);
}

View File

@@ -1,258 +0,0 @@
/*
* #%L
* Alfresco Records Management Module
* %%
* Copyright (C) 2005 - 2024 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.module.org_alfresco_module_rm.bulk.hold;
import static org.alfresco.model.ContentModel.PROP_NAME;
import static org.alfresco.rm.rest.api.model.HoldBulkOperationType.ADD;
import java.util.Collection;
import java.util.Collections;
import java.util.Locale;
import java.util.concurrent.atomic.AtomicInteger;
import org.alfresco.model.ContentModel;
import org.alfresco.module.org_alfresco_module_rm.bulk.BulkBaseService;
import org.alfresco.module.org_alfresco_module_rm.bulk.BulkOperation;
import org.alfresco.module.org_alfresco_module_rm.bulk.BulkStatusUpdater;
import org.alfresco.module.org_alfresco_module_rm.capability.CapabilityService;
import org.alfresco.module.org_alfresco_module_rm.capability.RMPermissionModel;
import org.alfresco.module.org_alfresco_module_rm.hold.HoldService;
import org.alfresco.repo.batch.BatchProcessWorkProvider;
import org.alfresco.repo.batch.BatchProcessor.BatchProcessWorker;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.security.permissions.AccessDeniedException;
import org.alfresco.rest.api.search.model.Query;
import org.alfresco.rest.framework.core.exceptions.InvalidArgumentException;
import org.alfresco.rm.rest.api.model.HoldBulkOperationType;
import org.alfresco.rm.rest.api.model.HoldBulkStatus;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.search.ResultSet;
import org.alfresco.service.cmr.search.SearchParameters;
import org.alfresco.service.cmr.security.AccessStatus;
import org.alfresco.service.cmr.security.PermissionService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.extensions.surf.util.I18NUtil;
/**
* Implementation of the {@link HoldBulkService} interface.
*/
@SuppressWarnings("PMD.PreserveStackTrace")
public class HoldBulkServiceImpl extends BulkBaseService<HoldBulkStatus> implements HoldBulkService
{
private static final Logger LOGGER = LoggerFactory.getLogger(HoldBulkServiceImpl.class);
private HoldService holdService;
private static final String MSG_ERR_ACCESS_DENIED = "permissions.err_access_denied";
private CapabilityService capabilityService;
private PermissionService permissionService;
private NodeService nodeService;
@Override
protected HoldBulkStatus getInitBulkStatus(String processId, long totalItems)
{
return new HoldBulkStatus(processId, null, null, 0, 0, totalItems, null);
}
@Override
protected BulkStatusUpdater getBulkStatusUpdater()
{
return new HoldBulkStatusUpdater((HoldBulkMonitor) bulkMonitor);
}
@Override
protected BatchProcessWorkProvider<NodeRef> getWorkProvider(BulkOperation bulkOperation, long totalItems,
BulkStatusUpdater bulkStatusUpdater)
{
return new AddToHoldWorkerProvider(new AtomicInteger(0), bulkOperation, totalItems, bulkStatusUpdater);
}
@Override
protected BatchProcessWorker<NodeRef> getWorkerProvider(NodeRef nodeRef, BulkOperation bulkOperation)
{
try
{
HoldBulkOperationType holdBulkOperationType = HoldBulkOperationType.valueOf(bulkOperation.operationType()
.toUpperCase(Locale.ENGLISH));
return switch (holdBulkOperationType)
{
case ADD -> new AddToHoldWorkerBatch(nodeRef);
};
}
catch (IllegalArgumentException e)
{
String errorMsg = "Unsupported action type when starting the bulk process: ";
if (LOGGER.isDebugEnabled())
{
LOGGER.debug("{} {}", errorMsg, bulkOperation.operationType(), e);
}
throw new InvalidArgumentException(errorMsg + bulkOperation.operationType());
}
}
@Override
protected void checkPermissions(NodeRef holdRef, BulkOperation bulkOperation)
{
if (!holdService.isHold(holdRef))
{
final String holdName = (String) nodeService.getProperty(holdRef, PROP_NAME);
throw new InvalidArgumentException(I18NUtil.getMessage("rm.hold.not-hold", holdName), null);
}
if (ADD.name().equals(bulkOperation.operationType()) && (!AccessStatus.ALLOWED.equals(
capabilityService.getCapabilityAccessState(holdRef, RMPermissionModel.ADD_TO_HOLD)) ||
permissionService.hasPermission(holdRef, RMPermissionModel.FILING) == AccessStatus.DENIED))
{
throw new AccessDeniedException(I18NUtil.getMessage(MSG_ERR_ACCESS_DENIED));
}
}
private class AddToHoldWorkerBatch implements BatchProcessWorker<NodeRef>
{
private final NodeRef holdRef;
private final String currentUser;
public AddToHoldWorkerBatch(NodeRef holdRef)
{
this.holdRef = holdRef;
currentUser = AuthenticationUtil.getFullyAuthenticatedUser();
}
@Override
public String getIdentifier(NodeRef entry)
{
return entry.getId();
}
@Override
public void beforeProcess()
{
AuthenticationUtil.pushAuthentication();
}
@Override
public void process(NodeRef entry) throws Throwable
{
AuthenticationUtil.setFullyAuthenticatedUser(currentUser);
holdService.addToHold(holdRef, entry);
}
@Override
public void afterProcess()
{
AuthenticationUtil.popAuthentication();
}
}
private class AddToHoldWorkerProvider implements BatchProcessWorkProvider<NodeRef>
{
private final AtomicInteger currentNodeNumber;
private final Query searchQuery;
private final String currentUser;
private final long totalItems;
private final BulkStatusUpdater bulkStatusUpdater;
public AddToHoldWorkerProvider(AtomicInteger currentNodeNumber, BulkOperation bulkOperation, long totalItems,
BulkStatusUpdater bulkStatusUpdater)
{
this.currentNodeNumber = currentNodeNumber;
this.searchQuery = bulkOperation.searchQuery();
this.totalItems = totalItems;
this.bulkStatusUpdater = bulkStatusUpdater;
currentUser = AuthenticationUtil.getFullyAuthenticatedUser();
}
@Override
public int getTotalEstimatedWorkSize()
{
return (int) totalItems;
}
@Override
public long getTotalEstimatedWorkSizeLong()
{
return totalItems;
}
@Override
public Collection<NodeRef> getNextWork()
{
AuthenticationUtil.pushAuthentication();
AuthenticationUtil.setFullyAuthenticatedUser(currentUser);
SearchParameters searchParams = getNextPageParameters();
ResultSet result = searchService.query(searchParams);
if (result.getNodeRefs().isEmpty())
{
return Collections.emptyList();
}
AuthenticationUtil.popAuthentication();
if (LOGGER.isDebugEnabled())
{
LOGGER.debug("Processing the next work for the batch processor, skipCount={}, size={}",
searchParams.getSkipCount(), result.getNumberFound());
}
currentNodeNumber.addAndGet(batchSize);
bulkStatusUpdater.update();
return result.getNodeRefs();
}
private SearchParameters getNextPageParameters()
{
SearchParameters searchParams = new SearchParameters();
searchMapper.setDefaults(searchParams);
searchMapper.fromQuery(searchParams, searchQuery);
searchParams.setSkipCount(currentNodeNumber.get());
searchParams.setMaxItems(batchSize);
searchParams.setLimit(batchSize);
searchParams.addSort("@" + ContentModel.PROP_CREATED, true);
return searchParams;
}
}
public void setHoldService(HoldService holdService)
{
this.holdService = holdService;
}
public void setCapabilityService(CapabilityService capabilityService)
{
this.capabilityService = capabilityService;
}
public void setPermissionService(PermissionService permissionService)
{
this.permissionService = permissionService;
}
public void setNodeService(NodeService nodeService)
{
this.nodeService = nodeService;
}
}

View File

@@ -1,69 +0,0 @@
/*
* #%L
* Alfresco Records Management Module
* %%
* Copyright (C) 2005 - 2024 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.module.org_alfresco_module_rm.bulk.hold;
import org.alfresco.module.org_alfresco_module_rm.bulk.BulkStatusUpdater;
import org.alfresco.repo.batch.BatchMonitor;
import org.alfresco.repo.batch.BatchMonitorEvent;
import org.alfresco.rm.rest.api.model.HoldBulkStatus;
/**
* An implementation of {@link BulkStatusUpdater} for the hold bulk operation
*/
public class HoldBulkStatusUpdater implements BulkStatusUpdater
{
private final Runnable task;
private BatchMonitor batchMonitor;
public HoldBulkStatusUpdater(HoldBulkMonitor holdBulkMonitor)
{
this.task = () -> holdBulkMonitor.updateBulkStatus(
new HoldBulkStatus(batchMonitor.getProcessName(), batchMonitor.getStartTime(),
batchMonitor.getEndTime(),
batchMonitor.getSuccessfullyProcessedEntriesLong() + batchMonitor.getTotalErrorsLong(),
batchMonitor.getTotalErrorsLong(), batchMonitor.getTotalResultsLong(),
batchMonitor.getLastError()));
}
@Override
public void update()
{
if (task != null && batchMonitor != null)
{
task.run();
}
}
@Override
public void publishEvent(Object event)
{
if (event instanceof BatchMonitorEvent batchMonitorEvent)
{
batchMonitor = batchMonitorEvent.getBatchMonitor();
}
}
}

View File

@@ -31,7 +31,6 @@ import java.util.HashMap;
import java.util.Map;
import org.alfresco.model.ContentModel;
import org.alfresco.module.org_alfresco_module_rm.capability.RMPermissionModel;
import org.alfresco.module.org_alfresco_module_rm.capability.declarative.DeclarativeCapability;
import org.alfresco.module.org_alfresco_module_rm.record.RecordService;
import org.alfresco.module.org_alfresco_module_rm.recordfolder.RecordFolderService;
@@ -76,7 +75,7 @@ public class CreateCapability extends DeclarativeCapability
@Override
public int evaluate(NodeRef nodeRef)
{
return evaluate(nodeRef, null, null);
return evaluate(nodeRef, null, null, null);
}
/**
@@ -85,9 +84,10 @@ public class CreateCapability extends DeclarativeCapability
* @param destination destination node reference
* @param linkee linkee node reference, can be null
* @param assocType association type, can be null
* @param recordType record type, can be null
* @return
*/
public int evaluate(NodeRef destination, NodeRef linkee, QName assocType)
public int evaluate(NodeRef destination, NodeRef linkee, QName assocType, QName recordType)
{
if (linkee != null)
{
@@ -105,7 +105,7 @@ public class CreateCapability extends DeclarativeCapability
{
if (recordService.isRecord(destination) &&
!recordService.isDeclared(destination) &&
permissionService.hasPermission(destination, RMPermissionModel.FILE_RECORDS) == AccessStatus.ALLOWED)
permissionService.hasPermission(destination, FILE_RECORDS) == AccessStatus.ALLOWED)
{
return AccessDecisionVoter.ACCESS_GRANTED;
}
@@ -115,7 +115,7 @@ public class CreateCapability extends DeclarativeCapability
if (recordService.isRecord(linkee) &&
recordService.isRecord(destination) &&
!recordService.isDeclared(destination) &&
permissionService.hasPermission(destination, RMPermissionModel.FILE_RECORDS) == AccessStatus.ALLOWED)
permissionService.hasPermission(destination, FILE_RECORDS) == AccessStatus.ALLOWED)
{
return AccessDecisionVoter.ACCESS_GRANTED;
}
@@ -132,14 +132,15 @@ public class CreateCapability extends DeclarativeCapability
// if the destination folder is not a record folder and the user has filling capability on it, grant access to create the record
if (checkConditions(destination, conditions) &&
!recordFolderService.isRecordFolder(destination) )
!recordFolderService.isRecordFolder(destination) &&
permissionService.hasPermission(destination, CREATE_MODIFY_DESTROY_FILEPLAN_METADATA) == AccessStatus.ALLOWED)
{
return AccessDecisionVoter.ACCESS_GRANTED;
}
if (checkConditions(destination, conditions) &&
recordFolderService.isRecordFolder(destination) &&
permissionService.hasPermission(destination, RMPermissionModel.FILE_RECORDS) == AccessStatus.ALLOWED)
permissionService.hasPermission(destination, FILE_RECORDS) == AccessStatus.ALLOWED)
{
return AccessDecisionVoter.ACCESS_GRANTED;
}
@@ -147,7 +148,7 @@ public class CreateCapability extends DeclarativeCapability
conditions.put("capabilityCondition.closed", Boolean.TRUE);
if (checkConditions(destination, conditions) &&
recordFolderService.isRecordFolder(destination) &&
permissionService.hasPermission(getFilePlanService().getFilePlan(destination), RMPermissionModel.DECLARE_RECORDS_IN_CLOSED_FOLDERS) == AccessStatus.ALLOWED)
permissionService.hasPermission(getFilePlanService().getFilePlan(destination), DECLARE_RECORDS_IN_CLOSED_FOLDERS) == AccessStatus.ALLOWED)
{
return AccessDecisionVoter.ACCESS_GRANTED;
}
@@ -156,32 +157,32 @@ public class CreateCapability extends DeclarativeCapability
conditions.put("capabilityCondition.cutoff", Boolean.TRUE);
if (checkConditions(destination, conditions) &&
recordFolderService.isRecordFolder(destination) &&
permissionService.hasPermission(getFilePlanService().getFilePlan(destination), RMPermissionModel.CREATE_MODIFY_RECORDS_IN_CUTOFF_FOLDERS) == AccessStatus.ALLOWED)
permissionService.hasPermission(getFilePlanService().getFilePlan(destination), CREATE_MODIFY_RECORDS_IN_CUTOFF_FOLDERS) == AccessStatus.ALLOWED)
{
return AccessDecisionVoter.ACCESS_GRANTED;
}
}
if (capabilityService.getCapability(RMPermissionModel.CREATE_MODIFY_DESTROY_FOLDERS).evaluate(destination) == AccessDecisionVoter.ACCESS_GRANTED)
if (null != recordType && recordType.equals(TYPE_RECORD_FOLDER) && capabilityService.getCapability(CREATE_MODIFY_DESTROY_FOLDERS).evaluate(destination) == AccessDecisionVoter.ACCESS_GRANTED)
{
return AccessDecisionVoter.ACCESS_GRANTED;
}
if (capabilityService.getCapability(RMPermissionModel.DECLARE_RECORDS_IN_CLOSED_FOLDERS).evaluate(destination) == AccessDecisionVoter.ACCESS_GRANTED)
if (capabilityService.getCapability(DECLARE_RECORDS_IN_CLOSED_FOLDERS).evaluate(destination) == AccessDecisionVoter.ACCESS_GRANTED)
{
return AccessDecisionVoter.ACCESS_GRANTED;
}
if (capabilityService.getCapability(RMPermissionModel.CREATE_MODIFY_RECORDS_IN_CUTOFF_FOLDERS).evaluate(destination) == AccessDecisionVoter.ACCESS_GRANTED)
if (capabilityService.getCapability(CREATE_MODIFY_RECORDS_IN_CUTOFF_FOLDERS).evaluate(destination) == AccessDecisionVoter.ACCESS_GRANTED)
{
return AccessDecisionVoter.ACCESS_GRANTED;
}
if (capabilityService.getCapability(RMPermissionModel.CREATE_MODIFY_DESTROY_FILEPLAN_METADATA).evaluate(destination) == AccessDecisionVoter.ACCESS_GRANTED)
if (capabilityService.getCapability(CREATE_MODIFY_DESTROY_FILEPLAN_METADATA).evaluate(destination) == AccessDecisionVoter.ACCESS_GRANTED)
{
return AccessDecisionVoter.ACCESS_GRANTED;
}
if (capabilityService.getCapability(RMPermissionModel.CREATE_HOLD).evaluate(destination) == AccessDecisionVoter.ACCESS_GRANTED)
if (capabilityService.getCapability(CREATE_HOLD).evaluate(destination) == AccessDecisionVoter.ACCESS_GRANTED)
{
return AccessDecisionVoter.ACCESS_GRANTED;
}
if (((ChangeOrDeleteReferencesCapability)capabilityService.getCapability(RMPermissionModel.CHANGE_OR_DELETE_REFERENCES)).evaluate(destination, linkee) == AccessDecisionVoter.ACCESS_GRANTED)
if (((ChangeOrDeleteReferencesCapability)capabilityService.getCapability(CHANGE_OR_DELETE_REFERENCES)).evaluate(destination, linkee) == AccessDecisionVoter.ACCESS_GRANTED)
{
return AccessDecisionVoter.ACCESS_GRANTED;
}

View File

@@ -28,6 +28,7 @@
package org.alfresco.module.org_alfresco_module_rm.capability.policy;
import org.alfresco.module.org_alfresco_module_rm.capability.impl.CreateCapability;
import org.alfresco.module.org_alfresco_module_rm.model.RecordsManagementModel;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.namespace.QName;
import org.aopalliance.intercept.MethodInvocation;
@@ -42,10 +43,18 @@ public class CreatePolicy extends AbstractBasePolicy
{
NodeRef linkee = null;
QName assocType = null;
QName recordType = null;
// get the destination node
NodeRef destination = getTestNode(invocation, params, cad.getParameters().get(0), cad.isParent());
//get the recordType
for (Object qname : invocation.getArguments()) {
if (qname != null && (qname.equals(RecordsManagementModel.TYPE_RECORD_FOLDER) || qname.equals(RecordsManagementModel.TYPE_RECORD_CATEGORY))) {
recordType = (QName) qname;
}
}
if (cad.getParameters().size() > 1)
{
// get the linkee when present
@@ -58,7 +67,7 @@ public class CreatePolicy extends AbstractBasePolicy
}
}
return ((CreateCapability) getCapabilityService().getCapability("Create")).evaluate(destination, linkee, assocType);
return ((CreateCapability) getCapabilityService().getCapability("Create")).evaluate(destination, linkee, assocType, recordType);
}
}

View File

@@ -1,120 +0,0 @@
/*
* #%L
* Alfresco Records Management Module
* %%
* Copyright (C) 2005 - 2024 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.rm.rest.api.holds;
import static org.alfresco.module.org_alfresco_module_rm.util.RMParameterCheck.checkNotBlank;
import static org.alfresco.util.ParameterCheck.mandatory;
import java.util.LinkedList;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import org.alfresco.module.org_alfresco_module_rm.bulk.hold.HoldBulkMonitor;
import org.alfresco.module.org_alfresco_module_rm.model.RecordsManagementModel;
import org.alfresco.rest.framework.core.exceptions.EntityNotFoundException;
import org.alfresco.rest.framework.core.exceptions.PermissionDeniedException;
import org.alfresco.rest.framework.core.exceptions.RelationshipResourceNotFoundException;
import org.alfresco.rest.framework.resource.RelationshipResource;
import org.alfresco.rest.framework.resource.actions.interfaces.RelationshipResourceAction;
import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo;
import org.alfresco.rest.framework.resource.parameters.Parameters;
import org.alfresco.rm.rest.api.impl.FilePlanComponentsApiUtils;
import org.alfresco.rm.rest.api.model.HoldBulkStatus;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.security.AccessStatus;
import org.alfresco.service.cmr.security.PermissionService;
import org.springframework.extensions.surf.util.I18NUtil;
@RelationshipResource(name = "bulk-statuses", entityResource = HoldsEntityResource.class, title = "Bulk statuses of a hold")
public class HoldsBulkStatusesRelation
implements RelationshipResourceAction.Read<HoldBulkStatus>, RelationshipResourceAction.ReadById<HoldBulkStatus>
{
private HoldBulkMonitor holdBulkMonitor;
private FilePlanComponentsApiUtils apiUtils;
private PermissionService permissionService;
@Override
public CollectionWithPagingInfo<HoldBulkStatus> readAll(String holdId, Parameters parameters)
{
// validate parameters
checkNotBlank("holdId", holdId);
mandatory("parameters", parameters);
NodeRef holdRef = apiUtils.lookupAndValidateNodeType(holdId, RecordsManagementModel.TYPE_HOLD);
checkReadPermissions(holdRef);
List<HoldBulkStatus> statuses = holdBulkMonitor.getBulkStatusesForHold(holdId);
List<HoldBulkStatus> page = statuses.stream()
.skip(parameters.getPaging().getSkipCount())
.limit(parameters.getPaging().getMaxItems())
.collect(Collectors.toCollection(LinkedList::new));
int totalItems = statuses.size();
boolean hasMore = parameters.getPaging().getSkipCount() + parameters.getPaging().getMaxItems() < totalItems;
return CollectionWithPagingInfo.asPaged(parameters.getPaging(), page, hasMore, totalItems);
}
@Override
public HoldBulkStatus readById(String holdId, String bulkStatusId, Parameters parameters)
throws RelationshipResourceNotFoundException
{
checkNotBlank("holdId", holdId);
checkNotBlank("bulkStatusId", bulkStatusId);
mandatory("parameters", parameters);
NodeRef holdRef = apiUtils.lookupAndValidateNodeType(holdId, RecordsManagementModel.TYPE_HOLD);
checkReadPermissions(holdRef);
return Optional.ofNullable(holdBulkMonitor.getBulkStatus(bulkStatusId)).orElseThrow(() -> new EntityNotFoundException(bulkStatusId));
}
private void checkReadPermissions(NodeRef holdRef)
{
if (permissionService.hasReadPermission(holdRef) == AccessStatus.DENIED)
{
throw new PermissionDeniedException(I18NUtil.getMessage("permissions.err_access_denied"));
}
}
public void setHoldBulkMonitor(HoldBulkMonitor holdBulkMonitor)
{
this.holdBulkMonitor = holdBulkMonitor;
}
public void setApiUtils(FilePlanComponentsApiUtils apiUtils)
{
this.apiUtils = apiUtils;
}
public void setPermissionService(PermissionService permissionService)
{
this.permissionService = permissionService;
}
}

View File

@@ -30,8 +30,6 @@ import static org.alfresco.module.org_alfresco_module_rm.util.RMParameterCheck.c
import static org.alfresco.util.ParameterCheck.mandatory;
import jakarta.servlet.http.HttpServletResponse;
import org.alfresco.module.org_alfresco_module_rm.bulk.BulkOperation;
import org.alfresco.module.org_alfresco_module_rm.bulk.hold.HoldBulkService;
import org.alfresco.module.org_alfresco_module_rm.hold.HoldService;
import org.alfresco.module.org_alfresco_module_rm.model.RecordsManagementModel;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
@@ -44,9 +42,6 @@ import org.alfresco.rest.framework.resource.parameters.Parameters;
import org.alfresco.rest.framework.webscripts.WithResponse;
import org.alfresco.rm.rest.api.impl.ApiNodesModelFactory;
import org.alfresco.rm.rest.api.impl.FilePlanComponentsApiUtils;
import org.alfresco.rm.rest.api.model.HoldBulkOperation;
import org.alfresco.rm.rest.api.model.HoldBulkOperationEntry;
import org.alfresco.rm.rest.api.model.HoldBulkStatus;
import org.alfresco.rm.rest.api.model.HoldDeletionReason;
import org.alfresco.rm.rest.api.model.HoldModel;
import org.alfresco.service.cmr.model.FileFolderService;
@@ -73,7 +68,6 @@ public class HoldsEntityResource implements
private ApiNodesModelFactory nodesModelFactory;
private HoldService holdService;
private TransactionService transactionService;
private HoldBulkService holdBulkService;
@Override
public void afterPropertiesSet() throws Exception
@@ -163,23 +157,6 @@ public class HoldsEntityResource implements
return reason;
}
@Operation("bulk")
@WebApiDescription(title = "Start the hold bulk operation",
successStatus = HttpServletResponse.SC_ACCEPTED)
public HoldBulkOperationEntry bulk(String holdId, HoldBulkOperation holdBulkOperation, Parameters parameters,
WithResponse withResponse)
{
// validate parameters
checkNotBlank("holdId", holdId);
mandatory("parameters", parameters);
NodeRef parentNodeRef = apiUtils.lookupAndValidateNodeType(holdId, RecordsManagementModel.TYPE_HOLD);
HoldBulkStatus holdBulkStatus = holdBulkService.execute(parentNodeRef,
new BulkOperation(holdBulkOperation.query(), holdBulkOperation.op().name()));
return new HoldBulkOperationEntry(holdBulkStatus.bulkStatusId(), holdBulkStatus.totalItems());
}
public void setApiUtils(FilePlanComponentsApiUtils apiUtils)
{
this.apiUtils = apiUtils;
@@ -204,9 +181,4 @@ public class HoldsEntityResource implements
{
this.transactionService = transactionService;
}
public void setHoldBulkService(HoldBulkService holdBulkService)
{
this.holdBulkService = holdBulkService;
}
}

View File

@@ -1,33 +0,0 @@
/*
* #%L
* Alfresco Records Management Module
* %%
* Copyright (C) 2005 - 2024 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.rm.rest.api.model;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.alfresco.rest.api.search.model.Query;
public record HoldBulkOperation(@JsonProperty(required = true) Query query, @JsonProperty(required = true) HoldBulkOperationType op) {}

View File

@@ -1,29 +0,0 @@
/*
* #%L
* Alfresco Records Management Module
* %%
* Copyright (C) 2005 - 2024 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.rm.rest.api.model;
public record HoldBulkOperationEntry(String bulkStatusId, long totalItems){}

View File

@@ -1,38 +0,0 @@
/*
* #%L
* Alfresco Records Management Module
* %%
* Copyright (C) 2005 - 2024 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.rm.rest.api.model;
/**
* This enum represents the types of bulk operations that can be performed on holds
*/
public enum HoldBulkOperationType
{
/**
* The ADD operation represents adding items to a hold in bulk.
*/
ADD
}

View File

@@ -1,69 +0,0 @@
/*
* #%L
* Alfresco Records Management Module
* %%
* Copyright (C) 2005 - 2024 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.rm.rest.api.model;
import java.io.Serializable;
import java.util.Date;
public record HoldBulkStatus(String bulkStatusId, Date startTime, Date endTime, long processedItems, long errorsCount,
long totalItems, String lastError) implements Serializable
{
public enum Status
{
PENDING("PENDING"),
IN_PROGRESS("IN PROGRESS"),
DONE("DONE");
private final String value;
Status(String value)
{
this.value = value;
}
public String getValue()
{
return value;
}
}
public String getStatus()
{
if (startTime == null && endTime == null)
{
return Status.PENDING.getValue();
}
else if (startTime != null && endTime == null)
{
return Status.IN_PROGRESS.getValue();
}
else
{
return Status.DONE.getValue();
}
}
}

View File

@@ -180,6 +180,7 @@ public class CreateRecordTest extends BaseRMTestCase
Set<Capability> capabilities = new HashSet<>(2);
capabilities.add(capabilityService.getCapability("ViewRecords"));
capabilities.add(capabilityService.getCapability("CreateRecords"));
capabilities.add(capabilityService.getCapability("CreateModifyDestroyFileplanMetadata"));
filePlanRoleService.createRole(filePlan, roleName, roleName, capabilities);
@@ -189,6 +190,7 @@ public class CreateRecordTest extends BaseRMTestCase
//give read and file permission to user on unfiled records container
filePlanPermissionService.setPermission(unfiledContainer , user, RMPermissionModel.FILING);
filePlanPermissionService.setPermission(unfiledContainer , user, RMPermissionModel.CREATE_MODIFY_DESTROY_FILEPLAN_METADATA);
}
public void when()

View File

@@ -1,119 +0,0 @@
/*
* #%L
* Alfresco Records Management Module
* %%
* Copyright (C) 2005 - 2024 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.module.org_alfresco_module_rm.bulk;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.when;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import org.alfresco.module.org_alfresco_module_rm.bulk.hold.DefaultHoldBulkMonitor;
import org.alfresco.module.org_alfresco_module_rm.bulk.hold.HoldBulkProcessDetails;
import org.alfresco.repo.cache.SimpleCache;
import org.alfresco.rm.rest.api.model.HoldBulkStatus;
import org.alfresco.service.cmr.repository.NodeRef;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
public class DefaultHoldBulkMonitorUnitTest
{
@Mock
private SimpleCache<String, HoldBulkStatus> holdProgressCache;
@Mock
private SimpleCache<String, List<HoldBulkProcessDetails>> holdProcessRegistry;
private DefaultHoldBulkMonitor holdBulkMonitor;
@Before
public void setUp()
{
MockitoAnnotations.openMocks(this);
holdBulkMonitor = new DefaultHoldBulkMonitor();
holdBulkMonitor.setHoldProgressCache(holdProgressCache);
holdBulkMonitor.setHoldProcessRegistry(holdProcessRegistry);
}
@Test
public void testUpdateBulkStatus()
{
HoldBulkStatus status = new HoldBulkStatus("bulkStatusId", null, null, 0L, 0L, 0L, null);
holdBulkMonitor.updateBulkStatus(status);
Mockito.verify(holdProgressCache).put("bulkStatusId", status);
}
@Test
public void testRegisterProcess()
{
NodeRef holdRef = new NodeRef("workspace://SpacesStore/holdId");
String processId = "processId";
when(holdProcessRegistry.get(holdRef.getId())).thenReturn(null);
holdBulkMonitor.registerProcess(holdRef, processId);
Mockito.verify(holdProcessRegistry)
.put(holdRef.getId(), Arrays.asList(new HoldBulkProcessDetails(processId, null)));
}
@Test
public void testGetBulkStatusesForHoldReturnsEmptyListWhenNoProcesses()
{
when(holdProcessRegistry.get("holdId")).thenReturn(null);
assertEquals(Collections.emptyList(), holdBulkMonitor.getBulkStatusesForHold("holdId"));
}
@Test
public void testGetBulkStatusesForHoldReturnsSortedStatuses()
{
HoldBulkStatus status1 = new HoldBulkStatus(null, new Date(1000), new Date(2000), 0L, 0L, 0L, null);
HoldBulkStatus status2 = new HoldBulkStatus(null, new Date(3000), null, 0L, 0L, 0L, null);
HoldBulkStatus status3 = new HoldBulkStatus(null, new Date(4000), null, 0L, 0L, 0L, null);
HoldBulkStatus status4 = new HoldBulkStatus(null, new Date(500), new Date(800), 0L, 0L, 0L, null);
HoldBulkStatus status5 = new HoldBulkStatus(null, null, null, 0L, 0L, 0L, null);
when(holdProcessRegistry.get("holdId")).thenReturn(
Arrays.asList("process1", "process2", "process3", "process4", "process5")
.stream().map(bulkStatusId -> new HoldBulkProcessDetails(bulkStatusId, null)).toList());
when(holdProgressCache.get("process1")).thenReturn(status1);
when(holdProgressCache.get("process2")).thenReturn(status2);
when(holdProgressCache.get("process3")).thenReturn(status3);
when(holdProgressCache.get("process4")).thenReturn(status4);
when(holdProgressCache.get("process5")).thenReturn(status5);
assertEquals(Arrays.asList(status5, status3, status2, status1, status4),
holdBulkMonitor.getBulkStatusesForHold("holdId"));
}
}

View File

@@ -7,7 +7,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-governance-services-community-repo-parent</artifactId>
<version>23.3.0.38-SNAPSHOT</version>
<version>23.3.0.54</version>
</parent>
<build>

View File

@@ -2314,112 +2314,6 @@ paths:
description: Unexpected error
schema:
$ref: '#/definitions/Error'
'/holds/{holdId}/bulk-statuses':
get:
tags:
- holds
operationId: listBulkStatuses
summary: Get bulk statuses
description: |
Gets bulk statuses for hold with id **holdId**.
parameters:
- $ref: '#/parameters/holdIdParam'
- $ref: '#/parameters/skipCountParam'
- $ref: '#/parameters/maxItemsParam'
responses:
'200':
description: Successful response
schema:
$ref: '#/definitions/HoldBulkStatusPaging'
'400':
description: |
Invalid parameter: **holdId** is not a valid format
'401':
description: Authentication failed
'403':
description: Current user does not have permission to read **holdId**
'404':
description: "**holdId** does not exist"
default:
description: Unexpected error
schema:
$ref: '#/definitions/Error'
'/holds/{holdId}/bulk-statuses/{bulkStatusId}':
get:
tags:
- holds
operationId: getBulkStatus
summary: Get a bulk status
description: |
Gets a bulk status specified by **bulkStatusId** for **holdId**.
parameters:
- $ref: '#/parameters/holdIdParam'
- $ref: '#/parameters/bulkStatusId'
responses:
'200':
description: Successful response
schema:
$ref: '#/definitions/HoldBulkStatus'
'400':
description: |
Invalid parameter: **holdId** or **bulkStatusId** is not a valid format
'401':
description: Authentication failed
'403':
description: Current user does not have permission to read **holdId**
'404':
description: "**holdId** or **bulkStatusId** does not exist"
default:
description: Unexpected error
schema:
$ref: '#/definitions/Error'
'/holds/{holdId}/bulk':
post:
tags:
- holds
operationId: startHoldBulkProcess
summary: Start the hold bulk process
description: |
Start the asynchronous bulk process for a hold with id **holdId** based on search query results.
```JSON
For example, the following JSON body starts the bulk process to add search query results
as children of a hold.
{
"query": {
"query": "SITE:swsdp and TYPE:content",
"language": "afts"
},
"op": "ADD"
}
```
parameters:
- $ref: '#/parameters/holdIdParam'
- in: body
name: holdBulkOperation
description: Bulk operation.
required: true
schema:
$ref: '#/definitions/HoldBulkOperation'
responses:
'202':
description: Successful response
schema:
$ref: '#/definitions/HoldBulkOperationEntry'
'400':
description: |
Invalid parameter: **holdId** is not a valid format or **HoldBulkOperation** is not valid
'401':
description: Authentication failed
'403':
description: Current user does not have permission to start the bulk process for **holdId**
'404':
description: "**holdId** does not exist"
default:
description: Unexpected error
schema:
$ref: '#/definitions/Error'
'/holds/{holdId}/delete':
post:
tags:
@@ -2968,12 +2862,6 @@ parameters:
description: The identifier of a child of a hold.
required: true
type: string
bulkStatusId:
name: bulkStatusId
in: path
description: The identifier of a bulk process.
required: true
type: string
## Record
recordIdParam:
name: recordId
@@ -4130,91 +4018,6 @@ definitions:
properties:
reason:
type: string
SearchRequestQuery:
type: object
required:
- query
properties:
language:
description: The query language in which the query is written.
type: string
default: afts
enum:
- afts
- lucene
- cmis
userQuery:
description: The search request typed in by the user
type: string
query:
description: The query which may have been generated in some way from the userQuery
type: string
HoldBulkOperation:
type: object
properties:
query:
$ref: '#/definitions/SearchRequestQuery'
op:
description: The operation type.
type: string
default: ADD
enum:
- ADD
HoldBulkOperationEntry:
type: object
properties:
bulkStatusId:
type: string
totalItems:
type: integer
format: int64
HoldBulkStatus:
type: object
properties:
bulkStatusId:
type: string
startTime:
type: string
format: date-time
endTime:
type: string
format: date-time
processedItems:
type: integer
format: int64
errorsCount:
type: integer
format: int64
totalItems:
type: integer
format: int64
lastError:
type: string
status:
type: string
enum:
- PENDING
- IN PROGRESS
- DONE
HoldBulkStatusEntry:
type: object
required:
- entry
properties:
entry:
$ref: '#/definitions/HoldBulkStatus'
HoldBulkStatusPaging:
type: object
properties:
list:
type: object
properties:
pagination:
$ref: '#/definitions/Pagination'
entries:
type: array
items:
$ref: '#/definitions/HoldBulkStatusEntry'
##
RequestBodyFile:
type: object

View File

@@ -7,7 +7,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-community-repo</artifactId>
<version>23.3.0.38-SNAPSHOT</version>
<version>23.3.0.54</version>
</parent>
<modules>

View File

@@ -8,7 +8,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-community-repo-amps</artifactId>
<version>23.3.0.38-SNAPSHOT</version>
<version>23.3.0.54</version>
</parent>
<properties>

View File

@@ -7,7 +7,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-community-repo</artifactId>
<version>23.3.0.38-SNAPSHOT</version>
<version>23.3.0.54</version>
</parent>
<dependencies>

View File

@@ -7,7 +7,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-community-repo</artifactId>
<version>23.3.0.38-SNAPSHOT</version>
<version>23.3.0.54</version>
</parent>
<properties>

View File

@@ -2,7 +2,7 @@
* #%L
* Alfresco Data model classes
* %%
* Copyright (C) 2005 - 2016 Alfresco Software Limited
* Copyright (C) 2005 - 2024 Alfresco Software Limited
* %%
* This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of
@@ -78,6 +78,28 @@ public interface PermissionService
*/
public static final String GUEST_AUTHORITY = "ROLE_GUEST";
/**
* The dynamic authority for the Admin service account.
*/
String ADMIN_SVC_AUTHORITY = "ROLE_ADMIN_SERVICE_ACCOUNT";
/**
* The dynamic authority for the Collaborator service account.
*/
String COLLABORATOR_SVC_AUTHORITY = "ROLE_COLLABORATOR_SERVICE_ACCOUNT";
/**
* The dynamic authority for the Editor service account.
*/
String EDITOR_SVC_AUTHORITY = "ROLE_EDITOR_SERVICE_ACCOUNT";
/**
* A convenient set of service account authorities to simplify checks
* for whether a given authority is a service account authority or not.
*/
Set<String> SVC_AUTHORITIES_SET = Set.of(ADMIN_SVC_AUTHORITY, COLLABORATOR_SVC_AUTHORITY,
EDITOR_SVC_AUTHORITY);
/**
* The permission for all - not defined in the model. Repsected in the code.
*/

View File

@@ -7,7 +7,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-community-repo</artifactId>
<version>23.3.0.38-SNAPSHOT</version>
<version>23.3.0.54</version>
</parent>
<dependencies>

View File

@@ -9,6 +9,6 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-community-repo-packaging</artifactId>
<version>23.3.0.38-SNAPSHOT</version>
<version>23.3.0.54</version>
</parent>
</project>

View File

@@ -7,7 +7,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-community-repo-packaging</artifactId>
<version>23.3.0.38-SNAPSHOT</version>
<version>23.3.0.54</version>
</parent>
<properties>

View File

@@ -7,7 +7,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-community-repo</artifactId>
<version>23.3.0.38-SNAPSHOT</version>
<version>23.3.0.54</version>
</parent>
<modules>

View File

@@ -1,3 +1,3 @@
SOLR6_TAG=2.0.10
SOLR6_TAG=2.0.11-A6
POSTGRES_TAG=15.4
ACTIVEMQ_TAG=5.18.3-jre17-rockylinux8

View File

@@ -6,7 +6,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-community-repo-packaging</artifactId>
<version>23.3.0.38-SNAPSHOT</version>
<version>23.3.0.54</version>
</parent>
<modules>

View File

@@ -7,7 +7,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-community-repo-tests</artifactId>
<version>23.3.0.38-SNAPSHOT</version>
<version>23.3.0.54</version>
</parent>
<organization>

View File

@@ -9,7 +9,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-community-repo-tests</artifactId>
<version>23.3.0.38-SNAPSHOT</version>
<version>23.3.0.54</version>
</parent>
<developers>

View File

@@ -9,7 +9,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-community-repo-tests</artifactId>
<version>23.3.0.38-SNAPSHOT</version>
<version>23.3.0.54</version>
</parent>
<developers>

View File

@@ -8,7 +8,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-community-repo-tests</artifactId>
<version>23.3.0.38-SNAPSHOT</version>
<version>23.3.0.54</version>
</parent>
<properties>

View File

@@ -9,7 +9,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-community-repo-tests</artifactId>
<version>23.3.0.38-SNAPSHOT</version>
<version>23.3.0.54</version>
</parent>
<developers>

View File

@@ -7,7 +7,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-community-repo-packaging</artifactId>
<version>23.3.0.38-SNAPSHOT</version>
<version>23.3.0.54</version>
</parent>
<properties>

28
pom.xml
View File

@@ -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>23.3.0.38-SNAPSHOT</version>
<version>23.3.0.54</version>
<packaging>pom</packaging>
<name>Alfresco Community Repo Parent</name>
@@ -51,20 +51,20 @@
<dependency.alfresco-server-root.version>7.0.1</dependency.alfresco-server-root.version>
<dependency.activiti-engine.version>5.23.0</dependency.activiti-engine.version>
<dependency.activiti.version>5.23.0</dependency.activiti.version>
<dependency.alfresco-transform-core.version>5.1.2-A1</dependency.alfresco-transform-core.version>
<dependency.alfresco-transform-service.version>4.1.2-A1</dependency.alfresco-transform-service.version>
<dependency.alfresco-transform-core.version>5.1.3-A3</dependency.alfresco-transform-core.version>
<dependency.alfresco-transform-service.version>4.1.3-A2</dependency.alfresco-transform-service.version>
<dependency.alfresco-greenmail.version>7.0</dependency.alfresco-greenmail.version>
<dependency.acs-event-model.version>0.0.27</dependency.acs-event-model.version>
<dependency.aspectj.version>1.9.20.1</dependency.aspectj.version>
<dependency.spring.version>6.0.19</dependency.spring.version>
<dependency.spring-security.version>6.2.2</dependency.spring-security.version>
<dependency.spring-security.version>6.3.0</dependency.spring-security.version>
<dependency.antlr.version>3.5.3</dependency.antlr.version>
<dependency.jackson.version>2.15.2</dependency.jackson.version>
<dependency.cxf.version>4.0.2</dependency.cxf.version>
<dependency.opencmis.version>1.0.0-jakarta-1</dependency.opencmis.version>
<dependency.webscripts.version>9.0</dependency.webscripts.version>
<dependency.bouncycastle.version>1.77</dependency.bouncycastle.version>
<dependency.bouncycastle.version>1.78.1</dependency.bouncycastle.version>
<dependency.mockito-core.version>5.4.0</dependency.mockito-core.version>
<dependency.assertj.version>3.24.2</dependency.assertj.version>
<dependency.org-json.version>20231013</dependency.org-json.version>
@@ -85,8 +85,8 @@
<dependency.truezip.version>7.7.10</dependency.truezip.version>
<dependency.poi.version>5.2.5</dependency.poi.version>
<dependency.jboss.logging.version>3.5.0.Final</dependency.jboss.logging.version>
<dependency.camel.version>4.0.0</dependency.camel.version> <!-- when bumping this version, please keep track/sync with included netty.io dependencies -->
<dependency.netty.version>4.1.96.Final</dependency.netty.version> <!-- must be in sync with camels transitive dependencies, e.g.: netty-common -->
<dependency.camel.version>4.0.5</dependency.camel.version> <!-- when bumping this version, please keep track/sync with included netty.io dependencies -->
<dependency.netty.version>4.1.110.Final</dependency.netty.version> <!-- must be in sync with camels transitive dependencies, e.g.: netty-common -->
<dependency.activemq.version>5.18.3</dependency.activemq.version>
<dependency.apache-compress.version>1.26.0</dependency.apache-compress.version>
<dependency.awaitility.version>4.2.0</dependency.awaitility.version>
@@ -113,7 +113,7 @@
<dependency.jakarta-json-path.version>2.9.0</dependency.jakarta-json-path.version>
<dependency.json-smart.version>2.5.0</dependency.json-smart.version>
<alfresco.googledrive.version>4.1.0</alfresco.googledrive.version>
<alfresco.aos-module.version>3.0.0</alfresco.aos-module.version>
<alfresco.aos-module.version>3.1.0-A1</alfresco.aos-module.version>
<alfresco.api-explorer.version>23.2.0</alfresco.api-explorer.version> <!-- Also in alfresco-enterprise-share -->
<alfresco.maven-plugin.version>2.2.0</alfresco.maven-plugin.version>
@@ -123,7 +123,7 @@
<dependency.mysql.version>8.0.30</dependency.mysql.version>
<dependency.mysql-image.version>8</dependency.mysql-image.version>
<dependency.mariadb.version>2.7.4</dependency.mariadb.version>
<dependency.tas-utility.version>5.0.0</dependency.tas-utility.version>
<dependency.tas-utility.version>5.0.1</dependency.tas-utility.version>
<dependency.rest-assured.version>5.3.2</dependency.rest-assured.version>
<dependency.tas-email.version>2.0.0</dependency.tas-email.version>
<dependency.tas-webdav.version>1.21</dependency.tas-webdav.version>
@@ -151,7 +151,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>23.3.0.54</tag>
</scm>
<distributionManagement>
@@ -1123,10 +1123,10 @@
<exclude>com.sun.xml.bind</exclude>
</excludes>
<includes>
<include>org.bouncycastle:bcprov-jdk18on:[1.77,)</include>
<include>org.bouncycastle:bcmail-jdk18on:[1.77,)</include>
<include>org.bouncycastle:bcpkix-jdk18on:[1.77,)</include>
<include>org.bouncycastle:bcutil-jdk18on:[1.77,)</include>
<include>org.bouncycastle:bcprov-jdk18on:[1.78.1,)</include>
<include>org.bouncycastle:bcmail-jdk18on:[1.78.1,)</include>
<include>org.bouncycastle:bcpkix-jdk18on:[1.78.1,)</include>
<include>org.bouncycastle:bcutil-jdk18on:[1.78.1,)</include>
</includes>
</bannedDependencies>
</rules>

View File

@@ -7,7 +7,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-community-repo</artifactId>
<version>23.3.0.38-SNAPSHOT</version>
<version>23.3.0.54</version>
</parent>
<dependencies>

View File

@@ -7,7 +7,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-community-repo</artifactId>
<version>23.3.0.38-SNAPSHOT</version>
<version>23.3.0.54</version>
</parent>
<dependencies>

View File

@@ -0,0 +1,83 @@
/*
* #%L
* Alfresco Repository
* %%
* Copyright (C) 2005 - 2024 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.security.permissions.dynamic;
import java.util.Optional;
import java.util.Set;
import org.alfresco.repo.serviceaccount.ServiceAccountRegistry;
import org.alfresco.repo.security.permissions.DynamicAuthority;
import org.alfresco.repo.security.permissions.PermissionReference;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.util.PropertyCheck;
import org.springframework.beans.factory.InitializingBean;
/**
* This class represents a dynamic authority for service accounts in the system.
*
* @author Jamal Kaabi-Mofrad
*/
public class ServiceAccountAuthority implements DynamicAuthority, InitializingBean
{
private String authority;
private ServiceAccountRegistry serviceAccountRegistry;
public void setAuthority(String authority)
{
this.authority = authority;
}
public void setServiceAccountRegistry(ServiceAccountRegistry serviceAccountRegistry)
{
this.serviceAccountRegistry = serviceAccountRegistry;
}
@Override
public void afterPropertiesSet() throws Exception
{
PropertyCheck.mandatory(this, "authority", authority);
PropertyCheck.mandatory(this, "serviceAccountRegistry", serviceAccountRegistry);
}
@Override
public boolean hasAuthority(NodeRef nodeRef, String userName)
{
Optional<String> role = serviceAccountRegistry.getServiceAccountRole(userName);
return role.isPresent() && role.get().equals(this.getAuthority());
}
@Override
public String getAuthority()
{
return this.authority;
}
@Override
public Set<PermissionReference> requiredFor()
{
return null;
}
}

View File

@@ -0,0 +1,61 @@
/*
* #%L
* Alfresco Repository
* %%
* Copyright (C) 2005 - 2024 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.serviceaccount;
import java.util.Optional;
import java.util.Set;
/**
* A service account registry that allows service accounts to be registered
* with their corresponding roles.
*
* @author Jamal Kaabi-Mofrad
*/
public interface ServiceAccountRegistry
{
/**
* Registers a service account with its corresponding role.
*
* @param serviceAccountName The name of the service account to be registered.
* @param serviceAccountRole The role of the service account to be registered.
*/
void register(String serviceAccountName, String serviceAccountRole);
/**
* Retrieves the role of a specific service account.
*
* @param serviceAccountName The name of the service account.
* @return An Optional containing the role of the service account if it exists, otherwise an empty Optional.
*/
Optional<String> getServiceAccountRole(String serviceAccountName);
/**
* Retrieves the names of all service accounts.
*
* @return A set of service account names. If no service accounts are present, an empty set is returned.
*/
Set<String> getServiceAccountNames();
}

View File

@@ -0,0 +1,157 @@
/*
* #%L
* Alfresco Repository
* %%
* Copyright (C) 2005 - 2024 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.serviceaccount;
import java.util.Locale;
import java.util.Optional;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import org.alfresco.service.cmr.security.PermissionService;
import org.alfresco.util.PropertyCheck;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
/**
* Processes the <b>alfresco-global</b> properties file and applies a naming convention to distinguish the service
* account's name and role.
* <p>
* The naming convention adheres to the following format:
* <p>
* <pre>
* {@code
* serviceaccount.role.<service-account-name>=<service-account-role>
* }
* </pre>
* <p>
* Please note, any property with an invalid role value will be disregarded and the corresponding service account
* will not be registered.
* <p>
* For instance, to register a service account named 'custom-app-sa' with the 'Editor' role (which allows it to
* update node properties), the following should be defined in the <b>alfresco-global</b> properties file:
* <ul>
* <li>serviceaccount.role.custom-app-sa=EDITOR_SERVICE_ACCOUNT</li>
* <li>or</li>
* <li>serviceaccount.role.custom-app-sa=ROLE_EDITOR_SERVICE_ACCOUNT</li>
* </ul>
*
* @author Jamal Kaabi-Mofrad
*/
public class ServiceAccountRegistryImpl implements ServiceAccountRegistry, InitializingBean
{
private static final Logger LOGGER = LoggerFactory.getLogger(ServiceAccountRegistryImpl.class);
public static final String KEY_PREFIX = "serviceaccount.role.";
private Properties globalProperties;
private final ConcurrentMap<String, String> saRoleMap = new ConcurrentHashMap<>();
public void setGlobalProperties(Properties globalProperties)
{
this.globalProperties = globalProperties;
}
@Override
public void register(String serviceAccountName, String serviceAccountRole)
{
saRoleMap.put(serviceAccountName, serviceAccountRole);
LOGGER.info("Service account '{}' is registered with the role '{}'.", serviceAccountName, serviceAccountRole);
}
@Override
public Optional<String> getServiceAccountRole(String serviceAccountName)
{
return Optional.ofNullable(saRoleMap.get(serviceAccountName));
}
@Override
public Set<String> getServiceAccountNames()
{
return Set.copyOf(saRoleMap.keySet());
}
private void init()
{
globalProperties.stringPropertyNames()
.stream()
.filter(key -> key.startsWith(KEY_PREFIX))
.forEach(key -> {
String name = key.substring(KEY_PREFIX.length());
if (isNotValidProperty(key, name, "name"))
{
return;
}
String role = globalProperties.getProperty(key);
if (isNotValidProperty(key, role, "role"))
{
return;
}
// Ensure the role is in uppercase and has the prefix
role = role.toUpperCase(Locale.ENGLISH);
role = getRoleWithPrefix(role);
if (!PermissionService.SVC_AUTHORITIES_SET.contains(role))
{
LOGGER.warn("Invalid service account role '{}'. The role is not recognized.", role);
return;
}
// Register the service account name with the corresponding role.
register(name, role);
});
}
@Override
public void afterPropertiesSet() throws Exception
{
PropertyCheck.mandatory(this, "globalProperties", globalProperties);
init();
}
private String getRoleWithPrefix(String saRole)
{
if (!saRole.startsWith(PermissionService.ROLE_PREFIX))
{
saRole = PermissionService.ROLE_PREFIX + saRole;
}
return saRole;
}
private boolean isNotValidProperty(String key, String value, String valueType)
{
if (StringUtils.isBlank(value))
{
LOGGER.warn("Invalid service account {} defined in the property '{}'. The {} cannot be an empty string.",
valueType, key, valueType);
return true;
}
return false;
}
}

View File

@@ -62,6 +62,7 @@ import static org.alfresco.transform.common.RequestParamMap.ENDPOINT_TRANSFORM_C
*/
public class CombinedConfig extends CombinedTransformConfig
{
public static final String X_ALFRESCO_RETRY_NEEDED_HEADER = "X-Alfresco-Retry-Needed";
private final Log log;
private ObjectMapper jsonObjectMapper = new ObjectMapper();
@@ -137,7 +138,7 @@ public class CombinedConfig extends CombinedTransformConfig
{
int transformCount = transformerCount();
configFileFinder.readFile(reader, remoteType+" on "+baseUrl, "json", baseUrl, log);
if (transformCount == transformerCount())
if (transformCount == transformerCount() || response.containsHeader(X_ALFRESCO_RETRY_NEEDED_HEADER))
{
successReadingConfig = false;
}

View File

@@ -143,4 +143,8 @@
</property>
</bean>
<bean id="serviceAccountRegistry" class="org.alfresco.repo.serviceaccount.ServiceAccountRegistryImpl">
<property name="globalProperties" ref="global-properties"/>
</bean>
</beans>

View File

@@ -13,462 +13,487 @@
<permissions>
<!-- Namespaces used in type references -->
<namespaces>
<namespace uri="http://www.alfresco.org/model/system/1.0" prefix="sys"/>
<namespace uri="http://www.alfresco.org/model/content/1.0" prefix="cm"/>
</namespaces>
<!-- -->
<!-- Permission sets link permissions and groups of permissions to types and aspects -->
<!-- defined in the model. Permissions defined against a type apply to all objects -->
<!-- that inherit from that type. Permissions defined against aspects apply to all -->
<!-- objects or only objects that have the aspect applied. For example, the permission -->
<!-- to lock an object could apply to any object but the permission to unlock an -->
<!-- object would only apply to objects that have the lockable aspect. -->
<!-- -->
<!-- =============================================== -->
<!-- Base permissions available on all types of node -->
<!-- =============================================== -->
<permissionSet type="sys:base" expose="all" >
<!-- ================= -->
<!-- Permission groups -->
<!-- ================= -->
<!-- -->
<!-- Permission groups are convenient groups of permissions. They may be used in -->
<!-- their own right or as the effective set of permissions. If an authority has -->
<!-- all the permissions that make up a permission group they also have that -->
<!-- permission group even though it has not been explicitly granted. -->
<!-- -->
<namespaces>
<namespace uri="http://www.alfresco.org/model/system/1.0" prefix="sys"/>
<namespace uri="http://www.alfresco.org/model/content/1.0" prefix="cm"/>
</namespaces>
<!-- =========== -->
<!-- Full access -->
<!-- =========== -->
<!-- -->
<!-- By default this is exposed for all objects unless inherited objects choose to -->
<!-- expose only selected objects at the object level. -->
<!-- -->
<permissionGroup name="FullControl" expose="true" allowFullControl="true" />
<!-- ============================================= -->
<!-- Convenient groupings of low level permissions -->
<!-- ============================================= -->
<permissionGroup name="Read" expose="true" allowFullControl="false">
<includePermissionGroup type="sys:base" permissionGroup="ReadProperties"/>
<includePermissionGroup type="sys:base" permissionGroup="ReadChildren"/>
<includePermissionGroup type="sys:base" permissionGroup="ReadContent"/>
</permissionGroup>
<permissionGroup name="Write" expose="true" allowFullControl="false">
<includePermissionGroup type="sys:base" permissionGroup="WriteProperties"/>
<includePermissionGroup type="sys:base" permissionGroup="WriteContent"/>
</permissionGroup>
<permissionGroup name="Delete" expose="true" allowFullControl="false">
<includePermissionGroup type="sys:base" permissionGroup="DeleteNode"/>
<includePermissionGroup type="sys:base" permissionGroup="DeleteChildren"/>
</permissionGroup>
<permissionGroup name="AddChildren" expose="true" allowFullControl="false">
<includePermissionGroup type="sys:base" permissionGroup="CreateChildren"/>
<includePermissionGroup type="sys:base" permissionGroup="LinkChildren"/>
</permissionGroup>
<permissionGroup name="Execute" allowFullControl="false" expose="false">
<includePermissionGroup type="sys:base" permissionGroup="ExecuteContent"/>
</permissionGroup>
<!-- Groups for low level permissions -->
<permissionGroup name="ReadProperties" expose="true" allowFullControl="false" />
<permissionGroup name="ReadChildren" expose="true" allowFullControl="false" />
<permissionGroup name="WriteProperties" expose="true" allowFullControl="false" />
<permissionGroup name="ReadContent" expose="false" allowFullControl="false" />
<permissionGroup name="WriteContent" expose="false" allowFullControl="false" />
<permissionGroup name="ExecuteContent" expose="false" allowFullControl="false" />
<permissionGroup name="DeleteNode" expose="true" allowFullControl="false" />
<permissionGroup name="DeleteChildren" expose="true" allowFullControl="false" />
<permissionGroup name="CreateChildren" expose="true" allowFullControl="false" />
<permissionGroup name="LinkChildren" expose="true" allowFullControl="false" />
<permissionGroup name="DeleteAssociations" expose="true" allowFullControl="false" />
<permissionGroup name="ReadAssociations" expose="true" allowFullControl="false" />
<permissionGroup name="CreateAssociations" expose="true" allowFullControl="false" />
<permissionGroup name="ReadPermissions" expose="true" allowFullControl="false" />
<permissionGroup name="ChangePermissions" expose="true" allowFullControl="false" />
<!-- =========== -->
<!-- Permissions -->
<!-- =========== -->
<!-- The permission to read properties on a node -->
<!-- -->
<!-- The properties of a node may ony be read if there is read access to the parent -->
<!-- node. ReadChildren access to the parent node is recursive for all nodes from -->
<!-- which the node inherits permissions. Access is required down the permission -->
<!-- tree at all points. -->
<!-- -->
<permission name="_ReadProperties" expose="false" >
<grantedToGroup permissionGroup="ReadProperties" />
<!-- Commented out parent permission check ...
<requiredPermission on="parent" name="_ReadChildren" implies="false"/>
-->
</permission>
<!-- The permission to read the children of a node -->
<!-- -->
<!-- This permission is recursive. It requires the same permission is granted to -->
<!-- all of the parent nodes from which this node inherits permissions -->
<!-- -->
<permission name="_ReadChildren" expose="false" >
<grantedToGroup permissionGroup="ReadChildren" />
<!-- Commented out parent permission check ...
<requiredPermission on="parent" name="_ReadChildren" implies="false"/>
-->
</permission>
<!-- The permission to write to the properties of a node -->
<!-- -->
<!-- This permission includes adding aspects to a node as they are stored as -->
<!-- a property. -->
<!-- -->
<permission name="_WriteProperties" expose="false" >
<grantedToGroup permissionGroup="WriteProperties" />
<!-- Commented out parent permission check ...
<requiredPermission on="parent" name="_ReadChildren" implies="false"/>
-->
</permission>
<!-- The permission to delete a node -->
<!-- -->
<!-- A node can only be deleted if there is delete permission on the node, if the -->
<!-- node is accessible via its parent, and if the node can be deleted from its -->
<!-- parent. Currently, there is no check that all the children can be deleted. -->
<!-- This check can be added but requires more work so the UI is not checking this -->
<!-- permission just to show the delete icon. -->
<!-- -->
<!-- The permission to read content. -->
<permission name="_ReadContent" expose="false">
<grantedToGroup permissionGroup="ReadContent"/>
<!-- Commented out parent permission check ...
<requiredPermission on="parent" name="_ReadChildren" implies="false"/>
-->
</permission>
<!-- -->
<!-- Permission sets link permissions and groups of permissions to types and aspects -->
<!-- defined in the model. Permissions defined against a type apply to all objects -->
<!-- that inherit from that type. Permissions defined against aspects apply to all -->
<!-- objects or only objects that have the aspect applied. For example, the permission -->
<!-- to lock an object could apply to any object but the permission to unlock an -->
<!-- object would only apply to objects that have the lockable aspect. -->
<!-- -->
<!-- =============================================== -->
<!-- Base permissions available on all types of node -->
<!-- =============================================== -->
<permissionSet type="sys:base" expose="all">
<!-- ================= -->
<!-- Permission groups -->
<!-- ================= -->
<!-- -->
<!-- Permission groups are convenient groups of permissions. They may be used in -->
<!-- their own right or as the effective set of permissions. If an authority has -->
<!-- all the permissions that make up a permission group they also have that -->
<!-- permission group even though it has not been explicitly granted. -->
<!-- -->
<!-- =========== -->
<!-- Full access -->
<!-- =========== -->
<!-- -->
<!-- By default this is exposed for all objects unless inherited objects choose to -->
<!-- expose only selected objects at the object level. -->
<!-- -->
<permissionGroup name="FullControl" expose="true" allowFullControl="true"/>
<permissionGroup name="AdminServiceAccount" expose="false" allowFullControl="false">
<includePermissionGroup type="sys:base" permissionGroup="Read"/>
<includePermissionGroup type="sys:base" permissionGroup="Write"/>
<includePermissionGroup type="sys:base" permissionGroup="AddChildren"/>
<includePermissionGroup type="sys:base" permissionGroup="Delete"/>
<includePermissionGroup type="sys:base" permissionGroup="ReadAssociations"/>
<includePermissionGroup type="sys:base" permissionGroup="CreateAssociations"/>
<includePermissionGroup type="sys:base" permissionGroup="DeleteAssociations"/>
<includePermissionGroup type="sys:base" permissionGroup="ReadPermissions"/>
</permissionGroup>
<permissionGroup name="CollaboratorServiceAccount" expose="false" allowFullControl="false">
<includePermissionGroup type="sys:base" permissionGroup="Read"/>
<includePermissionGroup type="sys:base" permissionGroup="Write"/>
<includePermissionGroup type="sys:base" permissionGroup="AddChildren"/>
</permissionGroup>
<permissionGroup name="EditorServiceAccount" expose="false" allowFullControl="false">
<includePermissionGroup type="sys:base" permissionGroup="Read"/>
<includePermissionGroup type="sys:base" permissionGroup="Write"/>
</permissionGroup>
<!-- ============================================= -->
<!-- Convenient groupings of low level permissions -->
<!-- ============================================= -->
<permissionGroup name="Read" expose="true" allowFullControl="false">
<includePermissionGroup type="sys:base" permissionGroup="ReadProperties"/>
<includePermissionGroup type="sys:base" permissionGroup="ReadChildren"/>
<includePermissionGroup type="sys:base" permissionGroup="ReadContent"/>
</permissionGroup>
<permissionGroup name="Write" expose="true" allowFullControl="false">
<includePermissionGroup type="sys:base" permissionGroup="WriteProperties"/>
<includePermissionGroup type="sys:base" permissionGroup="WriteContent"/>
</permissionGroup>
<permissionGroup name="Delete" expose="true" allowFullControl="false">
<includePermissionGroup type="sys:base" permissionGroup="DeleteNode"/>
<includePermissionGroup type="sys:base" permissionGroup="DeleteChildren"/>
</permissionGroup>
<permissionGroup name="AddChildren" expose="true" allowFullControl="false">
<includePermissionGroup type="sys:base" permissionGroup="CreateChildren"/>
<includePermissionGroup type="sys:base" permissionGroup="LinkChildren"/>
</permissionGroup>
<permissionGroup name="Execute" allowFullControl="false" expose="false">
<includePermissionGroup type="sys:base" permissionGroup="ExecuteContent"/>
</permissionGroup>
<!-- Groups for low level permissions -->
<permissionGroup name="ReadProperties" expose="true" allowFullControl="false"/>
<permissionGroup name="ReadChildren" expose="true" allowFullControl="false"/>
<permissionGroup name="WriteProperties" expose="true" allowFullControl="false"/>
<permissionGroup name="ReadContent" expose="false" allowFullControl="false"/>
<permissionGroup name="WriteContent" expose="false" allowFullControl="false"/>
<permissionGroup name="ExecuteContent" expose="false" allowFullControl="false"/>
<permissionGroup name="DeleteNode" expose="true" allowFullControl="false"/>
<permissionGroup name="DeleteChildren" expose="true" allowFullControl="false"/>
<permissionGroup name="CreateChildren" expose="true" allowFullControl="false"/>
<permissionGroup name="LinkChildren" expose="true" allowFullControl="false"/>
<permissionGroup name="DeleteAssociations" expose="true" allowFullControl="false"/>
<permissionGroup name="ReadAssociations" expose="true" allowFullControl="false"/>
<permissionGroup name="CreateAssociations" expose="true" allowFullControl="false"/>
<permissionGroup name="ReadPermissions" expose="true" allowFullControl="false"/>
<permissionGroup name="ChangePermissions" expose="true" allowFullControl="false"/>
<!-- =========== -->
<!-- Permissions -->
<!-- =========== -->
<!-- The permission to read properties on a node -->
<!-- -->
<!-- The properties of a node may ony be read if there is read access to the parent -->
<!-- node. ReadChildren access to the parent node is recursive for all nodes from -->
<!-- which the node inherits permissions. Access is required down the permission -->
<!-- tree at all points. -->
<!-- -->
<permission name="_ReadProperties" expose="false">
<grantedToGroup permissionGroup="ReadProperties"/>
<!-- Commented out parent permission check ...
<requiredPermission on="parent" name="_ReadChildren" implies="false"/>
-->
</permission>
<!-- The permission to read the children of a node -->
<!-- -->
<!-- This permission is recursive. It requires the same permission is granted to -->
<!-- all of the parent nodes from which this node inherits permissions -->
<!-- -->
<permission name="_ReadChildren" expose="false">
<grantedToGroup permissionGroup="ReadChildren"/>
<!-- Commented out parent permission check ...
<requiredPermission on="parent" name="_ReadChildren" implies="false"/>
-->
</permission>
<!-- The permission to write to the properties of a node -->
<!-- -->
<!-- This permission includes adding aspects to a node as they are stored as -->
<!-- a property. -->
<!-- -->
<permission name="_WriteProperties" expose="false">
<grantedToGroup permissionGroup="WriteProperties"/>
<!-- Commented out parent permission check ...
<requiredPermission on="parent" name="_ReadChildren" implies="false"/>
-->
</permission>
<!-- The permission to delete a node -->
<!-- -->
<!-- A node can only be deleted if there is delete permission on the node, if the -->
<!-- node is accessible via its parent, and if the node can be deleted from its -->
<!-- parent. Currently, there is no check that all the children can be deleted. -->
<!-- This check can be added but requires more work so the UI is not checking this -->
<!-- permission just to show the delete icon. -->
<!-- -->
<!-- The permission to read content. -->
<permission name="_ReadContent" expose="false">
<grantedToGroup permissionGroup="ReadContent"/>
<!-- Commented out parent permission check ...
<requiredPermission on="parent" name="_ReadChildren" implies="false"/>
-->
</permission>
<!-- The permission to write content. -->
<permission name="_WriteContent" expose="false">
<grantedToGroup permissionGroup="WriteContent"/>
<!-- Commented out parent permission check ...
<requiredPermission on="parent" name="_ReadChildren" implies="false"/>
-->
</permission>
<!-- Execute permission on content. -->
<permission name="_ExecuteContent" expose="false">
<grantedToGroup permissionGroup="ExecuteContent"/>
<!-- Commented out parent permission check ...
<requiredPermission on="parent" name="_ReadChildren" implies="false"/>
-->
</permission>
<permission name="_DeleteNode" expose="false">
<grantedToGroup permissionGroup="DeleteNode"/>
<!-- Commented out parent permission check ...
<requiredPermission on="parent" name="_ReadChildren" implies="false"/>
<requiredPermission on="parent" name="_DeleteChildren" implies="false"/>
<requiredPermission on="node" name="_DeleteChildren" implies="false"/>
-->
<!-- Remove the recursive check for now for performance -->
<!-- TODO: have one permission to check for delete on an item and one to check -->
<!-- child permissions when delete is called on the node service -->
<!-- <requiredPermission on="children" name="_DeleteNode" implies="false"/> -->
</permission>
<!-- The permission to delete children of a node -->
<!-- -->
<!-- At the moment this includes both unlink and delete -->
<!-- -->
<permission name="_DeleteChildren" expose="false">
<grantedToGroup permissionGroup="DeleteChildren"/>
<!-- Commented out parent permission check ...
<requiredPermission on="parent" name="_ReadChildren" implies="false"/>
-->
</permission>
<!-- The permission to create new nodes -->
<permission name="_CreateChildren" expose="false">
<grantedToGroup permissionGroup="CreateChildren"/>
<!-- Commented out parent permission check ...
<requiredPermission on="parent" name="_ReadChildren" implies="false" />
-->
</permission>
<!-- The permission to link nodes -->
<permission name="_LinkChildren" expose="false">
<grantedToGroup permissionGroup="LinkChildren"/>
<!-- Commented out parent permission check ...
<requiredPermission on="parent" name="_ReadChildren" implies="false"/>
-->
</permission>
<!-- The permission to delete associations between nodes (not children) -->
<permission name="_DeleteAssociations" expose="false">
<grantedToGroup permissionGroup="DeleteAssociations"/>
<!-- Commented out parent permission check ...
<requiredPermission on="parent" name="_ReadChildren" implies="false"/>
-->
</permission>
<!-- The permission to read associations -->
<permission name="_ReadAssociations" expose="false">
<grantedToGroup permissionGroup="ReadAssociations"/>
<!-- Commented out parent permission check ...
<requiredPermission on="parent" name="_ReadChildren" implies="false" />
-->
</permission>
<!-- The permission to create associations -->
<permission name="_CreateAssociations" expose="false">
<grantedToGroup permissionGroup="CreateAssociations"/>
<!-- Commented out parent permission check ...
<requiredPermission on="parent" name="_ReadChildren" implies="false" />
-->
</permission>
<!-- ==================================================== -->
<!-- Permissions related to the management of permissions -->
<!-- ==================================================== -->
<!-- The permission to read the permissions on a node -->
<permission name="_ReadPermissions" expose="false">
<grantedToGroup permissionGroup="ReadPermissions"/>
<!-- Commented out parent permission check ...
<requiredPermission on="parent" name="_ReadChildren" implies="false"/>
-->
</permission>
<!-- The permission to the change the permissions associated with a node -->
<permission name="_ChangePermissions" expose="false">
<grantedToGroup permissionGroup="ChangePermissions"/>
<!-- Commented out parent permission check ...
<requiredPermission on="parent" name="_ReadChildren" implies="false"/>
-->
</permission>
</permissionSet>
<!-- ================================================ -->
<!-- Permissions available to all content and folders -->
<!-- ================================================ -->
<permissionSet type="cm:cmobject" expose="selected">
<!-- Kept for backward compatibility - the administrator permission has -->
<!-- been removed to avoid confusion -->
<permissionGroup name="Administrator" allowFullControl="true" expose="false"/>
<!-- A coordinator can do anything to the object or its children unless the -->
<!-- permissions are set not to inherit or permission is denied. -->
<permissionGroup name="Coordinator" allowFullControl="true" expose="true"/>
<!-- A collaborator can do anything that an editor and a contributor can do -->
<permissionGroup name="Collaborator" allowFullControl="false" expose="true">
<includePermissionGroup permissionGroup="Editor" type="cm:cmobject"/>
<includePermissionGroup permissionGroup="Contributor" type="cm:cmobject"/>
</permissionGroup>
<!-- A contributor can create content and then they have full permission on what -->
<!-- they have created - via the permissions assigned to the owner. -->
<permissionGroup name="Contributor" allowFullControl="false" expose="true">
<!-- Contributor is a consumer who can add content, and then can modify via the -->
<!-- owner permissions. -->
<includePermissionGroup permissionGroup="Consumer" type="cm:cmobject"/>
<includePermissionGroup permissionGroup="AddChildren" type="sys:base"/>
<includePermissionGroup permissionGroup="ReadPermissions" type="sys:base"/>
</permissionGroup>
<!-- An editor can read and write to the object; they can not create -->
<!-- new nodes. They can check out content into a space to which they have -->
<!-- create permission. -->
<permissionGroup name="Editor" expose="true" allowFullControl="false">
<includePermissionGroup type="cm:cmobject" permissionGroup="Consumer"/>
<includePermissionGroup type="sys:base" permissionGroup="Write"/>
<includePermissionGroup type="cm:lockable" permissionGroup="CheckOut"/>
<includePermissionGroup type="sys:base" permissionGroup="ReadPermissions"/>
</permissionGroup>
<!-- The Consumer permission allows read to everything by default. -->
<permissionGroup name="Consumer" allowFullControl="false" expose="true">
<includePermissionGroup permissionGroup="Read" type="sys:base"/>
</permissionGroup>
<!-- records permission -->
<!-- Should be tied to the aspect -->
<!-- ownership should be removed when using this permission -->
<permissionGroup name="RecordAdministrator" allowFullControl="false" expose="false">
<includePermissionGroup type="sys:base" permissionGroup="ReadProperties"/>
<includePermissionGroup type="sys:base" permissionGroup="ReadChildren"/>
<includePermissionGroup type="sys:base" permissionGroup="WriteProperties"/>
<includePermissionGroup type="sys:base" permissionGroup="ReadContent"/>
<includePermissionGroup type="sys:base" permissionGroup="DeleteChildren"/>
<includePermissionGroup type="sys:base" permissionGroup="CreateChildren"/>
<includePermissionGroup type="sys:base" permissionGroup="LinkChildren"/>
<includePermissionGroup type="sys:base" permissionGroup="DeleteAssociations"/>
<includePermissionGroup type="sys:base" permissionGroup="CreateAssociations"/>
</permissionGroup>
</permissionSet>
<!-- =============================== -->
<!-- Permissions specific to content -->
<!-- =============================== -->
<permissionSet type="cm:content" expose="selected">
<!-- Content specific roles. -->
<permissionGroup name="Coordinator" extends="true" expose="true"/>
<permissionGroup name="Collaborator" extends="true" expose="true"/>
<permissionGroup name="Contributor" extends="true" expose="true"/>
<permissionGroup name="Editor" extends="true" expose="true"/>
<permissionGroup name="Consumer" extends="true" expose="true"/>
<permissionGroup name="RecordAdministrator" extends="true" expose="false"/>
</permissionSet>
<!-- The permission to write content. -->
<permission name="_WriteContent" expose="false">
<grantedToGroup permissionGroup="WriteContent" />
<!-- Commented out parent permission check ...
<requiredPermission on="parent" name="_ReadChildren" implies="false"/>
-->
</permission>
<!-- Execute permission on content. -->
<permission name="_ExecuteContent" expose="false">
<grantedToGroup permissionGroup="ExecuteContent" />
<!-- Commented out parent permission check ...
<requiredPermission on="parent" name="_ReadChildren" implies="false"/>
-->
</permission>
<permission name="_DeleteNode" expose="false" >
<grantedToGroup permissionGroup="DeleteNode" />
<!-- Commented out parent permission check ...
<requiredPermission on="parent" name="_ReadChildren" implies="false"/>
<requiredPermission on="parent" name="_DeleteChildren" implies="false"/>
<requiredPermission on="node" name="_DeleteChildren" implies="false"/>
-->
<!-- Remove the recursive check for now for performance -->
<!-- TODO: have one permission to check for delete on an item and one to check -->
<!-- child permissions when delete is called on the node service -->
<!-- <requiredPermission on="children" name="_DeleteNode" implies="false"/> -->
</permission>
<!-- The permission to delete children of a node -->
<!-- -->
<!-- At the moment this includes both unlink and delete -->
<!-- -->
<permission name="_DeleteChildren" expose="false" >
<grantedToGroup permissionGroup="DeleteChildren" />
<!-- Commented out parent permission check ...
<requiredPermission on="parent" name="_ReadChildren" implies="false"/>
-->
</permission>
<!-- The permission to create new nodes -->
<permission name="_CreateChildren" expose="false" >
<grantedToGroup permissionGroup="CreateChildren" />
<!-- Commented out parent permission check ...
<requiredPermission on="parent" name="_ReadChildren" implies="false" />
-->
</permission>
<!-- The permission to link nodes -->
<permission name="_LinkChildren" expose="false" >
<grantedToGroup permissionGroup="LinkChildren" />
<!-- Commented out parent permission check ...
<requiredPermission on="parent" name="_ReadChildren" implies="false"/>
-->
</permission>
<!-- The permission to delete associations between nodes (not children) -->
<permission name="_DeleteAssociations" expose="false" >
<grantedToGroup permissionGroup="DeleteAssociations" />
<!-- Commented out parent permission check ...
<requiredPermission on="parent" name="_ReadChildren" implies="false"/>
-->
</permission>
<!-- The permission to read associations -->
<permission name="_ReadAssociations" expose="false" >
<grantedToGroup permissionGroup="ReadAssociations" />
<!-- Commented out parent permission check ...
<requiredPermission on="parent" name="_ReadChildren" implies="false" />
-->
</permission>
<!-- The permission to create associations -->
<permission name="_CreateAssociations" expose="false" >
<grantedToGroup permissionGroup="CreateAssociations" />
<!-- Commented out parent permission check ...
<requiredPermission on="parent" name="_ReadChildren" implies="false" />
-->
</permission>
<!-- ==================================================== -->
<!-- Permissions related to the management of permissions -->
<!-- ==================================================== -->
<!-- The permission to read the permissions on a node -->
<permission name="_ReadPermissions" expose="false" >
<grantedToGroup permissionGroup="ReadPermissions" />
<!-- Commented out parent permission check ...
<requiredPermission on="parent" name="_ReadChildren" implies="false"/>
-->
</permission>
<!-- The permission to the change the permissions associated with a node -->
<permission name="_ChangePermissions" expose="false" >
<grantedToGroup permissionGroup="ChangePermissions" />
<!-- Commented out parent permission check ...
<requiredPermission on="parent" name="_ReadChildren" implies="false"/>
-->
</permission>
</permissionSet>
<!-- ================================================ -->
<!-- Permissions available to all content and folders -->
<!-- ================================================ -->
<permissionSet type="cm:cmobject" expose="selected">
<!-- Kept for backward compatibility - the administrator permission has -->
<!-- been removed to avoid confusion -->
<permissionGroup name="Administrator" allowFullControl="true" expose="false" />
<!-- A coordinator can do anything to the object or its children unless the -->
<!-- permissions are set not to inherit or permission is denied. -->
<permissionGroup name="Coordinator" allowFullControl="true" expose="true" />
<!-- A collaborator can do anything that an editor and a contributor can do -->
<permissionGroup name="Collaborator" allowFullControl="false" expose="true">
<includePermissionGroup permissionGroup="Editor" type="cm:cmobject" />
<includePermissionGroup permissionGroup="Contributor" type="cm:cmobject" />
</permissionGroup>
<!-- A contributor can create content and then they have full permission on what -->
<!-- they have created - via the permissions assigned to the owner. -->
<permissionGroup name="Contributor" allowFullControl="false" expose="true" >
<!-- Contributor is a consumer who can add content, and then can modify via the -->
<!-- owner permissions. -->
<includePermissionGroup permissionGroup="Consumer" type="cm:cmobject"/>
<includePermissionGroup permissionGroup="AddChildren" type="sys:base"/>
<includePermissionGroup permissionGroup="ReadPermissions" type="sys:base" />
</permissionGroup>
<!-- An editor can read and write to the object; they can not create -->
<!-- new nodes. They can check out content into a space to which they have -->
<!-- create permission. -->
<permissionGroup name="Editor" expose="true" allowFullControl="false" >
<includePermissionGroup type="cm:cmobject" permissionGroup="Consumer"/>
<includePermissionGroup type="sys:base" permissionGroup="Write"/>
<includePermissionGroup type="cm:lockable" permissionGroup="CheckOut"/>
<includePermissionGroup type="sys:base" permissionGroup="ReadPermissions"/>
</permissionGroup>
<!-- The Consumer permission allows read to everything by default. -->
<permissionGroup name="Consumer" allowFullControl="false" expose="true" >
<includePermissionGroup permissionGroup="Read" type="sys:base" />
</permissionGroup>
<!-- records permission -->
<!-- Should be tied to the aspect -->
<!-- ownership should be removed when using this permission -->
<permissionGroup name="RecordAdministrator" allowFullControl="false" expose="false">
<includePermissionGroup type="sys:base" permissionGroup="ReadProperties"/>
<includePermissionGroup type="sys:base" permissionGroup="ReadChildren"/>
<includePermissionGroup type="sys:base" permissionGroup="WriteProperties"/>
<includePermissionGroup type="sys:base" permissionGroup="ReadContent"/>
<includePermissionGroup type="sys:base" permissionGroup="DeleteChildren"/>
<includePermissionGroup type="sys:base" permissionGroup="CreateChildren"/>
<includePermissionGroup type="sys:base" permissionGroup="LinkChildren"/>
<includePermissionGroup type="sys:base" permissionGroup="DeleteAssociations"/>
<includePermissionGroup type="sys:base" permissionGroup="CreateAssociations"/>
</permissionGroup>
</permissionSet>
<!-- =============================== -->
<!-- Permissions specific to content -->
<!-- =============================== -->
<permissionSet type="cm:content" expose="selected">
<!-- Content specific roles. -->
<permissionGroup name="Coordinator" extends="true" expose="true"/>
<permissionGroup name="Collaborator" extends="true" expose="true"/>
<permissionGroup name="Contributor" extends="true" expose="true"/>
<permissionGroup name="Editor" extends="true" expose="true"/>
<permissionGroup name="Consumer" extends="true" expose="true"/>
<permissionGroup name="RecordAdministrator" extends="true" expose="false"/>
</permissionSet>
<permissionSet type="cm:folder" expose="selected">
<!-- Content folder specific roles. -->
<permissionGroup name="Coordinator" extends="true" expose="true"/>
<permissionGroup name="Collaborator" extends="true" expose="true"/>
<permissionGroup name="Contributor" extends="true" expose="true"/>
<permissionGroup name="Editor" extends="true" expose="true"/>
<permissionGroup name="Consumer" extends="true" expose="true"/>
<permissionGroup name="RecordAdministrator" extends="true" expose="false"/>
</permissionSet>
<!-- ============================================== -->
<!-- Permissions associated with the Ownable aspect -->
<!-- ============================================== -->
<permissionSet type="cm:ownable" expose="selected">
<!-- Permission control to allow ownership of the node to be taken from others -->
<permissionGroup name="TakeOwnership" requiresType="false" expose="false">
<includePermissionGroup permissionGroup="SetOwner" type="cm:ownable" />
</permissionGroup>
<permissionGroup name="SetOwner" requiresType="false" expose="false"/>
<!-- The low level permission to control setting the owner of a node -->
<permission name="_SetOwner" expose="false" requiresType="false">
<grantedToGroup permissionGroup="SetOwner" />
<!-- require to be able to reach the node and set properties in the node -->
<!-- Commented out parent permission check ...
<requiredPermission on="parent" name="_ReadChildren" />
-->
<requiredPermission on="node" type="sys:base" name="_WriteProperties" />
</permission>
</permissionSet>
<!-- =================================================== -->
<!-- Permission related to check in and cancel check out. -->
<!-- =================================================== -->
<!-- Content folder specific roles. -->
<permissionSet type="cm:workingcopy" expose="selected">
<permissionGroup name="Coordinator" extends="true" expose="true"/>
<permissionGroup name="Collaborator" extends="true" expose="true"/>
<permissionGroup name="Contributor" extends="true" expose="true"/>
<permissionGroup name="Editor" extends="true" expose="true"/>
<permissionGroup name="Consumer" extends="true" expose="true"/>
<permissionGroup name="RecordAdministrator" extends="true" expose="false"/>
<!-- Cancel Check Out permission - only exposed for the workingcopy aspect is present -->
<permissionGroup name="CancelCheckOut" requiresType="true" expose="false">
<includePermissionGroup permissionGroup="Unlock" type="cm:lockable" />
</permissionGroup>
</permissionSet>
<!-- ============================================== -->
<!-- Permissions associated with the Ownable aspect -->
<!-- ============================================== -->
<permissionSet type="cm:ownable" expose="selected">
<!-- Permission control to allow ownership of the node to be taken from others -->
<permissionGroup name="TakeOwnership" requiresType="false" expose="false">
<includePermissionGroup permissionGroup="SetOwner" type="cm:ownable"/>
</permissionGroup>
<permissionGroup name="SetOwner" requiresType="false" expose="false"/>
<!-- The low level permission to control setting the owner of a node -->
<permission name="_SetOwner" expose="false" requiresType="false">
<grantedToGroup permissionGroup="SetOwner"/>
<!-- require to be able to reach the node and set properties in the node -->
<!-- Commented out parent permission check ...
<requiredPermission on="parent" name="_ReadChildren" />
-->
<requiredPermission on="node" type="sys:base" name="_WriteProperties"/>
</permission>
</permissionSet>
<!-- =================================================== -->
<!-- Permission related to check in and cancel check out. -->
<!-- =================================================== -->
<permissionSet type="cm:workingcopy" expose="selected">
<!-- Cancel Check Out permission - only exposed for the workingcopy aspect is present -->
<permissionGroup name="CancelCheckOut" requiresType="true" expose="false">
<includePermissionGroup permissionGroup="Unlock" type="cm:lockable"/>
</permissionGroup>
<!-- Check In permission - only exposed when the workingcopy aspect is present -->
<permissionGroup name="CheckIn" requiresType="true" expose="false">
<includePermissionGroup permissionGroup="Unlock" type="cm:lockable"/>
</permissionGroup>
</permissionSet>
<!-- =================================================== -->
<!-- Permission related to lock, check out and check in. -->
<!-- =================================================== -->
<permissionSet type="cm:lockable" expose="selected">
<!-- At the moment these permissions are hidden so they do not appear in the list -->
<!-- of permissions. -->
<!-- Check Out permission - exposed for all object types -->
<permissionGroup name="CheckOut" requiresType="false" expose="false">
<includePermissionGroup permissionGroup="Lock" type="cm:lockable"/>
</permissionGroup>
<permissionGroup name="Lock" requiresType="false" expose="false"/>
<permissionGroup name="Unlock" requiresType="true" expose="false"/>
<!-- Low level lock permission -->
<permission name="_Lock" requiresType="false" expose="false">
<grantedToGroup permissionGroup="Lock"/>
<requiredPermission on="node" type="sys:base" name="Write"/>
</permission>
<!-- Low level unlock permission -->
<permission name="_Unlock" requiresType="true" expose="false">
<grantedToGroup permissionGroup="Unlock"/>
</permission>
</permissionSet>
<!-- ================== -->
<!-- Global permissions -->
<!-- ================== -->
<!-- -->
<!-- Global permissions apply regardless of any particular node context. -->
<!-- They can not be denied by the permissions set on any node. -->
<!-- -->
<!-- Admin can do anything to any node -->
<globalPermission permission="FullControl" authority="ROLE_ADMINISTRATOR"/>
<!-- For now, owners can always see, find and manipulate their stuff -->
<globalPermission permission="FullControl" authority="ROLE_OWNER"/>
<!-- Unlock is granted to the lock owner -->
<globalPermission permission="Unlock" authority="ROLE_LOCK_OWNER"/>
<!-- Check in is granted to the lock owner -->
<globalPermission permission="CheckIn" authority="ROLE_LOCK_OWNER"/>
<!-- Cancel check out is granted to the lock owner -->
<globalPermission permission="CancelCheckOut" authority="ROLE_LOCK_OWNER"/>
<!-- Service Account roles -->
<globalPermission permission="AdminServiceAccount" authority="ROLE_ADMIN_SERVICE_ACCOUNT"/>
<globalPermission permission="CollaboratorServiceAccount" authority="ROLE_COLLABORATOR_SERVICE_ACCOUNT"/>
<globalPermission permission="EditorServiceAccount" authority="ROLE_EDITOR_SERVICE_ACCOUNT"/>
<!-- Check In permission - only exposed when the workingcopy aspect is present -->
<permissionGroup name="CheckIn" requiresType="true" expose="false">
<includePermissionGroup permissionGroup="Unlock" type="cm:lockable" />
</permissionGroup>
</permissionSet>
<!-- =================================================== -->
<!-- Permission related to lock, check out and check in. -->
<!-- =================================================== -->
<permissionSet type="cm:lockable" expose="selected">
<!-- At the moment these permissions are hidden so they do not appear in the list -->
<!-- of permissions. -->
<!-- Check Out permission - exposed for all object types -->
<permissionGroup name="CheckOut" requiresType="false" expose="false">
<includePermissionGroup permissionGroup="Lock" type="cm:lockable" />
</permissionGroup>
<permissionGroup name="Lock" requiresType="false" expose="false"/>
<permissionGroup name="Unlock" requiresType="true" expose="false"/>
<!-- Low level lock permission -->
<permission name="_Lock" requiresType="false" expose="false">
<grantedToGroup permissionGroup="Lock" />
<requiredPermission on="node" type="sys:base" name="Write"/>
</permission>
<!-- Low level unlock permission -->
<permission name="_Unlock" requiresType="true" expose="false">
<grantedToGroup permissionGroup="Unlock" />
</permission>
</permissionSet>
<!-- ================== -->
<!-- Global permissions -->
<!-- ================== -->
<!-- -->
<!-- Global permissions apply regardless of any particular node context. -->
<!-- They can not be denied by the permissions set on any node. -->
<!-- -->
<!-- Admin can do anything to any node -->
<globalPermission permission="FullControl" authority="ROLE_ADMINISTRATOR"/>
<!-- For now, owners can always see, find and manipulate their stuff -->
<globalPermission permission="FullControl" authority="ROLE_OWNER"/>
<!-- Unlock is granted to the lock owner -->
<globalPermission permission="Unlock" authority="ROLE_LOCK_OWNER"/>
<!-- Check in is granted to the lock owner -->
<globalPermission permission="CheckIn" authority="ROLE_LOCK_OWNER"/>
<!-- Cancel check out is granted to the lock owner -->
<globalPermission permission="CancelCheckOut" authority="ROLE_LOCK_OWNER"/>
</permissions>

View File

@@ -102,6 +102,9 @@
<list>
<ref bean="ownerDynamicAuthority" />
<ref bean="lockOwnerDynamicAuthority" />
<ref bean="adminServiceAccountAuthority"/>
<ref bean="collaboratorServiceAccountAuthority"/>
<ref bean="editorServiceAccountAuthority"/>
</list>
</property>
<property name="fixedAclUpdater">
@@ -147,6 +150,23 @@
</property>
</bean>
<!-- Service account authority beans -->
<bean id="baseServiceAccountAuthority" abstract="true">
<property name="serviceAccountRegistry" ref="serviceAccountRegistry"/>
</bean>
<bean id="adminServiceAccountAuthority" class="org.alfresco.repo.security.permissions.dynamic.ServiceAccountAuthority"
parent="baseServiceAccountAuthority">
<property name="authority" value="#{T(org.alfresco.service.cmr.security.PermissionService).ADMIN_SVC_AUTHORITY}"/>
</bean>
<bean id="collaboratorServiceAccountAuthority" class="org.alfresco.repo.security.permissions.dynamic.ServiceAccountAuthority"
parent="baseServiceAccountAuthority">
<property name="authority" value="#{T(org.alfresco.service.cmr.security.PermissionService).COLLABORATOR_SVC_AUTHORITY}"/>
</bean>
<bean id="editorServiceAccountAuthority" class="org.alfresco.repo.security.permissions.dynamic.ServiceAccountAuthority"
parent="baseServiceAccountAuthority">
<property name="authority" value="#{T(org.alfresco.service.cmr.security.PermissionService).EDITOR_SVC_AUTHORITY}"/>
</bean>
<!-- =========================== -->
<!-- Permissions Model Bootstrap -->
<!-- =========================== -->

View File

@@ -261,7 +261,8 @@ import org.junit.runners.Suite;
org.alfresco.repo.event2.RepoEvent2UnitSuite.class,
org.alfresco.util.schemacomp.SchemaDifferenceHelperUnitTest.class,
org.alfresco.repo.tagging.TaggingServiceImplUnitTest.class
org.alfresco.repo.tagging.TaggingServiceImplUnitTest.class,
org.alfresco.repo.serviceaccount.ServiceAccountRegistryImplTest.class
})
public class AllUnitTestsSuite
{

View File

@@ -2,7 +2,7 @@
* #%L
* Alfresco Repository
* %%
* Copyright (C) 2005 - 2023 Alfresco Software Limited
* Copyright (C) 2005 - 2024 Alfresco Software Limited
* %%
* This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of
@@ -85,6 +85,7 @@ import org.junit.runners.Suite;
org.alfresco.repo.model.ModelTestSuite.class,
org.alfresco.repo.tenant.MultiTNodeServiceInterceptorTest.class,
org.alfresco.repo.transfer.RepoTransferReceiverImplTest.class,
org.alfresco.repo.security.permissions.dynamic.ServiceAccountRoleTest.class
})
public class AppContext05TestSuite
{

View File

@@ -45,6 +45,7 @@ import org.alfresco.repo.security.authority.AuthorityServiceTest;
import org.alfresco.repo.security.authority.DuplicateAuthorityTest;
import org.alfresco.repo.security.authority.ExtendedPermissionServiceTest;
import org.alfresco.repo.security.permissions.dynamic.LockOwnerDynamicAuthorityTest;
import org.alfresco.repo.security.permissions.dynamic.ServiceAccountRoleTest;
import org.alfresco.repo.security.permissions.impl.AclDaoComponentTest;
import org.alfresco.repo.security.permissions.impl.PermissionServiceTest;
import org.alfresco.repo.security.permissions.impl.ReadPermissionTest;
@@ -108,6 +109,7 @@ public class SecurityTestSuite extends TestSuite
suite.addTest(new JUnit4TestAdapter(LocalAuthenticationServiceTest.class));
suite.addTest(new JUnit4TestAdapter(ResetPasswordServiceImplTest.class));
suite.addTest(new JUnit4TestAdapter(ServiceAccountRoleTest.class));
return suite;
}
}

View File

@@ -0,0 +1,355 @@
/*
* #%L
* Alfresco Data model classes
* %%
* Copyright (C) 2005 - 2024 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.security.permissions.dynamic;
import static java.lang.System.currentTimeMillis;
import static org.junit.Assert.assertEquals;
import java.io.Serializable;
import java.util.Map;
import java.util.Properties;
import org.alfresco.model.ContentModel;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.security.AccessStatus;
import org.alfresco.service.cmr.security.PermissionService;
import org.alfresco.service.cmr.site.SiteInfo;
import org.alfresco.service.cmr.site.SiteService;
import org.alfresco.service.cmr.site.SiteVisibility;
import org.alfresco.service.namespace.QName;
import org.alfresco.util.test.junitrules.AlfrescoPerson;
import org.alfresco.util.test.junitrules.ApplicationContextInit;
import org.alfresco.util.test.junitrules.TemporaryNodes;
import org.alfresco.util.test.junitrules.TemporarySites;
import org.junit.After;
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.Test;
import org.junit.rules.RuleChain;
import org.springframework.context.ApplicationContext;
/**
* This test class demonstrates the permissions of the service account authorities.
* The service account authorities are used to grant permissions to service accounts.
* <p>
* The service account authorities are defined in the <i>alfresco-global.properties</i> file.
* Using the following naming convention:
* <pre>
* {@code
* serviceaccount.role.<service-account-name>=<service-account-role>
* }
* </pre>
* The service account roles that currently supported are:
* <ul>
* <li>{@link PermissionService#EDITOR_SVC_AUTHORITY}</li>
* <li>{@link PermissionService#COLLABORATOR_SVC_AUTHORITY}</li>
* <li>{@link PermissionService#ADMIN_SVC_AUTHORITY}</li>
* </ul>
* The test class relies on the following service accounts defined in the <i>alfresco-global.properties</i> file:
* <ul>
* <li>serviceaccount.role.test-editor-sa=ROLE_EDITOR_SERVICE_ACCOUNT</li>
* <li>serviceaccount.role.test-collaborator-sa=ROLE_COLLABORATOR_SERVICE_ACCOUNT</li>
* <li>serviceaccount.role.test-admin-sa=ROLE_ADMIN_SERVICE_ACCOUNT</li>
* </ul>
* <p>
* <b>Note:</b> There is no need to use public services (i.e., beans that start with a capital letter, such as NodeService)
* to validate roles permissions. This is because the security enforcement of public services (i.e., ACL checks) ultimately relies on
* the {@code permissionService.hasPermission()} method. Therefore, we can directly use the {@code permissionService.hasPermission()} method.
*
* @author Jamal Kaabi-Mofrad
*/
// Ignore the PMD warning about having too many test methods in this class; it makes the tests easier to read and maintain.
@SuppressWarnings("PMD.TooManyMethods")
public class ServiceAccountRoleTest
{
// Rule to initialise the default Alfresco spring configuration
private static final ApplicationContextInit APP_CONTEXT = new ApplicationContextInit();
// A rule to manage a test site
private static final TemporarySites TEST_SITES = new TemporarySites(APP_CONTEXT);
// A rule to manage test nodes reused across all the test methods
private static final TemporaryNodes TEST_NODES = new TemporaryNodes(APP_CONTEXT);
// Rules to create the users for the tests
private static final AlfrescoPerson NORMAL_USER = getAlfrescoPerson("john.doe" + currentTimeMillis());
private static final AlfrescoPerson EDITOR_SA = getAlfrescoPerson("test-editor-sa");
private static final AlfrescoPerson COLLABORATOR_SA = getAlfrescoPerson("test-collaborator-sa");
private static final AlfrescoPerson ADMIN_SA = getAlfrescoPerson("test-admin-sa");
private static final String TEST_TEXT_FILE_NAME = "testTextFile.txt";
// Tie them together in a static Rule Chain
@ClassRule
public static final RuleChain STATIC_RULE_CHAIN = RuleChain.outerRule(APP_CONTEXT)
.around(TEST_SITES)
.around(TEST_NODES)
.around(NORMAL_USER)
.around(EDITOR_SA)
.around(COLLABORATOR_SA)
.around(ADMIN_SA);
private static NodeService nodeService;
private static SiteService siteService;
private static PermissionService permissionService;
private static Properties globalProperties;
private static NodeRef testTextFile;
@BeforeClass
public static void initStaticData() throws Exception
{
ApplicationContext context = APP_CONTEXT.getApplicationContext();
nodeService = context.getBean("NodeService", NodeService.class);
siteService = context.getBean("SiteService", SiteService.class);
permissionService = context.getBean("permissionService", PermissionService.class);
globalProperties = context.getBean("global-properties", Properties.class);
// Check that the service account roles are defined in the global properties before starting the tests.
serviceAccountsShouldExistInGlobalProperties();
// Create a test site
SiteInfo testSite = createTestSite();
// Create a test text file in the test site
createTestFile(testSite);
// Clear the current security context to avoid any issues with the test setup
AuthenticationUtil.clearCurrentSecurityContext();
}
private static AlfrescoPerson getAlfrescoPerson(String username)
{
return new AlfrescoPerson(APP_CONTEXT, username);
}
private static SiteInfo createTestSite()
{
// Create a private test site to make sure no other non-members or
// non-site-admins can access the test site.
return TEST_SITES.createSite("sitePreset", "saTestSite" + currentTimeMillis(), "SA Test Site",
"sa test site desc", SiteVisibility.PRIVATE,
AuthenticationUtil.getAdminUserName());
}
private static void createTestFile(SiteInfo testSite)
{
// Create a test text file in the test site as the admin user
AuthenticationUtil.setAdminUserAsFullyAuthenticatedUser();
final NodeRef docLib = siteService.getContainer(testSite.getShortName(), SiteService.DOCUMENT_LIBRARY);
final NodeRef testFolder = TEST_NODES.createFolder(docLib, "testFolder", AuthenticationUtil.getAdminUserName());
testTextFile = TEST_NODES.createNodeWithTextContent(testFolder, TEST_TEXT_FILE_NAME, ContentModel.TYPE_CONTENT,
AuthenticationUtil.getAdminUserName(),
"The quick brown fox jumps over the lazy dog.");
Map<QName, Serializable> props = Map.of(ContentModel.PROP_NAME, TEST_TEXT_FILE_NAME,
ContentModel.PROP_DESCRIPTION, "Desc added by Admin.");
nodeService.setProperties(testTextFile, props);
}
private static void serviceAccountsShouldExistInGlobalProperties()
{
assertServiceAccountIsDefined(PermissionService.EDITOR_SVC_AUTHORITY, EDITOR_SA.getUsername());
assertServiceAccountIsDefined(PermissionService.COLLABORATOR_SVC_AUTHORITY, COLLABORATOR_SA.getUsername());
assertServiceAccountIsDefined(PermissionService.ADMIN_SVC_AUTHORITY, ADMIN_SA.getUsername());
}
private static void assertServiceAccountIsDefined(String expectedRole, String username)
{
assertEquals(expectedRole, globalProperties.getProperty("serviceaccount.role." + username));
}
@After
public void tearDown() throws Exception
{
AuthenticationUtil.clearCurrentSecurityContext();
}
@Test
public void normalUserReadAccessShouldBeDenied()
{
assertAccessDenied(NORMAL_USER, PermissionService.READ);
}
@Test
public void editorSaReadAccessShouldBeAllowed()
{
assertAccessAllowed(EDITOR_SA, PermissionService.READ);
}
@Test
public void collaboratorSaReadAccessShouldBeAllowed()
{
assertAccessAllowed(COLLABORATOR_SA, PermissionService.READ);
}
@Test
public void adminSaReadAccessShouldBeAllowed()
{
assertAccessAllowed(ADMIN_SA, PermissionService.READ);
}
@Test
public void normalUserWriteAccessShouldBeDenied()
{
assertAccessDenied(NORMAL_USER, PermissionService.WRITE);
}
@Test
public void editorSaWriteAccessShouldBeAllowed()
{
assertAccessAllowed(EDITOR_SA, PermissionService.WRITE);
}
@Test
public void collaboratorSaWriteAccessShouldBeAllowed()
{
assertAccessAllowed(COLLABORATOR_SA, PermissionService.WRITE);
}
@Test
public void adminSaWriteAccessShouldBeAllowed()
{
assertAccessAllowed(ADMIN_SA, PermissionService.WRITE);
}
@Test
public void normalUserAddChildrenAccessShouldBeDenied()
{
assertAccessDenied(NORMAL_USER, PermissionService.ADD_CHILDREN);
}
@Test
public void editorSaAddChildrenAccessShouldBeDenied()
{
assertAccessDenied(EDITOR_SA, PermissionService.ADD_CHILDREN);
}
@Test
public void collaboratorSaAddChildrenAccessShouldBeAllowed()
{
assertAccessAllowed(COLLABORATOR_SA, PermissionService.ADD_CHILDREN);
}
@Test
public void adminSaAddChildrenAccessShouldBeAllowed()
{
assertAccessAllowed(ADMIN_SA, PermissionService.ADD_CHILDREN);
}
@Test
public void normalUserDeleteAccessShouldBeDenied()
{
assertAccessDenied(NORMAL_USER, PermissionService.DELETE);
}
@Test
public void editorSaDeleteAccessShouldBeDenied()
{
assertAccessDenied(EDITOR_SA, PermissionService.DELETE);
}
@Test
public void collaboratorSaDeleteAccessShouldBeDenied()
{
assertAccessDenied(COLLABORATOR_SA, PermissionService.DELETE);
}
@Test
public void adminSaDeleteAccessShouldBeAllowed()
{
assertAccessAllowed(ADMIN_SA, PermissionService.DELETE);
}
@Test
public void normalUserAssociationAccessShouldBeDenied()
{
assertAccessDenied(NORMAL_USER, PermissionService.READ_ASSOCIATIONS);
assertAccessDenied(NORMAL_USER, PermissionService.CREATE_ASSOCIATIONS);
assertAccessDenied(NORMAL_USER, PermissionService.DELETE_ASSOCIATIONS);
}
@Test
public void editorSaAssociationAccessShouldBeDenied()
{
assertAccessDenied(EDITOR_SA, PermissionService.READ_ASSOCIATIONS);
assertAccessDenied(EDITOR_SA, PermissionService.CREATE_ASSOCIATIONS);
assertAccessDenied(EDITOR_SA, PermissionService.DELETE_ASSOCIATIONS);
}
@Test
public void collaboratorSaAssociationAccessShouldBeDenied()
{
assertAccessDenied(COLLABORATOR_SA, PermissionService.READ_ASSOCIATIONS);
assertAccessDenied(COLLABORATOR_SA, PermissionService.CREATE_ASSOCIATIONS);
assertAccessDenied(COLLABORATOR_SA, PermissionService.DELETE_ASSOCIATIONS);
}
@Test
public void adminSaAssociationAccessShouldBeAllowed()
{
assertAccessAllowed(ADMIN_SA, PermissionService.READ_ASSOCIATIONS);
assertAccessAllowed(ADMIN_SA, PermissionService.CREATE_ASSOCIATIONS);
assertAccessAllowed(ADMIN_SA, PermissionService.DELETE_ASSOCIATIONS);
}
@Test
public void normalUserReadPermissionsAccessShouldBeDenied()
{
assertAccessDenied(NORMAL_USER, PermissionService.READ_PERMISSIONS);
}
@Test
public void editorSaReadPermissionsAccessShouldBeDenied()
{
assertAccessDenied(EDITOR_SA, PermissionService.READ_PERMISSIONS);
}
@Test
public void collaboratorSaReadPermissionsAccessShouldBeDenied()
{
assertAccessDenied(COLLABORATOR_SA, PermissionService.READ_PERMISSIONS);
}
@Test
public void adminSaReadPermissionsAccessShouldBeAllowed()
{
assertAccessAllowed(ADMIN_SA, PermissionService.READ_PERMISSIONS);
}
private static void assertAccessDenied(AlfrescoPerson user, String permission)
{
AuthenticationUtil.setFullyAuthenticatedUser(user.getUsername());
assertEquals(AccessStatus.DENIED, permissionService.hasPermission(testTextFile, permission));
}
private static void assertAccessAllowed(AlfrescoPerson user, String permission)
{
AuthenticationUtil.setFullyAuthenticatedUser(user.getUsername());
assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(testTextFile, permission));
}
}

View File

@@ -2,7 +2,7 @@
* #%L
* Alfresco Repository
* %%
* Copyright (C) 2005 - 2023 Alfresco Software Limited
* Copyright (C) 2005 - 2024 Alfresco Software Limited
* %%
* This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of
@@ -66,6 +66,8 @@ import org.springframework.context.ApplicationContext;
public abstract class AbstractPermissionTest extends TestCase
{
public static final int NUMBER_OF_GLOBAL_PERMISSIONS = 8;
protected static final String USER2_LEMUR = "lemur";
protected static final String USER1_ANDY = "andy";

View File

@@ -2,7 +2,7 @@
* #%L
* Alfresco Repository
* %%
* Copyright (C) 2005 - 2016 Alfresco Software Limited
* Copyright (C) 2005 - 2024 Alfresco Software Limited
* %%
* This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of
@@ -73,6 +73,10 @@ import org.junit.runners.MethodSorters;
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class PermissionServiceTest extends AbstractPermissionTest
{
// The number of permissions in the system.
// See permissionDefinitions.xml and PermissionService interface
private static final int NUM_OF_PERMISSIONS = 39;
private SimplePermissionEntry denyAndyAll;
private SimplePermissionEntry allowAndyAll;
@@ -1764,7 +1768,7 @@ public class PermissionServiceTest extends AbstractPermissionTest
public void testGetSettablePermissionsForType()
{
Set<String> answer = permissionService.getSettablePermissions(QName.createQName("sys", "base", namespacePrefixResolver));
assertEquals(36, answer.size());
assertEquals(NUM_OF_PERMISSIONS, answer.size());
answer = permissionService.getSettablePermissions(QName.createQName("cm", "ownable", namespacePrefixResolver));
assertEquals(0, answer.size());
@@ -1784,15 +1788,15 @@ public class PermissionServiceTest extends AbstractPermissionTest
QName ownable = QName.createQName("cm", "ownable", namespacePrefixResolver);
Set<String> answer = permissionService.getSettablePermissions(rootNodeRef);
assertEquals(36, answer.size());
assertEquals(NUM_OF_PERMISSIONS, answer.size());
nodeService.addAspect(rootNodeRef, ownable, null);
answer = permissionService.getSettablePermissions(rootNodeRef);
assertEquals(36, answer.size());
assertEquals(NUM_OF_PERMISSIONS, answer.size());
nodeService.removeAspect(rootNodeRef, ownable);
answer = permissionService.getSettablePermissions(rootNodeRef);
assertEquals(36, answer.size());
assertEquals(NUM_OF_PERMISSIONS, answer.size());
}
@@ -1816,7 +1820,7 @@ public class PermissionServiceTest extends AbstractPermissionTest
{
runAs("andy");
assertEquals(36, permissionService.getPermissions(rootNodeRef).size());
assertEquals(NUM_OF_PERMISSIONS, permissionService.getPermissions(rootNodeRef).size());
assertEquals(0, countGranted(permissionService.getPermissions(rootNodeRef)));
assertEquals(0, permissionService.getAllSetPermissions(rootNodeRef).size());
@@ -1828,7 +1832,7 @@ public class PermissionServiceTest extends AbstractPermissionTest
assertEquals(1, permissionService.getAllSetPermissions(rootNodeRef).size());
runAs("andy");
assertEquals(36, permissionService.getPermissions(rootNodeRef).size());
assertEquals(NUM_OF_PERMISSIONS, permissionService.getPermissions(rootNodeRef).size());
assertEquals(2, countGranted(permissionService.getPermissions(rootNodeRef)));
assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED);
@@ -1933,7 +1937,7 @@ public class PermissionServiceTest extends AbstractPermissionTest
permissionService.setPermission(allowAndyRead);
runAs("andy");
assertEquals(36, permissionService.getPermissions(rootNodeRef).size());
assertEquals(NUM_OF_PERMISSIONS, permissionService.getPermissions(rootNodeRef).size());
assertEquals(7, countGranted(permissionService.getPermissions(rootNodeRef)));
assertEquals(1, permissionService.getAllSetPermissions(rootNodeRef).size());

View File

@@ -1,28 +1,28 @@
/*
* #%L
* Alfresco Repository
* %%
* Copyright (C) 2005 - 2016 Alfresco Software Limited
* %%
* This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms:
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
* #L%
*/
/*
* #%L
* Alfresco Repository
* %%
* Copyright (C) 2005 - 2024 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.security.permissions.impl.model;
import java.util.Collections;
@@ -94,7 +94,7 @@ public class PermissionModelTest extends AbstractPermissionTest
Set<PermissionReference> grantees = permissionModelDAO.getGranteePermissions(SimplePermissionReference.getPermissionReference(QName.createQName("cm", "cmobject",
namespacePrefixResolver), "Coordinator"));
assertEquals(69, grantees.size());
assertEquals(72, grantees.size());
}
public void testIncludePermissionGroups6()
@@ -109,17 +109,17 @@ public class PermissionModelTest extends AbstractPermissionTest
{
Set<PermissionReference> granters = permissionModelDAO.getGrantingPermissions(SimplePermissionReference.getPermissionReference(QName.createQName("sys", "base",
namespacePrefixResolver), "ReadProperties"));
assertEquals(14, granters.size());
assertEquals(17, granters.size());
granters = permissionModelDAO.getGrantingPermissions(SimplePermissionReference.getPermissionReference(QName.createQName("sys", "base", namespacePrefixResolver),
"_ReadProperties"));
assertEquals(15, granters.size());
assertEquals(18, granters.size());
}
public void testGlobalPermissions()
{
Set<? extends PermissionEntry> globalPermissions = permissionModelDAO.getGlobalPermissionEntries();
assertEquals(5, globalPermissions.size());
assertEquals(NUMBER_OF_GLOBAL_PERMISSIONS, globalPermissions.size());
}
public void testRequiredPermissions()

View File

@@ -0,0 +1,157 @@
/*
* #%L
* Alfresco Repository
* %%
* Copyright (C) 2005 - 2024 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.serviceaccount;
import static org.alfresco.service.cmr.security.PermissionService.ADMIN_SVC_AUTHORITY;
import static org.alfresco.service.cmr.security.PermissionService.COLLABORATOR_SVC_AUTHORITY;
import static org.alfresco.service.cmr.security.PermissionService.EDITOR_SVC_AUTHORITY;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.util.Optional;
import java.util.Properties;
import org.junit.Before;
import org.junit.Test;
/**
* Unit tests for the {@link ServiceAccountRegistryImpl} class.
*
* @author Jamal Kaabi-Mofrad
*/
public class ServiceAccountRegistryImplTest
{
private ServiceAccountRegistryImpl serviceAccountService;
private Properties globalProperties;
@Before
public void setUp() throws Exception
{
globalProperties = new Properties();
globalProperties.put("system.test.property", "test-prop");
globalProperties.put("repo.events.test.someKey", "test-event-value");
serviceAccountService = new ServiceAccountRegistryImpl();
serviceAccountService.setGlobalProperties(globalProperties);
serviceAccountService.afterPropertiesSet();
}
@Test
public void testNoDefinedServiceAccount()
{
Optional<String> nonExistentSa = serviceAccountService.getServiceAccountRole("nonExistentServiceAccount");
assertTrue(nonExistentSa.isEmpty());
assertTrue(serviceAccountService.getServiceAccountNames().isEmpty());
}
@Test
public void testInvalidServiceAccountName()
{
globalProperties.put("serviceaccount.role. ", ADMIN_SVC_AUTHORITY);
assertTrue("Invalid service account name.", serviceAccountService.getServiceAccountNames().isEmpty());
}
@Test
public void testInvalidServiceAccountRole() throws Exception
{
globalProperties.put("serviceaccount.role.testServiceAccount", "");
serviceAccountService.afterPropertiesSet();
Optional<String> testServiceAccount = serviceAccountService.getServiceAccountRole("testServiceAccount");
assertTrue("Invalid service account role.", testServiceAccount.isEmpty());
assertTrue(serviceAccountService.getServiceAccountNames().isEmpty());
}
@Test
public void testNotSupportedServiceAccountRole() throws Exception
{
globalProperties.put("serviceaccount.role.testServiceAccount", "testRole");
serviceAccountService.afterPropertiesSet();
Optional<String> testServiceAccount = serviceAccountService.getServiceAccountRole("testServiceAccount");
assertTrue("Not supported service account role.", testServiceAccount.isEmpty());
assertTrue(serviceAccountService.getServiceAccountNames().isEmpty());
}
@Test
public void testValidServiceAccount() throws Exception
{
globalProperties.put("serviceaccount.role.testServiceAccount", ADMIN_SVC_AUTHORITY);
serviceAccountService.afterPropertiesSet();
Optional<String> testServiceAccount = serviceAccountService.getServiceAccountRole("testServiceAccount");
assertFalse("The service account role is not empty.", testServiceAccount.isEmpty());
assertEquals(ADMIN_SVC_AUTHORITY, testServiceAccount.get());
assertEquals(1, serviceAccountService.getServiceAccountNames().size());
}
@Test
public void testManyServiceAccounts() throws Exception
{
globalProperties.put("serviceaccount.role.testEditorSA", EDITOR_SVC_AUTHORITY);
globalProperties.put("serviceaccount.role.testCollaboratorSA", COLLABORATOR_SVC_AUTHORITY);
globalProperties.put("serviceaccount.role.testAdminSA", ADMIN_SVC_AUTHORITY);
serviceAccountService.afterPropertiesSet();
assertEquals(3, serviceAccountService.getServiceAccountNames().size());
Optional<String> editorSA = serviceAccountService.getServiceAccountRole("testEditorSA");
assertFalse("The service account role is not empty.", editorSA.isEmpty());
assertEquals(EDITOR_SVC_AUTHORITY, editorSA.get());
Optional<String> collaboratorSA = serviceAccountService.getServiceAccountRole("testCollaboratorSA");
assertFalse("The service account role is not empty.", collaboratorSA.isEmpty());
assertEquals(COLLABORATOR_SVC_AUTHORITY, collaboratorSA.get());
Optional<String> adminSA = serviceAccountService.getServiceAccountRole("testAdminSA");
assertFalse("The service account role is not empty.", adminSA.isEmpty());
assertEquals(ADMIN_SVC_AUTHORITY, adminSA.get());
}
@Test
public void testValidServiceAccountRoleValues() throws Exception
{
globalProperties.put("serviceaccount.role.testEditorSA", "EDITOR_SERVICE_ACCOUNT");
globalProperties.put("serviceaccount.role.testCollaboratorSA", "COLLABORATOR_SERVICE_ACCOUNT");
globalProperties.put("serviceaccount.role.testAdminSA", "ADMIN_SERVICE_ACCOUNT");
serviceAccountService.afterPropertiesSet();
assertEquals(3, serviceAccountService.getServiceAccountNames().size());
Optional<String> editorSA = serviceAccountService.getServiceAccountRole("testEditorSA");
assertFalse("The service account role is not empty.", editorSA.isEmpty());
assertEquals(EDITOR_SVC_AUTHORITY, editorSA.get());
Optional<String> collaboratorSA = serviceAccountService.getServiceAccountRole("testCollaboratorSA");
assertFalse("The service account role is not empty.", collaboratorSA.isEmpty());
assertEquals(COLLABORATOR_SVC_AUTHORITY, collaboratorSA.get());
Optional<String> adminSA = serviceAccountService.getServiceAccountRole("testAdminSA");
assertFalse("The service account role is not empty.", adminSA.isEmpty());
assertEquals(ADMIN_SVC_AUTHORITY, adminSA.get());
}
}

View File

@@ -61,3 +61,8 @@ encryption.keystore.backup.type=JCEKS
# For CI override the default hashing algorithm for password storage to save build time.
system.preferred.password.encoding=sha256
# Test service accounts
serviceaccount.role.test-editor-sa=ROLE_EDITOR_SERVICE_ACCOUNT
serviceaccount.role.test-collaborator-sa=ROLE_COLLABORATOR_SERVICE_ACCOUNT
serviceaccount.role.test-admin-sa=ROLE_ADMIN_SERVICE_ACCOUNT

View File

@@ -59,7 +59,7 @@ services:
CLIENT_SSL_TRUST_STORE_TYPE: "JCEKS"
keycloak:
profiles: ["with-sso"]
image: quay.io/keycloak/keycloak:21.1.2
image: quay.io/keycloak/keycloak:24.0.3
environment:
- KEYCLOAK_ADMIN=admin
- KEYCLOAK_ADMIN_PASSWORD=admin