diff --git a/services/src/main/java/org/fao/geonet/api/records/MetadataSharingApi.java b/services/src/main/java/org/fao/geonet/api/records/MetadataSharingApi.java index f94d2fccd489..14f4e070d77b 100644 --- a/services/src/main/java/org/fao/geonet/api/records/MetadataSharingApi.java +++ b/services/src/main/java/org/fao/geonet/api/records/MetadataSharingApi.java @@ -1256,6 +1256,38 @@ private void updateOwnership(Integer groupIdentifier, serviceContext, String.valueOf(metadata.getId()))) { report.addNotEditableMetadataId(metadata.getId()); } else { + UserSession callerSession = ApiUtils.getUserSession(session); + Profile callerProfile = callerSession.getProfile(); + + if (callerProfile == Profile.Editor || callerProfile == Profile.Reviewer) { + int callerUserId = callerSession.getUserIdAsInt(); + Integer sourceUsr = metadata.getSourceInfo().getOwner(); + Integer sourceGrp = metadata.getSourceInfo().getGroupOwner(); + + if (callerProfile == Profile.Editor) { + if (!Objects.equals(sourceUsr, callerUserId)) { + report.addNotEditableMetadataId(metadata.getId()); + return; + } + } else { + Set reviewerGroups = accessMan.getReviewerGroups(callerSession); + if (sourceGrp == null || !reviewerGroups.contains(sourceGrp)) { + report.addNotEditableMetadataId(metadata.getId()); + return; + } + } + + if (!ReservedGroup.isReserved(groupIdentifier)) { + List myGroups = userGroupRepository.findGroupIds( + UserGroupSpecs.hasUserId(callerUserId)); + if (!myGroups.contains(groupIdentifier)) { + report.addMetadataInfos(metadata, + "Transfer to group " + groupIdentifier + " is not allowed for your profile."); + return; + } + } + } + // Retrieve the identifiers associated with the metadata uuid. // When the workflow is enabled, the metadata can have an approved and a working copy version. List idList = metadataUtils.findAllIdsBy(MetadataSpecs.hasMetadataUuid(uuid)); diff --git a/services/src/main/java/org/fao/geonet/api/users/transfer/TransferApi.java b/services/src/main/java/org/fao/geonet/api/users/transfer/TransferApi.java index 99d40a1151da..0178d49fa0fc 100644 --- a/services/src/main/java/org/fao/geonet/api/users/transfer/TransferApi.java +++ b/services/src/main/java/org/fao/geonet/api/users/transfer/TransferApi.java @@ -186,6 +186,20 @@ public List retrieveAllUserGroups( ); } return list; + } else if (myProfile == Profile.Reviewer || myProfile == Profile.Editor) { + List myGroups = userGroupRepository.findAll(UserGroupSpecs.hasUserId(session.getUserIdAsInt())) + .stream().map(ug -> ug.getGroup().getId()).collect(Collectors.toList()); + List userGroups = userGroupRepository.findAll(UserGroupSpecs.hasGroupIds(myGroups)); + + if (groupTypes != null) { + userGroups = userGroups.stream() + .filter(ug -> groupTypes.contains(ug.getGroup().getType())) + .collect(Collectors.toList()); + } + for (UserGroup ug : userGroups) { + list.add(new UserGroupsResponse(ug.getUser(), ug.getGroup(), ug.getProfile().name())); + } + return list; } else { throw new SecurityException("You don't have rights to do get the groups for this user"); } diff --git a/services/src/test/java/org/fao/geonet/api/records/MetadataSharingApiTest.java b/services/src/test/java/org/fao/geonet/api/records/MetadataSharingApiTest.java index 895dafe4b699..6d32713e7285 100644 --- a/services/src/test/java/org/fao/geonet/api/records/MetadataSharingApiTest.java +++ b/services/src/test/java/org/fao/geonet/api/records/MetadataSharingApiTest.java @@ -62,11 +62,13 @@ import java.util.List; import java.util.Map; +import static org.hamcrest.Matchers.greaterThan; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** @@ -92,6 +94,9 @@ public class MetadataSharingApiTest extends AbstractServiceIntegrationTest { private User editorUser; private User reviewerUser; + private User anotherEditorUser; + private User targetUser; + private Group foreignGroup; private int metadataId; private String metadataUuid; private ServiceContext context; @@ -460,6 +465,20 @@ private void createTestData() throws Exception { _userRepo.save(reviewerUser); _userGroupRepo.save(new UserGroup().setGroup(sampleGroup).setProfile(Profile.Reviewer).setUser(reviewerUser)); + anotherEditorUser = UserRepositoryTest.newUser(_inc); + anotherEditorUser.setUsername("anothereditor"); + anotherEditorUser.setProfile(Profile.Editor); + _userRepo.save(anotherEditorUser); + _userGroupRepo.save(new UserGroup().setGroup(sampleGroup).setProfile(Profile.Editor).setUser(anotherEditorUser)); + + targetUser = UserRepositoryTest.newUser(_inc); + targetUser.setUsername("targetuser"); + targetUser.setProfile(Profile.Editor); + _userRepo.save(targetUser); + _userGroupRepo.save(new UserGroup().setGroup(sampleGroup).setProfile(Profile.Editor).setUser(targetUser)); + + foreignGroup = _groupRepo.save(new Group().setName("foreignGroup")); + Metadata metadata = (Metadata) injectMetadataInDb(getSampleMetadataXml(), context, true); metadata.getSourceInfo().setOwner(editorUser.getId()); metadata.getSourceInfo().setGroupOwner(SAMPLE_GROUP_ID); @@ -467,4 +486,82 @@ private void createTestData() throws Exception { metadataId = metadata.getId(); metadataUuid = metadata.getUuid(); } + + @Test + public void transferOwnershipAsEditorSucceeds() throws Exception { + MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build(); + MockHttpSession mockHttpSession = loginAs(editorUser); + + mockMvc.perform(put("/srv/api/records/" + metadataUuid + "/ownership") + .session(mockHttpSession) + .param("groupIdentifier", String.valueOf(SAMPLE_GROUP_ID)) + .param("userIdentifier", String.valueOf(targetUser.getId())) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isCreated()) + .andExpect(jsonPath("$.numberOfRecordsProcessed").value(1)) + .andExpect(jsonPath("$.numberOfRecordsNotEditable").value(0)); + } + + @Test + public void transferOwnershipAsEditorForNotOwnedRecordFails() throws Exception { + // Grant editing permission to SAMPLE_GROUP_ID so that anotherEditorUser passes + // canEdit(), but the ownership check in updateOwnership() still blocks the transfer. + OperationAllowed editOp = new OperationAllowed(); + editOp.getId().setMetadataId(metadataId).setGroupId(SAMPLE_GROUP_ID) + .setOperationId(ReservedOperation.editing.getId()); + operationAllowedRepository.save(editOp); + + MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build(); + MockHttpSession mockHttpSession = loginAs(anotherEditorUser); + + mockMvc.perform(put("/srv/api/records/" + metadataUuid + "/ownership") + .session(mockHttpSession) + .param("groupIdentifier", String.valueOf(SAMPLE_GROUP_ID)) + .param("userIdentifier", String.valueOf(targetUser.getId())) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isCreated()) + .andExpect(jsonPath("$.numberOfRecordsNotEditable").value(1)); + } + + @Test + public void transferOwnershipAsEditorToForeignGroupFails() throws Exception { + MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build(); + MockHttpSession mockHttpSession = loginAs(editorUser); + + mockMvc.perform(put("/srv/api/records/" + metadataUuid + "/ownership") + .session(mockHttpSession) + .param("groupIdentifier", String.valueOf(foreignGroup.getId())) + .param("userIdentifier", String.valueOf(targetUser.getId())) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isCreated()) + .andExpect(jsonPath("$.numberOfRecordsProcessed").value(0)); + } + + @Test + public void transferOwnershipAsReviewerForGroupRecordSucceeds() throws Exception { + MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build(); + MockHttpSession mockHttpSession = loginAs(reviewerUser); + + mockMvc.perform(put("/srv/api/records/" + metadataUuid + "/ownership") + .session(mockHttpSession) + .param("groupIdentifier", String.valueOf(SAMPLE_GROUP_ID)) + .param("userIdentifier", String.valueOf(targetUser.getId())) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isCreated()) + .andExpect(jsonPath("$.numberOfRecordsProcessed").value(1)); + } + + @Test + public void retrieveUserGroupsAsEditorReturnsWorkgroupUsers() throws Exception { + MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build(); + MockHttpSession mockHttpSession = loginAs(editorUser); + + mockMvc.perform(get("/srv/api/users/groups") + .session(mockHttpSession) + .param("groupTypes", "Workspace") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$").isArray()) + .andExpect(jsonPath("$.length()").value(greaterThan(0))); + } } diff --git a/web-ui/src/main/resources/catalog/components/search/resultsview/partials/selection-widget.html b/web-ui/src/main/resources/catalog/components/search/resultsview/partials/selection-widget.html index b87767ae7a7e..4f3868cc515f 100644 --- a/web-ui/src/main/resources/catalog/components/search/resultsview/partials/selection-widget.html +++ b/web-ui/src/main/resources/catalog/components/search/resultsview/partials/selection-widget.html @@ -154,7 +154,7 @@
  • privileges
  • -
  • +
  •   transferOwnership