From e2ecfd022e3749a959e3b399239a5716db60bf40 Mon Sep 17 00:00:00 2001 From: Piotr Gawron <p.gawron@atcomp.pl> Date: Fri, 17 Jan 2025 13:24:23 +0100 Subject: [PATCH 1/5] possibility to change projectId --- .../web/api/project/NewMoveProjectDTO.java | 27 +++++++ .../web/api/project/NewProjectController.java | 33 +++++++-- .../web/ControllerIntegrationTest.java | 1 + .../api/project/NewProjectControllerTest.java | 72 ++++++++++--------- 4 files changed, 95 insertions(+), 38 deletions(-) create mode 100644 web/src/main/java/lcsb/mapviewer/web/api/project/NewMoveProjectDTO.java diff --git a/web/src/main/java/lcsb/mapviewer/web/api/project/NewMoveProjectDTO.java b/web/src/main/java/lcsb/mapviewer/web/api/project/NewMoveProjectDTO.java new file mode 100644 index 0000000000..3b2dc0ea35 --- /dev/null +++ b/web/src/main/java/lcsb/mapviewer/web/api/project/NewMoveProjectDTO.java @@ -0,0 +1,27 @@ +package lcsb.mapviewer.web.api.project; + +import lcsb.mapviewer.model.Project; +import lcsb.mapviewer.web.api.AbstractDTO; +import org.hibernate.validator.constraints.NotBlank; + +import javax.validation.constraints.Pattern; +import javax.validation.constraints.Size; + +public class NewMoveProjectDTO extends AbstractDTO { + @NotBlank + @Size(max = 255) + @Pattern(regexp = "^[a-z0-9A-Z\\-_]+$", message = "projectId can contain only alphanumeric characters and -_") + private String projectId; + + public String getProjectId() { + return projectId; + } + + public void setProjectId(final String projectId) { + this.projectId = projectId; + } + + public void saveToProject(final Project project) { + project.setProjectId(projectId); + } +} diff --git a/web/src/main/java/lcsb/mapviewer/web/api/project/NewProjectController.java b/web/src/main/java/lcsb/mapviewer/web/api/project/NewProjectController.java index ffa2e1cc89..20b7fb22fd 100644 --- a/web/src/main/java/lcsb/mapviewer/web/api/project/NewProjectController.java +++ b/web/src/main/java/lcsb/mapviewer/web/api/project/NewProjectController.java @@ -95,26 +95,49 @@ public class NewProjectController { } @PreAuthorize("hasAnyAuthority('IS_ADMIN', 'IS_CURATOR')") - @PostMapping(value = "/{projectId:.+}") + @PostMapping(value = "/") public ResponseEntity<?> addProject( - final @NotBlank @PathVariable(value = "projectId") String projectId, final Authentication authentication, final @Valid @RequestBody NewProjectDTO data) throws QueryException, ObjectNotFoundException, ObjectExistsException { - Project project = projectService.getProjectByProjectId(projectId); + Project project = projectService.getProjectByProjectId(data.getProjectId()); if (project != null) { throw new ObjectExistsException("Project with given projectId already exists"); } - project = new Project(projectId); + project = new Project(data.getProjectId()); data.saveToProject(project); project.setOwner(userService.getUserByLogin(authentication.getName())); projectService.add(project); - project = projectService.getProjectByProjectId(projectId, true); + project = projectService.getProjectByProjectId(data.getProjectId(), true); return serializer.prepareResponse(project, HttpStatus.CREATED); } + @PreAuthorize("hasAnyAuthority('IS_ADMIN', 'IS_CURATOR')") + @PostMapping(value = "/{projectId:.+}:move") + public ResponseEntity<?> moveProject( + final @NotBlank @PathVariable(value = "projectId") String projectId, + final Authentication authentication, + final @Valid @RequestBody NewMoveProjectDTO data) + throws ObjectNotFoundException, ObjectExistsException { + + if (projectService.getProjectByProjectId(data.getProjectId()) != null) { + throw new ObjectExistsException("Project with given projectId already exists"); + } + Project project = projectService.getProjectByProjectId(projectId); + + if (project == null) { + throw new ObjectExistsException("Project with given projectId does not exists"); + } + data.saveToProject(project); + + projectService.update(project); + + project = projectService.getProjectByProjectId(data.getProjectId(), true); + return serializer.prepareResponse(project, HttpStatus.OK); + } + @PreAuthorize("hasAnyAuthority('IS_ADMIN', 'WRITE_PROJECT:' + #projectId)") @PutMapping(value = "/{projectId:.+}") public ResponseEntity<?> updateProject( diff --git a/web/src/test/java/lcsb/mapviewer/web/ControllerIntegrationTest.java b/web/src/test/java/lcsb/mapviewer/web/ControllerIntegrationTest.java index 0244229828..6d303ca16b 100644 --- a/web/src/test/java/lcsb/mapviewer/web/ControllerIntegrationTest.java +++ b/web/src/test/java/lcsb/mapviewer/web/ControllerIntegrationTest.java @@ -764,6 +764,7 @@ public abstract class ControllerIntegrationTest extends TestUtils { protected Project createEmptyProject(final String projectId) { final Project project = new Project(projectId); + project.setVersion("1.0.0"); project.setOwner(userService.getUserByLogin(BUILT_IN_TEST_ADMIN_LOGIN)); project.setDirectory("289b78b436176091ad900020c933c544"); project.setName("Test Disease"); diff --git a/web/src/test/java/lcsb/mapviewer/web/api/project/NewProjectControllerTest.java b/web/src/test/java/lcsb/mapviewer/web/api/project/NewProjectControllerTest.java index 8ac583136c..049d0201e6 100644 --- a/web/src/test/java/lcsb/mapviewer/web/api/project/NewProjectControllerTest.java +++ b/web/src/test/java/lcsb/mapviewer/web/api/project/NewProjectControllerTest.java @@ -71,7 +71,6 @@ public class NewProjectControllerTest extends ControllerIntegrationTest { @Autowired private IMinervaJobService minervaJobService; - private static final String TEST_PROJECT = "TEST_PROJECT"; @Autowired private NewApiResponseSerializer newApiResponseSerializer; @@ -87,12 +86,13 @@ public class NewProjectControllerTest extends ControllerIntegrationTest { public void tearDown() throws Exception { minervaJobService.waitForTasksToFinish(); removeProject(TEST_PROJECT); + removeProject(TEST_PROJECT_2); removeUser(userService.getUserByLogin(CURATOR_LOGIN)); } @Test public void testGetProject() throws Exception { - createAndPersistProject(TEST_PROJECT); + createEmptyProject(TEST_PROJECT); final MockHttpSession session = createSession(BUILT_IN_TEST_ADMIN_LOGIN, BUILT_IN_TEST_ADMIN_PASSWORD); @@ -145,7 +145,7 @@ public class NewProjectControllerTest extends ControllerIntegrationTest { @Test public void testGetProjectWithoutPermission() throws Exception { - createAndPersistProject(TEST_PROJECT); + createEmptyProject(TEST_PROJECT); final RequestBuilder request = get("/minerva/new_api/projects/{projectId}/", TEST_PROJECT); @@ -159,7 +159,7 @@ public class NewProjectControllerTest extends ControllerIntegrationTest { final NewProjectDTO data = createProjectDTO(TEST_PROJECT); - final RequestBuilder request = post("/minerva/new_api/projects/{projectId}/", TEST_PROJECT) + final RequestBuilder request = post("/minerva/new_api/projects/") .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(data)) .session(session); @@ -172,28 +172,13 @@ public class NewProjectControllerTest extends ControllerIntegrationTest { assertNotNull(projectService.getProjectByProjectId(TEST_PROJECT)); } - @Test - public void testCreateProjectWithProjectIdMismatch() throws Exception { - final MockHttpSession session = createSession(BUILT_IN_TEST_ADMIN_LOGIN, BUILT_IN_TEST_ADMIN_PASSWORD); - - final NewProjectDTO data = createProjectDTO(TEST_PROJECT); - - final RequestBuilder request = post("/minerva/new_api/projects/{projectId}/", TEST_PROJECT_2) - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(data)) - .session(session); - - mockMvc.perform(request) - .andExpect(status().isBadRequest()); - } - @Test public void testCreateProjectExisting() throws Exception { final MockHttpSession session = createSession(BUILT_IN_TEST_ADMIN_LOGIN, BUILT_IN_TEST_ADMIN_PASSWORD); final NewProjectDTO data = createProjectDTO(BUILT_IN_PROJECT); - final RequestBuilder request = post("/minerva/new_api/projects/{projectId}/", BUILT_IN_PROJECT) + final RequestBuilder request = post("/minerva/new_api/projects/") .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(data)) .session(session); @@ -206,7 +191,7 @@ public class NewProjectControllerTest extends ControllerIntegrationTest { public void testUpdateProject() throws Exception { final MockHttpSession session = createSession(BUILT_IN_TEST_ADMIN_LOGIN, BUILT_IN_TEST_ADMIN_PASSWORD); - createAndPersistProject(TEST_PROJECT); + createEmptyProject(TEST_PROJECT); final NewProjectDTO data = createProjectDTO(TEST_PROJECT); @@ -228,7 +213,7 @@ public class NewProjectControllerTest extends ControllerIntegrationTest { public void testUpdateProjectWithMissingData() throws Exception { final MockHttpSession session = createSession(BUILT_IN_TEST_ADMIN_LOGIN, BUILT_IN_TEST_ADMIN_PASSWORD); - createAndPersistProject(TEST_PROJECT); + createEmptyProject(TEST_PROJECT); final NewProjectDTO data = createProjectDTO(TEST_PROJECT); data.setName(null); @@ -246,7 +231,7 @@ public class NewProjectControllerTest extends ControllerIntegrationTest { public void testUpdateProjectWithOldVersion() throws Exception { final MockHttpSession session = createSession(BUILT_IN_TEST_ADMIN_LOGIN, BUILT_IN_TEST_ADMIN_PASSWORD); - createAndPersistProject(TEST_PROJECT); + createEmptyProject(TEST_PROJECT); final NewProjectDTO data = createProjectDTO(TEST_PROJECT); @@ -264,7 +249,7 @@ public class NewProjectControllerTest extends ControllerIntegrationTest { public void testUpdateProjectWithGoodVersion() throws Exception { final MockHttpSession session = createSession(BUILT_IN_TEST_ADMIN_LOGIN, BUILT_IN_TEST_ADMIN_PASSWORD); - Project project = createAndPersistProject(TEST_PROJECT); + Project project = createEmptyProject(TEST_PROJECT); final String originalVersion = project.getEntityVersion() + ""; @@ -298,7 +283,7 @@ public class NewProjectControllerTest extends ControllerIntegrationTest { @Test public void testDeleteProject() throws Exception { final MockHttpSession session = createSession(BUILT_IN_TEST_ADMIN_LOGIN, BUILT_IN_TEST_ADMIN_PASSWORD); - createAndPersistProject(TEST_PROJECT); + createEmptyProject(TEST_PROJECT); final RequestBuilder request = delete("/minerva/new_api/projects/{projectId}/", TEST_PROJECT) .session(session); @@ -329,7 +314,7 @@ public class NewProjectControllerTest extends ControllerIntegrationTest { @Test public void testDeleteNoAccessProject() throws Exception { - createAndPersistProject(TEST_PROJECT); + createEmptyProject(TEST_PROJECT); final RequestBuilder request = delete("/minerva/new_api/projects/{projectId}/", TEST_PROJECT); @@ -349,7 +334,7 @@ public class NewProjectControllerTest extends ControllerIntegrationTest { @Test public void testDeleteProjectDropsPrivileges() throws Exception { - createAndPersistProject(TEST_PROJECT); + createEmptyProject(TEST_PROJECT); User curator = createCurator(CURATOR_LOGIN, CURATOR_PASSWORD); userService.grantUserPrivilege(curator, PrivilegeType.WRITE_PROJECT, TEST_PROJECT); @@ -372,7 +357,7 @@ public class NewProjectControllerTest extends ControllerIntegrationTest { public void testDeleteProjectWithGoodVersion() throws Exception { final MockHttpSession session = createSession(BUILT_IN_TEST_ADMIN_LOGIN, BUILT_IN_TEST_ADMIN_PASSWORD); - final Project project = createAndPersistProject(TEST_PROJECT); + final Project project = createEmptyProject(TEST_PROJECT); final String originalVersion = project.getEntityVersion() + ""; @@ -388,7 +373,7 @@ public class NewProjectControllerTest extends ControllerIntegrationTest { public void testDeleteProjectWithWrongVersion() throws Exception { final MockHttpSession session = createSession(BUILT_IN_TEST_ADMIN_LOGIN, BUILT_IN_TEST_ADMIN_PASSWORD); - createAndPersistProject(TEST_PROJECT); + createEmptyProject(TEST_PROJECT); final RequestBuilder request = delete("/minerva/new_api/projects/{projectId}/", TEST_PROJECT) .header("If-Match", "-1") @@ -410,7 +395,7 @@ public class NewProjectControllerTest extends ControllerIntegrationTest { public void testListProjectsWithoutAccess() throws Exception { final RequestBuilder request = get("/minerva/new_api/projects/"); - createAndPersistProject(TEST_PROJECT); + createEmptyProject(TEST_PROJECT); final String response = mockMvc.perform(request) .andExpect(status().isOk()) @@ -429,7 +414,7 @@ public class NewProjectControllerTest extends ControllerIntegrationTest { final RequestBuilder request = get("/minerva/new_api/projects/") .session(session); - createAndPersistProject(TEST_PROJECT); + createEmptyProject(TEST_PROJECT); final String response = mockMvc.perform(request) .andExpect(status().isOk()) @@ -446,7 +431,7 @@ public class NewProjectControllerTest extends ControllerIntegrationTest { public void testUpdateOwnerInProject() throws Exception { final MockHttpSession session = createSession(BUILT_IN_TEST_ADMIN_LOGIN, BUILT_IN_TEST_ADMIN_PASSWORD); - createAndPersistProject(TEST_PROJECT); + createEmptyProject(TEST_PROJECT); final NewUserLoginDTO data = new NewUserLoginDTO(); data.setLogin(Configuration.ANONYMOUS_LOGIN); @@ -469,7 +454,7 @@ public class NewProjectControllerTest extends ControllerIntegrationTest { public void testUpdateInvalidOwnerInProject() throws Exception { final MockHttpSession session = createSession(BUILT_IN_TEST_ADMIN_LOGIN, BUILT_IN_TEST_ADMIN_PASSWORD); - createAndPersistProject(TEST_PROJECT); + createEmptyProject(TEST_PROJECT); final NewUserLoginDTO data = new NewUserLoginDTO(); data.setLogin("blah"); @@ -530,4 +515,25 @@ public class NewProjectControllerTest extends ControllerIntegrationTest { assertNotNull(stacktraceService.getById(data.get("error-id"))); } + @Test + public void testMoveProject() throws Exception { + final MockHttpSession session = createSession(BUILT_IN_TEST_ADMIN_LOGIN, BUILT_IN_TEST_ADMIN_PASSWORD); + + createEmptyProject(TEST_PROJECT); + + NewMoveProjectDTO dto = new NewMoveProjectDTO(); + dto.setProjectId(TEST_PROJECT_2); + + final RequestBuilder request = post("/minerva/new_api/projects/{projectId}:move", TEST_PROJECT) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(dto)) + .session(session); + + mockMvc.perform(request) + .andExpect(status().isOk()); + + assertNull(projectService.getProjectByProjectId(TEST_PROJECT)); + assertNotNull(projectService.getProjectByProjectId(TEST_PROJECT_2)); + } + } -- GitLab From 5e304a6de52aaa3e63281a6eb07eddefa639987d Mon Sep 17 00:00:00 2001 From: Piotr Gawron <p.gawron@atcomp.pl> Date: Fri, 17 Jan 2025 13:30:37 +0100 Subject: [PATCH 2/5] don't allow to move default project or project shred in minerva net --- .../web/api/project/NewProjectController.java | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/web/src/main/java/lcsb/mapviewer/web/api/project/NewProjectController.java b/web/src/main/java/lcsb/mapviewer/web/api/project/NewProjectController.java index 20b7fb22fd..d39f671b4e 100644 --- a/web/src/main/java/lcsb/mapviewer/web/api/project/NewProjectController.java +++ b/web/src/main/java/lcsb/mapviewer/web/api/project/NewProjectController.java @@ -2,6 +2,7 @@ package lcsb.mapviewer.web.api.project; import lcsb.mapviewer.api.OperationNotAllowedException; import lcsb.mapviewer.api.QueryException; +import lcsb.mapviewer.api.UpdateConflictException; import lcsb.mapviewer.api.minervanet.MinervaNetController; import lcsb.mapviewer.model.Project; import lcsb.mapviewer.model.job.MinervaJob; @@ -120,7 +121,7 @@ public class NewProjectController { final @NotBlank @PathVariable(value = "projectId") String projectId, final Authentication authentication, final @Valid @RequestBody NewMoveProjectDTO data) - throws ObjectNotFoundException, ObjectExistsException { + throws ObjectNotFoundException, ObjectExistsException, UpdateConflictException { if (projectService.getProjectByProjectId(data.getProjectId()) != null) { throw new ObjectExistsException("Project with given projectId already exists"); @@ -130,6 +131,21 @@ public class NewProjectController { if (project == null) { throw new ObjectExistsException("Project with given projectId does not exists"); } + + if (configurationService.getConfigurationValue(ConfigurationElementType.DEFAULT_MAP).equals(data.getProjectId())) { + throw new UpdateConflictException("Cannot move project. Project is shared in minerva net"); + } + + boolean sharedProject = false; + try { + sharedProject = minervaNetController.getSharedProjects().contains(data.getProjectId()); + } catch (Exception e) { + throw new RuntimeException(e); + } + + if (sharedProject) { + throw new UpdateConflictException("Cannot move project. Project is shared in minerva net"); + } data.saveToProject(project); projectService.update(project); -- GitLab From 81b1713355142e00b6f6595597275c74df295009 Mon Sep 17 00:00:00 2001 From: Piotr Gawron <p.gawron@atcomp.pl> Date: Fri, 17 Jan 2025 16:15:15 +0100 Subject: [PATCH 3/5] allow to edit projectId --- frontend-js/src/main/js/ServerConnector.js | 31 ++++++++++++++ .../main/js/gui/admin/EditProjectDialog.js | 42 ++++++++++++++++++- .../src/main/js/gui/admin/MapsAdminPanel.js | 5 +-- 3 files changed, 73 insertions(+), 5 deletions(-) diff --git a/frontend-js/src/main/js/ServerConnector.js b/frontend-js/src/main/js/ServerConnector.js index 72ccbece8a..9e6b35a1bd 100644 --- a/frontend-js/src/main/js/ServerConnector.js +++ b/frontend-js/src/main/js/ServerConnector.js @@ -371,6 +371,10 @@ ServerConnector.getApiBaseUrl = function () { return this.getServerBaseUrl() + "api/"; }; +ServerConnector.getNewApiBaseUrl = function () { + return this.getServerBaseUrl() + "new_api/"; +}; + /** * * @returns {string} @@ -534,6 +538,11 @@ ServerConnector.getProjectUrl = function (queryParams, filterParams) { }); }; +ServerConnector.getMoveProjectUrl = function (queryParams, filterParams) { + var id = this.getIdOrAsterisk(queryParams.projectId); + return this.getNewApiBaseUrl() + 'projects/' + id + ':move'; +}; + ServerConnector.getArchiveProjectUrl = function (queryParams, filterParams) { var id = this.getIdOrAsterisk(queryParams.projectId); return this.getApiUrl({ @@ -1396,6 +1405,28 @@ ServerConnector.updateProject = function (project) { }); }; +/** + * + * @param {string} data.oldProjectId + * @param {string} data.newProjectId + * @return {Promise} + */ +ServerConnector.moveProject = function (data) { + var self = this; + var queryParams = { + projectId: data.oldProjectId + }; + + var filterParams = { + projectId: data.newProjectId + }; + + return self.sendJsonPostRequest(self.getMoveProjectUrl(queryParams), filterParams) + .catch(function (error) { + return self._processUpdateError(error); + }); +}; + ServerConnector.removeProject = function (projectId) { var self = this; var queryParams = { diff --git a/frontend-js/src/main/js/gui/admin/EditProjectDialog.js b/frontend-js/src/main/js/gui/admin/EditProjectDialog.js index 248a0a4c18..32715ab406 100644 --- a/frontend-js/src/main/js/gui/admin/EditProjectDialog.js +++ b/frontend-js/src/main/js/gui/admin/EditProjectDialog.js @@ -20,6 +20,7 @@ var logger = require('../../logger'); var guiUtils = new (require('../leftPanel/GuiUtils'))(); var xss = require('xss'); +var ConfigurationType = require("../../ConfigurationType"); /** * @@ -171,7 +172,12 @@ EditProjectDialog.prototype.createGeneralTabContent = function () { projectIdRow.appendChild(Functions.createElement({ type: "div", style: "display:table-cell", - name: "projectId" + content: "<input name='projectId'/>", + xss: false, + onchange: function () { + var project = self.getProject(); + return self.moveProject(project, $("[name='projectId']", this).val()); + } })); var licenseRow = Functions.createElement({ @@ -925,7 +931,14 @@ EditProjectDialog.prototype.init = function () { EditProjectDialog.prototype.projectDataUpdated = function (project) { var element = this.getElement(); $("[name='projectName']", element).val(xss(project.getName())); - $("[name='projectId']", element).html(xss(project.getProjectId())); + var disableProjectId = this.getConfiguration().getOption(ConfigurationType.DEFAULT_MAP).getValue() === project.getProjectId(); + $("[name='projectId']", element).val(xss(project.getProjectId())); + $("[name='projectId']", element).attr("disabled", disableProjectId); + if (disableProjectId) { + $("[name='projectId']", element).attr("title", "Cannot change projectId of projects shared in minervanet and default project"); + } else { + $("[name='projectId']", element).attr("title", ""); + } $("[name='projectVersion']", element).val(xss(project.getVersion())); $("[name='customLicenseName']", element).val(xss(project.getCustomLicenseName())); $("[name='customLicenseUrl']", element).val(xss(project.getCustomLicenseUrl())); @@ -1424,6 +1437,31 @@ EditProjectDialog.prototype.updateProject = function (project) { }).finally(GuiConnector.hideProcessing); }; +/** + * + * @param {Project} project + * @param {string} projectId + * @returns {Promise} + */ +EditProjectDialog.prototype.moveProject = function (project, projectId) { + var self = this; + + GuiConnector.showProcessing(); + return self.getServerConnector().moveProject({ + oldProjectId: project.getProjectId(), + newProjectId: projectId + }).then(function () { + project.setProjectId(projectId); + return project.callListeners("onreload"); + }).catch(function (error) { + if ((error instanceof NetworkError && error.statusCode === HttpStatus.BAD_REQUEST)) { + GuiConnector.alert(error.content.reason); + } else { + GuiConnector.alert(error); + } + }).finally(GuiConnector.hideProcessing); +}; + /** * * @returns {Promise} diff --git a/frontend-js/src/main/js/gui/admin/MapsAdminPanel.js b/frontend-js/src/main/js/gui/admin/MapsAdminPanel.js index 7829e075e8..5179db7a0a 100644 --- a/frontend-js/src/main/js/gui/admin/MapsAdminPanel.js +++ b/frontend-js/src/main/js/gui/admin/MapsAdminPanel.js @@ -324,7 +324,6 @@ MapsAdminPanel.prototype.init = function () { var self = this; var user self.getConfiguration().addListener("onOptionChanged", function (option) { - console.log(option.arg); if (option.arg.getType() === ConfigurationType.MINERVANET_AUTH_TOKEN || option.arg.getType() === ConfigurationType.MINERVANET_URL || option.arg.getType() === ConfigurationType.MINERVA_ROOT) { @@ -401,7 +400,7 @@ MapsAdminPanel.prototype.projectToTableRow = function (project, row, user) { var projectId = project.getProjectId(); var formattedProjectId; if (project.getStatus().toLowerCase() === "ok") { - formattedProjectId = "<a href='" + "index.xhtml?id=" + projectId + "' target='" + projectId + "'>" + projectId + "</a>"; + formattedProjectId = "<a href='" + "index.xhtml?id=" + projectId + "' target='" + project.getId() + "'>" + projectId + "</a>"; } else { formattedProjectId = projectId; } @@ -588,7 +587,7 @@ MapsAdminPanel.prototype.addUpdateListener = function (project) { for (var i = 0; i < length; i++) { var row = dataTable.row(i); var data = row.data(); - if (data[0].indexOf(">" + project.getProjectId() + "<") >= 0 || data[0].indexOf(project.getProjectId()) === 0) { + if (data[0].indexOf("target='" + project.getId() + "'") >= 0 || data[0].indexOf(project.getProjectId()) === 0) { self.projectToTableRow(project, data, user); var page = dataTable.page(); row.data(data).draw(); -- GitLab From 4edc9e267a05ba148fd2de84c718443e10fd345c Mon Sep 17 00:00:00 2001 From: Piotr Gawron <p.gawron@atcomp.pl> Date: Fri, 17 Jan 2025 16:48:11 +0100 Subject: [PATCH 4/5] move permissions when moving project --- .../persist/dao/security/PrivilegeDao.java | 34 ++++++++++++++---- .../dao/security/PrivilegeProperty.java | 9 +++++ .../mapviewer/services/impl/UserService.java | 21 +++++++++-- .../services/interfaces/IUserService.java | 2 ++ .../web/api/project/NewProjectController.java | 1 + .../api/project/NewProjectControllerTest.java | 36 +++++++++++++++++-- 6 files changed, 92 insertions(+), 11 deletions(-) create mode 100644 persist/src/main/java/lcsb/mapviewer/persist/dao/security/PrivilegeProperty.java diff --git a/persist/src/main/java/lcsb/mapviewer/persist/dao/security/PrivilegeDao.java b/persist/src/main/java/lcsb/mapviewer/persist/dao/security/PrivilegeDao.java index 8710681f00..99f4d7408d 100644 --- a/persist/src/main/java/lcsb/mapviewer/persist/dao/security/PrivilegeDao.java +++ b/persist/src/main/java/lcsb/mapviewer/persist/dao/security/PrivilegeDao.java @@ -1,18 +1,22 @@ package lcsb.mapviewer.persist.dao.security; -import java.util.Arrays; -import java.util.List; - -import org.springframework.stereotype.Repository; - import lcsb.mapviewer.common.Pair; +import lcsb.mapviewer.common.exception.InvalidArgumentException; import lcsb.mapviewer.model.security.Privilege; import lcsb.mapviewer.model.security.PrivilegeType; import lcsb.mapviewer.persist.dao.BaseDao; -import lcsb.mapviewer.persist.dao.MinervaEntityProperty; +import org.springframework.stereotype.Repository; + +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.Predicate; +import javax.persistence.criteria.Root; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; @Repository -public class PrivilegeDao extends BaseDao<Privilege, MinervaEntityProperty<Privilege>> { +public class PrivilegeDao extends BaseDao<Privilege, PrivilegeProperty> { public PrivilegeDao() { super(Privilege.class); @@ -31,4 +35,20 @@ public class PrivilegeDao extends BaseDao<Privilege, MinervaEntityProperty<Privi } } + @Override + protected Predicate createPredicate(final Map<PrivilegeProperty, Object> filterOptions, final Root<Privilege> root) { + final CriteriaBuilder builder = getSession().getCriteriaBuilder(); + final List<Predicate> predicates = new ArrayList<>(); + + for (final PrivilegeProperty key : filterOptions.keySet()) { + if (key.equals(PrivilegeProperty.OBJECT_ID)) { + final Predicate predicate = builder.and(root.get("objectId").in(filterOptions.get(key))); + predicates.add(predicate); + } else { + throw new InvalidArgumentException("Unknown property: " + key); + } + } + return builder.and(predicates.toArray(new Predicate[0])); + } + } diff --git a/persist/src/main/java/lcsb/mapviewer/persist/dao/security/PrivilegeProperty.java b/persist/src/main/java/lcsb/mapviewer/persist/dao/security/PrivilegeProperty.java new file mode 100644 index 0000000000..8c68aad835 --- /dev/null +++ b/persist/src/main/java/lcsb/mapviewer/persist/dao/security/PrivilegeProperty.java @@ -0,0 +1,9 @@ +package lcsb.mapviewer.persist.dao.security; + +import lcsb.mapviewer.model.security.Privilege; +import lcsb.mapviewer.persist.dao.MinervaEntityProperty; + +public enum PrivilegeProperty implements MinervaEntityProperty<Privilege> { + OBJECT_ID, + PRIVILEGE_TYPE, +} diff --git a/service/src/main/java/lcsb/mapviewer/services/impl/UserService.java b/service/src/main/java/lcsb/mapviewer/services/impl/UserService.java index 8d447ed0c5..f10ab4c3c2 100644 --- a/service/src/main/java/lcsb/mapviewer/services/impl/UserService.java +++ b/service/src/main/java/lcsb/mapviewer/services/impl/UserService.java @@ -16,6 +16,8 @@ import lcsb.mapviewer.model.user.UserClassAnnotators; import lcsb.mapviewer.model.user.annotator.AnnotatorData; import lcsb.mapviewer.persist.dao.map.DataOverlayDao; import lcsb.mapviewer.persist.dao.map.ProjectBackgroundDao; +import lcsb.mapviewer.persist.dao.security.PrivilegeDao; +import lcsb.mapviewer.persist.dao.security.PrivilegeProperty; import lcsb.mapviewer.persist.dao.user.EmailConfirmationTokenDao; import lcsb.mapviewer.persist.dao.user.ResetPasswordTokenDao; import lcsb.mapviewer.persist.dao.user.UserDao; @@ -30,6 +32,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.hibernate.Hibernate; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -47,7 +50,7 @@ import java.util.UUID; @Service public class UserService implements IUserService { - private static Logger logger = LogManager.getLogger(); + private static final Logger logger = LogManager.getLogger(); private final UserDao userDao; private final DataOverlayDao dataOverlayDao; @@ -58,6 +61,7 @@ public class UserService implements IUserService { private final ResetPasswordTokenDao resetPasswordTokenDao; private final EmailConfirmationTokenDao emailConfirmationTokenDao; private final EmailSender emailSender; + private final PrivilegeDao privilegeDao; @Autowired public UserService(final UserDao userDao, @@ -68,7 +72,7 @@ public class UserService implements IUserService { final EmailSender emailSender, final DataOverlayDao dataOverlayDao, final ProjectBackgroundDao projectBackgroundDao, - final EmailConfirmationTokenDao emailConfirmationTokenDao) { + final EmailConfirmationTokenDao emailConfirmationTokenDao, final PrivilegeDao privilegeDao) { this.userDao = userDao; this.ldapService = ldapService; this.configurationService = configurationService; @@ -78,6 +82,7 @@ public class UserService implements IUserService { this.emailSender = emailSender; this.dataOverlayDao = dataOverlayDao; this.projectBackgroundDao = projectBackgroundDao; + this.privilegeDao = privilegeDao; } @Override @@ -417,4 +422,16 @@ public class UserService implements IUserService { } } + @Override + public void moveProjectPrivileges(final String oldProjectId, final String newProjectId) { + + Map<PrivilegeProperty, Object> filterOptions = new HashMap<>(); + filterOptions.put(PrivilegeProperty.OBJECT_ID, oldProjectId); + List<Privilege> privileges = privilegeDao.getAll(filterOptions, Pageable.unpaged()).getContent(); + for (Privilege privilege : privileges) { + privilege.setObjectId(newProjectId); + privilegeDao.update(privilege); + } + } + } diff --git a/service/src/main/java/lcsb/mapviewer/services/interfaces/IUserService.java b/service/src/main/java/lcsb/mapviewer/services/interfaces/IUserService.java index 54d5a0a9c4..c0c0212379 100644 --- a/service/src/main/java/lcsb/mapviewer/services/interfaces/IUserService.java +++ b/service/src/main/java/lcsb/mapviewer/services/interfaces/IUserService.java @@ -80,4 +80,6 @@ public interface IUserService { User confirmEmail(String login, String token) throws InvalidTokenException; void sendUserActivatedEmail(User user); + + void moveProjectPrivileges(String oldProjectId, String projectId); } \ No newline at end of file diff --git a/web/src/main/java/lcsb/mapviewer/web/api/project/NewProjectController.java b/web/src/main/java/lcsb/mapviewer/web/api/project/NewProjectController.java index d39f671b4e..72c7266089 100644 --- a/web/src/main/java/lcsb/mapviewer/web/api/project/NewProjectController.java +++ b/web/src/main/java/lcsb/mapviewer/web/api/project/NewProjectController.java @@ -149,6 +149,7 @@ public class NewProjectController { data.saveToProject(project); projectService.update(project); + userService.moveProjectPrivileges(projectId, data.getProjectId()); project = projectService.getProjectByProjectId(data.getProjectId(), true); return serializer.prepareResponse(project, HttpStatus.OK); diff --git a/web/src/test/java/lcsb/mapviewer/web/api/project/NewProjectControllerTest.java b/web/src/test/java/lcsb/mapviewer/web/api/project/NewProjectControllerTest.java index 049d0201e6..90bd122f7d 100644 --- a/web/src/test/java/lcsb/mapviewer/web/api/project/NewProjectControllerTest.java +++ b/web/src/test/java/lcsb/mapviewer/web/api/project/NewProjectControllerTest.java @@ -29,6 +29,8 @@ import org.springframework.mock.web.MockHttpSession; import org.springframework.restdocs.payload.FieldDescriptor; import org.springframework.restdocs.payload.JsonFieldType; import org.springframework.restdocs.snippet.Snippet; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.web.servlet.RequestBuilder; @@ -37,6 +39,7 @@ import javax.servlet.http.HttpServletResponse; import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -519,8 +522,6 @@ public class NewProjectControllerTest extends ControllerIntegrationTest { public void testMoveProject() throws Exception { final MockHttpSession session = createSession(BUILT_IN_TEST_ADMIN_LOGIN, BUILT_IN_TEST_ADMIN_PASSWORD); - createEmptyProject(TEST_PROJECT); - NewMoveProjectDTO dto = new NewMoveProjectDTO(); dto.setProjectId(TEST_PROJECT_2); @@ -536,4 +537,35 @@ public class NewProjectControllerTest extends ControllerIntegrationTest { assertNotNull(projectService.getProjectByProjectId(TEST_PROJECT_2)); } + @Test + public void testMoveProjectMovePrivileges() throws Exception { + final MockHttpSession session = createSession(BUILT_IN_TEST_ADMIN_LOGIN, BUILT_IN_TEST_ADMIN_PASSWORD); + + User admin = userService.getUserByLogin(BUILT_IN_TEST_ADMIN_LOGIN); + userService.revokeUserPrivilege(admin, PrivilegeType.READ_PROJECT, TEST_PROJECT_2); + + createEmptyProject(TEST_PROJECT); + userService.grantUserPrivilege(admin, PrivilegeType.READ_PROJECT, TEST_PROJECT); + + NewMoveProjectDTO dto = new NewMoveProjectDTO(); + dto.setProjectId(TEST_PROJECT_2); + + final RequestBuilder request = post("/minerva/new_api/projects/{projectId}:move", TEST_PROJECT) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(dto)) + .session(session); + + mockMvc.perform(request) + .andExpect(status().isOk()); + + User user = userService.getUserByLogin(BUILT_IN_TEST_ADMIN_LOGIN, true); + + List<GrantedAuthority> authorities = user.getPrivileges().stream() + .map(privilege -> new SimpleGrantedAuthority(privilege.toString())) + .collect(Collectors.toList()); + + assertTrue(authorities.contains(new SimpleGrantedAuthority(PrivilegeType.READ_PROJECT + ":" + TEST_PROJECT_2))); + + } + } -- GitLab From 9720408562c79ea95ab76553a23ce9706184d448 Mon Sep 17 00:00:00 2001 From: Piotr Gawron <p.gawron@atcomp.pl> Date: Wed, 22 Jan 2025 11:31:07 +0100 Subject: [PATCH 5/5] fix tests --- .../mapviewer/web/api/project/NewProjectControllerTest.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/web/src/test/java/lcsb/mapviewer/web/api/project/NewProjectControllerTest.java b/web/src/test/java/lcsb/mapviewer/web/api/project/NewProjectControllerTest.java index 90bd122f7d..15a2de3f6a 100644 --- a/web/src/test/java/lcsb/mapviewer/web/api/project/NewProjectControllerTest.java +++ b/web/src/test/java/lcsb/mapviewer/web/api/project/NewProjectControllerTest.java @@ -520,6 +520,11 @@ public class NewProjectControllerTest extends ControllerIntegrationTest { @Test public void testMoveProject() throws Exception { + User admin = userService.getUserByLogin(BUILT_IN_TEST_ADMIN_LOGIN); + Project project = new Project(TEST_PROJECT); + project.setOwner(admin); + projectService.add(project); + final MockHttpSession session = createSession(BUILT_IN_TEST_ADMIN_LOGIN, BUILT_IN_TEST_ADMIN_PASSWORD); NewMoveProjectDTO dto = new NewMoveProjectDTO(); @@ -565,6 +570,7 @@ public class NewProjectControllerTest extends ControllerIntegrationTest { .collect(Collectors.toList()); assertTrue(authorities.contains(new SimpleGrantedAuthority(PrivilegeType.READ_PROJECT + ":" + TEST_PROJECT_2))); + assertFalse(authorities.contains(new SimpleGrantedAuthority(PrivilegeType.READ_PROJECT + ":" + TEST_PROJECT))); } -- GitLab