diff --git a/.github/workflows/releaseArtifacts.yml b/.github/workflows/releaseArtifacts.yml index 5369317b..d9b500a5 100644 --- a/.github/workflows/releaseArtifacts.yml +++ b/.github/workflows/releaseArtifacts.yml @@ -1,10 +1,20 @@ name: Release Artifacts + on: release: types: [created] + jobs: push: runs-on: ubuntu-latest + strategy: + matrix: + enable_security: [true, false] + include: + - enable_security: true + file_suffix: '-with-login' + - enable_security: false + file_suffix: '' steps: - uses: actions/checkout@v3.5.2 @@ -17,15 +27,17 @@ jobs: - name: Grant execute permission for gradlew run: chmod +x gradlew - - name: Generate jar + - name: Generate jar (With Security=${{ matrix.enable_security }}) run: ./gradlew clean createExe + env: + ENABLE_SECURITY: ${{ matrix.enable_security }} - name: Upload binaries to release uses: svenstaro/upload-release-action@v2 with: repo_token: ${{ secrets.GITHUB_TOKEN }} - file: ./build/launch4j/Stirling-PDF.exe - asset_name: Stirling-PDF.exe + file: ./build/launch4j/Stirling-PDF${{ matrix.file_suffix }}.exe + asset_name: Stirling-PDF${{ matrix.file_suffix }}.exe tag: ${{ github.ref }} overwrite: true @@ -33,13 +45,11 @@ jobs: id: versionNumber run: echo "::set-output name=versionNumber::$(./gradlew printVersion --quiet | tail -1)" - - name: Upload binaries to release + - name: Upload jar binaries to release uses: svenstaro/upload-release-action@v2 with: repo_token: ${{ secrets.GITHUB_TOKEN }} - file: ./build/libs/Stirling-PDF-${{ steps.versionNumber.outputs.versionNumber }}.jar - asset_name: Stirling-PDF.jar + file: ./build/libs/Stirling-PDF-${{ matrix.file_suffix }}-${{ steps.versionNumber.outputs.versionNumber }}.jar + asset_name: Stirling-PDF${{ matrix.file_suffix }}.jar tag: ${{ github.ref }} overwrite: true - - diff --git a/Dockerfile b/Dockerfile index c98a21c8..b406b879 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,11 @@ # Build jbig2enc in a separate stage FROM frooodle/stirling-pdf-base:beta4 +ARG ENABLE_SECURITY_DEFAULT=false +ENV ENABLE_SECURITY=$ENABLE_SECURITY_DEFAULT + +ARG VERSION_TAG=v0.12.2 + # Create scripts folder and copy local scripts RUN mkdir /scripts COPY ./scripts/* /scripts/ @@ -11,16 +16,18 @@ COPY src/main/resources/static/fonts/*.ttf /usr/share/fonts/opentype/noto/ COPY src/main/resources/static/fonts/*.otf /usr/share/fonts/opentype/noto/ RUN fc-cache -f -v -# Copy the application JAR file -COPY build/libs/*.jar app.jar +# Depending on the ENABLE_SECURITY flag, download the correct JAR +RUN if [ "$ENABLE_SECURITY" = "true" ]; then \ + wget -O app.jar https://github.com/Frooodle/Stirling-PDF/releases/download/$VERSION_TAG/Stirling-PDF-with-login-$VERSION_TAG.jar; \ + else \ + wget -O app.jar https://github.com/Frooodle/Stirling-PDF/releases/download/$VERSION_TAG/Stirling-PDF-$VERSION_TAG.jar; \ + fi # Expose the application port EXPOSE 8080 # Set environment variables ENV APP_HOME_NAME="Stirling PDF" -#ENV APP_HOME_DESCRIPTION="Personal PDF Website!" -#ENV APP_NAVBAR_NAME="Stirling PDF" # Run the application RUN chmod +x /scripts/init.sh diff --git a/build.gradle b/build.gradle index bff497c0..97616561 100644 --- a/build.gradle +++ b/build.gradle @@ -15,6 +15,24 @@ repositories { mavenCentral() } +sourceSets { + main { + java { + if (System.getenv('ENABLE_SECURITY') == 'false') { + exclude 'stirling/software/SPDF/config/security/**' + exclude 'stirling/software/SPDF/controller/api/UserController.java' + exclude 'stirling/software/SPDF/controller/web/AccountWebController.java' + exclude 'stirling/software/SPDF/model/ApiKeyAuthenticationToken.java' + exclude 'stirling/software/SPDF/model/Authority.java' + exclude 'stirling/software/SPDF/model/PersistentLogin.java' + exclude 'stirling/software/SPDF/model/User.java' + exclude 'stirling/software/SPDF/repository/**' + } + } + } +} + + openApi { apiDocsUrl = "http://localhost:8080/v3/api-docs" outputDir = file("$projectDir") @@ -48,11 +66,17 @@ dependencies { implementation 'org.yaml:snakeyaml:2.1' implementation 'org.springframework.boot:spring-boot-starter-web:3.1.2' implementation 'org.springframework.boot:spring-boot-starter-thymeleaf:3.1.2' - implementation 'org.springframework.boot:spring-boot-starter-security:3.1.2' + + if (System.getenv('ENABLE_SECURITY') != 'false') { + implementation 'org.springframework.boot:spring-boot-starter-security:3.1.2' + implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5:3.1.2.RELEASE' + implementation "org.springframework.boot:spring-boot-starter-data-jpa" + implementation "com.h2database:h2" + } + testImplementation 'org.springframework.boot:spring-boot-starter-test:3.1.2' - implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5:3.1.2.RELEASE' - implementation "org.springframework.boot:spring-boot-starter-data-jpa" - implementation "com.h2database:h2" + + // https://mvnrepository.com/artifact/org.apache.pdfbox/jbig2-imageio implementation group: 'org.apache.pdfbox', name: 'jbig2-imageio', version: '3.0.4' diff --git a/src/main/java/stirling/software/SPDF/SPdfApplication.java b/src/main/java/stirling/software/SPDF/SPdfApplication.java index 331ca734..4c3dcebb 100644 --- a/src/main/java/stirling/software/SPDF/SPdfApplication.java +++ b/src/main/java/stirling/software/SPDF/SPdfApplication.java @@ -8,15 +8,13 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.core.env.Environment; -import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; + import jakarta.annotation.PostConstruct; import stirling.software.SPDF.config.ConfigInitializer; import stirling.software.SPDF.utils.GeneralUtils; @SpringBootApplication -@EnableWebSecurity() -@EnableGlobalMethodSecurity(prePostEnabled = true) + //@EnableScheduling public class SPdfApplication { diff --git a/src/main/java/stirling/software/SPDF/config/security/InitialSetup.java b/src/main/java/stirling/software/SPDF/config/security/InitialSecuritySetup.java similarity index 95% rename from src/main/java/stirling/software/SPDF/config/security/InitialSetup.java rename to src/main/java/stirling/software/SPDF/config/security/InitialSecuritySetup.java index c28cc6a5..08b939b7 100644 --- a/src/main/java/stirling/software/SPDF/config/security/InitialSetup.java +++ b/src/main/java/stirling/software/SPDF/config/security/InitialSecuritySetup.java @@ -11,11 +11,12 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import jakarta.annotation.PostConstruct; +import stirling.software.SPDF.config.security.UserService; import stirling.software.SPDF.model.ApplicationProperties; import stirling.software.SPDF.model.Role; @Component -public class InitialSetup { +public class InitialSecuritySetup { @Autowired private UserService userService; 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 62d4a86d..2e0b8345 100644 --- a/src/main/java/stirling/software/SPDF/config/security/SecurityConfiguration.java +++ b/src/main/java/stirling/software/SPDF/config/security/SecurityConfiguration.java @@ -1,6 +1,9 @@ package stirling.software.SPDF.config.security; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; + import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -17,6 +20,8 @@ import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import stirling.software.SPDF.repository.JPATokenRepositoryImpl; @Configuration +@EnableWebSecurity() +@EnableGlobalMethodSecurity(prePostEnabled = true) public class SecurityConfiguration { @Autowired diff --git a/src/main/java/stirling/software/SPDF/config/UserBasedRateLimitingFilter.java b/src/main/java/stirling/software/SPDF/config/security/UserBasedRateLimitingFilter.java similarity index 96% rename from src/main/java/stirling/software/SPDF/config/UserBasedRateLimitingFilter.java rename to src/main/java/stirling/software/SPDF/config/security/UserBasedRateLimitingFilter.java index 60fad7e3..f23e5ce3 100644 --- a/src/main/java/stirling/software/SPDF/config/UserBasedRateLimitingFilter.java +++ b/src/main/java/stirling/software/SPDF/config/security/UserBasedRateLimitingFilter.java @@ -1,4 +1,4 @@ -package stirling.software.SPDF.config; +package stirling.software.SPDF.config.security; import java.io.IOException; import java.time.Duration; diff --git a/src/main/java/stirling/software/SPDF/controller/web/AccountWebController.java b/src/main/java/stirling/software/SPDF/controller/web/AccountWebController.java new file mode 100644 index 00000000..daf162dc --- /dev/null +++ b/src/main/java/stirling/software/SPDF/controller/web/AccountWebController.java @@ -0,0 +1,121 @@ +package stirling.software.SPDF.controller.web; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.io.Resource; +import org.springframework.core.io.ResourceLoader; +import org.springframework.core.io.support.ResourcePatternUtils; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; + +import io.swagger.v3.oas.annotations.Hidden; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.HttpServletRequest; +import stirling.software.SPDF.config.security.UserService; +import stirling.software.SPDF.model.User; +import stirling.software.SPDF.repository.UserRepository; +@Controller +@Tag(name = "Account Security", description = "Account Security APIs") +public class AccountWebController { + + + @GetMapping("/login") + public String login(HttpServletRequest request, Model model, Authentication authentication) { + if (authentication != null && authentication.isAuthenticated()) { + return "redirect:/"; + } + + if (request.getParameter("error") != null) { + + model.addAttribute("error", request.getParameter("error")); + } + if (request.getParameter("logout") != null) { + + model.addAttribute("logoutMessage", "You have been logged out."); + } + + return "login"; + } + @Autowired + private UserRepository userRepository; // Assuming you have a repository for user operations + + @Autowired + private UserService userService; // Assuming you have a repository for user operations + + @PreAuthorize("hasRole('ROLE_ADMIN')") + @GetMapping("/addUsers") + public String showAddUserForm(Model model) { + List allUsers = userRepository.findAll(); + model.addAttribute("users", allUsers); + return "addUsers"; + } + + + + @GetMapping("/account") + public String account(HttpServletRequest request, Model model, Authentication authentication) { + if (authentication == null || !authentication.isAuthenticated()) { + return "redirect:/"; + } + if (authentication != null && authentication.isAuthenticated()) { + Object principal = authentication.getPrincipal(); + + if (principal instanceof UserDetails) { + // Cast the principal object to UserDetails + UserDetails userDetails = (UserDetails) principal; + + // Retrieve username and other attributes + String username = userDetails.getUsername(); + + // Fetch user details from the database + Optional user = userRepository.findByUsername(username); // Assuming findByUsername method exists + if (!user.isPresent()) { + // Handle error appropriately + return "redirect:/error"; // Example redirection in case of error + } + + // Convert settings map to JSON string + ObjectMapper objectMapper = new ObjectMapper(); + String settingsJson; + try { + settingsJson = objectMapper.writeValueAsString(user.get().getSettings()); + } catch (JsonProcessingException e) { + // Handle JSON conversion error + e.printStackTrace(); + return "redirect:/error"; // Example redirection in case of error + } + + // Add attributes to the model + model.addAttribute("username", username); + model.addAttribute("role", user.get().getRolesAsString()); + model.addAttribute("settings", settingsJson); + } + } else { + return "redirect:/"; + } + return "account"; + } + + + + +} diff --git a/src/main/java/stirling/software/SPDF/controller/web/GeneralWebController.java b/src/main/java/stirling/software/SPDF/controller/web/GeneralWebController.java index a3a29bbc..3cc55a4e 100644 --- a/src/main/java/stirling/software/SPDF/controller/web/GeneralWebController.java +++ b/src/main/java/stirling/software/SPDF/controller/web/GeneralWebController.java @@ -9,7 +9,6 @@ import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Optional; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -17,105 +16,20 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; import org.springframework.core.io.support.ResourcePatternUtils; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.userdetails.UserDetails; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; -import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import io.swagger.v3.oas.annotations.Hidden; import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.servlet.http.HttpServletRequest; -import stirling.software.SPDF.config.security.UserService; -import stirling.software.SPDF.model.User; -import stirling.software.SPDF.repository.UserRepository; + @Controller @Tag(name = "General", description = "General APIs") public class GeneralWebController { - @GetMapping("/login") - public String login(HttpServletRequest request, Model model, Authentication authentication) { - if (authentication != null && authentication.isAuthenticated()) { - return "redirect:/"; - } - - if (request.getParameter("error") != null) { - - model.addAttribute("error", request.getParameter("error")); - } - if (request.getParameter("logout") != null) { - - model.addAttribute("logoutMessage", "You have been logged out."); - } - - return "login"; - } - @Autowired - private UserRepository userRepository; // Assuming you have a repository for user operations - - @Autowired - private UserService userService; // Assuming you have a repository for user operations - - @PreAuthorize("hasRole('ROLE_ADMIN')") - @GetMapping("/addUsers") - public String showAddUserForm(Model model) { - List allUsers = userRepository.findAll(); - model.addAttribute("users", allUsers); - return "addUsers"; - } - - - - @GetMapping("/account") - public String account(HttpServletRequest request, Model model, Authentication authentication) { - if (authentication == null || !authentication.isAuthenticated()) { - return "redirect:/"; - } - if (authentication != null && authentication.isAuthenticated()) { - Object principal = authentication.getPrincipal(); - - if (principal instanceof UserDetails) { - // Cast the principal object to UserDetails - UserDetails userDetails = (UserDetails) principal; - - // Retrieve username and other attributes - String username = userDetails.getUsername(); - - // Fetch user details from the database - Optional user = userRepository.findByUsername(username); // Assuming findByUsername method exists - if (!user.isPresent()) { - // Handle error appropriately - return "redirect:/error"; // Example redirection in case of error - } - - // Convert settings map to JSON string - ObjectMapper objectMapper = new ObjectMapper(); - String settingsJson; - try { - settingsJson = objectMapper.writeValueAsString(user.get().getSettings()); - } catch (JsonProcessingException e) { - // Handle JSON conversion error - e.printStackTrace(); - return "redirect:/error"; // Example redirection in case of error - } - - // Add attributes to the model - model.addAttribute("username", username); - model.addAttribute("role", user.get().getRolesAsString()); - model.addAttribute("settings", settingsJson); - } - } else { - return "redirect:/"; - } - return "account"; - } - - @GetMapping("/pipeline")