From fe5f013cad43afcc2249692b50e96b71c1f83f12 Mon Sep 17 00:00:00 2001 From: eastagiletracker <310448263+eastagiletracker@users.noreply.github.com> Date: Fri, 28 Aug 2026 15:50:59 +0700 Subject: [PATCH] fix(Authoring): Reject request if project is not valid JSON Validate the request body before it reaches disk. saveProject wrote the raw body to project.json first and only parsed it afterwards, so a malformed body replaced the stored unit with unparseable content. --- .../author/project/AuthorAPIController.java | 15 +++++ .../project/AuthorAPIControllerTest.java | 56 +++++++++++++++++++ 2 files changed, 71 insertions(+) diff --git a/src/main/java/org/wise/portal/presentation/web/controllers/author/project/AuthorAPIController.java b/src/main/java/org/wise/portal/presentation/web/controllers/author/project/AuthorAPIController.java index 21d6f6ee0..c525785e3 100644 --- a/src/main/java/org/wise/portal/presentation/web/controllers/author/project/AuthorAPIController.java +++ b/src/main/java/org/wise/portal/presentation/web/controllers/author/project/AuthorAPIController.java @@ -251,6 +251,9 @@ protected SimpleResponse saveProject(Authentication auth, throws JSONException, ObjectNotFoundException { User user = userService.retrieveUserByUsername(auth.getName()); if (projectService.canAuthorProject(project, user)) { + if (!isValidProjectJSONString(projectJSONString)) { + return new ErrorResponse("errorSavingProject"); + } try { projectService.evictProjectContentCache(project.getId()); projectService.saveProjectContentToDisk(projectJSONString, project); @@ -265,6 +268,18 @@ protected SimpleResponse saveProject(Authentication auth, } } + private boolean isValidProjectJSONString(String projectJSONString) { + if (projectJSONString == null) { + return false; + } + try { + new JSONObject(projectJSONString); + return true; + } catch (JSONException e) { + return false; + } + } + @GetMapping("/config") @ResponseBody @SuppressWarnings("unchecked") diff --git a/src/test/java/org/wise/portal/presentation/web/controllers/author/project/AuthorAPIControllerTest.java b/src/test/java/org/wise/portal/presentation/web/controllers/author/project/AuthorAPIControllerTest.java index 834c1fcc2..f06b06b1f 100644 --- a/src/test/java/org/wise/portal/presentation/web/controllers/author/project/AuthorAPIControllerTest.java +++ b/src/test/java/org/wise/portal/presentation/web/controllers/author/project/AuthorAPIControllerTest.java @@ -305,4 +305,60 @@ public void getAssetFileNames_withASpace_shouldReturnFileNames() throws Exceptio assertTrue(fileNames.contains("carbon dioxide.png")); assertTrue(fileNames.contains("carbon monoxide.png")); } + + @Test + public void saveProject_whenProjectJSONIsMalformed_shouldNotSaveAndReturnErrorSavingProject() + throws Exception { + expect(userService.retrieveUserByUsername(TEACHER_USERNAME)).andReturn(teacher1); + replay(userService); + expect(projectService.canAuthorProject(project1, teacher1)).andReturn(true); + replay(projectService); + String malformedProjectJSONString = "{\"metadata\":{\"title\":\"New Title\"}"; + SimpleResponse response = authorAPIController.saveProject(teacherAuth, project1, + malformedProjectJSONString); + assertEquals("error", response.getStatus()); + assertEquals("errorSavingProject", response.getMessageCode()); + verify(userService, projectService); + } + + @Test + public void saveProject_whenProjectJSONIsNotAnObject_shouldNotSaveAndReturnErrorSavingProject() + throws Exception { + expect(userService.retrieveUserByUsername(TEACHER_USERNAME)).andReturn(teacher1); + replay(userService); + expect(projectService.canAuthorProject(project1, teacher1)).andReturn(true); + replay(projectService); + String projectJSONArrayString = "[{\"metadata\":{\"title\":\"New Title\"}}]"; + SimpleResponse response = authorAPIController.saveProject(teacherAuth, project1, + projectJSONArrayString); + assertEquals("error", response.getStatus()); + assertEquals("errorSavingProject", response.getMessageCode()); + verify(userService, projectService); + } + + @Test + public void saveProject_whenProjectJSONIsEmpty_shouldNotSaveAndReturnErrorSavingProject() + throws Exception { + expect(userService.retrieveUserByUsername(TEACHER_USERNAME)).andReturn(teacher1); + replay(userService); + expect(projectService.canAuthorProject(project1, teacher1)).andReturn(true); + replay(projectService); + SimpleResponse response = authorAPIController.saveProject(teacherAuth, project1, ""); + assertEquals("error", response.getStatus()); + assertEquals("errorSavingProject", response.getMessageCode()); + verify(userService, projectService); + } + + @Test + public void saveProject_whenProjectJSONIsNull_shouldNotSaveAndReturnErrorSavingProject() + throws Exception { + expect(userService.retrieveUserByUsername(TEACHER_USERNAME)).andReturn(teacher1); + replay(userService); + expect(projectService.canAuthorProject(project1, teacher1)).andReturn(true); + replay(projectService); + SimpleResponse response = authorAPIController.saveProject(teacherAuth, project1, null); + assertEquals("error", response.getStatus()); + assertEquals("errorSavingProject", response.getMessageCode()); + verify(userService, projectService); + } }