Ajay Yadav commited on
Commit
688925d
·
1 Parent(s): 8fb8b38

Initial deployment of da-autocompliance-dev

Browse files
Files changed (46) hide show
  1. Dockerfile +27 -0
  2. README.md +34 -6
  3. build.gradle.kts +70 -0
  4. src/main/docker/Dockerfile +23 -0
  5. src/main/docker/Dockerfile.alpine-jlink +43 -0
  6. src/main/docker/Dockerfile.layered +34 -0
  7. src/main/docker/Dockerfile.native +20 -0
  8. src/main/java/com/dalab/autocompliance/DaAutocomplianceApplication.java +30 -0
  9. src/main/java/com/dalab/autocompliance/config/SpringSecurityAuditorAware.java +31 -0
  10. src/main/java/com/dalab/autocompliance/controller/ComplianceController.java +160 -0
  11. src/main/java/com/dalab/autocompliance/dto/AssetComplianceStatusDTO.java +44 -0
  12. src/main/java/com/dalab/autocompliance/dto/ComplianceControlDTO.java +29 -0
  13. src/main/java/com/dalab/autocompliance/dto/ComplianceReportDTO.java +59 -0
  14. src/main/java/com/dalab/autocompliance/dto/ComplianceReportDefinitionDTO.java +27 -0
  15. src/main/java/com/dalab/autocompliance/dto/ControlEvaluationRequestDTO.java +31 -0
  16. src/main/java/com/dalab/autocompliance/dto/ControlEvaluationResponseDTO.java +24 -0
  17. src/main/java/com/dalab/autocompliance/dto/ReportGenerationRequestDTO.java +27 -0
  18. src/main/java/com/dalab/autocompliance/dto/ReportGenerationResponseDTO.java +23 -0
  19. src/main/java/com/dalab/autocompliance/dto/ReportJobStatusDTO.java +27 -0
  20. src/main/java/com/dalab/autocompliance/model/entity/AbstractAuditableEntity.java +38 -0
  21. src/main/java/com/dalab/autocompliance/model/entity/ComplianceControlEntity.java +74 -0
  22. src/main/java/com/dalab/autocompliance/model/entity/ComplianceGeneratedReportEntity.java +59 -0
  23. src/main/java/com/dalab/autocompliance/model/entity/ComplianceReportDefinitionEntity.java +55 -0
  24. src/main/java/com/dalab/autocompliance/model/entity/ComplianceReportJobEntity.java +56 -0
  25. src/main/java/com/dalab/autocompliance/model/entity/ControlEvaluationJobEntity.java +67 -0
  26. src/main/java/com/dalab/autocompliance/model/entity/ReportFindingData.java +26 -0
  27. src/main/java/com/dalab/autocompliance/model/entity/ReportSummaryData.java +19 -0
  28. src/main/java/com/dalab/autocompliance/model/mapper/ComplianceControlMapper.java +22 -0
  29. src/main/java/com/dalab/autocompliance/model/mapper/ComplianceGeneratedReportMapper.java +46 -0
  30. src/main/java/com/dalab/autocompliance/model/mapper/ComplianceReportDefinitionMapper.java +25 -0
  31. src/main/java/com/dalab/autocompliance/model/mapper/ComplianceReportJobMapper.java +41 -0
  32. src/main/java/com/dalab/autocompliance/model/mapper/ControlEvaluationJobMapper.java +37 -0
  33. src/main/java/com/dalab/autocompliance/model/repository/ComplianceControlRepository.java +22 -0
  34. src/main/java/com/dalab/autocompliance/model/repository/ComplianceGeneratedReportRepository.java +28 -0
  35. src/main/java/com/dalab/autocompliance/model/repository/ComplianceReportDefinitionRepository.java +16 -0
  36. src/main/java/com/dalab/autocompliance/model/repository/ComplianceReportJobRepository.java +18 -0
  37. src/main/java/com/dalab/autocompliance/model/repository/ControlEvaluationJobRepository.java +19 -0
  38. src/main/java/com/dalab/autocompliance/service/IComplianceService.java +77 -0
  39. src/main/java/com/dalab/autocompliance/service/exception/ControlNotFoundException.java +15 -0
  40. src/main/java/com/dalab/autocompliance/service/exception/GeneratedReportNotFoundException.java +15 -0
  41. src/main/java/com/dalab/autocompliance/service/exception/ReportDefinitionNotFoundException.java +15 -0
  42. src/main/java/com/dalab/autocompliance/service/exception/ReportJobNotFoundException.java +15 -0
  43. src/main/java/com/dalab/autocompliance/service/impl/ComplianceServiceImpl.java +324 -0
  44. src/main/resources/application.properties +57 -0
  45. src/test/java/com/dalab/autocompliance/controller/ComplianceControllerTest.java +161 -0
  46. src/test/java/com/dalab/autocompliance/service/impl/ComplianceServiceImplTest.java +1 -0
Dockerfile ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM openjdk:21-jdk-slim
2
+
3
+ WORKDIR /app
4
+
5
+ # Install required packages
6
+ RUN apt-get update && apt-get install -y \
7
+ curl \
8
+ wget \
9
+ && rm -rf /var/lib/apt/lists/*
10
+
11
+ # Copy application files
12
+ COPY . .
13
+
14
+ # Build application (if build.gradle.kts exists)
15
+ RUN if [ -f "build.gradle.kts" ]; then \
16
+ ./gradlew build -x test; \
17
+ fi
18
+
19
+ # Expose port
20
+ EXPOSE 8080
21
+
22
+ # Health check
23
+ HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
24
+ CMD curl -f http://localhost:8080/actuator/health || exit 1
25
+
26
+ # Run application
27
+ CMD ["java", "-jar", "build/libs/da-autocompliance.jar"]
README.md CHANGED
@@ -1,10 +1,38 @@
1
  ---
2
- title: Da Autocompliance Dev
3
- emoji: 📉
4
- colorFrom: indigo
5
- colorTo: yellow
6
  sdk: docker
7
- pinned: false
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: da-autocompliance (dev)
3
+ emoji: 🔧
4
+ colorFrom: blue
5
+ colorTo: green
6
  sdk: docker
7
+ app_port: 8080
8
  ---
9
 
10
+ # da-autocompliance - dev Environment
11
+
12
+ This is the da-autocompliance microservice deployed in the dev environment.
13
+
14
+ ## Features
15
+
16
+ - RESTful API endpoints
17
+ - Health monitoring via Actuator
18
+ - JWT authentication integration
19
+ - PostgreSQL database connectivity
20
+
21
+ ## API Documentation
22
+
23
+ Once deployed, API documentation will be available at:
24
+ - Swagger UI: https://huggingface.co/spaces/dalabsai/da-autocompliance-dev/swagger-ui.html
25
+ - Health Check: https://huggingface.co/spaces/dalabsai/da-autocompliance-dev/actuator/health
26
+
27
+ ## Environment
28
+
29
+ - **Environment**: dev
30
+ - **Port**: 8080
31
+ - **Java Version**: 21
32
+ - **Framework**: Spring Boot
33
+
34
+ ## Deployment
35
+
36
+ This service is automatically deployed via the DALab CI/CD pipeline.
37
+
38
+ Last updated: 2025-06-16 23:40:18
build.gradle.kts ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ plugins {
2
+ id("org.springframework.boot") version "3.2.5"
3
+ id("io.spring.dependency-management") version "1.1.4"
4
+ java
5
+ }
6
+
7
+ group = "com.dalab"
8
+ version = "0.0.1-SNAPSHOT"
9
+
10
+ java {
11
+ sourceCompatibility = JavaVersion.VERSION_17
12
+ }
13
+
14
+ repositories {
15
+ mavenCentral()
16
+ }
17
+
18
+ dependencyManagement {
19
+ imports {
20
+ // Consider adding Spring Cloud BOM if needed for Feign, etc.
21
+ // mavenBom("org.springframework.cloud:spring-cloud-dependencies:YYYY.X.X")
22
+ }
23
+ }
24
+
25
+ val mapstructVersion = "1.5.5.Final"
26
+
27
+ dependencies {
28
+ // Common Spring Boot dependencies
29
+ implementation("org.springframework.boot:spring-boot-starter-actuator")
30
+ implementation("org.springframework.boot:spring-boot-starter-web")
31
+ implementation("org.springframework.boot:spring-boot-starter-validation")
32
+ implementation("org.springframework.boot:spring-boot-starter-security")
33
+ implementation("org.springframework.boot:spring-boot-starter-oauth2-resource-server")
34
+ implementation("org.springframework.boot:spring-boot-starter-oauth2-client")
35
+
36
+ // Shared proto dependency
37
+ implementation(project(":da-protos"))
38
+
39
+ // AutoCompliance-specific dependencies
40
+ implementation("org.springframework.boot:spring-boot-starter-data-jpa")
41
+ implementation("org.springframework.kafka:spring-kafka")
42
+ implementation("org.springdoc:springdoc-openapi-starter-webmvc-ui:2.2.0")
43
+
44
+ // MapStruct
45
+ implementation("org.mapstruct:mapstruct:$mapstructVersion")
46
+ annotationProcessor("org.mapstruct:mapstruct-processor:$mapstructVersion")
47
+ annotationProcessor("org.projectlombok:lombok-mapstruct-binding:0.2.0")
48
+
49
+ // Database
50
+ runtimeOnly("org.postgresql:postgresql")
51
+
52
+ // Common development and runtime dependencies
53
+ developmentOnly("org.springframework.boot:spring-boot-devtools")
54
+ annotationProcessor("org.springframework.boot:spring-boot-configuration-processor")
55
+
56
+ // Lombok
57
+ compileOnly("org.projectlombok:lombok")
58
+ annotationProcessor("org.projectlombok:lombok")
59
+
60
+ // Test dependencies
61
+ testImplementation("org.springframework.boot:spring-boot-starter-test") {
62
+ exclude(group = "org.junit.vintage", module = "junit-vintage-engine")
63
+ }
64
+ testImplementation("org.springframework.security:spring-security-test")
65
+ testImplementation("org.springframework.kafka:spring-kafka-test")
66
+ }
67
+
68
+ tasks.named<org.springframework.boot.gradle.tasks.bundling.BootJar>("bootJar") {
69
+ archiveFileName.set("${project.name}.jar")
70
+ }
src/main/docker/Dockerfile ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Ultra-lean container using Google Distroless
2
+ # Expected final size: ~120-180MB (minimal base + JRE + JAR only)
3
+
4
+ FROM gcr.io/distroless/java21-debian12:nonroot
5
+
6
+ # Set working directory
7
+ WORKDIR /app
8
+
9
+ # Copy JAR file
10
+ COPY build/libs/da-autocompliance.jar app.jar
11
+
12
+ # Expose standard Spring Boot port
13
+ EXPOSE 8080
14
+
15
+ # Run application (distroless has no shell, so use exec form)
16
+ ENTRYPOINT ["java", \
17
+ "-XX:+UseContainerSupport", \
18
+ "-XX:MaxRAMPercentage=75.0", \
19
+ "-XX:+UseG1GC", \
20
+ "-XX:+UseStringDeduplication", \
21
+ "-Djava.security.egd=file:/dev/./urandom", \
22
+ "-Dspring.backgroundpreinitializer.ignore=true", \
23
+ "-jar", "app.jar"]
src/main/docker/Dockerfile.alpine-jlink ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Ultra-minimal Alpine + Custom JRE
2
+ # Expected size: ~120-160MB
3
+
4
+ # Stage 1: Create custom JRE with only needed modules
5
+ FROM eclipse-temurin:21-jdk-alpine as jre-builder
6
+ WORKDIR /app
7
+
8
+ # Analyze JAR to find required modules
9
+ COPY build/libs/*.jar app.jar
10
+ RUN jdeps --ignore-missing-deps --print-module-deps app.jar > modules.txt
11
+
12
+ # Create minimal JRE with only required modules
13
+ RUN jlink \
14
+ --add-modules $(cat modules.txt),java.logging,java.xml,java.sql,java.naming,java.desktop,java.management,java.security.jgss,java.instrument \
15
+ --strip-debug \
16
+ --no-man-pages \
17
+ --no-header-files \
18
+ --compress=2 \
19
+ --output /custom-jre
20
+
21
+ # Stage 2: Production image
22
+ FROM alpine:3.19
23
+ RUN apk add --no-cache tzdata && \
24
+ addgroup -g 1001 -S appgroup && \
25
+ adduser -u 1001 -S appuser -G appgroup
26
+
27
+ # Copy custom JRE
28
+ COPY --from=jre-builder /custom-jre /opt/java
29
+ ENV JAVA_HOME=/opt/java
30
+ ENV PATH="$JAVA_HOME/bin:$PATH"
31
+
32
+ WORKDIR /app
33
+ COPY build/libs/*.jar app.jar
34
+ RUN chown appuser:appgroup app.jar
35
+
36
+ USER appuser
37
+ EXPOSE 8080
38
+
39
+ ENTRYPOINT ["java", \
40
+ "-XX:+UseContainerSupport", \
41
+ "-XX:MaxRAMPercentage=70.0", \
42
+ "-XX:+UseG1GC", \
43
+ "-jar", "app.jar"]
src/main/docker/Dockerfile.layered ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Ultra-optimized layered build using Distroless
2
+ # Expected size: ~180-220MB with better caching
3
+
4
+ FROM gcr.io/distroless/java21-debian12:nonroot as base
5
+
6
+ # Stage 1: Extract JAR layers for optimal caching
7
+ FROM eclipse-temurin:21-jdk-alpine as extractor
8
+ WORKDIR /app
9
+ COPY build/libs/*.jar app.jar
10
+ RUN java -Djarmode=layertools -jar app.jar extract
11
+
12
+ # Stage 2: Production image with extracted layers
13
+ FROM base
14
+ WORKDIR /app
15
+
16
+ # Copy layers in dependency order (best caching)
17
+ COPY --from=extractor /app/dependencies/ ./
18
+ COPY --from=extractor /app/spring-boot-loader/ ./
19
+ COPY --from=extractor /app/snapshot-dependencies/ ./
20
+ COPY --from=extractor /app/application/ ./
21
+
22
+ EXPOSE 8080
23
+
24
+ # Optimized JVM settings for micro-containers
25
+ ENTRYPOINT ["java", \
26
+ "-XX:+UseContainerSupport", \
27
+ "-XX:MaxRAMPercentage=70.0", \
28
+ "-XX:+UseG1GC", \
29
+ "-XX:+UseStringDeduplication", \
30
+ "-XX:+CompactStrings", \
31
+ "-Xshare:on", \
32
+ "-Djava.security.egd=file:/dev/./urandom", \
33
+ "-Dspring.backgroundpreinitializer.ignore=true", \
34
+ "org.springframework.boot.loader.JarLauncher"]
src/main/docker/Dockerfile.native ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # GraalVM Native Image - Ultra-fast startup, tiny size
2
+ # Expected size: ~50-80MB, startup <100ms
3
+ # Note: Requires native compilation support in Spring Boot
4
+
5
+ # Stage 1: Native compilation
6
+ FROM ghcr.io/graalvm/graalvm-ce:ol9-java21 as native-builder
7
+ WORKDIR /app
8
+
9
+ # Install native-image
10
+ RUN gu install native-image
11
+
12
+ # Copy source and build native executable
13
+ COPY . .
14
+ RUN ./gradlew nativeCompile
15
+
16
+ # Stage 2: Minimal runtime
17
+ FROM scratch
18
+ COPY --from=native-builder /app/build/native/nativeCompile/app /app
19
+ EXPOSE 8080
20
+ ENTRYPOINT ["/app"]
src/main/java/com/dalab/autocompliance/DaAutocomplianceApplication.java ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.dalab.autocompliance;
2
+
3
+ import io.swagger.v3.oas.models.OpenAPI;
4
+ import io.swagger.v3.oas.models.info.Info;
5
+ import io.swagger.v3.oas.models.info.License;
6
+ import org.springframework.boot.SpringApplication;
7
+ import org.springframework.boot.autoconfigure.SpringBootApplication;
8
+ import org.springframework.context.annotation.Bean;
9
+ import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
10
+ import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
11
+
12
+ @SpringBootApplication
13
+ @EnableMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
14
+ @EnableJpaAuditing(auditorAwareRef = "auditorProvider")
15
+ public class DaAutocomplianceApplication {
16
+
17
+ public static void main(String[] args) {
18
+ SpringApplication.run(DaAutocomplianceApplication.class, args);
19
+ }
20
+
21
+ @Bean
22
+ public OpenAPI customOpenAPI() {
23
+ return new OpenAPI()
24
+ .info(new Info().title("DALab AutoCompliance Service API")
25
+ .version("v1")
26
+ .description("API for managing compliance reports, checks, and asset compliance status.")
27
+ .termsOfService("http://swagger.io/terms/")
28
+ .license(new License().name("Apache 2.0").url("http://springdoc.org")));
29
+ }
30
+ }
src/main/java/com/dalab/autocompliance/config/SpringSecurityAuditorAware.java ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.dalab.autocompliance.config;
2
+
3
+ import org.springframework.data.domain.AuditorAware;
4
+ import org.springframework.security.core.Authentication;
5
+ import org.springframework.security.core.context.SecurityContextHolder;
6
+ import org.springframework.security.core.userdetails.User;
7
+ import org.springframework.stereotype.Component;
8
+
9
+ import java.util.Optional;
10
+
11
+ @Component("auditorProvider")
12
+ public class SpringSecurityAuditorAware implements AuditorAware<String> {
13
+
14
+ @Override
15
+ public Optional<String> getCurrentAuditor() {
16
+ Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
17
+
18
+ if (authentication == null || !authentication.isAuthenticated() || authentication.getPrincipal() instanceof String && "anonymousUser".equals(authentication.getPrincipal())) {
19
+ return Optional.of("system"); // Default to system if no user or anonymous
20
+ }
21
+
22
+ Object principal = authentication.getPrincipal();
23
+ if (principal instanceof User) {
24
+ return Optional.of(((User) principal).getUsername());
25
+ } else if (principal instanceof String) {
26
+ return Optional.of((String) principal);
27
+ }
28
+ // For other principal types, you might need custom logic or return a default
29
+ return Optional.of("system");
30
+ }
31
+ }
src/main/java/com/dalab/autocompliance/controller/ComplianceController.java ADDED
@@ -0,0 +1,160 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.dalab.autocompliance.controller;
2
+
3
+ import com.dalab.autocompliance.dto.ComplianceReportDefinitionDTO;
4
+ import com.dalab.autocompliance.dto.ReportGenerationRequestDTO;
5
+ import com.dalab.autocompliance.dto.ReportGenerationResponseDTO;
6
+ import com.dalab.autocompliance.dto.ReportJobStatusDTO;
7
+ import com.dalab.autocompliance.dto.ComplianceReportDTO;
8
+ import com.dalab.autocompliance.dto.AssetComplianceStatusDTO;
9
+ import com.dalab.autocompliance.dto.ComplianceControlDTO;
10
+ import com.dalab.autocompliance.dto.ControlEvaluationRequestDTO;
11
+ import com.dalab.autocompliance.dto.ControlEvaluationResponseDTO;
12
+ import com.dalab.autocompliance.service.IComplianceService;
13
+ import io.swagger.v3.oas.annotations.Operation;
14
+ import io.swagger.v3.oas.annotations.Parameter;
15
+ import io.swagger.v3.oas.annotations.responses.ApiResponse;
16
+ import io.swagger.v3.oas.annotations.tags.Tag;
17
+ import jakarta.validation.Valid;
18
+ import lombok.RequiredArgsConstructor;
19
+ import org.springframework.http.HttpStatus;
20
+ import org.springframework.http.ResponseEntity;
21
+ import org.springframework.security.access.prepost.PreAuthorize;
22
+ import org.springframework.web.bind.annotation.*;
23
+
24
+ import java.util.List;
25
+
26
+ @RestController
27
+ @RequestMapping("/api/v1/compliance")
28
+ @Tag(name = "Compliance API", description = "APIs for managing compliance checks, reports, and evaluations")
29
+ @RequiredArgsConstructor
30
+ public class ComplianceController {
31
+
32
+ private final IComplianceService complianceService;
33
+
34
+ @Operation(summary = "List available compliance report definitions",
35
+ description = "Provides a list of all configured compliance report types that can be generated.",
36
+ responses = {
37
+ @ApiResponse(responseCode = "200", description = "Successfully retrieved report definitions")
38
+ })
39
+ @GetMapping("/reports")
40
+ @PreAuthorize("hasAnyRole('ADMIN', 'DATA_STEWARD', 'USER', 'AUDITOR')") // Broad access to see what reports are available
41
+ public ResponseEntity<List<ComplianceReportDefinitionDTO>> listAvailableReportDefinitions() {
42
+ List<ComplianceReportDefinitionDTO> definitions = complianceService.listAvailableReportDefinitions();
43
+ return ResponseEntity.ok(definitions);
44
+ }
45
+
46
+ @Operation(summary = "Generate a compliance report",
47
+ description = "Triggers the asynchronous generation of a specific compliance report based on its type and provided parameters.",
48
+ responses = {
49
+ @ApiResponse(responseCode = "202", description = "Report generation request accepted."),
50
+ @ApiResponse(responseCode = "400", description = "Invalid request (e.g., missing parameters, report type not found)"),
51
+ @ApiResponse(responseCode = "404", description = "Report type not found") // Covered by 400 in current impl
52
+ })
53
+ @PostMapping("/reports/{reportType}/generate")
54
+ @PreAuthorize("hasAnyRole('ADMIN', 'DATA_STEWARD')") // Users who can trigger generation
55
+ public ResponseEntity<ReportGenerationResponseDTO> generateComplianceReport(
56
+ @Parameter(description = "Unique identifier of the report type to generate", required = true)
57
+ @PathVariable String reportType,
58
+ @Parameter(description = "Parameters required for the report generation", required = true)
59
+ @Valid @RequestBody ReportGenerationRequestDTO request) {
60
+
61
+ ReportGenerationResponseDTO response = complianceService.generateComplianceReport(reportType, request);
62
+
63
+ if ("FAILED_VALIDATION".equals(response.getStatus())) {
64
+ return ResponseEntity.badRequest().body(response);
65
+ }
66
+ return ResponseEntity.status(HttpStatus.ACCEPTED).body(response);
67
+ }
68
+
69
+ @Operation(summary = "Get compliance report generation job status",
70
+ description = "Retrieves the current status of an asynchronous report generation job.",
71
+ responses = {
72
+ @ApiResponse(responseCode = "200", description = "Successfully retrieved job status."),
73
+ @ApiResponse(responseCode = "404", description = "Job ID not found.")
74
+ })
75
+ @GetMapping("/reports/jobs/{jobId}")
76
+ @PreAuthorize("hasAnyRole('ADMIN', 'DATA_STEWARD', 'USER', 'AUDITOR')") // Users who can view job status
77
+ public ResponseEntity<ReportJobStatusDTO> getReportGenerationJobStatus(
78
+ @Parameter(description = "ID of the report generation job", required = true)
79
+ @PathVariable String jobId) {
80
+ ReportJobStatusDTO jobStatus = complianceService.getReportGenerationJobStatus(jobId);
81
+ if ("NOT_FOUND".equals(jobStatus.getStatus())) {
82
+ return ResponseEntity.status(HttpStatus.NOT_FOUND).body(jobStatus);
83
+ }
84
+ return ResponseEntity.ok(jobStatus);
85
+ }
86
+
87
+ @Operation(summary = "Retrieve a generated compliance report",
88
+ description = "Fetches the detailed contents of a previously generated compliance report.",
89
+ responses = {
90
+ @ApiResponse(responseCode = "200", description = "Successfully retrieved the report."),
91
+ @ApiResponse(responseCode = "404", description = "Report ID not found.")
92
+ })
93
+ @GetMapping("/reports/results/{reportId}")
94
+ @PreAuthorize("hasAnyRole('ADMIN', 'DATA_STEWARD', 'USER', 'AUDITOR')") // Users who can view reports
95
+ public ResponseEntity<ComplianceReportDTO> getGeneratedReport(
96
+ @Parameter(description = "ID of the generated compliance report", required = true)
97
+ @PathVariable String reportId) {
98
+ ComplianceReportDTO report = complianceService.getGeneratedReport(reportId);
99
+ if (report == null) {
100
+ return ResponseEntity.notFound().build();
101
+ }
102
+ return ResponseEntity.ok(report);
103
+ }
104
+
105
+ @Operation(summary = "Get compliance status for an asset",
106
+ description = "Retrieves the overall compliance status and recent findings for a specific asset.",
107
+ responses = {
108
+ @ApiResponse(responseCode = "200", description = "Successfully retrieved asset compliance status."),
109
+ @ApiResponse(responseCode = "404", description = "Asset ID not found or no compliance data available.")
110
+ })
111
+ @GetMapping("/assets/{assetId}/status")
112
+ @PreAuthorize("hasAnyRole('ADMIN', 'DATA_STEWARD', 'USER', 'AUDITOR')")
113
+ public ResponseEntity<AssetComplianceStatusDTO> getAssetComplianceStatus(
114
+ @Parameter(description = "ID of the asset (e.g., cloud resource ID)", required = true)
115
+ @PathVariable String assetId) {
116
+ AssetComplianceStatusDTO status = complianceService.getAssetComplianceStatus(assetId);
117
+ if ("UNKNOWN".equals(status.getOverallComplianceStatus()) && status.getRelevantReportIds() == null) { // Basic check for 'not found'
118
+ return ResponseEntity.status(HttpStatus.NOT_FOUND).body(status); // Provide status with UNKNOWN if created that way
119
+ }
120
+ return ResponseEntity.ok(status);
121
+ }
122
+
123
+ @Operation(summary = "List available compliance controls",
124
+ description = "Provides a list of all configured and typically enabled compliance controls that can be evaluated.",
125
+ responses = {
126
+ @ApiResponse(responseCode = "200", description = "Successfully retrieved compliance controls")
127
+ })
128
+ @GetMapping("/controls")
129
+ @PreAuthorize("hasAnyRole('ADMIN', 'DATA_STEWARD', 'USER', 'AUDITOR')") // Broad access to see available controls
130
+ public ResponseEntity<List<ComplianceControlDTO>> listAvailableControls() {
131
+ List<ComplianceControlDTO> controls = complianceService.listAvailableControls();
132
+ return ResponseEntity.ok(controls);
133
+ }
134
+
135
+ @Operation(summary = "Evaluate a compliance control",
136
+ description = "Triggers the asynchronous evaluation of a specific compliance control.",
137
+ responses = {
138
+ @ApiResponse(responseCode = "202", description = "Control evaluation request accepted."),
139
+ @ApiResponse(responseCode = "400", description = "Invalid request (e.g., missing parameters, control not enabled)"),
140
+ @ApiResponse(responseCode = "404", description = "Control ID not found")
141
+ })
142
+ @PostMapping("/controls/{controlId}/evaluate")
143
+ @PreAuthorize("hasAnyRole('ADMIN', 'DATA_STEWARD')") // Users who can trigger evaluations
144
+ public ResponseEntity<ControlEvaluationResponseDTO> evaluateControl(
145
+ @Parameter(description = "Unique identifier of the control to evaluate", required = true)
146
+ @PathVariable String controlId,
147
+ @Parameter(description = "Parameters for the control evaluation", required = true)
148
+ @Valid @RequestBody ControlEvaluationRequestDTO request) {
149
+
150
+ ControlEvaluationResponseDTO response = complianceService.evaluateControl(controlId, request);
151
+
152
+ if ("FAILED_VALIDATION".equals(response.getStatus())) {
153
+ return ResponseEntity.badRequest().body(response);
154
+ }
155
+ // Assuming ControlNotFoundException is handled by a global exception handler to return 404
156
+ return ResponseEntity.status(HttpStatus.ACCEPTED).body(response);
157
+ }
158
+
159
+ // TODO: Add endpoint for GET /controls/evaluations/{jobId} (status of a control evaluation job)
160
+ }
src/main/java/com/dalab/autocompliance/dto/AssetComplianceStatusDTO.java ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.dalab.autocompliance.dto;
2
+
3
+ import lombok.Builder;
4
+ import lombok.Data;
5
+ import lombok.NoArgsConstructor;
6
+ import lombok.AllArgsConstructor;
7
+
8
+ import java.time.LocalDateTime;
9
+ import java.util.List;
10
+ import java.util.Map;
11
+
12
+ /**
13
+ * DTO representing the compliance status of a specific asset.
14
+ */
15
+ @Data
16
+ @Builder
17
+ @NoArgsConstructor
18
+ @AllArgsConstructor
19
+ public class AssetComplianceStatusDTO {
20
+ private String assetId;
21
+ private String assetType; // e.g., GCP_COMPUTE_INSTANCE, AWS_S3_BUCKET
22
+ private String assetName; // User-friendly name if available
23
+ private String overallComplianceStatus; // e.g., COMPLIANT, NON_COMPLIANT, PARTIALLY_COMPLIANT, UNKNOWN
24
+ private LocalDateTime lastEvaluatedAt;
25
+ private int totalChecksApplied;
26
+ private int compliantChecks;
27
+ private int nonCompliantChecks;
28
+ private List<ComplianceFindingSummaryDTO> recentNonCompliantFindings; // Summaries of key issues
29
+
30
+ // Could also include links to full reports involving this asset
31
+ private List<String> relevantReportIds;
32
+
33
+ @Data
34
+ @Builder
35
+ @NoArgsConstructor
36
+ @AllArgsConstructor
37
+ public static class ComplianceFindingSummaryDTO {
38
+ private String checkId;
39
+ private String description;
40
+ private String severity;
41
+ private String reportId; // Report where this finding was detailed
42
+ private LocalDateTime findingTimestamp;
43
+ }
44
+ }
src/main/java/com/dalab/autocompliance/dto/ComplianceControlDTO.java ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.dalab.autocompliance.dto;
2
+
3
+ import lombok.Builder;
4
+ import lombok.Data;
5
+ import lombok.NoArgsConstructor;
6
+ import lombok.AllArgsConstructor;
7
+
8
+ import java.util.List;
9
+ import java.util.Map;
10
+
11
+ /**
12
+ * DTO representing a single, executable compliance control or check.
13
+ */
14
+ @Data
15
+ @Builder
16
+ @NoArgsConstructor
17
+ @AllArgsConstructor
18
+ public class ComplianceControlDTO {
19
+ private String controlId; // Unique identifier for the control (e.g., "CIS-GCP-1.1", "PCI-REQ-3.4")
20
+ private String name; // Short, human-readable name of the control
21
+ private String description; // Detailed description of what the control checks
22
+ private String severity; // e.g., CRITICAL, HIGH, MEDIUM, LOW, INFORMATIONAL
23
+ private List<String> applicableFrameworks; // e.g., ["CIS Benchmark v1.3", "PCI DSS v3.2.1"]
24
+ private List<String> targetAssetTypes; // Asset types this control applies to (e.g., "GCP_BUCKET", "AWS_IAM_USER")
25
+ private String remediationSteps; // Suggested steps to remediate a non-compliant finding
26
+ private Map<String, String> evaluationParametersDefinition; // Parameters needed to evaluate this control (name: type)
27
+ private String detailsLink; // Link to external documentation for this control
28
+ private boolean enabled; // Whether this control is currently active for evaluation
29
+ }
src/main/java/com/dalab/autocompliance/dto/ComplianceReportDTO.java ADDED
@@ -0,0 +1,59 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.dalab.autocompliance.dto;
2
+
3
+ import lombok.Builder;
4
+ import lombok.Data;
5
+ import lombok.NoArgsConstructor;
6
+ import lombok.AllArgsConstructor;
7
+
8
+ import java.time.LocalDateTime;
9
+ import java.util.List;
10
+ import java.util.Map;
11
+
12
+ /**
13
+ * DTO representing a generated compliance report.
14
+ */
15
+ @Data
16
+ @Builder
17
+ @NoArgsConstructor
18
+ @AllArgsConstructor
19
+ public class ComplianceReportDTO {
20
+ private String reportId; // Unique ID of this generated report instance
21
+ private String reportType; // e.g., "cis-gcp-foundations-v1.3"
22
+ private String displayName; // User-friendly name of the report type
23
+ private LocalDateTime generationTimestamp;
24
+ private String overallStatus; // e.g., COMPLIANT, NON_COMPLIANT, PARTIALLY_COMPLIANT, ERROR
25
+ private Map<String, Object> generationParameters; // Parameters used to generate this report
26
+
27
+ private SummaryDTO summary; // High-level summary statistics
28
+ private List<FindingDTO> findings; // Detailed list of findings
29
+
30
+ // Nested DTOs for structure
31
+ @Data
32
+ @Builder
33
+ @NoArgsConstructor
34
+ @AllArgsConstructor
35
+ public static class SummaryDTO {
36
+ private int totalChecks;
37
+ private int compliantChecks;
38
+ private int nonCompliantChecks;
39
+ private int errorChecks;
40
+ private double complianceScore; // e.g., percentage
41
+ }
42
+
43
+ @Data
44
+ @Builder
45
+ @NoArgsConstructor
46
+ @AllArgsConstructor
47
+ public static class FindingDTO {
48
+ private String checkId; // Unique identifier for the specific check/control
49
+ private String description;
50
+ private String assetId; // ID of the asset evaluated
51
+ private String assetType;
52
+ private String assetName; // User-friendly name of the asset
53
+ private String status; // COMPLIANT, NON_COMPLIANT, ERROR, NOT_APPLICABLE
54
+ private String severity; // INFO, LOW, MEDIUM, HIGH, CRITICAL
55
+ private String recommendation;
56
+ private Map<String, Object> details; // Specific details about the finding
57
+ private String evidenceLink; // Link to evidence if applicable
58
+ }
59
+ }
src/main/java/com/dalab/autocompliance/dto/ComplianceReportDefinitionDTO.java ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.dalab.autocompliance.dto;
2
+
3
+ import lombok.Builder;
4
+ import lombok.Data;
5
+ import lombok.NoArgsConstructor;
6
+ import lombok.AllArgsConstructor;
7
+
8
+ import java.util.List;
9
+ import java.util.Map;
10
+
11
+ /**
12
+ * DTO representing the definition of a compliance report type.
13
+ */
14
+ @Data
15
+ @Builder
16
+ @NoArgsConstructor
17
+ @AllArgsConstructor
18
+ public class ComplianceReportDefinitionDTO {
19
+ private String reportType; // Unique identifier for the report type (e.g., "pci-dss-q1", "cis-benchmark-gcp")
20
+ private String displayName; // User-friendly name (e.g., "PCI DSS Requirement 1 Check", "CIS Google Cloud Platform Foundation Benchmark")
21
+ private String description;
22
+ private String version; // Version of the report definition/benchmark
23
+ private List<String> applicableComplianceFrameworks; // e.g., ["PCI DSS v3.2.1", "HIPAA"]
24
+ private List<String> targetAssetTypes; // e.g., ["GCP_COMPUTE_INSTANCE", "AWS_S3_BUCKET"]
25
+ private Map<String, String> generationParameters; // Key-value pairs for parameters needed to generate this report (e.g., {"gcpProjectId": "string"})
26
+ private String detailsLink; // Link to more information about this compliance check/report
27
+ }
src/main/java/com/dalab/autocompliance/dto/ControlEvaluationRequestDTO.java ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.dalab.autocompliance.dto;
2
+
3
+ import lombok.Builder;
4
+ import lombok.Data;
5
+ import lombok.NoArgsConstructor;
6
+ import lombok.AllArgsConstructor;
7
+
8
+ import jakarta.validation.constraints.NotEmpty;
9
+ import java.util.List;
10
+ import java.util.Map;
11
+
12
+ /**
13
+ * DTO for requesting the evaluation of a specific compliance control.
14
+ */
15
+ @Data
16
+ @Builder
17
+ @NoArgsConstructor
18
+ @AllArgsConstructor
19
+ public class ControlEvaluationRequestDTO {
20
+
21
+ // Specific assets to target for this evaluation. If empty, might apply to all relevant assets.
22
+ private List<String> targetAssetIds;
23
+
24
+ // Override or provide specific parameters for this evaluation run for the control.
25
+ // These would align with or override those in ComplianceControlDTO.evaluationParametersDefinition.
26
+ private Map<String, Object> evaluationParameters;
27
+
28
+ private String triggeredBy; // Optional: User or system that triggered the evaluation
29
+ private String notificationEmail; // Optional: Email to notify upon completion/failure
30
+ private boolean forceReevaluation; // Optional: If true, re-evaluate even if recent results exist
31
+ }
src/main/java/com/dalab/autocompliance/dto/ControlEvaluationResponseDTO.java ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.dalab.autocompliance.dto;
2
+
3
+ import lombok.Builder;
4
+ import lombok.Data;
5
+ import lombok.NoArgsConstructor;
6
+ import lombok.AllArgsConstructor;
7
+
8
+ import java.time.LocalDateTime;
9
+
10
+ /**
11
+ * DTO representing the response after submitting a control evaluation request.
12
+ */
13
+ @Data
14
+ @Builder
15
+ @NoArgsConstructor
16
+ @AllArgsConstructor
17
+ public class ControlEvaluationResponseDTO {
18
+ private String jobId; // ID of the asynchronous evaluation job
19
+ private String controlId;
20
+ private String status; // e.g., ACCEPTED, FAILED_VALIDATION, QUEUED
21
+ private LocalDateTime submittedAt;
22
+ private String message; // e.g., "Control evaluation accepted and queued.", "Invalid parameters for control."
23
+ private int targetedAssetCount; // Number of assets targeted for this evaluation run
24
+ }
src/main/java/com/dalab/autocompliance/dto/ReportGenerationRequestDTO.java ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.dalab.autocompliance.dto;
2
+
3
+ import lombok.Builder;
4
+ import lombok.Data;
5
+ import lombok.NoArgsConstructor;
6
+ import lombok.AllArgsConstructor;
7
+
8
+ import jakarta.validation.constraints.NotEmpty;
9
+ import java.util.Map;
10
+
11
+ /**
12
+ * DTO for requesting the generation of a specific compliance report.
13
+ */
14
+ @Data
15
+ @Builder
16
+ @NoArgsConstructor
17
+ @AllArgsConstructor
18
+ public class ReportGenerationRequestDTO {
19
+
20
+ // Parameters required for this specific report type, matching those defined in ComplianceReportDefinitionDTO.generationParameters
21
+ // For example, {"gcpProjectId": "my-gcp-project", "targetRegions": ["us-central1", "us-east1"]}
22
+ @NotEmpty(message = "Generation parameters cannot be empty.")
23
+ private Map<String, Object> parameters;
24
+
25
+ private String triggeredBy; // Optional: User or system that triggered the generation
26
+ private String notificationEmail; // Optional: Email to notify upon completion/failure
27
+ }
src/main/java/com/dalab/autocompliance/dto/ReportGenerationResponseDTO.java ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.dalab.autocompliance.dto;
2
+
3
+ import lombok.Builder;
4
+ import lombok.Data;
5
+ import lombok.NoArgsConstructor;
6
+ import lombok.AllArgsConstructor;
7
+
8
+ import java.time.LocalDateTime;
9
+
10
+ /**
11
+ * DTO representing the response after submitting a report generation request.
12
+ */
13
+ @Data
14
+ @Builder
15
+ @NoArgsConstructor
16
+ @AllArgsConstructor
17
+ public class ReportGenerationResponseDTO {
18
+ private String jobId; // ID of the asynchronous generation job
19
+ private String reportType;
20
+ private String status; // e.g., ACCEPTED, FAILED_VALIDATION, QUEUED
21
+ private LocalDateTime submittedAt;
22
+ private String message; // e.g., "Report generation accepted and queued.", "Invalid parameters for report type."
23
+ }
src/main/java/com/dalab/autocompliance/dto/ReportJobStatusDTO.java ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.dalab.autocompliance.dto;
2
+
3
+ import lombok.Builder;
4
+ import lombok.Data;
5
+ import lombok.NoArgsConstructor;
6
+ import lombok.AllArgsConstructor;
7
+
8
+ import java.time.LocalDateTime;
9
+
10
+ /**
11
+ * DTO for representing the status of a compliance report generation job.
12
+ */
13
+ @Data
14
+ @Builder
15
+ @NoArgsConstructor
16
+ @AllArgsConstructor
17
+ public class ReportJobStatusDTO {
18
+ private String jobId;
19
+ private String reportType;
20
+ private String status; // e.g., QUEUED, PROCESSING, COMPLETED_SUCCESS, COMPLETED_FAILURE, NOT_FOUND
21
+ private LocalDateTime submittedAt;
22
+ private LocalDateTime startedAt;
23
+ private LocalDateTime completedAt;
24
+ private String message; // e.g., "Processing started", "Report generated successfully", "Failed due to: ..."
25
+ private String reportId; // ID of the generated report if completed successfully
26
+ private String downloadLink; // Link to download the report if applicable
27
+ }
src/main/java/com/dalab/autocompliance/model/entity/AbstractAuditableEntity.java ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.dalab.autocompliance.model.entity;
2
+
3
+ import jakarta.persistence.Column;
4
+ import jakarta.persistence.EntityListeners;
5
+ import jakarta.persistence.MappedSuperclass;
6
+ import lombok.Data;
7
+ import org.springframework.data.annotation.CreatedBy;
8
+ import org.springframework.data.annotation.CreatedDate;
9
+ import org.springframework.data.annotation.LastModifiedBy;
10
+ import org.springframework.data.annotation.LastModifiedDate;
11
+ import org.springframework.data.jpa.domain.support.AuditingEntityListener;
12
+
13
+ import java.io.Serializable;
14
+ import java.time.Instant;
15
+
16
+ @Data
17
+ @MappedSuperclass
18
+ @EntityListeners(AuditingEntityListener.class)
19
+ public abstract class AbstractAuditableEntity implements Serializable {
20
+
21
+ private static final long serialVersionUID = 1L;
22
+
23
+ @CreatedBy
24
+ @Column(name = "created_by", nullable = false, length = 50, updatable = false)
25
+ private String createdBy;
26
+
27
+ @CreatedDate
28
+ @Column(name = "created_date", nullable = false, updatable = false)
29
+ private Instant createdDate = Instant.now();
30
+
31
+ @LastModifiedBy
32
+ @Column(name = "last_modified_by", length = 50)
33
+ private String lastModifiedBy;
34
+
35
+ @LastModifiedDate
36
+ @Column(name = "last_modified_date")
37
+ private Instant lastModifiedDate = Instant.now();
38
+ }
src/main/java/com/dalab/autocompliance/model/entity/ComplianceControlEntity.java ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.dalab.autocompliance.model.entity;
2
+
3
+ import java.util.List;
4
+ import java.util.Map;
5
+
6
+ import org.hibernate.annotations.JdbcTypeCode;
7
+ import org.hibernate.type.SqlTypes;
8
+
9
+ import jakarta.persistence.CollectionTable;
10
+ import jakarta.persistence.Column;
11
+ import jakarta.persistence.ElementCollection;
12
+ import jakarta.persistence.Entity;
13
+ import jakarta.persistence.FetchType;
14
+ import jakarta.persistence.Id;
15
+ import jakarta.persistence.Index;
16
+ import jakarta.persistence.JoinColumn;
17
+ import jakarta.persistence.Lob;
18
+ import jakarta.persistence.Table;
19
+ import lombok.AllArgsConstructor;
20
+ import lombok.Builder;
21
+ import lombok.Data;
22
+ import lombok.EqualsAndHashCode;
23
+ import lombok.NoArgsConstructor;
24
+
25
+ @Entity
26
+ @Table(name = "compliance_controls", indexes = {
27
+ @Index(name = "idx_control_severity", columnList = "severity"),
28
+ @Index(name = "idx_control_enabled", columnList = "enabled")
29
+ })
30
+ @Data
31
+ @EqualsAndHashCode(callSuper = true)
32
+ @NoArgsConstructor
33
+ @AllArgsConstructor
34
+ @Builder
35
+ public class ComplianceControlEntity extends AbstractAuditableEntity {
36
+
37
+ @Id
38
+ @Column(nullable = false, unique = true, length = 100)
39
+ private String controlId;
40
+
41
+ @Column(nullable = false, length = 255)
42
+ private String name;
43
+
44
+ @Lob
45
+ @JdbcTypeCode(SqlTypes.LONGVARCHAR)
46
+ private String description;
47
+
48
+ @Column(length = 50)
49
+ private String severity;
50
+
51
+ @ElementCollection(fetch = FetchType.EAGER)
52
+ @CollectionTable(name = "control_frameworks", joinColumns = @JoinColumn(name = "control_id"))
53
+ @Column(name = "framework_name")
54
+ private List<String> applicableFrameworks;
55
+
56
+ @ElementCollection(fetch = FetchType.EAGER)
57
+ @CollectionTable(name = "control_asset_types", joinColumns = @JoinColumn(name = "control_id"))
58
+ @Column(name = "asset_type")
59
+ private List<String> targetAssetTypes;
60
+
61
+ @Lob
62
+ @JdbcTypeCode(SqlTypes.LONGVARCHAR)
63
+ private String remediationSteps;
64
+
65
+ @JdbcTypeCode(SqlTypes.JSON)
66
+ @Column(columnDefinition = "jsonb")
67
+ private Map<String, String> evaluationParametersDefinition;
68
+
69
+ @Column(length = 512)
70
+ private String detailsLink;
71
+
72
+ @Builder.Default
73
+ private boolean enabled = true;
74
+ }
src/main/java/com/dalab/autocompliance/model/entity/ComplianceGeneratedReportEntity.java ADDED
@@ -0,0 +1,59 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.dalab.autocompliance.model.entity;
2
+
3
+ import jakarta.persistence.*;
4
+ import lombok.Data;
5
+ import lombok.EqualsAndHashCode;
6
+ import lombok.NoArgsConstructor;
7
+ import lombok.AllArgsConstructor;
8
+ import lombok.Builder;
9
+ import org.hibernate.annotations.JdbcTypeCode;
10
+ import org.hibernate.type.SqlTypes;
11
+
12
+ import java.time.LocalDateTime;
13
+ import java.util.List;
14
+ import java.util.Map;
15
+
16
+ @Entity
17
+ @Table(name = "compliance_generated_reports")
18
+ @Data
19
+ @EqualsAndHashCode(callSuper = true)
20
+ @NoArgsConstructor
21
+ @AllArgsConstructor
22
+ @Builder
23
+ public class ComplianceGeneratedReportEntity extends AbstractAuditableEntity {
24
+
25
+ @Id
26
+ @Column(length = 36) // UUID length
27
+ private String reportId;
28
+
29
+ @Column(nullable = false, length = 100)
30
+ private String reportType; // Corresponds to ComplianceReportDefinitionEntity.reportType
31
+
32
+ @Column(length = 255)
33
+ private String displayName; // User-friendly name of the report type at the time of generation
34
+
35
+ @Column(nullable = false)
36
+ private LocalDateTime generationTimestamp;
37
+
38
+ @Column(length = 50)
39
+ private String overallStatus; // e.g., COMPLIANT, NON_COMPLIANT, PARTIALLY_COMPLIANT, ERROR
40
+
41
+ @JdbcTypeCode(SqlTypes.JSON)
42
+ @Column(columnDefinition = "jsonb")
43
+ private Map<String, Object> generationParameters; // Parameters used to generate this specific report instance
44
+
45
+ // Storing summary and findings as JSONB.
46
+ // If specific fields within these need to be frequently queried, consider normalizing them.
47
+ @JdbcTypeCode(SqlTypes.JSON)
48
+ @Column(columnDefinition = "jsonb")
49
+ private ReportSummaryData summaryData; // Corresponds to ComplianceReportDTO.SummaryDTO
50
+
51
+ @JdbcTypeCode(SqlTypes.JSON)
52
+ @Column(columnDefinition = "jsonb")
53
+ private List<ReportFindingData> findingsData; // Corresponds to List<ComplianceReportDTO.FindingDTO>
54
+
55
+ // Link back to the job that generated this report
56
+ @OneToOne(fetch = FetchType.LAZY)
57
+ @JoinColumn(name = "job_id", referencedColumnName = "jobId")
58
+ private ComplianceReportJobEntity reportJob;
59
+ }
src/main/java/com/dalab/autocompliance/model/entity/ComplianceReportDefinitionEntity.java ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.dalab.autocompliance.model.entity;
2
+
3
+ import jakarta.persistence.*;
4
+ import lombok.Data;
5
+ import lombok.EqualsAndHashCode;
6
+ import lombok.NoArgsConstructor;
7
+ import lombok.AllArgsConstructor;
8
+ import lombok.Builder;
9
+ import org.hibernate.annotations.JdbcTypeCode;
10
+ import org.hibernate.type.SqlTypes;
11
+
12
+ import java.util.List;
13
+ import java.util.Map;
14
+
15
+ @Entity
16
+ @Table(name = "compliance_report_definitions")
17
+ @Data
18
+ @EqualsAndHashCode(callSuper = true)
19
+ @NoArgsConstructor
20
+ @AllArgsConstructor
21
+ @Builder
22
+ public class ComplianceReportDefinitionEntity extends AbstractAuditableEntity {
23
+
24
+ @Id
25
+ @Column(nullable = false, unique = true, length = 100)
26
+ private String reportType; // e.g., "cis-gcp-foundations-v1.3"
27
+
28
+ @Column(nullable = false, length = 255)
29
+ private String displayName;
30
+
31
+ @Lob
32
+ @JdbcTypeCode(SqlTypes.LONGVARCHAR)
33
+ private String description;
34
+
35
+ @Column(length = 50)
36
+ private String version;
37
+
38
+ @ElementCollection(fetch = FetchType.EAGER)
39
+ @CollectionTable(name = "report_def_frameworks", joinColumns = @JoinColumn(name = "report_type"))
40
+ @Column(name = "framework_name")
41
+ private List<String> applicableComplianceFrameworks;
42
+
43
+ @ElementCollection(fetch = FetchType.EAGER)
44
+ @CollectionTable(name = "report_def_asset_types", joinColumns = @JoinColumn(name = "report_type"))
45
+ @Column(name = "asset_type")
46
+ private List<String> targetAssetTypes;
47
+
48
+ // Storing parameter definitions (name and type, e.g., "gcpProjectId": "string")
49
+ @JdbcTypeCode(SqlTypes.JSON)
50
+ @Column(columnDefinition = "jsonb")
51
+ private Map<String, String> generationParametersDefinition; // Renamed for clarity from DTO's 'generationParameters'
52
+
53
+ @Column(length = 512)
54
+ private String detailsLink;
55
+ }
src/main/java/com/dalab/autocompliance/model/entity/ComplianceReportJobEntity.java ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.dalab.autocompliance.model.entity;
2
+
3
+ import jakarta.persistence.*;
4
+ import lombok.Data;
5
+ import lombok.EqualsAndHashCode;
6
+ import lombok.NoArgsConstructor;
7
+ import lombok.AllArgsConstructor;
8
+ import lombok.Builder;
9
+
10
+ import java.time.LocalDateTime;
11
+ import java.util.Map;
12
+
13
+ @Entity
14
+ @Table(name = "compliance_report_jobs")
15
+ @Data
16
+ @EqualsAndHashCode(callSuper = true)
17
+ @NoArgsConstructor
18
+ @AllArgsConstructor
19
+ @Builder
20
+ public class ComplianceReportJobEntity extends AbstractAuditableEntity {
21
+
22
+ @Id
23
+ @Column(length = 36) // UUID length
24
+ private String jobId;
25
+
26
+ @Column(nullable = false, length = 100)
27
+ private String reportType; // Corresponds to ComplianceReportDefinitionEntity.reportType
28
+
29
+ @Column(nullable = false, length = 50)
30
+ private String status; // e.g., QUEUED, PROCESSING, COMPLETED_SUCCESS, COMPLETED_FAILURE
31
+
32
+ private LocalDateTime submittedAt;
33
+ private LocalDateTime startedAt;
34
+ private LocalDateTime completedAt;
35
+
36
+ @Lob
37
+ @Basic(fetch = FetchType.LAZY) // Message could be long, fetch lazily
38
+ private String message;
39
+
40
+ @Column(length = 36) // UUID length, if report is generated
41
+ private String generatedReportId; // Links to ComplianceGeneratedReportEntity.reportId
42
+
43
+ // Parameters used for this specific job instance.
44
+ // In DTO this was Map<String, Object>. For DB, JSON is suitable.
45
+ // Consider if complex objects need to be queryable. If so, separate columns or normalized tables might be better.
46
+ // For now, storing as JSONB is a flexible approach.
47
+ @org.hibernate.annotations.JdbcTypeCode(org.hibernate.type.SqlTypes.JSON)
48
+ @Column(columnDefinition = "jsonb")
49
+ private Map<String, Object> generationParameters;
50
+
51
+ @Column(length = 255)
52
+ private String triggeredBy; // User or system that triggered it
53
+
54
+ @Column(length = 255)
55
+ private String notificationEmail; // Email for notification
56
+ }
src/main/java/com/dalab/autocompliance/model/entity/ControlEvaluationJobEntity.java ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.dalab.autocompliance.model.entity;
2
+
3
+ import jakarta.persistence.*;
4
+ import lombok.Data;
5
+ import lombok.EqualsAndHashCode;
6
+ import lombok.NoArgsConstructor;
7
+ import lombok.AllArgsConstructor;
8
+ import lombok.Builder;
9
+ import org.hibernate.annotations.JdbcTypeCode;
10
+ import org.hibernate.type.SqlTypes;
11
+
12
+ import java.time.LocalDateTime;
13
+ import java.util.List;
14
+ import java.util.Map;
15
+
16
+ @Entity
17
+ @Table(name = "compliance_control_evaluation_jobs", indexes = {
18
+ @Index(name = "idx_ctrl_eval_job_control_id", columnList = "controlId"),
19
+ @Index(name = "idx_ctrl_eval_job_status", columnList = "status")
20
+ })
21
+ @Data
22
+ @EqualsAndHashCode(callSuper = true)
23
+ @NoArgsConstructor
24
+ @AllArgsConstructor
25
+ @Builder
26
+ public class ControlEvaluationJobEntity extends AbstractAuditableEntity {
27
+
28
+ @Id
29
+ @Column(length = 36) // UUID length
30
+ private String jobId;
31
+
32
+ @Column(nullable = false, length = 100)
33
+ private String controlId; // Foreign key to ComplianceControlEntity.controlId
34
+
35
+ @Column(nullable = false, length = 50)
36
+ private String status; // e.g., QUEUED, PROCESSING, COMPLETED_SUCCESS, COMPLETED_FAILURE, PARTIAL_FAILURE
37
+
38
+ private LocalDateTime submittedAt;
39
+ private LocalDateTime startedAt;
40
+ private LocalDateTime completedAt;
41
+
42
+ @Lob
43
+ @Basic(fetch = FetchType.LAZY)
44
+ private String message; // Overall message for the job execution
45
+
46
+ @ElementCollection(fetch = FetchType.LAZY) // Store targeted asset IDs
47
+ @CollectionTable(name = "control_eval_job_target_assets", joinColumns = @JoinColumn(name = "job_id"))
48
+ @Column(name = "asset_id")
49
+ private List<String> targetAssetIds;
50
+
51
+ @JdbcTypeCode(SqlTypes.JSON)
52
+ @Column(columnDefinition = "jsonb")
53
+ private Map<String, Object> evaluationParameters; // Parameters used for this specific evaluation run
54
+
55
+ private boolean forceReevaluation;
56
+
57
+ @Column(length = 255)
58
+ private String triggeredBy;
59
+
60
+ @Column(length = 255)
61
+ private String notificationEmail;
62
+
63
+ // In a more detailed model, you might have a list of ControlEvaluationResultEntity linked here.
64
+ // For now, the results (findings) would likely be new entries in the main GeneratedReport or a similar table,
65
+ // or a dedicated ControlFindingEntity. This part needs further thought based on how results are stored/queried.
66
+ // For simplicity, this job entity primarily tracks the execution.
67
+ }
src/main/java/com/dalab/autocompliance/model/entity/ReportFindingData.java ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.dalab.autocompliance.model.entity;
2
+
3
+ import lombok.Data;
4
+ import lombok.NoArgsConstructor;
5
+ import lombok.AllArgsConstructor;
6
+ import lombok.Builder;
7
+
8
+ import java.util.Map;
9
+
10
+ // This is a simple POJO, not an entity. Used for JSONB mapping.
11
+ @Data
12
+ @Builder
13
+ @NoArgsConstructor
14
+ @AllArgsConstructor
15
+ public class ReportFindingData {
16
+ private String checkId;
17
+ private String description;
18
+ private String assetId;
19
+ private String assetType;
20
+ private String assetName;
21
+ private String status;
22
+ private String severity;
23
+ private String recommendation;
24
+ private Map<String, Object> details;
25
+ private String evidenceLink;
26
+ }
src/main/java/com/dalab/autocompliance/model/entity/ReportSummaryData.java ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.dalab.autocompliance.model.entity;
2
+
3
+ import lombok.Data;
4
+ import lombok.NoArgsConstructor;
5
+ import lombok.AllArgsConstructor;
6
+ import lombok.Builder;
7
+
8
+ // This is a simple POJO, not an entity. Used for JSONB mapping.
9
+ @Data
10
+ @Builder
11
+ @NoArgsConstructor
12
+ @AllArgsConstructor
13
+ public class ReportSummaryData {
14
+ private int totalChecks;
15
+ private int compliantChecks;
16
+ private int nonCompliantChecks;
17
+ private int errorChecks;
18
+ private double complianceScore;
19
+ }
src/main/java/com/dalab/autocompliance/model/mapper/ComplianceControlMapper.java ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.dalab.autocompliance.model.mapper;
2
+
3
+ import com.dalab.autocompliance.dto.ComplianceControlDTO;
4
+ import com.dalab.autocompliance.model.entity.ComplianceControlEntity;
5
+ import org.mapstruct.Mapper;
6
+ import org.mapstruct.factory.Mappers;
7
+
8
+ import java.util.List;
9
+
10
+ @Mapper(componentModel = "spring")
11
+ public interface ComplianceControlMapper {
12
+
13
+ ComplianceControlMapper INSTANCE = Mappers.getMapper(ComplianceControlMapper.class);
14
+
15
+ ComplianceControlDTO toDto(ComplianceControlEntity entity);
16
+
17
+ ComplianceControlEntity toEntity(ComplianceControlDTO dto);
18
+
19
+ List<ComplianceControlDTO> toDtoList(List<ComplianceControlEntity> entities);
20
+
21
+ List<ComplianceControlEntity> toEntityList(List<ComplianceControlDTO> dtos);
22
+ }
src/main/java/com/dalab/autocompliance/model/mapper/ComplianceGeneratedReportMapper.java ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.dalab.autocompliance.model.mapper;
2
+
3
+ import java.util.List;
4
+
5
+ import org.mapstruct.Mapper;
6
+ import org.mapstruct.Mapping;
7
+ import org.mapstruct.factory.Mappers;
8
+
9
+ import com.dalab.autocompliance.dto.ComplianceReportDTO;
10
+ import com.dalab.autocompliance.model.entity.ComplianceGeneratedReportEntity;
11
+ import com.dalab.autocompliance.model.entity.ReportFindingData;
12
+ import com.dalab.autocompliance.model.entity.ReportSummaryData;
13
+
14
+ /**
15
+ * Mapper interface for converting between ComplianceGeneratedReportEntity and DTOs.
16
+ */
17
+ @Mapper(componentModel = "spring")
18
+ public interface ComplianceGeneratedReportMapper {
19
+
20
+ ComplianceGeneratedReportMapper INSTANCE = Mappers.getMapper(ComplianceGeneratedReportMapper.class);
21
+
22
+ // Entity to DTO
23
+ @Mapping(source = "summaryData", target = "summary")
24
+ @Mapping(source = "findingsData", target = "findings")
25
+ ComplianceReportDTO toDto(ComplianceGeneratedReportEntity entity);
26
+
27
+ List<ComplianceReportDTO> toDtoList(List<ComplianceGeneratedReportEntity> entities);
28
+
29
+ // DTO to Entity
30
+ @Mapping(source = "summary", target = "summaryData")
31
+ @Mapping(source = "findings", target = "findingsData")
32
+ @Mapping(target = "reportJob", ignore = true) // Typically set separately or via jobId
33
+ ComplianceGeneratedReportEntity toEntity(ComplianceReportDTO dto);
34
+
35
+ // Mappings for nested POJOs (if MapStruct doesn't handle them automatically, which it often does)
36
+ // These might not be strictly necessary if names and types match and MapStruct can infer them.
37
+ ReportSummaryData toReportSummaryData(ComplianceReportDTO.SummaryDTO summaryDTO);
38
+ ComplianceReportDTO.SummaryDTO toSummaryDTO(ReportSummaryData reportSummaryData);
39
+
40
+ List<ReportFindingData> toReportFindingDataList(List<ComplianceReportDTO.FindingDTO> findingDTOs);
41
+ List<ComplianceReportDTO.FindingDTO> toFindingDTOList(List<ReportFindingData> reportFindingDataList);
42
+
43
+ ReportFindingData toReportFindingData(ComplianceReportDTO.FindingDTO findingDTO);
44
+ ComplianceReportDTO.FindingDTO toFindingDTO(ReportFindingData reportFindingData);
45
+
46
+ }
src/main/java/com/dalab/autocompliance/model/mapper/ComplianceReportDefinitionMapper.java ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.dalab.autocompliance.model.mapper;
2
+
3
+ import com.dalab.autocompliance.dto.ComplianceReportDefinitionDTO;
4
+ import com.dalab.autocompliance.model.entity.ComplianceReportDefinitionEntity;
5
+ import org.mapstruct.Mapper;
6
+ import org.mapstruct.Mapping;
7
+ import org.mapstruct.factory.Mappers;
8
+
9
+ import java.util.List;
10
+
11
+ @Mapper(componentModel = "spring")
12
+ public interface ComplianceReportDefinitionMapper {
13
+
14
+ ComplianceReportDefinitionMapper INSTANCE = Mappers.getMapper(ComplianceReportDefinitionMapper.class);
15
+
16
+ @Mapping(source = "generationParametersDefinition", target = "generationParameters")
17
+ ComplianceReportDefinitionDTO toDto(ComplianceReportDefinitionEntity entity);
18
+
19
+ @Mapping(source = "generationParameters", target = "generationParametersDefinition")
20
+ ComplianceReportDefinitionEntity toEntity(ComplianceReportDefinitionDTO dto);
21
+
22
+ List<ComplianceReportDefinitionDTO> toDtoList(List<ComplianceReportDefinitionEntity> entities);
23
+
24
+ List<ComplianceReportDefinitionEntity> toEntityList(List<ComplianceReportDefinitionDTO> dtos);
25
+ }
src/main/java/com/dalab/autocompliance/model/mapper/ComplianceReportJobMapper.java ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.dalab.autocompliance.model.mapper;
2
+
3
+ import com.dalab.autocompliance.dto.ReportJobStatusDTO;
4
+ import com.dalab.autocompliance.dto.ReportGenerationRequestDTO; // For mapping parameters
5
+ import com.dalab.autocompliance.model.entity.ComplianceReportJobEntity;
6
+ import org.mapstruct.Mapper;
7
+ import org.mapstruct.Mapping;
8
+ import org.mapstruct.Named;
9
+ import org.mapstruct.factory.Mappers;
10
+
11
+ import java.util.Map;
12
+ import java.util.List;
13
+
14
+ /**
15
+ * Mapper interface for converting between ComplianceReportJobEntity and DTOs.
16
+ */
17
+ @Mapper
18
+ public interface ComplianceReportJobMapper {
19
+
20
+ ComplianceReportJobMapper INSTANCE = Mappers.getMapper(ComplianceReportJobMapper.class);
21
+
22
+ // From Entity to DTO (ReportJobStatusDTO)
23
+ @Mapping(target = "reportId", ignore = true)
24
+ @Mapping(target = "downloadLink", ignore = true)
25
+ ReportJobStatusDTO toDto(ComplianceReportJobEntity entity);
26
+
27
+ List<ReportJobStatusDTO> toDtoList(List<ComplianceReportJobEntity> entities);
28
+
29
+ // From DTO (ReportGenerationRequestDTO for submission) to Entity
30
+ // Note: JobId will be set in service. Status will be set. reportId will be null initially.
31
+ @Mapping(source = "dto.parameters", target = "generationParameters") // Map<String, Object> to Map<String, Object>
32
+ @Mapping(target = "jobId", ignore = true) // Set by service
33
+ @Mapping(target = "status", ignore = true) // Set by service
34
+ @Mapping(target = "submittedAt", ignore = true) // Set by service or DB default
35
+ @Mapping(target = "startedAt", ignore = true)
36
+ @Mapping(target = "completedAt", ignore = true)
37
+ @Mapping(target = "message", ignore = true)
38
+ @Mapping(target = "generatedReportId", ignore = true)
39
+ ComplianceReportJobEntity fromGenerationRequest(ReportGenerationRequestDTO dto, String reportType);
40
+
41
+ }
src/main/java/com/dalab/autocompliance/model/mapper/ControlEvaluationJobMapper.java ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.dalab.autocompliance.model.mapper;
2
+
3
+ import java.time.LocalDateTime;
4
+ import java.util.UUID;
5
+
6
+ import org.mapstruct.Mapper;
7
+ import org.mapstruct.Mapping;
8
+ import org.mapstruct.factory.Mappers;
9
+
10
+ import com.dalab.autocompliance.dto.ControlEvaluationRequestDTO;
11
+ import com.dalab.autocompliance.dto.ControlEvaluationResponseDTO;
12
+ import com.dalab.autocompliance.model.entity.ControlEvaluationJobEntity;
13
+
14
+ /**
15
+ * Mapper interface for converting between ControlEvaluationJobEntity and DTOs.
16
+ */
17
+ @Mapper(imports = {UUID.class, LocalDateTime.class})
18
+ public interface ControlEvaluationJobMapper {
19
+
20
+ ControlEvaluationJobMapper INSTANCE = Mappers.getMapper(ControlEvaluationJobMapper.class);
21
+
22
+ // To DTO (ControlEvaluationResponseDTO is simpler, doesn't carry all entity details by default)
23
+ @Mapping(target = "targetedAssetCount", expression = "java(entity.getTargetAssetIds() != null ? entity.getTargetAssetIds().size() : 0)")
24
+ ControlEvaluationResponseDTO toResponseDto(ControlEvaluationJobEntity entity);
25
+
26
+ // From Request DTO to Entity
27
+ @Mapping(target = "jobId", expression = "java(UUID.randomUUID().toString())")
28
+ @Mapping(target = "submittedAt", expression = "java(LocalDateTime.now())")
29
+ @Mapping(target = "status", constant = "QUEUED")
30
+ @Mapping(target = "startedAt", ignore = true)
31
+ @Mapping(target = "completedAt", ignore = true)
32
+ @Mapping(target = "message", ignore = true)
33
+ ControlEvaluationJobEntity fromRequestDto(ControlEvaluationRequestDTO requestDto, String controlId);
34
+
35
+ // If you need a more detailed DTO for job status (like ReportJobStatusDTO), you'd create another one.
36
+ // For now, the response DTO is used for the immediate POST response.
37
+ }
src/main/java/com/dalab/autocompliance/model/repository/ComplianceControlRepository.java ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.dalab.autocompliance.model.repository;
2
+
3
+ import com.dalab.autocompliance.model.entity.ComplianceControlEntity;
4
+ import org.springframework.data.jpa.repository.JpaRepository;
5
+ import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
6
+ import org.springframework.stereotype.Repository;
7
+
8
+ import java.util.List;
9
+ import java.util.Optional;
10
+
11
+ @Repository
12
+ public interface ComplianceControlRepository extends JpaRepository<ComplianceControlEntity, String>, JpaSpecificationExecutor<ComplianceControlEntity> {
13
+ // String is the type of the ID (controlId)
14
+
15
+ Optional<ComplianceControlEntity> findByControlIdIgnoreCase(String controlId);
16
+
17
+ List<ComplianceControlEntity> findByEnabledTrue();
18
+
19
+ List<ComplianceControlEntity> findBySeverityIgnoreCaseAndEnabledTrue(String severity);
20
+
21
+ // JpaSpecificationExecutor allows for dynamic queries based on criteria, useful for filtering controls
22
+ }
src/main/java/com/dalab/autocompliance/model/repository/ComplianceGeneratedReportRepository.java ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.dalab.autocompliance.model.repository;
2
+
3
+ import com.dalab.autocompliance.model.entity.ComplianceGeneratedReportEntity;
4
+ import org.springframework.data.domain.Page;
5
+ import org.springframework.data.domain.Pageable;
6
+ import org.springframework.data.jpa.repository.JpaRepository;
7
+ import org.springframework.data.jpa.repository.Query;
8
+ import org.springframework.data.repository.query.Param;
9
+ import org.springframework.stereotype.Repository;
10
+
11
+ import java.util.List;
12
+
13
+ @Repository
14
+ public interface ComplianceGeneratedReportRepository extends JpaRepository<ComplianceGeneratedReportEntity, String> {
15
+ // String is the type of the ID (reportId)
16
+
17
+ Page<ComplianceGeneratedReportEntity> findByReportTypeIgnoreCase(String reportType, Pageable pageable);
18
+
19
+ // Example of a query that might be needed to get asset status (though this is complex for JSONB)
20
+ // This is a conceptual query; actual JSONB querying capabilities depend heavily on the database (e.g., PostgreSQL).
21
+ // It's often better to process this in the service layer after fetching relevant reports,
22
+ // or use a dedicated search index if performance is critical.
23
+ @Query(value = "SELECT cgr.* FROM compliance_generated_reports cgr " +
24
+ "WHERE EXISTS (SELECT 1 FROM jsonb_array_elements(cgr.findings_data) AS finding " +
25
+ "WHERE finding ->> 'assetId' = :assetId)",
26
+ nativeQuery = true)
27
+ List<ComplianceGeneratedReportEntity> findReportsContainingAssetId(@Param("assetId") String assetId);
28
+ }
src/main/java/com/dalab/autocompliance/model/repository/ComplianceReportDefinitionRepository.java ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.dalab.autocompliance.model.repository;
2
+
3
+ import com.dalab.autocompliance.model.entity.ComplianceReportDefinitionEntity;
4
+ import org.springframework.data.jpa.repository.JpaRepository;
5
+ import org.springframework.stereotype.Repository;
6
+
7
+ import java.util.Optional;
8
+
9
+ @Repository
10
+ public interface ComplianceReportDefinitionRepository extends JpaRepository<ComplianceReportDefinitionEntity, String> {
11
+ // String is the type of the ID (reportType)
12
+
13
+ Optional<ComplianceReportDefinitionEntity> findByReportTypeIgnoreCase(String reportType);
14
+
15
+ boolean existsByReportTypeIgnoreCase(String reportType);
16
+ }
src/main/java/com/dalab/autocompliance/model/repository/ComplianceReportJobRepository.java ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.dalab.autocompliance.model.repository;
2
+
3
+ import com.dalab.autocompliance.model.entity.ComplianceReportJobEntity;
4
+ import org.springframework.data.domain.Page;
5
+ import org.springframework.data.domain.Pageable;
6
+ import org.springframework.data.jpa.repository.JpaRepository;
7
+ import org.springframework.stereotype.Repository;
8
+
9
+ import java.util.List;
10
+
11
+ @Repository
12
+ public interface ComplianceReportJobRepository extends JpaRepository<ComplianceReportJobEntity, String> {
13
+ // String is the type of the ID (jobId)
14
+
15
+ Page<ComplianceReportJobEntity> findByReportTypeIgnoreCase(String reportType, Pageable pageable);
16
+
17
+ List<ComplianceReportJobEntity> findByStatusIn(List<String> statuses); // For finding active jobs etc.
18
+ }
src/main/java/com/dalab/autocompliance/model/repository/ControlEvaluationJobRepository.java ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.dalab.autocompliance.model.repository;
2
+
3
+ import com.dalab.autocompliance.model.entity.ControlEvaluationJobEntity;
4
+ import org.springframework.data.domain.Page;
5
+ import org.springframework.data.domain.Pageable;
6
+ import org.springframework.data.jpa.repository.JpaRepository;
7
+ import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
8
+ import org.springframework.stereotype.Repository;
9
+
10
+ import java.util.List;
11
+
12
+ @Repository
13
+ public interface ControlEvaluationJobRepository extends JpaRepository<ControlEvaluationJobEntity, String>, JpaSpecificationExecutor<ControlEvaluationJobEntity> {
14
+ // String is the type of the ID (jobId)
15
+
16
+ Page<ControlEvaluationJobEntity> findByControlIdIgnoreCase(String controlId, Pageable pageable);
17
+
18
+ List<ControlEvaluationJobEntity> findByStatusIn(List<String> statuses);
19
+ }
src/main/java/com/dalab/autocompliance/service/IComplianceService.java ADDED
@@ -0,0 +1,77 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.dalab.autocompliance.service;
2
+
3
+ import com.dalab.autocompliance.dto.ComplianceReportDefinitionDTO;
4
+ import com.dalab.autocompliance.dto.ReportGenerationRequestDTO;
5
+ import com.dalab.autocompliance.dto.ReportGenerationResponseDTO;
6
+ import com.dalab.autocompliance.dto.ReportJobStatusDTO;
7
+ import com.dalab.autocompliance.dto.ComplianceReportDTO;
8
+ import com.dalab.autocompliance.dto.AssetComplianceStatusDTO;
9
+ import com.dalab.autocompliance.dto.ComplianceControlDTO;
10
+ import com.dalab.autocompliance.dto.ControlEvaluationRequestDTO;
11
+ import com.dalab.autocompliance.dto.ControlEvaluationResponseDTO;
12
+ // Future DTOs:
13
+ // import com.dalab.autocompliance.dto.ReportGenerationRequestDTO;
14
+ // import com.dalab.autocompliance.dto.ReportGenerationResponseDTO;
15
+ // import com.dalab.autocompliance.dto.ReportJobStatusDTO;
16
+
17
+ import java.util.List;
18
+
19
+ public interface IComplianceService {
20
+
21
+ /**
22
+ * Lists all available compliance report definitions.
23
+ * @return A list of ComplianceReportDefinitionDTOs.
24
+ */
25
+ List<ComplianceReportDefinitionDTO> listAvailableReportDefinitions();
26
+
27
+ /**
28
+ * Initiates the generation of a specific compliance report.
29
+ * @param reportType The type of report to generate.
30
+ * @param request DTO containing parameters for report generation.
31
+ * @return A response DTO with the job ID and status.
32
+ */
33
+ ReportGenerationResponseDTO generateComplianceReport(String reportType, ReportGenerationRequestDTO request);
34
+
35
+ /**
36
+ * Retrieves the status of a specific compliance report generation job.
37
+ * @param jobId The ID of the job.
38
+ * @return A DTO with the job status details.
39
+ */
40
+ ReportJobStatusDTO getReportGenerationJobStatus(String jobId);
41
+
42
+ /**
43
+ * Retrieves a generated compliance report by its ID.
44
+ * @param reportId The ID of the generated report.
45
+ * @return The ComplianceReportDTO or null if not found.
46
+ */
47
+ ComplianceReportDTO getGeneratedReport(String reportId);
48
+
49
+ /**
50
+ * Retrieves the overall compliance status for a specific asset.
51
+ * @param assetId The ID of the asset.
52
+ * @return AssetComplianceStatusDTO or null if asset not found or no status available.
53
+ */
54
+ AssetComplianceStatusDTO getAssetComplianceStatus(String assetId);
55
+
56
+ /**
57
+ * Lists all available (and typically enabled) compliance controls.
58
+ * @return A list of ComplianceControlDTOs.
59
+ */
60
+ List<ComplianceControlDTO> listAvailableControls();
61
+
62
+ /**
63
+ * Initiates the evaluation of a specific compliance control.
64
+ * @param controlId The ID of the control to evaluate.
65
+ * @param request DTO containing parameters for the evaluation.
66
+ * @return A response DTO with the job ID and status.
67
+ */
68
+ ControlEvaluationResponseDTO evaluateControl(String controlId, ControlEvaluationRequestDTO request);
69
+
70
+ // TODO: Add method to get status of a control evaluation job
71
+ // ControlEvaluationJobStatusDTO getControlEvaluationJobStatus(String jobId);
72
+
73
+ // Methods to be added later:
74
+ // ReportGenerationResponseDTO generateComplianceReport(String reportType, ReportGenerationRequestDTO request);
75
+ // ReportJobStatusDTO getReportGenerationJobStatus(String jobId);
76
+ // ... and others for controls and asset status
77
+ }
src/main/java/com/dalab/autocompliance/service/exception/ControlNotFoundException.java ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.dalab.autocompliance.service.exception;
2
+
3
+ import org.springframework.http.HttpStatus;
4
+ import org.springframework.web.bind.annotation.ResponseStatus;
5
+
6
+ @ResponseStatus(HttpStatus.NOT_FOUND)
7
+ public class ControlNotFoundException extends RuntimeException {
8
+ public ControlNotFoundException(String message) {
9
+ super(message);
10
+ }
11
+
12
+ public ControlNotFoundException(String message, Throwable cause) {
13
+ super(message, cause);
14
+ }
15
+ }
src/main/java/com/dalab/autocompliance/service/exception/GeneratedReportNotFoundException.java ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.dalab.autocompliance.service.exception;
2
+
3
+ import org.springframework.http.HttpStatus;
4
+ import org.springframework.web.bind.annotation.ResponseStatus;
5
+
6
+ @ResponseStatus(HttpStatus.NOT_FOUND)
7
+ public class GeneratedReportNotFoundException extends RuntimeException {
8
+ public GeneratedReportNotFoundException(String message) {
9
+ super(message);
10
+ }
11
+
12
+ public GeneratedReportNotFoundException(String message, Throwable cause) {
13
+ super(message, cause);
14
+ }
15
+ }
src/main/java/com/dalab/autocompliance/service/exception/ReportDefinitionNotFoundException.java ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.dalab.autocompliance.service.exception;
2
+
3
+ import org.springframework.http.HttpStatus;
4
+ import org.springframework.web.bind.annotation.ResponseStatus;
5
+
6
+ @ResponseStatus(HttpStatus.NOT_FOUND)
7
+ public class ReportDefinitionNotFoundException extends RuntimeException {
8
+ public ReportDefinitionNotFoundException(String message) {
9
+ super(message);
10
+ }
11
+
12
+ public ReportDefinitionNotFoundException(String message, Throwable cause) {
13
+ super(message, cause);
14
+ }
15
+ }
src/main/java/com/dalab/autocompliance/service/exception/ReportJobNotFoundException.java ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.dalab.autocompliance.service.exception;
2
+
3
+ import org.springframework.http.HttpStatus;
4
+ import org.springframework.web.bind.annotation.ResponseStatus;
5
+
6
+ @ResponseStatus(HttpStatus.NOT_FOUND)
7
+ public class ReportJobNotFoundException extends RuntimeException {
8
+ public ReportJobNotFoundException(String message) {
9
+ super(message);
10
+ }
11
+
12
+ public ReportJobNotFoundException(String message, Throwable cause) {
13
+ super(message, cause);
14
+ }
15
+ }
src/main/java/com/dalab/autocompliance/service/impl/ComplianceServiceImpl.java ADDED
@@ -0,0 +1,324 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.dalab.autocompliance.service.impl;
2
+
3
+ import java.time.LocalDateTime;
4
+ import java.util.ArrayList;
5
+ import java.util.List;
6
+ import java.util.Map;
7
+ import java.util.UUID;
8
+ import java.util.stream.Collectors;
9
+
10
+ import org.springframework.stereotype.Service;
11
+ import org.springframework.transaction.annotation.Transactional;
12
+
13
+ import com.dalab.autocompliance.dto.AssetComplianceStatusDTO;
14
+ import com.dalab.autocompliance.dto.ComplianceControlDTO;
15
+ import com.dalab.autocompliance.dto.ComplianceReportDTO;
16
+ import com.dalab.autocompliance.dto.ComplianceReportDefinitionDTO;
17
+ import com.dalab.autocompliance.dto.ControlEvaluationRequestDTO;
18
+ import com.dalab.autocompliance.dto.ControlEvaluationResponseDTO;
19
+ import com.dalab.autocompliance.dto.ReportGenerationRequestDTO;
20
+ import com.dalab.autocompliance.dto.ReportGenerationResponseDTO;
21
+ import com.dalab.autocompliance.dto.ReportJobStatusDTO;
22
+ import com.dalab.autocompliance.model.entity.ComplianceControlEntity;
23
+ import com.dalab.autocompliance.model.entity.ComplianceGeneratedReportEntity;
24
+ import com.dalab.autocompliance.model.entity.ComplianceReportDefinitionEntity;
25
+ import com.dalab.autocompliance.model.entity.ComplianceReportJobEntity;
26
+ import com.dalab.autocompliance.model.entity.ControlEvaluationJobEntity;
27
+ import com.dalab.autocompliance.model.mapper.ComplianceControlMapper;
28
+ import com.dalab.autocompliance.model.mapper.ComplianceGeneratedReportMapper;
29
+ import com.dalab.autocompliance.model.mapper.ComplianceReportDefinitionMapper;
30
+ import com.dalab.autocompliance.model.mapper.ComplianceReportJobMapper;
31
+ import com.dalab.autocompliance.model.mapper.ControlEvaluationJobMapper;
32
+ import com.dalab.autocompliance.model.repository.ComplianceControlRepository;
33
+ import com.dalab.autocompliance.model.repository.ComplianceGeneratedReportRepository;
34
+ import com.dalab.autocompliance.model.repository.ComplianceReportDefinitionRepository;
35
+ import com.dalab.autocompliance.model.repository.ComplianceReportJobRepository;
36
+ import com.dalab.autocompliance.model.repository.ControlEvaluationJobRepository;
37
+ import com.dalab.autocompliance.service.IComplianceService;
38
+ import com.dalab.autocompliance.service.exception.ControlNotFoundException;
39
+ import com.dalab.autocompliance.service.exception.GeneratedReportNotFoundException;
40
+ import com.dalab.autocompliance.service.exception.ReportDefinitionNotFoundException;
41
+ import com.dalab.autocompliance.service.exception.ReportJobNotFoundException;
42
+
43
+ import jakarta.annotation.PostConstruct;
44
+ import lombok.RequiredArgsConstructor;
45
+ import lombok.extern.slf4j.Slf4j;
46
+
47
+ @Service("complianceService")
48
+ @RequiredArgsConstructor
49
+ @Slf4j
50
+ public class ComplianceServiceImpl implements IComplianceService {
51
+
52
+ private final ComplianceReportDefinitionRepository reportDefinitionRepository;
53
+ private final ComplianceReportJobRepository reportJobRepository;
54
+ private final ComplianceGeneratedReportRepository generatedReportRepository;
55
+ private final ComplianceControlRepository controlRepository;
56
+ private final ControlEvaluationJobRepository controlEvaluationJobRepository;
57
+
58
+ private final ComplianceReportDefinitionMapper reportDefinitionMapper;
59
+ private final ComplianceReportJobMapper reportJobMapper;
60
+ private final ComplianceGeneratedReportMapper generatedReportMapper;
61
+ private final ComplianceControlMapper controlMapper;
62
+ private final ControlEvaluationJobMapper controlEvaluationJobMapper;
63
+
64
+ @PostConstruct
65
+ @Transactional
66
+ public void initDefaultData() {
67
+ initDefaultReportDefinitions();
68
+ initDefaultControls();
69
+ }
70
+
71
+ public void initDefaultReportDefinitions() {
72
+ if (reportDefinitionRepository.count() == 0) {
73
+ log.info("Initializing default compliance report definitions...");
74
+ List<ComplianceReportDefinitionEntity> defaultDefinitions = new ArrayList<>();
75
+ defaultDefinitions.add(ComplianceReportDefinitionEntity.builder()
76
+ .reportType("cis-gcp-foundations-v1.3")
77
+ .displayName("CIS Google Cloud Platform Foundations Benchmark v1.3")
78
+ .description("Checks for compliance against the CIS GCP Foundations Benchmark v1.3.")
79
+ .version("1.3.0")
80
+ .applicableComplianceFrameworks(List.of("CIS Benchmark"))
81
+ .targetAssetTypes(List.of("GCP_PROJECT", "GCP_IAM_POLICY", "GCP_VPC_NETWORK"))
82
+ .generationParametersDefinition(Map.of("gcpProjectId", "string", "targetRegions", "list_string"))
83
+ .detailsLink("https://www.cisecurity.org/benchmark/google_cloud_computing_platform/")
84
+ .build());
85
+
86
+ defaultDefinitions.add(ComplianceReportDefinitionEntity.builder()
87
+ .reportType("pci-dss-aws-v3.2.1")
88
+ .displayName("PCI DSS v3.2.1 for AWS Infrastructure")
89
+ .description("Assesses AWS resources against applicable PCI DSS v3.2.1 requirements.")
90
+ .version("3.2.1")
91
+ .applicableComplianceFrameworks(List.of("PCI DSS v3.2.1"))
92
+ .targetAssetTypes(List.of("AWS_EC2_INSTANCE", "AWS_S3_BUCKET", "AWS_VPC"))
93
+ .generationParametersDefinition(Map.of("awsAccountId", "string", "awsRegion", "string"))
94
+ .detailsLink("https://aws.amazon.com/compliance/pci-dss/")
95
+ .build());
96
+ reportDefinitionRepository.saveAll(defaultDefinitions);
97
+ log.info("{} default compliance report definitions initialized.", defaultDefinitions.size());
98
+ }
99
+ }
100
+
101
+ public void initDefaultControls() {
102
+ if (controlRepository.count() == 0) {
103
+ log.info("Initializing default compliance controls...");
104
+ List<ComplianceControlEntity> defaultControls = new ArrayList<>();
105
+ defaultControls.add(ComplianceControlEntity.builder()
106
+ .controlId("CIS-GCP-1.1")
107
+ .name("Ensure that corporate login credentials are used instead of gmail.com accounts")
108
+ .description("GCP supports using Google Workspace or Cloud Identity to manage user identities. It is recommended to use managed corporate accounts instead of personal gmail.com accounts for managing cloud resources.")
109
+ .severity("MEDIUM")
110
+ .applicableFrameworks(List.of("CIS GCP Foundations Benchmark v1.3"))
111
+ .targetAssetTypes(List.of("GCP_IAM_POLICY"))
112
+ .remediationSteps("Migrate any gmail.com accounts with IAM roles to managed corporate accounts.")
113
+ .evaluationParametersDefinition(Map.of("gcpProjectId", "string"))
114
+ .detailsLink("https://www.cisecurity.org/benchmark/google_cloud_computing_platform/") // Point to specific control if possible
115
+ .enabled(true)
116
+ .build());
117
+ defaultControls.add(ComplianceControlEntity.builder()
118
+ .controlId("PCI-REQ-8.2.3")
119
+ .name("Use strong passwords (or passphrases)")
120
+ .description("Ensure passwords/passphrases meet minimum complexity requirements as defined by PCI DSS requirement 8.2.3.")
121
+ .severity("HIGH")
122
+ .applicableFrameworks(List.of("PCI DSS v3.2.1"))
123
+ .targetAssetTypes(List.of("AWS_IAM_USER", "Linux_Server")) // Example asset types
124
+ .remediationSteps("Configure password policies to enforce complexity. For existing users, enforce password change at next login.")
125
+ .evaluationParametersDefinition(Map.of("minPasswordLength", "integer", "requiresUppercase", "boolean")) // Example params
126
+ .detailsLink("https://www.pcisecuritystandards.org/") // Point to specific control
127
+ .enabled(true)
128
+ .build());
129
+ controlRepository.saveAll(defaultControls);
130
+ log.info("{} default compliance controls initialized.", defaultControls.size());
131
+ }
132
+ }
133
+
134
+ @Override
135
+ @Transactional(readOnly = true)
136
+ public List<ComplianceReportDefinitionDTO> listAvailableReportDefinitions() {
137
+ log.debug("Fetching all available report definitions.");
138
+ List<ComplianceReportDefinitionEntity> entities = reportDefinitionRepository.findAll();
139
+ return reportDefinitionMapper.toDtoList(entities);
140
+ }
141
+
142
+ @Override
143
+ @Transactional
144
+ public ReportGenerationResponseDTO generateComplianceReport(String reportType, ReportGenerationRequestDTO request) {
145
+ log.info("Request to generate report type: {} with parameters: {}", reportType, request.getParameters());
146
+
147
+ ComplianceReportDefinitionEntity definitionEntity = reportDefinitionRepository.findByReportTypeIgnoreCase(reportType)
148
+ .orElseThrow(() -> {
149
+ log.warn("Report type '{}' not found.", reportType);
150
+ return new ReportDefinitionNotFoundException("Report type " + reportType + " not found.");
151
+ });
152
+
153
+ // Validate parameters (basic check for key presence)
154
+ if (request.getParameters() == null ||
155
+ !request.getParameters().keySet().containsAll(definitionEntity.getGenerationParametersDefinition().keySet())) {
156
+ String errorMsg = String.format("Missing required parameters for report type '%s'. Required: %s, Provided: %s",
157
+ reportType, definitionEntity.getGenerationParametersDefinition().keySet(),
158
+ request.getParameters() != null ? request.getParameters().keySet() : "null");
159
+ log.warn(errorMsg);
160
+ // For immediate feedback, we can return a response, though throwing an exception is also an option.
161
+ return ReportGenerationResponseDTO.builder()
162
+ .jobId(null).reportType(reportType).status("FAILED_VALIDATION")
163
+ .submittedAt(LocalDateTime.now()).message(errorMsg)
164
+ .build();
165
+ }
166
+
167
+ ComplianceReportJobEntity jobEntity = reportJobMapper.fromGenerationRequest(request, reportType);
168
+ jobEntity.setJobId(UUID.randomUUID().toString());
169
+ jobEntity.setStatus("QUEUED");
170
+ jobEntity.setSubmittedAt(LocalDateTime.now());
171
+ jobEntity.setReportType(definitionEntity.getReportType()); // Ensure canonical report type
172
+ // triggeredBy and notificationEmail are mapped by mapper if present in request
173
+
174
+ reportJobRepository.save(jobEntity);
175
+ log.info("Report generation job {} for report type '{}' accepted and queued.", jobEntity.getJobId(), reportType);
176
+
177
+ // TODO: Trigger actual asynchronous report generation (e.g., via @Async method or Kafka message)
178
+ // This async process would later update the jobEntity status and link to a ComplianceGeneratedReportEntity.
179
+
180
+ return ReportGenerationResponseDTO.builder()
181
+ .jobId(jobEntity.getJobId()).reportType(reportType).status("ACCEPTED")
182
+ .submittedAt(jobEntity.getSubmittedAt()).message("Report generation accepted and queued.")
183
+ .build();
184
+ }
185
+
186
+ @Override
187
+ @Transactional(readOnly = true)
188
+ public ReportJobStatusDTO getReportGenerationJobStatus(String jobId) {
189
+ log.debug("Fetching status for job ID: {}", jobId);
190
+ ComplianceReportJobEntity jobEntity = reportJobRepository.findById(jobId)
191
+ .orElseThrow(() -> {
192
+ log.warn("Job ID '{}' not found.", jobId);
193
+ return new ReportJobNotFoundException("Job ID " + jobId + " not found.");
194
+ });
195
+ return reportJobMapper.toDto(jobEntity);
196
+ }
197
+
198
+ @Override
199
+ @Transactional(readOnly = true)
200
+ public ComplianceReportDTO getGeneratedReport(String reportId) {
201
+ log.debug("Fetching generated report for report ID: {}", reportId);
202
+ ComplianceGeneratedReportEntity reportEntity = generatedReportRepository.findById(reportId)
203
+ .orElseThrow(() -> {
204
+ log.warn("Report ID '{}' not found.", reportId);
205
+ return new GeneratedReportNotFoundException("Generated report with ID " + reportId + " not found.");
206
+ });
207
+ return generatedReportMapper.toDto(reportEntity);
208
+ }
209
+
210
+ @Override
211
+ @Transactional(readOnly = true)
212
+ public AssetComplianceStatusDTO getAssetComplianceStatus(String assetId) {
213
+ log.debug("Fetching compliance status for asset ID: {}", assetId);
214
+
215
+ // This is a simplified implementation. A more robust solution would involve:
216
+ // 1. Querying `ComplianceGeneratedReportEntity` for reports containing this assetId (potentially using the native query or service-layer filtering).
217
+ // 2. Aggregating findings from those reports.
218
+ // 3. Determining overall status based on those findings.
219
+ // For now, we mock it by looking for the dummy asset in any dummy report.
220
+
221
+ List<ComplianceGeneratedReportEntity> reportsWithAsset = generatedReportRepository.findReportsContainingAssetId(assetId);
222
+
223
+ if (reportsWithAsset.isEmpty()) {
224
+ log.warn("Asset ID '{}' not found in any generated reports or no compliance data available.", assetId);
225
+ return AssetComplianceStatusDTO.builder()
226
+ .assetId(assetId)
227
+ .overallComplianceStatus("UNKNOWN") // Or NOT_EVALUATED
228
+ .lastEvaluatedAt(LocalDateTime.now()) // Or null if never evaluated
229
+ .build();
230
+ }
231
+
232
+ List<AssetComplianceStatusDTO.ComplianceFindingSummaryDTO> assetFindingsSummaries = new ArrayList<>();
233
+ List<String> relevantReportIds = new ArrayList<>();
234
+ int compliantCount = 0;
235
+ int nonCompliantCount = 0;
236
+ String assetType = "UNKNOWN";
237
+ String assetName = "UNKNOWN";
238
+ LocalDateTime lastEvalTime = null;
239
+
240
+ for (ComplianceGeneratedReportEntity reportEntity : reportsWithAsset) {
241
+ relevantReportIds.add(reportEntity.getReportId());
242
+ if (reportEntity.getFindingsData() != null) {
243
+ for (com.dalab.autocompliance.model.entity.ReportFindingData findingData : reportEntity.getFindingsData()) {
244
+ if (assetId.equals(findingData.getAssetId())) {
245
+ // Update asset details from the first finding containing them (could be more sophisticated)
246
+ if ("UNKNOWN".equals(assetType) && findingData.getAssetType() != null) assetType = findingData.getAssetType();
247
+ if ("UNKNOWN".equals(assetName) && findingData.getAssetName() != null) assetName = findingData.getAssetName();
248
+
249
+ if ("NON_COMPLIANT".equalsIgnoreCase(findingData.getStatus())) {
250
+ nonCompliantCount++;
251
+ assetFindingsSummaries.add(AssetComplianceStatusDTO.ComplianceFindingSummaryDTO.builder()
252
+ .checkId(findingData.getCheckId())
253
+ .description(findingData.getDescription())
254
+ .severity(findingData.getSeverity())
255
+ .reportId(reportEntity.getReportId())
256
+ .findingTimestamp(reportEntity.getGenerationTimestamp())
257
+ .build());
258
+ } else if ("COMPLIANT".equalsIgnoreCase(findingData.getStatus())) {
259
+ compliantCount++;
260
+ }
261
+ // Update last evaluation time for this asset based on reports
262
+ if (lastEvalTime == null || reportEntity.getGenerationTimestamp().isAfter(lastEvalTime)) {
263
+ lastEvalTime = reportEntity.getGenerationTimestamp();
264
+ }
265
+ }
266
+ }
267
+ }
268
+ }
269
+
270
+ String overallStatus = "UNKNOWN";
271
+ if (compliantCount > 0 || nonCompliantCount > 0) {
272
+ overallStatus = nonCompliantCount > 0 ? "NON_COMPLIANT" : "COMPLIANT";
273
+ } else if (!reportsWithAsset.isEmpty()) {
274
+ overallStatus = "NO_FINDINGS"; // Or EVALUATED_COMPLIANT if all checks were compliant
275
+ }
276
+
277
+ return AssetComplianceStatusDTO.builder()
278
+ .assetId(assetId)
279
+ .assetType(assetType)
280
+ .assetName(assetName)
281
+ .overallComplianceStatus(overallStatus)
282
+ .lastEvaluatedAt(lastEvalTime != null ? lastEvalTime : LocalDateTime.now()) // Fallback, should ideally be from data
283
+ .totalChecksApplied(compliantCount + nonCompliantCount) // This is simplified, total checks could be different
284
+ .compliantChecks(compliantCount)
285
+ .nonCompliantChecks(nonCompliantCount)
286
+ .recentNonCompliantFindings(assetFindingsSummaries.stream().limit(5).collect(Collectors.toList()))
287
+ .relevantReportIds(relevantReportIds)
288
+ .build();
289
+ }
290
+
291
+ @Override
292
+ @Transactional(readOnly = true)
293
+ public List<ComplianceControlDTO> listAvailableControls() {
294
+ log.debug("Fetching all available and enabled compliance controls.");
295
+ List<ComplianceControlEntity> entities = controlRepository.findByEnabledTrue();
296
+ return controlMapper.toDtoList(entities);
297
+ }
298
+
299
+ @Override
300
+ @Transactional
301
+ public ControlEvaluationResponseDTO evaluateControl(String controlId, ControlEvaluationRequestDTO request) {
302
+ log.info("Request to evaluate control ID: {} with request: {}", controlId, request);
303
+
304
+ ComplianceControlEntity controlEntity = controlRepository.findByControlIdIgnoreCase(controlId)
305
+ .orElseThrow(() -> {
306
+ log.warn("Control ID '{}' not found.", controlId);
307
+ return new ControlNotFoundException("Control ID " + controlId + " not found.");
308
+ });
309
+
310
+ // Create and save the evaluation job
311
+ ControlEvaluationJobEntity jobEntity = controlEvaluationJobMapper.fromRequestDto(request, controlId);
312
+ jobEntity = controlEvaluationJobRepository.save(jobEntity);
313
+
314
+ log.info("Created control evaluation job {} for control {}", jobEntity.getJobId(), controlId);
315
+
316
+ // TODO: Implement actual control evaluation logic
317
+ // This would typically involve:
318
+ // 1. Fetching relevant assets based on request parameters
319
+ // 2. Applying the control's evaluation logic
320
+ // 3. Updating the job status and results
321
+
322
+ return controlEvaluationJobMapper.toResponseDto(jobEntity);
323
+ }
324
+ }
src/main/resources/application.properties ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # DALab AutoCompliance Service Configuration
2
+ spring.application.name=da-autocompliance
3
+ server.port=8080
4
+
5
+ # Database Configuration - da_autocompliance database
6
+ spring.datasource.url=jdbc:postgresql://localhost:5432/da_autocompliance
7
+ spring.datasource.username=da_autocompliance_user
8
+ spring.datasource.password=da_autocompliance_pass
9
+ spring.datasource.driver-class-name=org.postgresql.Driver
10
+
11
+ # JPA Configuration
12
+ spring.jpa.hibernate.ddl-auto=update
13
+ spring.jpa.show-sql=false
14
+ spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
15
+ spring.jpa.properties.hibernate.format_sql=true
16
+
17
+ # Common entities database configuration (for da-protos entities)
18
+ dalab.common.datasource.url=jdbc:postgresql://localhost:5432/dalab_common
19
+ dalab.common.datasource.username=dalab_common_user
20
+ dalab.common.datasource.password=dalab_common_pass
21
+
22
+ # Kafka Configuration
23
+ spring.kafka.bootstrap-servers=localhost:9092
24
+ spring.kafka.consumer.group-id=da-autocompliance-group
25
+ spring.kafka.consumer.auto-offset-reset=earliest
26
+ spring.kafka.consumer.key-deserializer=org.apache.kafka.common.serialization.StringDeserializer
27
+ spring.kafka.consumer.value-deserializer=org.springframework.kafka.support.serializer.JsonDeserializer
28
+ spring.kafka.consumer.properties.spring.json.trusted.packages=*
29
+ spring.kafka.producer.key-serializer=org.apache.kafka.common.serialization.StringSerializer
30
+ spring.kafka.producer.value-serializer=org.springframework.kafka.support.serializer.JsonSerializer
31
+
32
+ # Kafka Topics
33
+ dalab.kafka.topics.policy-actions=dalab.policies.actions
34
+ dalab.kafka.topics.compliance-events=dalab.compliance.events
35
+
36
+ # Security Configuration (Keycloak JWT)
37
+ spring.security.oauth2.resourceserver.jwt.issuer-uri=http://localhost:8180/realms/dalab
38
+ spring.security.oauth2.resourceserver.jwt.jwk-set-uri=http://localhost:8180/realms/dalab/protocol/openid-connect/certs
39
+
40
+ # Compliance Configuration
41
+ compliance.evaluation.batch.size=100
42
+ compliance.reports.default.retention.days=365
43
+ compliance.alerts.enabled=true
44
+
45
+ # Actuator Configuration
46
+ management.endpoints.web.exposure.include=health,info,metrics,prometheus
47
+ management.endpoint.health.show-details=when-authorized
48
+ management.metrics.export.prometheus.enabled=true
49
+
50
+ # OpenAPI Documentation
51
+ springdoc.api-docs.path=/v3/api-docs
52
+ springdoc.swagger-ui.path=/swagger-ui.html
53
+
54
+ # Logging Configuration
55
+ logging.level.com.dalab.autocompliance=INFO
56
+ logging.level.org.springframework.kafka=WARN
57
+ logging.level.org.springframework.security=WARN
src/test/java/com/dalab/autocompliance/controller/ComplianceControllerTest.java ADDED
@@ -0,0 +1,161 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.dalab.autocompliance.controller;
2
+
3
+ import com.dalab.autocompliance.dto.*;
4
+ import com.dalab.autocompliance.service.IComplianceService;
5
+ import com.fasterxml.jackson.databind.ObjectMapper;
6
+ import org.junit.jupiter.api.BeforeEach;
7
+ import org.junit.jupiter.api.Test;
8
+ import org.springframework.beans.factory.annotation.Autowired;
9
+ import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
10
+ import org.springframework.boot.test.mock.mockito.MockBean;
11
+ import org.springframework.http.MediaType;
12
+ import org.springframework.security.test.context.support.WithMockUser;
13
+ import org.springframework.test.web.servlet.MockMvc;
14
+
15
+ import java.time.LocalDateTime;
16
+ import java.util.Collections;
17
+ import java.util.List;
18
+ import java.util.Map;
19
+ import java.util.UUID;
20
+
21
+ import static org.mockito.ArgumentMatchers.any;
22
+ import static org.mockito.ArgumentMatchers.anyString;
23
+ import static org.mockito.BDDMockito.given;
24
+ import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
25
+ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
26
+ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
27
+
28
+ @WebMvcTest(ComplianceController.class)
29
+ class ComplianceControllerTest {
30
+
31
+ @Autowired
32
+ private MockMvc mockMvc;
33
+
34
+ @MockBean
35
+ private IComplianceService complianceService;
36
+
37
+ @Autowired
38
+ private ObjectMapper objectMapper;
39
+
40
+ private ComplianceReportDefinitionDTO reportDefinitionDTO;
41
+ private ReportGenerationRequestDTO reportGenerationRequestDTO;
42
+ private ReportGenerationResponseDTO reportGenerationResponseDTO;
43
+ private ReportJobStatusDTO reportJobStatusDTO;
44
+ private ComplianceReportDTO complianceReportDTO;
45
+ private AssetComplianceStatusDTO assetComplianceStatusDTO;
46
+ private ComplianceControlDTO complianceControlDTO;
47
+ private ControlEvaluationRequestDTO controlEvaluationRequestDTO;
48
+ private ControlEvaluationResponseDTO controlEvaluationResponseDTO;
49
+
50
+ @BeforeEach
51
+ void setUp() {
52
+ reportDefinitionDTO = ComplianceReportDefinitionDTO.builder().reportType("test-report").displayName("Test Report").build();
53
+
54
+ reportGenerationRequestDTO = ReportGenerationRequestDTO.builder().parameters(Map.of("param1", "value1")).build();
55
+ reportGenerationResponseDTO = ReportGenerationResponseDTO.builder().jobId(UUID.randomUUID().toString()).status("ACCEPTED").build();
56
+
57
+ reportJobStatusDTO = ReportJobStatusDTO.builder().jobId(UUID.randomUUID().toString()).status("COMPLETED_SUCCESS").build();
58
+
59
+ complianceReportDTO = ComplianceReportDTO.builder().reportId(UUID.randomUUID().toString()).overallStatus("COMPLIANT").build();
60
+
61
+ assetComplianceStatusDTO = AssetComplianceStatusDTO.builder().assetId("asset-123").overallComplianceStatus("COMPLIANT").build();
62
+
63
+ complianceControlDTO = ComplianceControlDTO.builder().controlId("control-001").name("Test Control").enabled(true).build();
64
+
65
+ controlEvaluationRequestDTO = ControlEvaluationRequestDTO.builder().targetAssetIds(List.of("asset-123")).build();
66
+ controlEvaluationResponseDTO = ControlEvaluationResponseDTO.builder().jobId(UUID.randomUUID().toString()).status("ACCEPTED").build();
67
+ }
68
+
69
+ @Test
70
+ @WithMockUser(roles = "USER")
71
+ void listAvailableReportDefinitions_shouldReturnOk() throws Exception {
72
+ given(complianceService.listAvailableReportDefinitions()).willReturn(Collections.singletonList(reportDefinitionDTO));
73
+ mockMvc.perform(get("/api/v1/compliance/reports"))
74
+ .andExpect(status().isOk())
75
+ .andExpect(jsonPath("$[0].reportType").value("test-report"));
76
+ }
77
+
78
+ @Test
79
+ @WithMockUser(roles = "ADMIN")
80
+ void generateComplianceReport_shouldReturnAccepted() throws Exception {
81
+ given(complianceService.generateComplianceReport(anyString(), any(ReportGenerationRequestDTO.class))).willReturn(reportGenerationResponseDTO);
82
+ mockMvc.perform(post("/api/v1/compliance/reports/test-report/generate").with(csrf())
83
+ .contentType(MediaType.APPLICATION_JSON)
84
+ .content(objectMapper.writeValueAsString(reportGenerationRequestDTO)))
85
+ .andExpect(status().isAccepted())
86
+ .andExpect(jsonPath("$.status").value("ACCEPTED"));
87
+ }
88
+
89
+ @Test
90
+ @WithMockUser(roles = "USER")
91
+ void getReportGenerationJobStatus_shouldReturnOk() throws Exception {
92
+ given(complianceService.getReportGenerationJobStatus(anyString())).willReturn(reportJobStatusDTO);
93
+ mockMvc.perform(get("/api/v1/compliance/reports/jobs/some-job-id"))
94
+ .andExpect(status().isOk())
95
+ .andExpect(jsonPath("$.status").value("COMPLETED_SUCCESS"));
96
+ }
97
+
98
+ @Test
99
+ @WithMockUser(roles = "USER")
100
+ void getReportGenerationJobStatus_whenNotFound_shouldReturnNotFound() throws Exception {
101
+ ReportJobStatusDTO notFoundDto = ReportJobStatusDTO.builder().jobId("not-found-id").status("NOT_FOUND").build();
102
+ given(complianceService.getReportGenerationJobStatus("not-found-id")).willReturn(notFoundDto);
103
+ mockMvc.perform(get("/api/v1/compliance/reports/jobs/not-found-id"))
104
+ .andExpect(status().isNotFound());
105
+ }
106
+
107
+ @Test
108
+ @WithMockUser(roles = "USER")
109
+ void getGeneratedReport_shouldReturnOk() throws Exception {
110
+ given(complianceService.getGeneratedReport(anyString())).willReturn(complianceReportDTO);
111
+ mockMvc.perform(get("/api/v1/compliance/reports/results/some-report-id"))
112
+ .andExpect(status().isOk())
113
+ .andExpect(jsonPath("$.overallStatus").value("COMPLIANT"));
114
+ }
115
+
116
+ @Test
117
+ @WithMockUser(roles = "USER")
118
+ void getGeneratedReport_whenNotFound_shouldReturnNotFound() throws Exception {
119
+ given(complianceService.getGeneratedReport("not-found-id")).willReturn(null);
120
+ mockMvc.perform(get("/api/v1/compliance/reports/results/not-found-id"))
121
+ .andExpect(status().isNotFound());
122
+ }
123
+
124
+ @Test
125
+ @WithMockUser(roles = "USER")
126
+ void getAssetComplianceStatus_shouldReturnOk() throws Exception {
127
+ given(complianceService.getAssetComplianceStatus(anyString())).willReturn(assetComplianceStatusDTO);
128
+ mockMvc.perform(get("/api/v1/compliance/assets/asset-123"))
129
+ .andExpect(status().isOk())
130
+ .andExpect(jsonPath("$.overallComplianceStatus").value("COMPLIANT"));
131
+ }
132
+
133
+ @Test
134
+ @WithMockUser(roles = "USER")
135
+ void getAssetComplianceStatus_whenNotFound_shouldReturnNotFound() throws Exception {
136
+ AssetComplianceStatusDTO notFoundStatus = AssetComplianceStatusDTO.builder().assetId("asset-not-found").overallComplianceStatus("UNKNOWN").relevantReportIds(null).build();
137
+ given(complianceService.getAssetComplianceStatus("asset-not-found")).willReturn(notFoundStatus);
138
+ mockMvc.perform(get("/api/v1/compliance/assets/asset-not-found"))
139
+ .andExpect(status().isNotFound());
140
+ }
141
+
142
+ @Test
143
+ @WithMockUser(roles = "USER")
144
+ void listAvailableControls_shouldReturnOk() throws Exception {
145
+ given(complianceService.listAvailableControls()).willReturn(Collections.singletonList(complianceControlDTO));
146
+ mockMvc.perform(get("/api/v1/compliance/controls"))
147
+ .andExpect(status().isOk())
148
+ .andExpect(jsonPath("$[0].controlId").value("control-001"));
149
+ }
150
+
151
+ @Test
152
+ @WithMockUser(roles = "ADMIN")
153
+ void evaluateControl_shouldReturnAccepted() throws Exception {
154
+ given(complianceService.evaluateControl(anyString(), any(ControlEvaluationRequestDTO.class))).willReturn(controlEvaluationResponseDTO);
155
+ mockMvc.perform(post("/api/v1/compliance/controls/control-001/evaluate").with(csrf())
156
+ .contentType(MediaType.APPLICATION_JSON)
157
+ .content(objectMapper.writeValueAsString(controlEvaluationRequestDTO)))
158
+ .andExpect(status().isAccepted())
159
+ .andExpect(jsonPath("$.status").value("ACCEPTED"));
160
+ }
161
+ }
src/test/java/com/dalab/autocompliance/service/impl/ComplianceServiceImplTest.java ADDED
@@ -0,0 +1 @@
 
 
1
+