From 121af0501afdba9249efac3587a06c57f3c2c249 Mon Sep 17 00:00:00 2001 From: Anthony Stirling <77850077+Frooodle@users.noreply.github.com> Date: Sat, 9 Mar 2024 14:03:46 +0000 Subject: [PATCH] fixes for user permissions (#892) --- Dockerfile | 5 +- Dockerfile-lite | 2 + Dockerfile-ultra-lite | 1 + build.gradle | 2 +- .../docker-compose-latest-security.yml | 3 + scripts/init-without-ocr.sh | 7 +- scripts/init.sh | 8 +- .../CustomAuthenticationFailureHandler.java | 1 - .../security/SecurityConfiguration.java | 8 +- .../SPDF/controller/api/UserController.java | 21 +- .../api/misc/ExtractImageScansController.java | 254 +++++++++--------- test.sh | 3 +- 12 files changed, 163 insertions(+), 152 deletions(-) diff --git a/Dockerfile b/Dockerfile index 86d7bdda..02243563 100644 --- a/Dockerfile +++ b/Dockerfile @@ -33,6 +33,7 @@ RUN echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /et curl \ openjdk17-jre \ su-exec \ + shadow \ # Doc conversion libreoffice@testing \ # OCR MY PDF (unpaper for descew and other advanced featues) @@ -52,8 +53,8 @@ RUN echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /et chmod +x /scripts/init.sh && \ # User permissions addgroup -S stirlingpdfgroup && adduser -S stirlingpdfuser -G stirlingpdfgroup && \ - chown -R stirlingpdfuser:stirlingpdfgroup $HOME /scripts /usr/share/fonts/opentype/noto /configs /customFiles /pipeline && \ - chown stirlingpdfuser:stirlingpdfgroup /app.jar + chown -R stirlingpdfuser:stirlingpdfgroup $HOME /scripts /usr/share/fonts/opentype/noto /configs /customFiles /pipeline && \ + chown stirlingpdfuser:stirlingpdfgroup /app.jar EXPOSE 8080 diff --git a/Dockerfile-lite b/Dockerfile-lite index 982858aa..42786f15 100644 --- a/Dockerfile-lite +++ b/Dockerfile-lite @@ -29,8 +29,10 @@ RUN echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /et tini \ bash \ curl \ + gcc \ openjdk17-jre \ su-exec \ + shadow \ # Doc conversion libreoffice@testing \ # python and pip diff --git a/Dockerfile-ultra-lite b/Dockerfile-ultra-lite index 3836f477..eed8d783 100644 --- a/Dockerfile-ultra-lite +++ b/Dockerfile-ultra-lite @@ -30,6 +30,7 @@ RUN mkdir /configs /logs /customFiles && \ bash \ curl \ su-exec \ + shadow \ openjdk17-jre && \ echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /etc/apk/repositories && \ echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/community" | tee -a /etc/apk/repositories && \ diff --git a/build.gradle b/build.gradle index 98a02fa2..011ad008 100644 --- a/build.gradle +++ b/build.gradle @@ -12,7 +12,7 @@ plugins { import com.github.jk1.license.render.* group = 'stirling.software' -version = '0.22.0' +version = '0.22.1' sourceCompatibility = '17' repositories { diff --git a/exampleYmlFiles/docker-compose-latest-security.yml b/exampleYmlFiles/docker-compose-latest-security.yml index 830c425f..30fd8e25 100644 --- a/exampleYmlFiles/docker-compose-latest-security.yml +++ b/exampleYmlFiles/docker-compose-latest-security.yml @@ -21,6 +21,9 @@ services: environment: DOCKER_ENABLE_SECURITY: "true" SECURITY_ENABLELOGIN: "true" + PUID: 1002 + GGID: 1002 + UMASK: "022" SYSTEM_DEFAULTLOCALE: en-US UI_APPNAME: Stirling-PDF UI_HOMEDESCRIPTION: Demo site for Stirling-PDF Latest with Security diff --git a/scripts/init-without-ocr.sh b/scripts/init-without-ocr.sh index 2408b83a..7bd129c8 100644 --- a/scripts/init-without-ocr.sh +++ b/scripts/init-without-ocr.sh @@ -5,19 +5,18 @@ if [ ! -z "$PUID" ] && [ "$PUID" != "$(id -u stirlingpdfuser)" ]; then usermod -o -u "$PUID" stirlingpdfuser fi -if [ ! -z "$PGID" ] && [ "$PGID" != "$(id -g stirlingpdfgroup)" ]; then +if [ ! -z "$PGID" ] && [ "$PGID" != "$(getent group stirlingpdfgroup | cut -d: -f3)" ]; then groupmod -o -g "$PGID" stirlingpdfgroup fi umask "$UMASK" echo "Setting permissions and ownership for necessary directories..." -chown -R stirlingpdfuser:stirlingpdfgroup /logs /scripts /usr/share/fonts/opentype/noto /usr/share/tessdata /configs /customFiles -chmod -R 755 /logs /scripts /usr/share/fonts/opentype/noto /usr/share/tessdata /configs /customFiles +chown -R stirlingpdfuser:stirlingpdfgroup $HOME /logs /scripts /usr/share/fonts/opentype/noto /usr/share/tessdata /configs /customFiles /pipeline /app.jar +chmod -R 755 /logs /scripts /usr/share/fonts/opentype/noto /usr/share/tessdata /configs /customFiles /pipeline /app.jar if [[ "$INSTALL_BOOK_AND_ADVANCED_HTML_OPS" == "true" ]]; then apk add --no-cache calibre@testing fi - /scripts/download-security-jar.sh # Run the main command diff --git a/scripts/init.sh b/scripts/init.sh index b5f266b8..58d64e59 100644 --- a/scripts/init.sh +++ b/scripts/init.sh @@ -13,20 +13,20 @@ if [ -d /usr/share/tesseract-ocr/5/tessdata ]; then cp -r /usr/share/tesseract-ocr/5/tessdata/* /usr/share/tessdata || true; fi - # Update the user and group IDs as per environment variables if [ ! -z "$PUID" ] && [ "$PUID" != "$(id -u stirlingpdfuser)" ]; then usermod -o -u "$PUID" stirlingpdfuser fi -if [ ! -z "$PGID" ] && [ "$PGID" != "$(id -g stirlingpdfgroup)" ]; then + +if [ ! -z "$PGID" ] && [ "$PGID" != "$(getent group stirlingpdfgroup | cut -d: -f3)" ]; then groupmod -o -g "$PGID" stirlingpdfgroup fi umask "$UMASK" echo "Setting permissions and ownership for necessary directories..." -chown -R stirlingpdfuser:stirlingpdfgroup /logs /scripts /usr/share/fonts/opentype/noto /usr/share/tessdata /configs /customFiles -chmod -R 755 /logs /scripts /usr/share/fonts/opentype/noto /usr/share/tessdata /configs /customFiles +chown -R stirlingpdfuser:stirlingpdfgroup $HOME /logs /scripts /usr/share/fonts/opentype/noto /usr/share/tessdata /configs /customFiles /pipeline /app.jar +chmod -R 755 /logs /scripts /usr/share/fonts/opentype/noto /usr/share/tessdata /configs /customFiles /pipeline /app.jar diff --git a/src/main/java/stirling/software/SPDF/config/security/CustomAuthenticationFailureHandler.java b/src/main/java/stirling/software/SPDF/config/security/CustomAuthenticationFailureHandler.java index 2d059b23..038e087f 100644 --- a/src/main/java/stirling/software/SPDF/config/security/CustomAuthenticationFailureHandler.java +++ b/src/main/java/stirling/software/SPDF/config/security/CustomAuthenticationFailureHandler.java @@ -60,6 +60,5 @@ public class CustomAuthenticationFailureHandler extends SimpleUrlAuthenticationF return user.isPresent() && user.get().getAuthorities().stream() .anyMatch(authority -> "ROLE_DEMO_USER".equals(authority.getAuthority())); - } } diff --git a/src/main/java/stirling/software/SPDF/config/security/SecurityConfiguration.java b/src/main/java/stirling/software/SPDF/config/security/SecurityConfiguration.java index 1f171136..e9497f20 100644 --- a/src/main/java/stirling/software/SPDF/config/security/SecurityConfiguration.java +++ b/src/main/java/stirling/software/SPDF/config/security/SecurityConfiguration.java @@ -66,10 +66,11 @@ public class SecurityConfiguration { sessionManagement -> sessionManagement .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED) - .maximumSessions(3) - .maxSessionsPreventsLogin(true) + .maximumSessions(10) + .maxSessionsPreventsLogin(false) .sessionRegistry(sessionRegistry()) .expiredUrl("/login?logout=true")); + http.formLogin( formLogin -> formLogin @@ -92,8 +93,7 @@ public class SecurityConfiguration { .addLogoutHandler( (request, response, authentication) -> { HttpSession session = - request.getSession( - false); + request.getSession(false); if (session != null) { String sessionId = session.getId(); sessionRegistry() diff --git a/src/main/java/stirling/software/SPDF/controller/api/UserController.java b/src/main/java/stirling/software/SPDF/controller/api/UserController.java index 9554cc80..07c4b934 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/UserController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/UserController.java @@ -56,8 +56,8 @@ public class UserController { @PostMapping("/change-username") public RedirectView changeUsername( Principal principal, - @RequestParam String currentPassword, - @RequestParam String newUsername, + @RequestParam(name = "currentPassword") String currentPassword, + @RequestParam(name = "newUsername") String newUsername, HttpServletRequest request, HttpServletResponse response, RedirectAttributes redirectAttributes) { @@ -95,8 +95,8 @@ public class UserController { @PostMapping("/change-password-on-login") public RedirectView changePasswordOnLogin( Principal principal, - @RequestParam String currentPassword, - @RequestParam String newPassword, + @RequestParam(name = "currentPassword") String currentPassword, + @RequestParam(name = "newPassword") String newPassword, HttpServletRequest request, HttpServletResponse response, RedirectAttributes redirectAttributes) { @@ -128,8 +128,8 @@ public class UserController { @PostMapping("/change-password") public RedirectView changePassword( Principal principal, - @RequestParam String currentPassword, - @RequestParam String newPassword, + @RequestParam(name = "currentPassword") String currentPassword, + @RequestParam(name = "newPassword") String newPassword, HttpServletRequest request, HttpServletResponse response, RedirectAttributes redirectAttributes) { @@ -180,9 +180,9 @@ public class UserController { @PreAuthorize("hasRole('ROLE_ADMIN')") @PostMapping("/admin/saveUser") public RedirectView saveUser( - @RequestParam String username, - @RequestParam String password, - @RequestParam String role, + @RequestParam(name = "username") String username, + @RequestParam(name = "password") String password, + @RequestParam(name = "role") String role, @RequestParam(name = "forceChange", required = false, defaultValue = "false") boolean forceChange) { @@ -207,7 +207,8 @@ public class UserController { @PreAuthorize("hasRole('ROLE_ADMIN')") @PostMapping("/admin/deleteUser/{username}") - public RedirectView deleteUser(@PathVariable String username, Authentication authentication) { + public RedirectView deleteUser( + @PathVariable(name = "username") String username, Authentication authentication) { if (!userService.usernameExists(username)) { return new RedirectView("/addUsers?messageType=deleteUsernameExists"); diff --git a/src/main/java/stirling/software/SPDF/controller/api/misc/ExtractImageScansController.java b/src/main/java/stirling/software/SPDF/controller/api/misc/ExtractImageScansController.java index 3eee727f..855826db 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/misc/ExtractImageScansController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/misc/ExtractImageScansController.java @@ -72,130 +72,133 @@ public class ExtractImageScansController { String extension = fileName.substring(fileName.lastIndexOf(".") + 1); List images = new ArrayList<>(); - + List tempImageFiles = new ArrayList<>(); Path tempInputFile = null; Path tempZipFile = null; List tempDirs = new ArrayList<>(); - + try { - // Check if input file is a PDF - if ("pdf".equalsIgnoreCase(extension)) { - // Load PDF document - try (PDDocument document = Loader.loadPDF(form.getFileInput().getBytes())) { - PDFRenderer pdfRenderer = new PDFRenderer(document); - int pageCount = document.getNumberOfPages(); - images = new ArrayList<>(); - - // Create images of all pages - for (int i = 0; i < pageCount; i++) { - // Create temp file to save the image - Path tempFile = Files.createTempFile("image_", ".png"); - - // Render image and save as temp file - BufferedImage image = pdfRenderer.renderImageWithDPI(i, 300); - ImageIO.write(image, "png", tempFile.toFile()); - - // Add temp file path to images list - images.add(tempFile.toString()); - tempImageFiles.add(tempFile); - } - } - } else { - tempInputFile = Files.createTempFile("input_", "." + extension); - Files.copy( - form.getFileInput().getInputStream(), - tempInputFile, - StandardCopyOption.REPLACE_EXISTING); - // Add input file path to images list - images.add(tempInputFile.toString()); - } - - List processedImageBytes = new ArrayList<>(); - - // Process each image - for (int i = 0; i < images.size(); i++) { - - Path tempDir = Files.createTempDirectory("openCV_output"); - tempDirs.add(tempDir); - List command = - new ArrayList<>( - Arrays.asList( - "python3", - "./scripts/split_photos.py", - images.get(i), - tempDir.toString(), - "--angle_threshold", - String.valueOf(form.getAngleThreshold()), - "--tolerance", - String.valueOf(form.getTolerance()), - "--min_area", - String.valueOf(form.getMinArea()), - "--min_contour_area", - String.valueOf(form.getMinContourArea()), - "--border_size", - String.valueOf(form.getBorderSize()))); - - // Run CLI command - ProcessExecutorResult returnCode = - ProcessExecutor.getInstance(ProcessExecutor.Processes.PYTHON_OPENCV) - .runCommandWithOutputHandling(command); - - // Read the output photos in temp directory - List tempOutputFiles = Files.list(tempDir).sorted().collect(Collectors.toList()); - for (Path tempOutputFile : tempOutputFiles) { - byte[] imageBytes = Files.readAllBytes(tempOutputFile); - processedImageBytes.add(imageBytes); - } - // Clean up the temporary directory - FileUtils.deleteDirectory(tempDir.toFile()); - } - - // Create zip file if multiple images - if (processedImageBytes.size() > 1) { - String outputZipFilename = fileName.replaceFirst("[.][^.]+$", "") + "_processed.zip"; - tempZipFile = Files.createTempFile("output_", ".zip"); - - try (ZipOutputStream zipOut = - new ZipOutputStream(new FileOutputStream(tempZipFile.toFile()))) { - // Add processed images to the zip - for (int i = 0; i < processedImageBytes.size(); i++) { - ZipEntry entry = - new ZipEntry( - fileName.replaceFirst("[.][^.]+$", "") - + "_" - + (i + 1) - + ".png"); - zipOut.putNextEntry(entry); - zipOut.write(processedImageBytes.get(i)); - zipOut.closeEntry(); - } - } - - byte[] zipBytes = Files.readAllBytes(tempZipFile); - - // Clean up the temporary zip file - Files.delete(tempZipFile); - - return WebResponseUtils.bytesToWebResponse( - zipBytes, outputZipFilename, MediaType.APPLICATION_OCTET_STREAM); - } else { - // Return the processed image as a response - byte[] imageBytes = processedImageBytes.get(0); - return WebResponseUtils.bytesToWebResponse( - imageBytes, - fileName.replaceFirst("[.][^.]+$", "") + ".png", - MediaType.IMAGE_PNG); - } - } finally { - // Cleanup logic for all temporary files and directories - tempImageFiles.forEach(path -> { - try { - Files.deleteIfExists(path); - } catch (IOException e) { - logger.error("Failed to delete temporary image file: " + path, e); + // Check if input file is a PDF + if ("pdf".equalsIgnoreCase(extension)) { + // Load PDF document + try (PDDocument document = Loader.loadPDF(form.getFileInput().getBytes())) { + PDFRenderer pdfRenderer = new PDFRenderer(document); + int pageCount = document.getNumberOfPages(); + images = new ArrayList<>(); + + // Create images of all pages + for (int i = 0; i < pageCount; i++) { + // Create temp file to save the image + Path tempFile = Files.createTempFile("image_", ".png"); + + // Render image and save as temp file + BufferedImage image = pdfRenderer.renderImageWithDPI(i, 300); + ImageIO.write(image, "png", tempFile.toFile()); + + // Add temp file path to images list + images.add(tempFile.toString()); + tempImageFiles.add(tempFile); + } } - }); + } else { + tempInputFile = Files.createTempFile("input_", "." + extension); + Files.copy( + form.getFileInput().getInputStream(), + tempInputFile, + StandardCopyOption.REPLACE_EXISTING); + // Add input file path to images list + images.add(tempInputFile.toString()); + } + + List processedImageBytes = new ArrayList<>(); + + // Process each image + for (int i = 0; i < images.size(); i++) { + + Path tempDir = Files.createTempDirectory("openCV_output"); + tempDirs.add(tempDir); + List command = + new ArrayList<>( + Arrays.asList( + "python3", + "./scripts/split_photos.py", + images.get(i), + tempDir.toString(), + "--angle_threshold", + String.valueOf(form.getAngleThreshold()), + "--tolerance", + String.valueOf(form.getTolerance()), + "--min_area", + String.valueOf(form.getMinArea()), + "--min_contour_area", + String.valueOf(form.getMinContourArea()), + "--border_size", + String.valueOf(form.getBorderSize()))); + + // Run CLI command + ProcessExecutorResult returnCode = + ProcessExecutor.getInstance(ProcessExecutor.Processes.PYTHON_OPENCV) + .runCommandWithOutputHandling(command); + + // Read the output photos in temp directory + List tempOutputFiles = + Files.list(tempDir).sorted().collect(Collectors.toList()); + for (Path tempOutputFile : tempOutputFiles) { + byte[] imageBytes = Files.readAllBytes(tempOutputFile); + processedImageBytes.add(imageBytes); + } + // Clean up the temporary directory + FileUtils.deleteDirectory(tempDir.toFile()); + } + + // Create zip file if multiple images + if (processedImageBytes.size() > 1) { + String outputZipFilename = + fileName.replaceFirst("[.][^.]+$", "") + "_processed.zip"; + tempZipFile = Files.createTempFile("output_", ".zip"); + + try (ZipOutputStream zipOut = + new ZipOutputStream(new FileOutputStream(tempZipFile.toFile()))) { + // Add processed images to the zip + for (int i = 0; i < processedImageBytes.size(); i++) { + ZipEntry entry = + new ZipEntry( + fileName.replaceFirst("[.][^.]+$", "") + + "_" + + (i + 1) + + ".png"); + zipOut.putNextEntry(entry); + zipOut.write(processedImageBytes.get(i)); + zipOut.closeEntry(); + } + } + + byte[] zipBytes = Files.readAllBytes(tempZipFile); + + // Clean up the temporary zip file + Files.delete(tempZipFile); + + return WebResponseUtils.bytesToWebResponse( + zipBytes, outputZipFilename, MediaType.APPLICATION_OCTET_STREAM); + } else { + // Return the processed image as a response + byte[] imageBytes = processedImageBytes.get(0); + return WebResponseUtils.bytesToWebResponse( + imageBytes, + fileName.replaceFirst("[.][^.]+$", "") + ".png", + MediaType.IMAGE_PNG); + } + } finally { + // Cleanup logic for all temporary files and directories + tempImageFiles.forEach( + path -> { + try { + Files.deleteIfExists(path); + } catch (IOException e) { + logger.error("Failed to delete temporary image file: " + path, e); + } + }); if (tempZipFile != null && Files.exists(tempZipFile)) { try { @@ -205,13 +208,14 @@ public class ExtractImageScansController { } } - tempDirs.forEach(dir -> { - try { - FileUtils.deleteDirectory(dir.toFile()); - } catch (IOException e) { - logger.error("Failed to delete temporary directory: " + dir, e); - } - }); + tempDirs.forEach( + dir -> { + try { + FileUtils.deleteDirectory(dir.toFile()); + } catch (IOException e) { + logger.error("Failed to delete temporary directory: " + dir, e); + } + }); } } } diff --git a/test.sh b/test.sh index f17b0651..205809e9 100644 --- a/test.sh +++ b/test.sh @@ -18,7 +18,8 @@ check_health() { fi done echo -e "\n$service_name is healthy!" - + echo "Printing logs for $service_name:" + docker logs "$service_name" return 0 }