auditTest

This commit is contained in:
Anthony Stirling 2025-04-08 14:55:52 +01:00
parent 4d6f951604
commit 8e363f503e
8 changed files with 232 additions and 3 deletions

View File

@ -92,4 +92,4 @@ EXPOSE 8080/tcp
# Set user and run command
ENTRYPOINT ["tini", "--", "/scripts/init.sh"]
CMD ["sh", "-c", "java -Dfile.encoding=UTF-8 -jar /app.jar & /opt/venv/bin/unoserver --port 2003 --interface 0.0.0.0"]
CMD ["sh", "-c", "java -Dfile.encoding=UTF-8 -jar /app.jar & /opt/venv/bin/unoserver --port 2003 --interface 127.0.0.1"]

View File

@ -43,7 +43,12 @@ ENV DOCKER_ENABLE_SECURITY=false \
PYTHONPATH=/usr/lib/libreoffice/program:/opt/venv/lib/python3.12/site-packages \
UNO_PATH=/usr/lib/libreoffice/program \
URE_BOOTSTRAP=file:///usr/lib/libreoffice/program/fundamentalrc \
PATH=$PATH:/opt/venv/bin
PATH=$PATH:/opt/venv/bin \
LIBREOFFICE_HOME=/usr/lib/libreoffice \
SAL_USE_VCLPLUGIN=svp \
OOO_FORCE_DESKTOP=headless \
DISPLAY=:99 \
LD_LIBRARY_PATH=/usr/lib/libreoffice/program
# JDK for app
@ -53,6 +58,11 @@ RUN echo "@main https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /etc/a
apk upgrade --no-cache -a && \
apk add --no-cache \
ca-certificates \
libxcb libx11 libxtst libxi \
xvfb-run \
dbus \
cairo \
mesa-dri-gallium \
tzdata \
tini \
bash \
@ -101,4 +111,4 @@ RUN echo "@main https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /etc/a
EXPOSE 8080/tcp
# Set user and run command
ENTRYPOINT ["tini", "--", "/scripts/init.sh"]
CMD ["sh", "-c", "java -Dfile.encoding=UTF-8 -jar /app.jar & /opt/venv/bin/unoserver --port 2003 --interface 0.0.0.0"]
CMD ["sh", "-c", "java -Dfile.encoding=UTF-8 -jar /app.jar & /opt/venv/bin/unoserver --port 2003 --interface 127.0.0.1"]

View File

@ -429,6 +429,7 @@ dependencies {
// Exclude Tomcat and include Jetty
implementation("org.springframework.boot:spring-boot-starter-web:$springBootVersion")
implementation("org.springframework.boot:spring-boot-starter-aop:$springBootVersion")
implementation "org.springframework.boot:spring-boot-starter-jetty:$springBootVersion"
implementation "org.springframework.boot:spring-boot-starter-thymeleaf:$springBootVersion"

View File

@ -0,0 +1,10 @@
package stirling.software.SPDF.config.security;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@EnableAspectJAutoProxy
public class AopConfig {
// No body needed; annotation-driven
}

View File

@ -0,0 +1,91 @@
package stirling.software.SPDF.config.security;
import jakarta.servlet.http.HttpServletRequest;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import java.util.Map;
@Aspect
@Component
public class AuditAspect {
@Autowired
private AuditService auditService;
@Before(
"@annotation(RequestMapping) || @annotation(GetMapping) || @annotation(PostMapping) || " +
"@annotation(PutMapping) || @annotation(DeleteMapping) || @annotation(PatchMapping)"
)
public void logApiCall(JoinPoint joinPoint) {
try {
// Grab the current request
HttpServletRequest request =
((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
// Basic info
String method = request.getMethod();
String endpoint = request.getRequestURI();
Map<String, String[]> requestParams = request.getParameterMap();
String userAgent = request.getHeader("User-Agent");
String ipAddress = getClientIp(request);
String username = getCurrentUsername();
// Extract file metadata if multipart
MultipartFile[] files = extractFiles(request);
// Pass to the audit service
auditService.logRequest(
method,
endpoint,
requestParams,
userAgent,
ipAddress,
username,
files
);
} catch (Exception e) {
// Log and continue
e.printStackTrace();
}
}
private String getClientIp(HttpServletRequest request) {
// Some networks use X-Forwarded-For to pass client IP
String xForwardedFor = request.getHeader("X-Forwarded-For");
if (xForwardedFor != null && !xForwardedFor.isEmpty()) {
return xForwardedFor.split(",")[0];
}
return request.getRemoteAddr();
}
private String getCurrentUsername() {
// If using Spring Security
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth != null && auth.isAuthenticated()) {
return auth.getName();
}
return "anonymous";
}
private MultipartFile[] extractFiles(HttpServletRequest request) {
if (request instanceof MultipartHttpServletRequest multipartRequest) {
Map<String, MultipartFile> fileMap = multipartRequest.getFileMap();
if (!fileMap.isEmpty()) {
return fileMap.values().toArray(new MultipartFile[0]);
}
}
return new MultipartFile[0];
}
}

View File

@ -0,0 +1,75 @@
package stirling.software.SPDF.config.security;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.model.api.security.AuditLog;
import stirling.software.SPDF.repository.AuditLogRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.Map;
import java.util.stream.Collectors;
@Service
@Slf4j
public class AuditService {
@Autowired
private AuditLogRepository auditLogRepository;
@Autowired
private ObjectMapper objectMapper;
public void logRequest(
String method,
String endpoint,
Map<String, String[]> requestParams,
String userAgent,
String ipAddress,
String username,
MultipartFile[] files
) {
try {
// Build the AuditLog entity
AuditLog auditLog = new AuditLog();
auditLog.setMethod(method);
auditLog.setEndpoint(endpoint);
// Convert all text form fields (key -> array of values) to JSON for readability
String paramsJson = requestParams != null && !requestParams.isEmpty()
? objectMapper.writeValueAsString(requestParams)
: null;
auditLog.setRequestParams(paramsJson);
auditLog.setUserAgent(userAgent);
auditLog.setIpAddress(ipAddress);
auditLog.setUsername(username);
auditLog.setTimestamp(LocalDateTime.now());
// Only log metadata for files
if (files != null && files.length > 0) {
String fileNamesStr = Arrays.stream(files)
.map(file -> String.format(
"[name=%s, size=%d, type=%s]",
file.getOriginalFilename(),
file.getSize(),
file.getContentType()
))
.collect(Collectors.joining("; "));
auditLog.setFileNames(fileNamesStr);
}
log.info(auditLog.toString());
// Persist
auditLogRepository.save(auditLog);
} catch (Exception e) {
// Log error but do not disrupt the main request flow
e.printStackTrace();
}
}
}

View File

@ -0,0 +1,31 @@
package stirling.software.SPDF.model.api.security;
import jakarta.persistence.*;
import lombok.Data;
import java.time.LocalDateTime;
@Entity
@Table(name = "audit_logs")
@Data
public class AuditLog {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String method;
private String endpoint;
@Lob
private String requestParams; // text form params in JSON
private String userAgent;
private String ipAddress;
private String username;
private LocalDateTime timestamp;
// Store file metadata (comma or semicolon-separated)
@Lob
private String fileNames;
}

View File

@ -0,0 +1,11 @@
package stirling.software.SPDF.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import stirling.software.SPDF.model.api.security.AuditLog;
@Repository
public interface AuditLogRepository extends JpaRepository<AuditLog, Long> {
// Additional custom queries, if needed
}