changes pipeline

This commit is contained in:
Anthony Stirling
2023-12-20 19:29:13 +00:00
parent 1ea3fb209b
commit eab9e3cffc
15 changed files with 289 additions and 100 deletions

View File

@@ -0,0 +1,70 @@
package stirling.software.SPDF.config.security;
import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
public class IPRateLimitingFilter implements Filter {
private final ConcurrentHashMap<String, AtomicInteger> requestCounts = new ConcurrentHashMap<>();
private final ConcurrentHashMap<String, AtomicInteger> getCounts = new ConcurrentHashMap<>();
private final int maxRequests;
private final int maxGetRequests;
public IPRateLimitingFilter(int maxRequests, int maxGetRequests) {
this.maxRequests = maxRequests;
this.maxGetRequests = maxGetRequests;
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
if (request instanceof HttpServletRequest) {
HttpServletRequest httpRequest = (HttpServletRequest) request;
String method = httpRequest.getMethod();
String requestURI = httpRequest.getRequestURI();
// Check if the request is for static resources
boolean isStaticResource = requestURI.startsWith("/css/")
|| requestURI.startsWith("/js/")
|| requestURI.startsWith("/images/")
|| requestURI.startsWith("/public/")
|| requestURI.startsWith("/pdfjs/")
|| requestURI.endsWith(".svg");
// If it's a static resource, just continue the filter chain and skip the logic below
if (isStaticResource) {
chain.doFilter(request, response);
return;
}
String clientIp = request.getRemoteAddr();
requestCounts.computeIfAbsent(clientIp, k -> new AtomicInteger(0));
System.out.println(requestCounts.get(clientIp).get() + ", " + requestURI );
if (!"GET".equalsIgnoreCase(method)) {
if (requestCounts.get(clientIp).incrementAndGet() > maxRequests) {
// Handle limit exceeded (e.g., send error response)
response.getWriter().write("Rate limit exceeded");
return;
}
} else {
if (requestCounts.get(clientIp).incrementAndGet() > maxGetRequests) {
// Handle limit exceeded (e.g., send error response)
response.getWriter().write("GET Rate limit exceeded");
return;
}
}
}
chain.doFilter(request, response);
}
public void resetRequestCounts() {
requestCounts.clear();
getCounts.clear();
}
}

View File

@@ -0,0 +1,18 @@
package stirling.software.SPDF.config.security;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
@Component
public class RateLimitResetScheduler {
private final IPRateLimitingFilter rateLimitingFilter;
public RateLimitResetScheduler(IPRateLimitingFilter rateLimitingFilter) {
this.rateLimitingFilter = rateLimitingFilter;
}
@Scheduled(cron = "0 0 0 * * MON") // At 00:00 every Monday
public void resetRateLimit() {
rateLimitingFilter.resetRequestCounts();
}
}

View File

@@ -13,6 +13,7 @@ import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
@@ -51,11 +52,13 @@ public class SecurityConfiguration {
if(loginEnabledValue) {
http.csrf(csrf -> csrf.disable());
http.addFilterAfter(firstLoginFilter, UsernamePasswordAuthenticationFilter.class);
//http.addFilterBefore(rateLimitingFilter(), UsernamePasswordAuthenticationFilter.class);
//http.addFilterAfter(firstLoginFilter, UsernamePasswordAuthenticationFilter.class);
http
.formLogin(formLogin -> formLogin
.loginPage("/login")
.defaultSuccessUrl("/")
// .defaultSuccessUrl("/")
.successHandler(new SavedRequestAwareAuthenticationSuccessHandler())
.failureHandler(new CustomAuthenticationFailureHandler())
.permitAll()
)
@@ -85,7 +88,14 @@ public class SecurityConfiguration {
return http.build();
}
@Bean
public IPRateLimitingFilter rateLimitingFilter() {
int maxRequestsPerIp = 10000; // Example limit
return new IPRateLimitingFilter(maxRequestsPerIp, maxRequestsPerIp);
}
@Bean
public DaoAuthenticationProvider authenticationProvider() {

View File

@@ -29,12 +29,12 @@ import stirling.software.SPDF.model.api.SplitPdfBySectionsRequest;
import stirling.software.SPDF.utils.WebResponseUtils;
@RestController
@RequestMapping("/api/v1/general")
@Tag(name = "Misc", description = "Miscellaneous APIs")
@Tag(name = "General", description = "General APIs")
public class SplitPdfBySectionsController {
@PostMapping(value = "/split-pdf-by-sections", consumes = "multipart/form-data")
@Operation(summary = "Split PDF pages into smaller sections", description = "Split each page of a PDF into smaller sections based on the user's choice (halves, thirds, quarters, etc.), both vertically and horizontally. Input: PDF, Split Parameters. Output: ZIP containing split documents.")
@Operation(summary = "Split PDF pages into smaller sections", description = "Split each page of a PDF into smaller sections based on the user's choice (halves, thirds, quarters, etc.), both vertically and horizontally. Input:PDF Output:ZIP-PDF Type:SISO")
public ResponseEntity<byte[]> splitPdf(@ModelAttribute SplitPdfBySectionsRequest request) throws Exception {
List<ByteArrayOutputStream> splitDocumentsBoas = new ArrayList<>();

View File

@@ -26,13 +26,13 @@ import stirling.software.SPDF.utils.WebResponseUtils;
@RestController
@RequestMapping("/api/v1/general")
@Tag(name = "Misc", description = "Miscellaneous APIs")
@Tag(name = "General", description = "General APIs")
public class SplitPdfBySizeController {
@PostMapping(value = "/split-by-size-or-count", consumes = "multipart/form-data")
@Operation(summary = "Auto split PDF pages into separate documents based on size or count", description = "split PDF into multiple paged documents based on size/count, ie if 20 pages and split into 5, it does 5 documents each 4 pages\r\n"
+ " if 10MB and each page is 1MB and you enter 2MB then 5 docs each 2MB (rounded so that it accepts 1.9MB but not 2.1MB) Input:PDF Output:ZIP Type:SIMO")
+ " if 10MB and each page is 1MB and you enter 2MB then 5 docs each 2MB (rounded so that it accepts 1.9MB but not 2.1MB) Input:PDF Output:ZIP-PDF Type:SISO")
public ResponseEntity<byte[]> autoSplitPdf(@ModelAttribute SplitPdfBySizeOrCountRequest request) throws Exception {
List<ByteArrayOutputStream> splitDocumentsBoas = new ArrayList<ByteArrayOutputStream>();

View File

@@ -28,7 +28,7 @@ import stirling.software.SPDF.model.api.extract.PDFFilePage;
@RestController
@RequestMapping("/api/v1/convert")
@Tag(name = "General", description = "General APIs")
@Tag(name = "Convert", description = "Convert APIs")
public class ExtractController {
private static final Logger logger = LoggerFactory.getLogger(CropController.class);

View File

@@ -43,7 +43,7 @@ public class AutoSplitPdfController {
private static final String QR_CONTENT = "https://github.com/Frooodle/Stirling-PDF";
@PostMapping(value = "/auto-split-pdf", consumes = "multipart/form-data")
@Operation(summary = "Auto split PDF pages into separate documents", description = "This endpoint accepts a PDF file, scans each page for a specific QR code, and splits the document at the QR code boundaries. The output is a zip file containing each separate PDF document. Input:PDF Output:ZIP Type:SISO")
@Operation(summary = "Auto split PDF pages into separate documents", description = "This endpoint accepts a PDF file, scans each page for a specific QR code, and splits the document at the QR code boundaries. The output is a zip file containing each separate PDF document. Input:PDF Output:ZIP-PDF Type:SISO")
public ResponseEntity<byte[]> autoSplitPdf(@ModelAttribute AutoSplitPdfRequest request) throws IOException {
MultipartFile file = request.getFileInput();
boolean duplexMode = request.isDuplexMode();

View File

@@ -15,6 +15,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import stirling.software.SPDF.model.api.PDFFile;
import stirling.software.SPDF.utils.WebResponseUtils;
@@ -25,6 +26,7 @@ public class ShowJavascript {
private static final Logger logger = LoggerFactory.getLogger(ShowJavascript.class);
@PostMapping(consumes = "multipart/form-data", value = "/show-javascript")
@Operation(summary = "Grabs all JS from a PDF and returns a single JS file with all code", description = "desc. Input:PDF Output:JS Type:SISO")
public ResponseEntity<byte[]> extractHeader(@ModelAttribute PDFFile request) throws Exception {
MultipartFile inputFile = request.getFileInput();
String script = "";

View File

@@ -421,8 +421,11 @@ public class PipelineController {
@PostMapping("/handleData")
public ResponseEntity<byte[]> handleData(@ModelAttribute HandleDataRequest request) {
MultipartFile[] files = request.getFileInputs();
String jsonString = request.getJsonString();
MultipartFile[] files = request.getFileInput();
String jsonString = request.getJson();
if(files == null) {
return null;
}
logger.info("Received POST request to /handleData with {} files", files.length);
try {
List<Resource> outputFiles = handleFiles(files, jsonString);

View File

@@ -1,5 +1,6 @@
package stirling.software.SPDF.controller.web;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
@@ -41,31 +42,33 @@ public class GeneralWebController {
model.addAttribute("currentPage", "pipeline");
List<String> pipelineConfigs = new ArrayList<>();
try (Stream<Path> paths = Files.walk(Paths.get("./pipeline/defaultWebUIConfigs/"))) {
List<Path> jsonFiles = paths
.filter(Files::isRegularFile)
.filter(p -> p.toString().endsWith(".json"))
.collect(Collectors.toList());
for (Path jsonFile : jsonFiles) {
String content = Files.readString(jsonFile, StandardCharsets.UTF_8);
pipelineConfigs.add(content);
}
List<Map<String, String>> pipelineConfigsWithNames = new ArrayList<>();
for (String config : pipelineConfigs) {
Map<String, Object> jsonContent = new ObjectMapper().readValue(config, new TypeReference<Map<String, Object>>(){});
String name = (String) jsonContent.get("name");
Map<String, String> configWithName = new HashMap<>();
configWithName.put("json", config);
configWithName.put("name", name);
pipelineConfigsWithNames.add(configWithName);
}
model.addAttribute("pipelineConfigsWithNames", pipelineConfigsWithNames);
} catch (IOException e) {
e.printStackTrace();
}
if(new File("./pipeline/defaultWebUIConfigs/").exists()) {
try (Stream<Path> paths = Files.walk(Paths.get("./pipeline/defaultWebUIConfigs/"))) {
List<Path> jsonFiles = paths
.filter(Files::isRegularFile)
.filter(p -> p.toString().endsWith(".json"))
.collect(Collectors.toList());
for (Path jsonFile : jsonFiles) {
String content = Files.readString(jsonFile, StandardCharsets.UTF_8);
pipelineConfigs.add(content);
}
List<Map<String, String>> pipelineConfigsWithNames = new ArrayList<>();
for (String config : pipelineConfigs) {
Map<String, Object> jsonContent = new ObjectMapper().readValue(config, new TypeReference<Map<String, Object>>(){});
String name = (String) jsonContent.get("name");
Map<String, String> configWithName = new HashMap<>();
configWithName.put("json", config);
configWithName.put("name", name);
pipelineConfigsWithNames.add(configWithName);
}
model.addAttribute("pipelineConfigsWithNames", pipelineConfigsWithNames);
} catch (IOException e) {
e.printStackTrace();
}
}
model.addAttribute("pipelineConfigs", pipelineConfigs);

View File

@@ -13,8 +13,8 @@ import lombok.NoArgsConstructor;
public class HandleDataRequest {
@Schema(description = "The input files")
private MultipartFile[] fileInputs;
private MultipartFile[] fileInput;
@Schema(description = "JSON String")
private String jsonString;
private String json;
}