From ee5e34ca3293cb3d1b53ff4d2c7a164b1cf05541 Mon Sep 17 00:00:00 2001 From: Damian Ujma <92095156+damianujma@users.noreply.github.com> Date: Fri, 16 May 2025 18:55:45 +0200 Subject: [PATCH] ACS-9399 Add AGS Roles V1 API (Read) (#3350) Co-authored-by: SatyamSah5 Co-authored-by: bsayan2 --- .../rm/community/model/CapabilityModel.java | 30 ++ .../rest/rm/community/model/GroupModel.java | 30 ++ .../rest/rm/community/model/role/Role.java | 91 ++++++ .../community/model/role/RoleCollection.java | 32 +++ .../rm/community/model/role/RoleEntry.java | 47 ++++ .../rm/community/model/user/UserRoles.java | 2 +- .../requests/gscore/api/FilePlanAPI.java | 36 +++ .../rm/community/base/BaseRMRestTest.java | 5 + .../rm/community/fileplans/FilePlanTests.java | 184 +++++++++++- .../rm-public-rest-context.xml | 12 +- .../org/alfresco/rm/rest/api/RMRoles.java | 56 ++++ .../api/fileplans/FilePlanRolesRelation.java | 72 +++++ .../rest/api/impl/ApiNodesModelFactory.java | 37 +++ .../rm/rest/api/impl/RMRolesImpl.java | 264 ++++++++++++++++++ .../rm/rest/api/model/CapabilityModel.java | 30 ++ .../rm/rest/api/model/GroupModel.java | 30 ++ .../alfresco/rm/rest/api/model/RoleModel.java | 32 +++ .../rm/rest/api/model/RoleModelList.java | 32 +++ .../rm/rest/api/roles/package-info.java | 37 +++ .../rm/rest/api/impl/RMRolesImplUnitTest.java | 177 ++++++++++++ .../main/webapp/definitions/gs-core-api.yaml | 2 +- 21 files changed, 1233 insertions(+), 5 deletions(-) create mode 100644 amps/ags/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/rm/community/model/CapabilityModel.java create mode 100644 amps/ags/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/rm/community/model/GroupModel.java create mode 100644 amps/ags/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/rm/community/model/role/Role.java create mode 100644 amps/ags/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/rm/community/model/role/RoleCollection.java create mode 100644 amps/ags/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/rm/community/model/role/RoleEntry.java create mode 100644 amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/rm/rest/api/RMRoles.java create mode 100644 amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/rm/rest/api/fileplans/FilePlanRolesRelation.java create mode 100644 amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/rm/rest/api/impl/RMRolesImpl.java create mode 100644 amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/rm/rest/api/model/CapabilityModel.java create mode 100644 amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/rm/rest/api/model/GroupModel.java create mode 100644 amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/rm/rest/api/model/RoleModel.java create mode 100644 amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/rm/rest/api/model/RoleModelList.java create mode 100644 amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/rm/rest/api/roles/package-info.java create mode 100644 amps/ags/rm-community/rm-community-repo/unit-test/java/org/alfresco/rm/rest/api/impl/RMRolesImplUnitTest.java diff --git a/amps/ags/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/rm/community/model/CapabilityModel.java b/amps/ags/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/rm/community/model/CapabilityModel.java new file mode 100644 index 0000000000..4ac8b5b4d7 --- /dev/null +++ b/amps/ags/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/rm/community/model/CapabilityModel.java @@ -0,0 +1,30 @@ +/* + * #%L + * Alfresco Records Management Module + * %% + * Copyright (C) 2005 - 2025 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * - + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * - + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * - + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * - + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + * #L% + */ +package org.alfresco.rest.rm.community.model; + +public record CapabilityModel(String name, String title, String description, GroupModel group, int index) +{} diff --git a/amps/ags/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/rm/community/model/GroupModel.java b/amps/ags/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/rm/community/model/GroupModel.java new file mode 100644 index 0000000000..5b6f146c94 --- /dev/null +++ b/amps/ags/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/rm/community/model/GroupModel.java @@ -0,0 +1,30 @@ +/* + * #%L + * Alfresco Records Management Module + * %% + * Copyright (C) 2005 - 2025 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * - + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * - + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * - + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * - + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + * #L% + */ +package org.alfresco.rest.rm.community.model; + +public record GroupModel(String id, String title) +{} diff --git a/amps/ags/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/rm/community/model/role/Role.java b/amps/ags/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/rm/community/model/role/Role.java new file mode 100644 index 0000000000..664d212bad --- /dev/null +++ b/amps/ags/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/rm/community/model/role/Role.java @@ -0,0 +1,91 @@ +/*- + * #%L + * Alfresco Records Management Module + * %% + * Copyright (C) 2005 - 2025 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * - + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * - + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * - + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * - + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + * #L% + */ +package org.alfresco.rest.rm.community.model.role; + +import java.util.List; +import java.util.Objects; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import org.alfresco.rest.rm.community.model.CapabilityModel; +import org.alfresco.utility.model.TestModel; + +/** + * POJO for role + */ +@Builder +@Data +@NoArgsConstructor +@AllArgsConstructor +public class Role extends TestModel +{ + + @JsonProperty(required = true) + private String name; + + @JsonProperty(required = true) + private List capabilities; + + @JsonProperty(required = true) + private String displayLabel; + + @JsonProperty(required = true) + private String groupShortName; + + private List assignedUsers; + + private List assignedGroups; + + private String roleGroupName; + + @Override + public boolean equals(Object o) + { + if (this == o) + { + return true; + } + if (o == null || getClass() != o.getClass()) + { + return false; + } + Role role = (Role) o; + return Objects.equals(name, role.name) && Objects.equals(capabilities, role.capabilities) + && Objects.equals(displayLabel, role.displayLabel) && Objects.equals(groupShortName, role.groupShortName) && Objects.equals(assignedUsers, role.assignedUsers) + && Objects.equals(assignedGroups, role.assignedGroups) && Objects.equals(roleGroupName, role.roleGroupName); + } + + @Override + public int hashCode() + { + return Objects.hash(name, capabilities, displayLabel, groupShortName, assignedUsers, assignedGroups, roleGroupName); + } +} diff --git a/amps/ags/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/rm/community/model/role/RoleCollection.java b/amps/ags/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/rm/community/model/role/RoleCollection.java new file mode 100644 index 0000000000..92277b612a --- /dev/null +++ b/amps/ags/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/rm/community/model/role/RoleCollection.java @@ -0,0 +1,32 @@ +/*- + * #%L + * Alfresco Records Management Module + * %% + * Copyright (C) 2005 - 2025 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * - + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * - + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * - + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * - + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + * #L% + */ +package org.alfresco.rest.rm.community.model.role; + +import org.alfresco.rest.core.RestModels; + +public class RoleCollection extends RestModels +{} diff --git a/amps/ags/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/rm/community/model/role/RoleEntry.java b/amps/ags/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/rm/community/model/role/RoleEntry.java new file mode 100644 index 0000000000..6d450c25e8 --- /dev/null +++ b/amps/ags/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/rm/community/model/role/RoleEntry.java @@ -0,0 +1,47 @@ +/*- + * #%L + * Alfresco Records Management Module + * %% + * Copyright (C) 2005 - 2025 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * - + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * - + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * - + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * - + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + * #L% + */ +package org.alfresco.rest.rm.community.model.role; + +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 RoleEntry extends RestModels +{ + @JsonProperty + private Role entry; +} diff --git a/amps/ags/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/rm/community/model/user/UserRoles.java b/amps/ags/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/rm/community/model/user/UserRoles.java index ee58fd33dd..6f5f3f5e03 100644 --- a/amps/ags/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/rm/community/model/user/UserRoles.java +++ b/amps/ags/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/rm/community/model/user/UserRoles.java @@ -35,7 +35,7 @@ package org.alfresco.rest.rm.community.model.user; */ public enum UserRoles { - IN_PLACE_WRITERS("ExtendedWriters", "In-Place Writers"), ROLE_RM_ADMIN("Administrator", "Records Management Administrator"), ROLE_RM_MANAGER("RecordsManager", "Records Management Manager"), ROLE_RM_POWER_USER("PowerUser", "Records Management Power User"), ROLE_RM_SECURITY_OFFICER("SecurityOfficer", "Records Management Security Officer"), ROLE_RM_USER("User", "Records Management User"); + IN_PLACE_WRITERS("ExtendedWriters", "In-Place Writers"), ROLE_RM_ADMIN("Administrator", "Records Management Administrator"), ROLE_RM_MANAGER("RecordsManager", "Records Management Manager"), ROLE_RM_POWER_USER("PowerUser", "Records Management Power User"), ROLE_RM_SECURITY_OFFICER("SecurityOfficer", "Records Management Security Officer"), ROLE_RM_USER("User", "Records Management User"), IN_PLACE_READERS("ExtendedReaders", "In-Place Readers"); public final String roleId; public final String displayName; diff --git a/amps/ags/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/rm/community/requests/gscore/api/FilePlanAPI.java b/amps/ags/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/rm/community/requests/gscore/api/FilePlanAPI.java index 40573006bc..ca855c71e3 100644 --- a/amps/ags/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/rm/community/requests/gscore/api/FilePlanAPI.java +++ b/amps/ags/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/rm/community/requests/gscore/api/FilePlanAPI.java @@ -43,6 +43,7 @@ import org.alfresco.rest.rm.community.model.hold.Hold; import org.alfresco.rest.rm.community.model.hold.HoldCollection; import org.alfresco.rest.rm.community.model.recordcategory.RecordCategory; import org.alfresco.rest.rm.community.model.recordcategory.RecordCategoryCollection; +import org.alfresco.rest.rm.community.model.role.RoleCollection; import org.alfresco.rest.rm.community.requests.RMModelRequest; /** @@ -303,4 +304,39 @@ public class FilePlanAPI extends RMModelRequest { return getHolds(filePlanId, EMPTY); } + + /** + * Gets the roles of a file plan. + * + * @param filePlanId + * The identifier of a file plan + * @param parameters + * The URL parameters to add + * @return The {Pagination and RoleModel Entries} for the given {@code filePlanId} + * @throws RuntimeException + * for the following cases: + *
    + *
  • authentication fails
  • + *
  • current user does not have permission to read {@code filePlanId}
  • + *
  • {@code filePlanId} does not exist
  • + *
+ */ + public RoleCollection getFilePlanRoles(String filePlanId, String parameters) + { + mandatoryString("filePlanId", filePlanId); + return getRmRestWrapper().processModels(RoleCollection.class, simpleRequest( + GET, + "file-plans/{filePlanId}/roles?{parameters}", + filePlanId, + parameters)); + } + + /** + * See {@link #getFilePlanRoles(String, String)} + */ + public RoleCollection getFilePlanRoles(String filePlanId) + { + return getFilePlanRoles(filePlanId, EMPTY); + } + } diff --git a/amps/ags/rm-automation/rm-automation-community-rest-api/src/test/java/org/alfresco/rest/rm/community/base/BaseRMRestTest.java b/amps/ags/rm-automation/rm-automation-community-rest-api/src/test/java/org/alfresco/rest/rm/community/base/BaseRMRestTest.java index bc99309fb7..f501ab15d5 100644 --- a/amps/ags/rm-automation/rm-automation-community-rest-api/src/test/java/org/alfresco/rest/rm/community/base/BaseRMRestTest.java +++ b/amps/ags/rm-automation/rm-automation-community-rest-api/src/test/java/org/alfresco/rest/rm/community/base/BaseRMRestTest.java @@ -93,6 +93,7 @@ import org.alfresco.rest.rm.community.requests.gscore.api.RecordsAPI; import org.alfresco.rest.search.RestRequestQueryModel; import org.alfresco.rest.search.SearchNodeModel; import org.alfresco.rest.search.SearchRequest; +import org.alfresco.rest.v0.RMRolesAndActionsAPI; import org.alfresco.rest.v0.SearchAPI; import org.alfresco.utility.Utility; import org.alfresco.utility.data.DataUserAIS; @@ -127,6 +128,10 @@ public class BaseRMRestTest extends RestTest @Getter(value = PROTECTED) private SearchAPI searchApi; + @Autowired + @Getter(PROTECTED) + private RMRolesAndActionsAPI rmRolesAndActionsV0API; + protected static final String iso8601_DateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX"; /** diff --git a/amps/ags/rm-automation/rm-automation-community-rest-api/src/test/java/org/alfresco/rest/rm/community/fileplans/FilePlanTests.java b/amps/ags/rm-automation/rm-automation-community-rest-api/src/test/java/org/alfresco/rest/rm/community/fileplans/FilePlanTests.java index eb14b5ca98..74ba849bf8 100644 --- a/amps/ags/rm-automation/rm-automation-community-rest-api/src/test/java/org/alfresco/rest/rm/community/fileplans/FilePlanTests.java +++ b/amps/ags/rm-automation/rm-automation-community-rest-api/src/test/java/org/alfresco/rest/rm/community/fileplans/FilePlanTests.java @@ -28,6 +28,7 @@ package org.alfresco.rest.rm.community.fileplans; import static java.util.Arrays.asList; +import static com.google.common.collect.Sets.newHashSet; import static org.springframework.http.HttpStatus.CONFLICT; import static org.springframework.http.HttpStatus.CREATED; import static org.springframework.http.HttpStatus.FORBIDDEN; @@ -56,19 +57,27 @@ import static org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanCo import static org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanComponentType.UNFILED_CONTAINER_TYPE; import static org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanComponentType.UNFILED_RECORD_FOLDER_TYPE; import static org.alfresco.rest.rm.community.model.user.UserPermissions.PERMISSION_FILING; +import static org.alfresco.rest.rm.community.model.user.UserRoles.IN_PLACE_READERS; +import static org.alfresco.rest.rm.community.model.user.UserRoles.IN_PLACE_WRITERS; +import static org.alfresco.rest.rm.community.model.user.UserRoles.ROLE_RM_ADMIN; import static org.alfresco.rest.rm.community.model.user.UserRoles.ROLE_RM_MANAGER; +import static org.alfresco.rest.rm.community.model.user.UserRoles.ROLE_RM_POWER_USER; +import static org.alfresco.rest.rm.community.model.user.UserRoles.ROLE_RM_SECURITY_OFFICER; +import static org.alfresco.rest.rm.community.model.user.UserRoles.ROLE_RM_USER; import static org.alfresco.utility.data.RandomData.getRandomAlphanumeric; import static org.alfresco.utility.data.RandomData.getRandomName; import java.util.ArrayList; import java.util.List; import java.util.NoSuchElementException; +import java.util.Set; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import org.alfresco.rest.rm.community.base.BaseRMRestTest; import org.alfresco.rest.rm.community.base.DataProviderClass; +import org.alfresco.rest.rm.community.model.CapabilityModel; import org.alfresco.rest.rm.community.model.fileplan.FilePlan; import org.alfresco.rest.rm.community.model.fileplan.FilePlanProperties; import org.alfresco.rest.rm.community.model.hold.Hold; @@ -76,6 +85,9 @@ import org.alfresco.rest.rm.community.model.hold.HoldCollection; import org.alfresco.rest.rm.community.model.recordcategory.RecordCategory; import org.alfresco.rest.rm.community.model.recordcategory.RecordCategoryCollection; import org.alfresco.rest.rm.community.model.recordcategory.RecordCategoryProperties; +import org.alfresco.rest.rm.community.model.role.Role; +import org.alfresco.rest.rm.community.model.role.RoleCollection; +import org.alfresco.rest.rm.community.model.user.UserCapabilities; import org.alfresco.rest.rm.community.requests.gscore.api.RMSiteAPI; import org.alfresco.utility.constants.ContainerName; import org.alfresco.utility.model.UserModel; @@ -87,6 +99,7 @@ import org.alfresco.utility.report.Bug; * @author Rodica Sutu * @since 2.6 */ +@SuppressWarnings("PMD.UnitTestShouldIncludeAssert") public class FilePlanTests extends BaseRMRestTest { // ** Number of children (for children creation test) */ @@ -266,7 +279,7 @@ public class FilePlanTests extends BaseRMRestTest * When I ask the API to create a root record category * Then it is created as a root record category * - * + * *
      * Given that a file plan exists
      * When I use the API to create a folder (cm:folder type) into the fileplan
@@ -314,7 +327,7 @@ public class FilePlanTests extends BaseRMRestTest
      * When I ask the API to create a root category having the same name
      * Then  the response code received is 409 - name clashes with an existing node
      * 
- * + * *
      * Given a root category
      * When I ask the API to create a root category having the same name  with autoRename parameter on true
@@ -594,4 +607,171 @@ public class FilePlanTests extends BaseRMRestTest
             }
         });
     }
+
+    /**
+     * 
+     * Given that a file plan exists
+     * When rmAdmin user ask the API for roles
+     * It provides list of all default roles
+     * 
+ */ + @Test + public void listFilePlanAllDefaultRoles() + { + List defaultRolesDisplayNames = asList(IN_PLACE_READERS.displayName, ROLE_RM_ADMIN.displayName, ROLE_RM_MANAGER.displayName, ROLE_RM_POWER_USER.displayName, ROLE_RM_USER.displayName, IN_PLACE_WRITERS.displayName, ROLE_RM_SECURITY_OFFICER.displayName); + // Call to new API to get the roles and capabilities + RoleCollection roleCollection = getRestAPIFactory().getFilePlansAPI().getFilePlanRoles(FILE_PLAN_ALIAS); + assertStatusCode(OK); + roleCollection.getEntries().forEach(roleModelEntry -> { + Role role = roleModelEntry.getEntry(); + assertTrue(defaultRolesDisplayNames.contains(role.getDisplayLabel())); + assertNotNull(role.getCapabilities()); + }); + } + + /** + *
+     * Given that a file plan exists
+     * When rmAdmin user ask the API for roles with SystemRoles as false
+     * It provides list of all roles excluding SystemRoles
+     * 
+ */ + @Test + public void listFilePlanAllRolesExcludeSystemRoles() + { + String parameters = "where=(systemRoles=false)"; + List systemRolesDisplayNames = asList(IN_PLACE_WRITERS.displayName, IN_PLACE_READERS.displayName); + // Call to new API to get the roles and capabilities + RoleCollection roleCollection = getRestAPIFactory().getFilePlansAPI().getFilePlanRoles(FILE_PLAN_ALIAS, parameters); + assertStatusCode(OK); + roleCollection.getEntries().forEach(roleModelEntry -> { + Role role = roleModelEntry.getEntry(); + assertFalse(systemRolesDisplayNames.contains(role.getDisplayLabel())); + assertNotNull(role.getCapabilities()); + }); + } + + /** + *
+     * Given that a file plan exists
+     * When a non-RM user asks the API for the roles
+     * Then the status code 403 (Permission denied) is return
+     * 
+ */ + @Test + public void nonRmUserFilePlanRoles() + { + // Create a random user + UserModel nonRMuser = getDataUser().createRandomTestUser("testUser"); + // Call to new API to get the roles and capabilities + getRestAPIFactory().getFilePlansAPI(nonRMuser).getFilePlanRoles(FILE_PLAN_ALIAS); + assertStatusCode(FORBIDDEN); + } + + /** + *
+     * Given that a file plan exists
+     * When a RM_Manager user asks the API for the roles
+     * returns the RM_Manager role and capabilities
+     * 
+ */ + @Test + public void rmManagerFilePlanRolesAndCapabilities() + { + // Create a random user + UserModel managerUser = getDataUser().createRandomTestUser("managerUser"); + // Assign RecordsManager role to user + getRestAPIFactory().getRMUserAPI().assignRoleToUser(managerUser.getUsername(), ROLE_RM_MANAGER.roleId); + String parameters = "where=(personId='" + managerUser.getUsername() + "')"; + // Call to new API to get the roles and capabilities + RoleCollection roleCollection = getRestAPIFactory().getFilePlansAPI(managerUser).getFilePlanRoles(FILE_PLAN_ALIAS, parameters); + roleCollection.getEntries().forEach(roleModelEntry -> { + Role role = roleModelEntry.getEntry(); + assertEquals(ROLE_RM_MANAGER.displayName, role.getDisplayLabel()); + assertNotNull(role.getCapabilities()); + }); + + } + + /** + *
+     * Given that a file plan exists
+     * When a User with more than one role asks the API for the roles and relation
+     * returns the roles and capabilities
+     * 
+ */ + @Test + public void multipleRoleUserFilePlanRolesAndCapabilities() + { + // Create a random user + UserModel rmUser = getDataUser().createRandomTestUser("rmUser"); + // Assign rmUser role to user + getRestAPIFactory().getRMUserAPI().assignRoleToUser(rmUser.getUsername(), ROLE_RM_USER.roleId); + getRestAPIFactory().getRMUserAPI().assignRoleToUser(rmUser.getUsername(), ROLE_RM_POWER_USER.roleId); + String parameters = "where=(personId='" + rmUser.getUsername() + "')"; + // Call to new API to get the roles and capabilities + RoleCollection roleCollection = getRestAPIFactory().getFilePlansAPI(rmUser).getFilePlanRoles(FILE_PLAN_ALIAS, parameters); + assertStatusCode(OK); + assertEquals(roleCollection.getEntries().size(), 2); + roleCollection.getEntries().forEach(roleModelEntry -> { + Role role = roleModelEntry.getEntry(); + assertTrue(role.getDisplayLabel().equals(ROLE_RM_USER.displayName) || role.getDisplayLabel().equals(ROLE_RM_POWER_USER.displayName)); + assertNotNull(role.getCapabilities()); + }); + } + + /** + *
+     * Given that a file plan exists
+     * When a new user with a new role asks the API for the roles and relation
+     * returns the new role and new capabilities
+     * 
+ */ + @Test + public void newRoleUserFilePlanRolesAndCapabilities() + { + /** A list of capabilities. */ + Set newCapabilities = newHashSet(UserCapabilities.VIEW_RECORDS_CAP, UserCapabilities.DECLARE_RECORDS_CAP); + // Create a new role using old API + getRmRolesAndActionsV0API().createRole(getAdminUser().getUsername(), getAdminUser().getPassword(), "NewTestRole", + "New Role Label", newCapabilities); + // Create a random user + UserModel rmNewUser = getDataUser().createRandomTestUser("rmPowerUser"); + // Assign New role to user + getRestAPIFactory().getRMUserAPI().assignRoleToUser(rmNewUser.getUsername(), "NewTestRole"); + String parameters = "where=(personId='" + rmNewUser.getUsername() + "')"; + // Call to new API to get the roles and capabilities + RoleCollection roleCollection = getRestAPIFactory().getFilePlansAPI(rmNewUser).getFilePlanRoles(FILE_PLAN_ALIAS, parameters); + assertStatusCode(OK); + assertEquals(roleCollection.getEntries().size(), 1); + roleCollection.getEntries().forEach(roleModelEntry -> { + List capabilities = roleModelEntry.getEntry().getCapabilities(); + capabilities.forEach(capabilityModel -> { + assertTrue(newCapabilities.contains(capabilityModel.name())); + }); + }); + } + + /** + *
+     * Given that a file plan exists
+     * When API call happens with Capability filter
+     * returns roles associated with the capability
+     * 
+ */ + @Test + public void filePlanRolesAndCapabilitiesFilter() + { + String parameters = "where=(systemRoles=true and capabilityName in ('ManageRules'))"; + // Call to new API to get the roles and capabilities, filter by capability, include assigned users + RoleCollection roleCollection = getRestAPIFactory().getFilePlansAPI().getFilePlanRoles(FILE_PLAN_ALIAS, parameters); + assertStatusCode(OK); + assertEquals(roleCollection.getEntries().size(), 1); + roleCollection.getEntries().forEach(roleModelEntry -> { + Role role = roleModelEntry.getEntry(); + assertEquals(ROLE_RM_ADMIN.displayName, role.getDisplayLabel()); + assertNotNull(role.getCapabilities()); + }); + } + } diff --git a/amps/ags/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/rm-public-rest-context.xml b/amps/ags/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/rm-public-rest-context.xml index efb01d2730..67152cf0f6 100644 --- a/amps/ags/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/rm-public-rest-context.xml +++ b/amps/ags/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/rm-public-rest-context.xml @@ -78,6 +78,11 @@ + + + + + @@ -228,6 +233,11 @@ + + + + + @@ -280,4 +290,4 @@ - \ No newline at end of file + diff --git a/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/rm/rest/api/RMRoles.java b/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/rm/rest/api/RMRoles.java new file mode 100644 index 0000000000..13deb866fc --- /dev/null +++ b/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/rm/rest/api/RMRoles.java @@ -0,0 +1,56 @@ +/* + * #%L + * Alfresco Records Management Module + * %% + * Copyright (C) 2005 - 2025 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * - + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * - + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * - + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * - + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + * #L% + */ +package org.alfresco.rm.rest.api; + +import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo; +import org.alfresco.rest.framework.resource.parameters.Parameters; +import org.alfresco.rm.rest.api.model.RoleModel; +import org.alfresco.service.cmr.repository.NodeRef; + +/** + * RM Roles API + */ +public interface RMRoles +{ + + String PARAM_INCLUDE_ASSIGNED_USERS = "assignedUsers"; + String PARAM_INCLUDE_ASSIGNED_GROUPS = "assignedGroups"; + String PARAM_INCLUDE_SYSTEM_ROLES = "systemRoles"; + String PARAM_CAPABILITY_NAME = "capabilityName"; + String PARAM_PERSON_ID = "personId"; + + /** + * Gets a list of roles. + * + * @param filePlan + * the file plan node reference + * @param parameters + * the {@link Parameters} object to get the parameters passed into the request including: - filter, sort & paging params (where, orderBy, skipCount, maxItems) - include param (personId, includeSystemRoles) + * @return a paged list of {@code org.alfresco.rm.rest.api.model.RoleModel} objects + */ + CollectionWithPagingInfo getRoles(NodeRef filePlan, Parameters parameters); +} diff --git a/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/rm/rest/api/fileplans/FilePlanRolesRelation.java b/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/rm/rest/api/fileplans/FilePlanRolesRelation.java new file mode 100644 index 0000000000..183234f42e --- /dev/null +++ b/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/rm/rest/api/fileplans/FilePlanRolesRelation.java @@ -0,0 +1,72 @@ +/* + * #%L + * Alfresco Records Management Module + * %% + * Copyright (C) 2005 - 2025 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * - + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * - + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * - + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * - + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + * #L% + */ +package org.alfresco.rm.rest.api.fileplans; + +import static org.alfresco.util.ParameterCheck.mandatory; + +import org.springframework.beans.factory.InitializingBean; + +import org.alfresco.module.org_alfresco_module_rm.model.RecordsManagementModel; +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.RMRoles; +import org.alfresco.rm.rest.api.impl.FilePlanComponentsApiUtils; +import org.alfresco.rm.rest.api.model.RoleModel; +import org.alfresco.service.cmr.repository.NodeRef; + +@RelationshipResource(name = "roles", entityResource = FilePlanEntityResource.class, title = "Roles in a file plan") +public class FilePlanRolesRelation implements RelationshipResourceAction.Read, InitializingBean +{ + private RMRoles rmRoles; + private FilePlanComponentsApiUtils apiUtils; + + @Override + public void afterPropertiesSet() throws Exception + { + mandatory("rmRoles", this.rmRoles); + mandatory("apiUtils", this.apiUtils); + } + + @Override + public CollectionWithPagingInfo readAll(String filePlanId, Parameters params) + { + NodeRef filePlanNodeRef = apiUtils.lookupAndValidateNodeType(filePlanId, RecordsManagementModel.TYPE_FILE_PLAN); + return rmRoles.getRoles(filePlanNodeRef, params); + } + + public void setRmRoles(RMRoles rmRoles) + { + this.rmRoles = rmRoles; + } + + public void setApiUtils(FilePlanComponentsApiUtils apiUtils) + { + this.apiUtils = apiUtils; + } +} diff --git a/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/rm/rest/api/impl/ApiNodesModelFactory.java b/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/rm/rest/api/impl/ApiNodesModelFactory.java index b5e0a94ce7..9519d03541 100644 --- a/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/rm/rest/api/impl/ApiNodesModelFactory.java +++ b/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/rm/rest/api/impl/ApiNodesModelFactory.java @@ -30,6 +30,7 @@ package org.alfresco.rm.rest.api.impl; import java.io.Serializable; import java.util.ArrayList; import java.util.Arrays; +import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -42,12 +43,15 @@ import org.slf4j.LoggerFactory; import org.alfresco.model.ContentModel; import org.alfresco.module.org_alfresco_module_rm.RecordsManagementServiceRegistry; +import org.alfresco.module.org_alfresco_module_rm.capability.Capability; +import org.alfresco.module.org_alfresco_module_rm.capability.Group; import org.alfresco.module.org_alfresco_module_rm.disposition.DispositionActionDefinition; import org.alfresco.module.org_alfresco_module_rm.disposition.DispositionActionDefinitionImpl; import org.alfresco.module.org_alfresco_module_rm.disposition.DispositionSchedule; import org.alfresco.module.org_alfresco_module_rm.disposition.DispositionService; import org.alfresco.module.org_alfresco_module_rm.event.RecordsManagementEvent; import org.alfresco.module.org_alfresco_module_rm.model.RecordsManagementModel; +import org.alfresco.module.org_alfresco_module_rm.role.Role; import org.alfresco.rest.api.Nodes; import org.alfresco.rest.api.model.AssocChild; import org.alfresco.rest.api.model.ContentInfo; @@ -55,7 +59,9 @@ import org.alfresco.rest.api.model.Node; import org.alfresco.rest.api.model.UserInfo; import org.alfresco.rest.framework.jacksonextensions.BeanPropertiesFilter; import org.alfresco.rest.framework.resource.parameters.Parameters; +import org.alfresco.rm.rest.api.model.CapabilityModel; import org.alfresco.rm.rest.api.model.FilePlan; +import org.alfresco.rm.rest.api.model.GroupModel; import org.alfresco.rm.rest.api.model.HoldModel; import org.alfresco.rm.rest.api.model.RMNode; import org.alfresco.rm.rest.api.model.Record; @@ -66,6 +72,7 @@ import org.alfresco.rm.rest.api.model.RetentionPeriod; import org.alfresco.rm.rest.api.model.RetentionSchedule; import org.alfresco.rm.rest.api.model.RetentionScheduleActionDefinition; import org.alfresco.rm.rest.api.model.RetentionSteps; +import org.alfresco.rm.rest.api.model.RoleModel; import org.alfresco.rm.rest.api.model.Transfer; import org.alfresco.rm.rest.api.model.TransferChild; import org.alfresco.rm.rest.api.model.TransferContainer; @@ -696,6 +703,36 @@ public class ApiNodesModelFactory (String) info.getProperties().get(RecordsManagementModel.PROP_HOLD_REASON)); } + public RoleModel createRoleModel(Role role, List assignedUsers, List assignedGroups) + { + return new RoleModel(role.getName(), + role.getDisplayLabel(), + role.getCapabilities() + .stream() + .map(this::createCapabilityModel) + .sorted(Comparator.comparing(CapabilityModel::name)) + .toList(), + role.getRoleGroupName(), + role.getGroupShortName(), + assignedUsers, + assignedGroups); + } + + public CapabilityModel createCapabilityModel(Capability capability) + { + return new CapabilityModel(capability.getName(), capability.getTitle(), capability.getDescription(), + createGroupModel(capability.getGroup()), capability.getIndex()); + } + + public GroupModel createGroupModel(Group group) + { + if (group == null) + { + return null; + } + return new GroupModel(group.getId(), group.getTitle()); + } + /** * Creates an object of type FilePlan * diff --git a/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/rm/rest/api/impl/RMRolesImpl.java b/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/rm/rest/api/impl/RMRolesImpl.java new file mode 100644 index 0000000000..5f32c5bf9b --- /dev/null +++ b/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/rm/rest/api/impl/RMRolesImpl.java @@ -0,0 +1,264 @@ +/* + * #%L + * Alfresco Records Management Module + * %% + * Copyright (C) 2005 - 2025 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * - + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * - + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * - + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * - + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + * #L% + */ +package org.alfresco.rm.rest.api.impl; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +import org.alfresco.module.org_alfresco_module_rm.role.FilePlanRoleService; +import org.alfresco.module.org_alfresco_module_rm.role.Role; +import org.alfresco.rest.antlr.WhereClauseParser; +import org.alfresco.rest.framework.core.exceptions.InvalidArgumentException; +import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo; +import org.alfresco.rest.framework.resource.parameters.Parameters; +import org.alfresco.rest.framework.resource.parameters.where.Query; +import org.alfresco.rest.framework.resource.parameters.where.QueryHelper; +import org.alfresco.rest.workflow.api.impl.MapBasedQueryWalker; +import org.alfresco.rm.rest.api.RMRoles; +import org.alfresco.rm.rest.api.model.RoleModel; +import org.alfresco.service.cmr.repository.NodeRef; + +public class RMRolesImpl implements RMRoles +{ + private ApiNodesModelFactory nodesModelFactory; + private FilePlanRoleService filePlanRoleService; + + private static final Set LIST_ROLES_QUERY_PROPERTIES = new HashSet<>(List.of(PARAM_PERSON_ID, PARAM_INCLUDE_SYSTEM_ROLES, PARAM_CAPABILITY_NAME)); + + @Override + public CollectionWithPagingInfo getRoles(NodeRef filePlan, Parameters parameters) + { + var rolesFilter = getRolesFilter(parameters.getQuery()); + var roles = getRolesByFilter(filePlan, rolesFilter); + + var filteredRoles = roles.stream() + .map(role -> createRoleModel(filePlan, role, parameters.getInclude())) + .filter(hasRoleCapabilities(rolesFilter.getCapabilities())) + .toList(); + var page = filteredRoles + .stream() + .sorted(Comparator.comparing(RoleModel::name)) + .skip(parameters.getPaging().getSkipCount()) + .limit(parameters.getPaging().getMaxItems()) + .collect(Collectors.toCollection(LinkedList::new)); + + int totalItems = filteredRoles.size(); + boolean hasMore = parameters.getPaging().getSkipCount() + parameters.getPaging().getMaxItems() < totalItems; + return CollectionWithPagingInfo.asPaged(parameters.getPaging(), page, hasMore, totalItems); + } + + private Predicate hasRoleCapabilities(List capabilities) + { + return role -> capabilities == null || + capabilities.isEmpty() || + role.capabilities().stream().anyMatch(capability -> capabilities.contains(capability.name())); + } + + private Set getRolesByFilter(NodeRef filePlan, RolesFilter rolesFilter) + { + if (rolesFilter.getPersonId() != null) + { + return filePlanRoleService.getRolesByUser(filePlan, rolesFilter.getPersonId(), rolesFilter.includeSystemRoles()); + } + else + { + return filePlanRoleService.getRoles(filePlan, rolesFilter.includeSystemRoles()); + } + } + + private RoleModel createRoleModel(NodeRef filePlan, Role role, List include) + { + List assignedUsers = getAssignedUsers(filePlan, role, include); + List assignedGroups = getAssignedGroups(filePlan, role, include); + + return nodesModelFactory.createRoleModel(role, assignedUsers, assignedGroups); + } + + private List getAssignedUsers(NodeRef filePlan, Role role, List include) + { + if (include != null && include.contains(PARAM_INCLUDE_ASSIGNED_USERS)) + { + return new ArrayList<>(filePlanRoleService.getAllAssignedToRole(filePlan, role.getName())); + } + return null; + } + + private List getAssignedGroups(NodeRef filePlan, Role role, List include) + { + if (include != null && include.contains(PARAM_INCLUDE_ASSIGNED_GROUPS)) + { + return new ArrayList<>(filePlanRoleService.getGroupsAssignedToRole(filePlan, role.getName())); + } + return null; + } + + public void setNodesModelFactory(ApiNodesModelFactory nodesModelFactory) + { + this.nodesModelFactory = nodesModelFactory; + } + + public void setFilePlanRoleService(FilePlanRoleService filePlanRoleService) + { + this.filePlanRoleService = filePlanRoleService; + } + + private RolesFilter getRolesFilter(Query queryParameters) + { + var rolesFilterBuilder = RolesFilter.builder(); + + if (queryParameters != null) + { + var propertyWalker = new RolesQueryWalker(); + QueryHelper.walk(queryParameters, propertyWalker); + + rolesFilterBuilder + .withPersonId(propertyWalker.getPersonId()) + .withCapabilities(propertyWalker.getCapabilitiesNames()) + .withIncludeSystemRoles(propertyWalker.includeSystemRoles()); + } + return rolesFilterBuilder.build(); + } + + private static class RolesQueryWalker extends MapBasedQueryWalker + { + private List capabilitiesNames; + + public RolesQueryWalker() + { + super(LIST_ROLES_QUERY_PROPERTIES, null); + } + + @Override + public void in(String propertyName, boolean negated, String... propertyValues) + { + if (negated) + { + throw new InvalidArgumentException("Cannot use NOT for " + propertyName); + } + + if (PARAM_CAPABILITY_NAME.equalsIgnoreCase(propertyName)) + { + capabilitiesNames = Arrays.asList(propertyValues); + } + } + + @Override + public void and() + { + // allow AND, e.g. personId='123' AND includeSystemRoles=true + } + + public List getCapabilitiesNames() + { + return this.capabilitiesNames; + } + + public String getPersonId() + { + return getProperty(PARAM_PERSON_ID, WhereClauseParser.EQUALS, String.class); + } + + public Boolean includeSystemRoles() + { + return getProperty(PARAM_INCLUDE_SYSTEM_ROLES, WhereClauseParser.EQUALS, Boolean.class); + } + } +} + +class RolesFilter +{ + private String personId; + private boolean includeSystemRoles; + private List capabilities; + + private RolesFilter() + {} + + public static RolesFilterBuilder builder() + { + return new RolesFilterBuilder(); + } + + public String getPersonId() + { + return personId; + } + + public boolean includeSystemRoles() + { + return includeSystemRoles; + } + + public List getCapabilities() + { + return capabilities; + } + + public static class RolesFilterBuilder + { + private String personId; + private boolean includeSystemRoles = true; + private List capabilities; + + public RolesFilterBuilder withPersonId(String personId) + { + this.personId = personId; + return this; + } + + public RolesFilterBuilder withIncludeSystemRoles(Boolean includeSystemRoles) + { + if (includeSystemRoles != null) + { + this.includeSystemRoles = includeSystemRoles; + } + return this; + } + + public RolesFilterBuilder withCapabilities(List capabilities) + { + this.capabilities = capabilities; + return this; + } + + public RolesFilter build() + { + RolesFilter rolesFilter = new RolesFilter(); + rolesFilter.personId = this.personId; + rolesFilter.includeSystemRoles = this.includeSystemRoles; + rolesFilter.capabilities = this.capabilities; + return rolesFilter; + } + } +} diff --git a/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/rm/rest/api/model/CapabilityModel.java b/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/rm/rest/api/model/CapabilityModel.java new file mode 100644 index 0000000000..6f4c8df45d --- /dev/null +++ b/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/rm/rest/api/model/CapabilityModel.java @@ -0,0 +1,30 @@ +/* + * #%L + * Alfresco Records Management Module + * %% + * Copyright (C) 2005 - 2025 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * - + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * - + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * - + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * - + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + * #L% + */ +package org.alfresco.rm.rest.api.model; + +public record CapabilityModel(String name, String title, String description, GroupModel group, int index) +{} diff --git a/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/rm/rest/api/model/GroupModel.java b/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/rm/rest/api/model/GroupModel.java new file mode 100644 index 0000000000..2d97d847c1 --- /dev/null +++ b/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/rm/rest/api/model/GroupModel.java @@ -0,0 +1,30 @@ +/* + * #%L + * Alfresco Records Management Module + * %% + * Copyright (C) 2005 - 2025 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * - + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * - + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * - + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * - + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + * #L% + */ +package org.alfresco.rm.rest.api.model; + +public record GroupModel(String id, String title) +{} diff --git a/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/rm/rest/api/model/RoleModel.java b/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/rm/rest/api/model/RoleModel.java new file mode 100644 index 0000000000..d2e973d67b --- /dev/null +++ b/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/rm/rest/api/model/RoleModel.java @@ -0,0 +1,32 @@ +/* + * #%L + * Alfresco Records Management Module + * %% + * Copyright (C) 2005 - 2025 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * - + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * - + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * - + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * - + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + * #L% + */ +package org.alfresco.rm.rest.api.model; + +import java.util.List; + +public record RoleModel(String name, String displayLabel, List capabilities, String roleGroupName, String groupShortName, List assignedUsers, List assignedGroups) +{} diff --git a/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/rm/rest/api/model/RoleModelList.java b/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/rm/rest/api/model/RoleModelList.java new file mode 100644 index 0000000000..08fbc9f49f --- /dev/null +++ b/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/rm/rest/api/model/RoleModelList.java @@ -0,0 +1,32 @@ +/* + * #%L + * Alfresco Records Management Module + * %% + * Copyright (C) 2005 - 2025 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * - + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * - + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * - + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * - + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + * #L% + */ +package org.alfresco.rm.rest.api.model; + +import java.util.List; + +public record RoleModelList(List roleModelList) +{} diff --git a/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/rm/rest/api/roles/package-info.java b/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/rm/rest/api/roles/package-info.java new file mode 100644 index 0000000000..92fb07f052 --- /dev/null +++ b/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/rm/rest/api/roles/package-info.java @@ -0,0 +1,37 @@ +/* + * #%L + * Alfresco Records Management Module + * %% + * Copyright (C) 2005 - 2025 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * - + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * - + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * - + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * - + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + * #L% + */ + +/** + * Package info that defines the Information Governance Roles REST API + * + * @author Damian Ujma + */ +@WebApi(name = "gs", scope = Api.SCOPE.PUBLIC, version = 1) +package org.alfresco.rm.rest.api.roles; + +import org.alfresco.rest.framework.Api; +import org.alfresco.rest.framework.WebApi; diff --git a/amps/ags/rm-community/rm-community-repo/unit-test/java/org/alfresco/rm/rest/api/impl/RMRolesImplUnitTest.java b/amps/ags/rm-community/rm-community-repo/unit-test/java/org/alfresco/rm/rest/api/impl/RMRolesImplUnitTest.java new file mode 100644 index 0000000000..369b03c07b --- /dev/null +++ b/amps/ags/rm-community/rm-community-repo/unit-test/java/org/alfresco/rm/rest/api/impl/RMRolesImplUnitTest.java @@ -0,0 +1,177 @@ +/* + * #%L + * Alfresco Records Management Module + * %% + * Copyright (C) 2005 - 2025 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * - + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * - + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * - + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * - + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + * #L% + */ +package org.alfresco.rm.rest.api.impl; + +import org.alfresco.module.org_alfresco_module_rm.capability.Capability; +import org.alfresco.module.org_alfresco_module_rm.role.FilePlanRoleService; +import org.alfresco.module.org_alfresco_module_rm.role.Role; +import org.alfresco.module.org_alfresco_module_rm.test.util.BaseUnitTest; +import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo; +import org.alfresco.rest.framework.resource.parameters.Paging; +import org.alfresco.rest.framework.resource.parameters.Parameters; +import org.alfresco.rest.framework.tools.RecognizedParamsExtractor; +import org.alfresco.rm.rest.api.model.CapabilityModel; +import org.alfresco.rm.rest.api.model.RoleModel; +import org.alfresco.service.cmr.repository.NodeRef; +import org.junit.Before; +import org.junit.Test; + +import java.util.List; +import java.util.Set; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.*; + +public class RMRolesImplUnitTest extends BaseUnitTest { + + private final RecognizedParamsExtractor queryExtractor = new RecognizedParamsExtractor() {}; + + private RMRolesImpl rmRolesImpl; + private FilePlanRoleService mockedFilePlanRoleService; + private ApiNodesModelFactory mockedNodesModelFactory; + + private final Capability viewRecordsCapability = mock(Capability.class); + private final Capability editMetadataCapability = mock(Capability.class); + + private final Role role1 = new Role("Role1", "Role 1", Set.of(viewRecordsCapability), "Group1"); + private final Role role2 = new Role("Role2", "Role 2", Set.of(editMetadataCapability), "Group2"); + + private final RoleModel roleModel1 = new RoleModel("Role1", "Role 1", List.of(new CapabilityModel("ViewRecords", "", "", null, 0)), "Group1", null, List.of("User1"), List.of("Group1")); + private final RoleModel roleModel2 = new RoleModel("Role2", "Role 2", List.of(new CapabilityModel("EditMetadata", "", "", null, 0)), "Group2", null, List.of("User2"), List.of("Group2")); + + private final NodeRef filePlan = new NodeRef("workspace://SpacesStore/testFilePlan"); + + private final Parameters parameters = mock(Parameters.class); + private final Paging paging = mock(Paging.class); + + @Before + public void setUp() { + mockedFilePlanRoleService = mock(FilePlanRoleService.class); + mockedNodesModelFactory = mock(ApiNodesModelFactory.class); + + rmRolesImpl = new RMRolesImpl(); + rmRolesImpl.setFilePlanRoleService(mockedFilePlanRoleService); + rmRolesImpl.setNodesModelFactory(mockedNodesModelFactory); + + when(mockedFilePlanRoleService.getRoles(filePlan, true)).thenReturn(Set.of(role1, role2)); + when(mockedNodesModelFactory.createRoleModel(eq(role1), any(), any())).thenReturn(roleModel1); + when(mockedNodesModelFactory.createRoleModel(eq(role2), any(), any())).thenReturn(roleModel2); + + when(viewRecordsCapability.getName()).thenReturn("ViewRecords"); + when(editMetadataCapability.getName()).thenReturn("EditMetadata"); + + when(parameters.getPaging()).thenReturn(paging); + when(paging.getSkipCount()).thenReturn(0); + when(paging.getMaxItems()).thenReturn(10); + } + + @Test + public void testGetRoles_NoFilters() { + // when + CollectionWithPagingInfo result = rmRolesImpl.getRoles(filePlan, parameters); + + // then + List roleModelList = (List) result.getCollection(); + assertEquals(2, (int) result.getTotalItems()); + assertEquals(List.of(roleModel1, roleModel2), roleModelList); + verify(mockedFilePlanRoleService).getRoles(filePlan, true); + verify(mockedNodesModelFactory).createRoleModel(eq(role1), any(), any()); + verify(mockedNodesModelFactory).createRoleModel(eq(role2), any(), any()); + } + + @Test + public void testGetRoles_WithPersonId() { + // given + String personId = "testUser"; + when(mockedFilePlanRoleService.getRolesByUser(filePlan, personId, true)).thenReturn(Set.of(role1)); + when(parameters.getQuery()).thenReturn(queryExtractor.getWhereClause("(personId='" + personId + "')")); + + // when + CollectionWithPagingInfo result = rmRolesImpl.getRoles(filePlan, parameters); + + // then + assertEquals(1, (int) result.getTotalItems()); + assertEquals(List.of(roleModel1), result.getCollection()); + verify(mockedFilePlanRoleService).getRolesByUser(filePlan, personId, true); + verify(mockedNodesModelFactory).createRoleModel(eq(role1), any(), any()); + } + + @Test + public void testGetNonSystemRoles() { + //given + when(mockedFilePlanRoleService.getRoles(filePlan, false)).thenReturn(Set.of(role2)); + when(parameters.getQuery()).thenReturn(queryExtractor.getWhereClause("(systemRoles=false)")); + + // when + CollectionWithPagingInfo result = rmRolesImpl.getRoles(filePlan, parameters); + + // then + assertEquals(1, (int) result.getTotalItems()); + assertEquals(List.of(roleModel2), result.getCollection()); + verify(mockedFilePlanRoleService).getRoles(filePlan, false); + verify(mockedNodesModelFactory).createRoleModel(eq(role2), any(), any()); + } + + @Test + public void testGetRoles_WithCapabilitiesFilter() { + // given + when(parameters.getQuery()).thenReturn(queryExtractor.getWhereClause("(capabilityName IN ('ViewRecords'))")); + + // when + CollectionWithPagingInfo result = rmRolesImpl.getRoles(filePlan, parameters); + + // then + assertEquals(1, (int) result.getTotalItems()); + assertEquals(List.of(roleModel1), result.getCollection()); + verify(mockedFilePlanRoleService).getRoles(filePlan, true); + verify(mockedNodesModelFactory).createRoleModel(eq(role1), any(), any()); + } + + @Test + public void testGetRoles_IncludeAssignedUsersAndGroups() { + // given + when(mockedFilePlanRoleService.getRoles(filePlan, true)).thenReturn(Set.of(role1)); + when(mockedFilePlanRoleService.getAllAssignedToRole(filePlan, "Role1")).thenReturn(Set.of("User1")); + when(mockedFilePlanRoleService.getGroupsAssignedToRole(filePlan, "Role1")).thenReturn(Set.of("Group1")); + + when(parameters.getInclude()).thenReturn(List.of("assignedUsers", "assignedGroups")); + + // when + CollectionWithPagingInfo result = rmRolesImpl.getRoles(filePlan, parameters); + + // then + List roleModelList = (List) result.getCollection(); + assertEquals(1, (int) result.getTotalItems()); + assertEquals(List.of(roleModel1), roleModelList); + assertEquals(List.of("User1"), roleModelList.get(0).assignedUsers()); + assertEquals(List.of("Group1"), roleModelList.get(0).assignedGroups()); + verify(mockedFilePlanRoleService).getRoles(filePlan, true); + verify(mockedFilePlanRoleService).getAllAssignedToRole(filePlan, "Role1"); + verify(mockedFilePlanRoleService).getGroupsAssignedToRole(filePlan, "Role1"); + verify(mockedNodesModelFactory).createRoleModel(role1, List.of("User1"), List.of("Group1")); + } +} diff --git a/amps/ags/rm-community/rm-community-rest-api-explorer/src/main/webapp/definitions/gs-core-api.yaml b/amps/ags/rm-community/rm-community-rest-api-explorer/src/main/webapp/definitions/gs-core-api.yaml index 0e899770f7..6ea8fa3b1c 100644 --- a/amps/ags/rm-community/rm-community-rest-api-explorer/src/main/webapp/definitions/gs-core-api.yaml +++ b/amps/ags/rm-community/rm-community-rest-api-explorer/src/main/webapp/definitions/gs-core-api.yaml @@ -4929,4 +4929,4 @@ definitions: - SiteConsumer - SiteCollaborator - SiteContributor - - SiteManager \ No newline at end of file + - SiteManager