Add files via upload

This commit is contained in:
Anthony Stirling 2023-08-13 01:15:26 +01:00 committed by GitHub
parent cd2728105e
commit ab9a22d8e7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 382 additions and 314 deletions

View file

@ -1,53 +1,53 @@
package stirling.software.SPDF.config; package stirling.software.SPDF.config;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
@Configuration @Configuration
public class AppConfig { public class AppConfig {
@Bean(name = "loginEnabled") @Bean(name = "loginEnabled")
public boolean loginEnabled() { public boolean loginEnabled() {
String appName = System.getProperty("login.enabled"); String appName = System.getProperty("login.enabled");
if (appName == null) if (appName == null)
appName = System.getenv("login.enabled"); appName = System.getenv("login.enabled");
return (appName != null) ? Boolean.valueOf(appName) : false; return (appName != null) ? Boolean.valueOf(appName) : false;
} }
@Bean(name = "appName") @Bean(name = "appName")
public String appName() { public String appName() {
String appName = System.getProperty("APP_HOME_NAME"); String appName = System.getProperty("APP_HOME_NAME");
if (appName == null) if (appName == null)
appName = System.getenv("APP_HOME_NAME"); appName = System.getenv("APP_HOME_NAME");
return (appName != null) ? appName : "Stirling PDF"; return (appName != null) ? appName : "Stirling PDF";
} }
@Bean(name = "appVersion") @Bean(name = "appVersion")
public String appVersion() { public String appVersion() {
String version = getClass().getPackage().getImplementationVersion(); String version = getClass().getPackage().getImplementationVersion();
return (version != null) ? version : "0.0.0"; return (version != null) ? version : "0.0.0";
} }
@Bean(name = "homeText") @Bean(name = "homeText")
public String homeText() { public String homeText() {
String homeText = System.getProperty("APP_HOME_DESCRIPTION"); String homeText = System.getProperty("APP_HOME_DESCRIPTION");
if (homeText == null) if (homeText == null)
homeText = System.getenv("APP_HOME_DESCRIPTION"); homeText = System.getenv("APP_HOME_DESCRIPTION");
return (homeText != null) ? homeText : "null"; return (homeText != null) ? homeText : "null";
} }
@Bean(name = "navBarText") @Bean(name = "navBarText")
public String navBarText() { public String navBarText() {
String navBarText = System.getProperty("APP_NAVBAR_NAME"); String navBarText = System.getProperty("APP_NAVBAR_NAME");
if (navBarText == null) if (navBarText == null)
navBarText = System.getenv("APP_NAVBAR_NAME"); navBarText = System.getenv("APP_NAVBAR_NAME");
if (navBarText == null) if (navBarText == null)
navBarText = System.getProperty("APP_HOME_NAME"); navBarText = System.getProperty("APP_HOME_NAME");
if (navBarText == null) if (navBarText == null)
navBarText = System.getenv("APP_HOME_NAME"); navBarText = System.getenv("APP_HOME_NAME");
return (navBarText != null) ? navBarText : "Stirling PDF"; return (navBarText != null) ? navBarText : "Stirling PDF";
} }
} }

View file

@ -1,83 +1,83 @@
package stirling.software.SPDF.config; package stirling.software.SPDF.config;
import java.util.List; import java.util.List;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.ModelAndView;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.ModelAndView;
public class CleanUrlInterceptor implements HandlerInterceptor { public class CleanUrlInterceptor implements HandlerInterceptor {
private static final List<String> ALLOWED_PARAMS = Arrays.asList("lang", "endpoint", "endpoints", "logout", "error"); private static final List<String> ALLOWED_PARAMS = Arrays.asList("lang", "endpoint", "endpoints", "logout", "error");
@Override @Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception { throws Exception {
String queryString = request.getQueryString(); String queryString = request.getQueryString();
if (queryString != null && !queryString.isEmpty()) { if (queryString != null && !queryString.isEmpty()) {
String requestURI = request.getRequestURI(); String requestURI = request.getRequestURI();
Map<String, String> parameters = new HashMap<>(); Map<String, String> parameters = new HashMap<>();
// Keep only the allowed parameters // Keep only the allowed parameters
String[] queryParameters = queryString.split("&"); String[] queryParameters = queryString.split("&");
for (String param : queryParameters) { for (String param : queryParameters) {
String[] keyValue = param.split("="); String[] keyValue = param.split("=");
System.out.print("astirli " + keyValue[0]); System.out.print("astirli " + keyValue[0]);
if (keyValue.length != 2) { if (keyValue.length != 2) {
continue; continue;
} }
System.out.print("astirli2 " + keyValue[0]); System.out.print("astirli2 " + keyValue[0]);
if (ALLOWED_PARAMS.contains(keyValue[0])) { if (ALLOWED_PARAMS.contains(keyValue[0])) {
parameters.put(keyValue[0], keyValue[1]); parameters.put(keyValue[0], keyValue[1]);
} }
} }
// If there are any parameters that are not allowed // If there are any parameters that are not allowed
if (parameters.size() != queryParameters.length) { if (parameters.size() != queryParameters.length) {
// Construct new query string // Construct new query string
StringBuilder newQueryString = new StringBuilder(); StringBuilder newQueryString = new StringBuilder();
for (Map.Entry<String, String> entry : parameters.entrySet()) { for (Map.Entry<String, String> entry : parameters.entrySet()) {
if (newQueryString.length() > 0) { if (newQueryString.length() > 0) {
newQueryString.append("&"); newQueryString.append("&");
} }
newQueryString.append(entry.getKey()).append("=").append(entry.getValue()); newQueryString.append(entry.getKey()).append("=").append(entry.getValue());
} }
// Redirect to the URL with only allowed query parameters // Redirect to the URL with only allowed query parameters
String redirectUrl = requestURI + "?" + newQueryString; String redirectUrl = requestURI + "?" + newQueryString;
response.sendRedirect(redirectUrl); response.sendRedirect(redirectUrl);
return false; return false;
} }
} }
return true; return true;
} }
@Override @Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) { ModelAndView modelAndView) {
} }
@Override @Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
Exception ex) { Exception ex) {
} }
} }

View file

@ -1,26 +1,26 @@
package stirling.software.SPDF.config; package stirling.software.SPDF.config;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.HandlerInterceptor;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
@Component @Component
public class EndpointInterceptor implements HandlerInterceptor { public class EndpointInterceptor implements HandlerInterceptor {
@Autowired @Autowired
private EndpointConfiguration endpointConfiguration; private EndpointConfiguration endpointConfiguration;
@Override @Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception { throws Exception {
String requestURI = request.getRequestURI(); String requestURI = request.getRequestURI();
if (!endpointConfiguration.isEndpointEnabled(requestURI)) { if (!endpointConfiguration.isEndpointEnabled(requestURI)) {
response.sendError(HttpServletResponse.SC_FORBIDDEN, "This endpoint is disabled"); response.sendError(HttpServletResponse.SC_FORBIDDEN, "This endpoint is disabled");
return false; return false;
} }
return true; return true;
} }
} }

View file

@ -1,24 +1,24 @@
package stirling.software.SPDF.config; package stirling.software.SPDF.config;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import io.micrometer.core.instrument.Meter; import io.micrometer.core.instrument.Meter;
import io.micrometer.core.instrument.config.MeterFilter; import io.micrometer.core.instrument.config.MeterFilter;
import io.micrometer.core.instrument.config.MeterFilterReply; import io.micrometer.core.instrument.config.MeterFilterReply;
@Configuration @Configuration
public class MetricsConfig { public class MetricsConfig {
@Bean @Bean
public MeterFilter meterFilter() { public MeterFilter meterFilter() {
return new MeterFilter() { return new MeterFilter() {
@Override @Override
public MeterFilterReply accept(Meter.Id id) { public MeterFilterReply accept(Meter.Id id) {
if (id.getName().equals("http.requests")) { if (id.getName().equals("http.requests")) {
return MeterFilterReply.NEUTRAL; return MeterFilterReply.NEUTRAL;
} }
return MeterFilterReply.DENY; return MeterFilterReply.DENY;
} }
}; };
} }
} }

View file

@ -1,48 +1,48 @@
package stirling.software.SPDF.config; package stirling.software.SPDF.config;
import java.io.IOException; import java.io.IOException;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter; import org.springframework.web.filter.OncePerRequestFilter;
import io.micrometer.core.instrument.Counter; import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.MeterRegistry;
import jakarta.servlet.FilterChain; import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException; import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
@Component @Component
public class MetricsFilter extends OncePerRequestFilter { public class MetricsFilter extends OncePerRequestFilter {
private final MeterRegistry meterRegistry; private final MeterRegistry meterRegistry;
@Autowired @Autowired
public MetricsFilter(MeterRegistry meterRegistry) { public MetricsFilter(MeterRegistry meterRegistry) {
this.meterRegistry = meterRegistry; this.meterRegistry = meterRegistry;
} }
@Override @Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException { throws ServletException, IOException {
String uri = request.getRequestURI(); String uri = request.getRequestURI();
//System.out.println("uri="+uri + ", method=" + request.getMethod() ); //System.out.println("uri="+uri + ", method=" + request.getMethod() );
// Ignore static resources // Ignore static resources
if (!(uri.startsWith("/js") || uri.startsWith("api-docs") || uri.endsWith("robots.txt") || uri.startsWith("/images") || uri.endsWith(".png") || uri.endsWith(".ico") || uri.endsWith(".css") || uri.endsWith(".svg")|| uri.endsWith(".js") || uri.contains("swagger") || uri.startsWith("/api"))) { if (!(uri.startsWith("/js") || uri.startsWith("api-docs") || uri.endsWith("robots.txt") || uri.startsWith("/images") || uri.endsWith(".png") || uri.endsWith(".ico") || uri.endsWith(".css") || uri.endsWith(".svg")|| uri.endsWith(".js") || uri.contains("swagger") || uri.startsWith("/api"))) {
Counter counter = Counter.builder("http.requests") Counter counter = Counter.builder("http.requests")
.tag("uri", uri) .tag("uri", uri)
.tag("method", request.getMethod()) .tag("method", request.getMethod())
.register(meterRegistry); .register(meterRegistry);
counter.increment(); counter.increment();
//System.out.println("Counted"); //System.out.println("Counted");
} }
filterChain.doFilter(request, response); filterChain.doFilter(request, response);
} }
} }

View file

@ -1,36 +1,36 @@
package stirling.software.SPDF.config; package stirling.software.SPDF.config;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.Properties; import java.util.Properties;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import io.swagger.v3.oas.models.Components; import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Info; import io.swagger.v3.oas.models.info.Info;
@Configuration @Configuration
public class OpenApiConfig { public class OpenApiConfig {
@Bean @Bean
public OpenAPI customOpenAPI() { public OpenAPI customOpenAPI() {
String version = getClass().getPackage().getImplementationVersion(); String version = getClass().getPackage().getImplementationVersion();
if (version == null) { if (version == null) {
Properties props = new Properties(); Properties props = new Properties();
try (InputStream input = getClass().getClassLoader().getResourceAsStream("version.properties")) { try (InputStream input = getClass().getClassLoader().getResourceAsStream("version.properties")) {
props.load(input); props.load(input);
version = props.getProperty("version"); version = props.getProperty("version");
} catch (IOException ex) { } catch (IOException ex) {
ex.printStackTrace(); ex.printStackTrace();
version = "1.0.0"; // default version if all else fails version = "1.0.0"; // default version if all else fails
} }
} }
return new OpenAPI().components(new Components()).info( return new OpenAPI().components(new Components()).info(
new Info().title("Stirling PDF API").version(version).description("API documentation for all Server-Side processing.\nPlease note some functionality might be UI only and missing from here.")); new Info().title("Stirling PDF API").version(version).description("API documentation for all Server-Side processing.\nPlease note some functionality might be UI only and missing from here."));
} }
} }

View file

@ -1,20 +1,20 @@
package stirling.software.SPDF.config; package stirling.software.SPDF.config;
import org.springframework.context.ApplicationListener; import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent; import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.time.LocalDateTime; import java.time.LocalDateTime;
@Component @Component
public class StartupApplicationListener implements ApplicationListener<ContextRefreshedEvent> { public class StartupApplicationListener implements ApplicationListener<ContextRefreshedEvent> {
public static LocalDateTime startTime; public static LocalDateTime startTime;
@Override @Override
public void onApplicationEvent(ContextRefreshedEvent event) { public void onApplicationEvent(ContextRefreshedEvent event) {
startTime = LocalDateTime.now(); startTime = LocalDateTime.now();
} }
} }

View file

@ -0,0 +1,68 @@
package stirling.software.SPDF.config;
import java.io.IOException;
import java.time.Duration;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import io.github.bucket4j.Bandwidth;
import io.github.bucket4j.Bucket;
import io.github.bucket4j.Bucket4j;
import io.github.bucket4j.Refill;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
@Component
public class UserBasedRateLimitingFilter extends OncePerRequestFilter {
private final Map<String, Bucket> buckets = new ConcurrentHashMap<>();
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
String method = request.getMethod();
if (!"POST".equalsIgnoreCase(method)) {
filterChain.doFilter(request, response);
return;
}
String identifier;
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null && authentication.isAuthenticated()) {
UserDetails userDetails = (UserDetails) authentication.getPrincipal();
identifier = userDetails.getUsername();
} else {
identifier = request.getRemoteAddr(); // Use IP as identifier if not authenticated
}
Bucket userBucket = buckets.computeIfAbsent(identifier, k -> createUserBucket());
if (userBucket.tryConsume(1)) {
filterChain.doFilter(request, response);
} else {
response.setStatus(HttpStatus.TOO_MANY_REQUESTS.value());
response.getWriter().write("Rate limit exceeded.");
return;
}
}
//https://www.baeldung.com/spring-bucket4j
private Bucket createUserBucket() {
Refill refill = Refill.of(3, Duration.ofDays(1));
Bandwidth limit = Bandwidth.classic(3, refill).withInitialTokens(3);
return Bucket4j.builder().addLimit(limit).build();
}
}

View file

@ -1,27 +1,27 @@
package stirling.software.SPDF.config; package stirling.software.SPDF.config;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration @Configuration
public class WebMvcConfig implements WebMvcConfigurer { public class WebMvcConfig implements WebMvcConfigurer {
@Autowired @Autowired
private EndpointInterceptor endpointInterceptor; private EndpointInterceptor endpointInterceptor;
@Override @Override
public void addInterceptors(InterceptorRegistry registry) { public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(endpointInterceptor); registry.addInterceptor(endpointInterceptor);
} }
@Override @Override
public void addResourceHandlers(ResourceHandlerRegistry registry) { public void addResourceHandlers(ResourceHandlerRegistry registry) {
// Handler for external static resources // Handler for external static resources
registry.addResourceHandler("/**") registry.addResourceHandler("/**")
.addResourceLocations("file:customFiles/static/", "classpath:/static/") .addResourceLocations("file:customFiles/static/", "classpath:/static/")
.setCachePeriod(0); // Optional: disable caching .setCachePeriod(0); // Optional: disable caching
} }
} }