Skip to content

Commit e8d2b6d

Browse files
committed
Add controllers
1 parent e236274 commit e8d2b6d

25 files changed

Lines changed: 2335 additions & 0 deletions

build.gradle.kts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ repositories {
1919
dependencies {
2020
implementation("org.springframework.boot:spring-boot-starter-web")
2121
implementation("org.springframework.boot:spring-boot-starter-thymeleaf")
22+
implementation("org.springdoc:springdoc-openapi-starter-webmvc-ui:2.6.0")
23+
implementation("org.xerial:sqlite-jdbc:3.47.1.0")
24+
implementation("org.dom4j:dom4j:2.1.4")
2225
testImplementation("org.springframework.boot:spring-boot-starter-test")
2326
testImplementation(platform("org.junit:junit-bom:5.10.0"))
2427
testImplementation("org.junit.jupiter:junit-jupiter")
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package org.example.config;
2+
3+
import io.swagger.v3.oas.models.OpenAPI;
4+
import io.swagger.v3.oas.models.info.Contact;
5+
import io.swagger.v3.oas.models.info.Info;
6+
import io.swagger.v3.oas.models.servers.Server;
7+
import org.springframework.context.annotation.Bean;
8+
import org.springframework.context.annotation.Configuration;
9+
10+
import java.util.List;
11+
12+
@Configuration
13+
public class OpenApiConfig {
14+
15+
@Bean
16+
public OpenAPI customOpenAPI() {
17+
return new OpenAPI()
18+
.info(new Info()
19+
.title("Monitoring Platform API")
20+
.version("1.0.0")
21+
.description("API for system monitoring, alerts, notifications, and reporting")
22+
.contact(new Contact().name("API Support").email("support@example.com")))
23+
.servers(List.of(new Server().url("http://localhost:8081").description("Development")));
24+
}
25+
}
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
package org.example.controller;
2+
3+
import io.swagger.v3.oas.annotations.Operation;
4+
import io.swagger.v3.oas.annotations.Parameter;
5+
import io.swagger.v3.oas.annotations.tags.Tag;
6+
import org.example.service.AlertStorageService;
7+
import org.slf4j.Logger;
8+
import org.slf4j.LoggerFactory;
9+
import org.springframework.http.ResponseEntity;
10+
import org.springframework.web.bind.annotation.*;
11+
12+
import java.io.File;
13+
import java.io.IOException;
14+
import java.nio.charset.StandardCharsets;
15+
import java.nio.file.Files;
16+
import java.nio.file.Path;
17+
import java.util.HashMap;
18+
import java.util.Map;
19+
20+
@RestController
21+
@RequestMapping("/api/alerts")
22+
@Tag(name = "Alerts", description = "Alert definition management")
23+
public class AlertController {
24+
25+
private static final Logger log = LoggerFactory.getLogger(AlertController.class);
26+
private final AlertStorageService storage;
27+
28+
public AlertController(AlertStorageService storage) {
29+
this.storage = storage;
30+
}
31+
32+
// VULNERABLE: Path traversal via filename
33+
@GetMapping("/content")
34+
@Operation(summary = "Get alert definition", description = "Read alert definition file")
35+
public ResponseEntity<Map<String, Object>> getAlert(
36+
@Parameter(description = "Alert filename") @RequestParam(defaultValue = "alert-cpu-high.yml") String filename) {
37+
38+
String filePath = storage.buildFilePath(filename);
39+
File file = new File(filePath);
40+
41+
if (!file.exists()) {
42+
return ResponseEntity.status(404).body(Map.of("error", "Alert not found", "filename", filename));
43+
}
44+
45+
try {
46+
String content = Files.readString(file.toPath(), StandardCharsets.UTF_8);
47+
Map<String, Object> result = new HashMap<>();
48+
result.put("filename", filename);
49+
result.put("content", content);
50+
result.put("path", filePath);
51+
result.put("size", file.length());
52+
return ResponseEntity.ok(result);
53+
} catch (IOException e) {
54+
return ResponseEntity.internalServerError().body(Map.of("error", "Failed to read alert"));
55+
}
56+
}
57+
58+
// VULNERABLE: Path traversal via filename
59+
@PostMapping("/save")
60+
@Operation(summary = "Save alert definition", description = "Create or update alert definition file")
61+
public ResponseEntity<Map<String, Object>> saveAlert(
62+
@Parameter(description = "Alert filename") @RequestParam(defaultValue = "alert-custom.yml") String filename,
63+
@Parameter(description = "Alert content") @RequestParam(defaultValue = "name: Custom Alert\ntype: threshold") String content) {
64+
65+
String filePath = storage.buildFilePath(filename);
66+
File file = new File(filePath);
67+
68+
try {
69+
File parent = file.getParentFile();
70+
if (parent != null && !parent.exists()) {
71+
parent.mkdirs();
72+
}
73+
Files.writeString(file.toPath(), content, StandardCharsets.UTF_8);
74+
return ResponseEntity.ok(Map.of("status", "saved", "filename", filename, "path", filePath, "size", file.length()));
75+
} catch (IOException e) {
76+
return ResponseEntity.internalServerError().body(Map.of("error", "Failed to save alert"));
77+
}
78+
}
79+
80+
// SECURE: Path validation
81+
@GetMapping("/secureContent")
82+
@Operation(summary = "Get alert definition (secure)", description = "Read alert with path validation")
83+
public ResponseEntity<Map<String, Object>> getAlertSecure(
84+
@Parameter(description = "Alert name") @RequestParam(defaultValue = "cpu-high") String alertName) {
85+
86+
if (!storage.isValidAlertName(alertName)) {
87+
return ResponseEntity.badRequest().body(Map.of("error", "Invalid alert name"));
88+
}
89+
90+
Path alertPath = storage.buildSecurePath(alertName);
91+
if (!storage.isPathWithinBase(alertPath)) {
92+
return ResponseEntity.badRequest().body(Map.of("error", "Invalid alert path"));
93+
}
94+
95+
File file = alertPath.toFile();
96+
if (!file.exists()) {
97+
return ResponseEntity.status(404).body(Map.of("error", "Alert not found", "name", alertName));
98+
}
99+
100+
try {
101+
String content = Files.readString(alertPath, StandardCharsets.UTF_8);
102+
return ResponseEntity.ok(Map.of("name", alertName, "content", content, "size", file.length()));
103+
} catch (IOException e) {
104+
return ResponseEntity.internalServerError().body(Map.of("error", "Failed to read alert"));
105+
}
106+
}
107+
108+
// SECURE: Path validation
109+
@PostMapping("/secureSave")
110+
@Operation(summary = "Save alert definition (secure)", description = "Save alert with path validation")
111+
public ResponseEntity<Map<String, Object>> saveAlertSecure(
112+
@Parameter(description = "Alert name") @RequestParam(defaultValue = "custom-alert") String alertName,
113+
@Parameter(description = "Alert content") @RequestParam(defaultValue = "name: Custom Alert\ntype: threshold") String content) {
114+
115+
if (!storage.isValidAlertName(alertName)) {
116+
return ResponseEntity.badRequest().body(Map.of("error", "Invalid alert name"));
117+
}
118+
119+
Path alertPath = storage.buildSecurePath(alertName);
120+
if (!storage.isPathWithinBase(alertPath)) {
121+
return ResponseEntity.badRequest().body(Map.of("error", "Invalid alert path"));
122+
}
123+
124+
try {
125+
Files.writeString(alertPath, content, StandardCharsets.UTF_8);
126+
return ResponseEntity.ok(Map.of("status", "saved", "name", alertName, "size", Files.size(alertPath)));
127+
} catch (IOException e) {
128+
return ResponseEntity.internalServerError().body(Map.of("error", "Failed to save alert"));
129+
}
130+
}
131+
132+
@GetMapping("/list")
133+
@Operation(summary = "List alerts", description = "Get all alert definitions")
134+
public ResponseEntity<Map<String, Object>> listAlerts() {
135+
var alerts = storage.listAlerts();
136+
return ResponseEntity.ok(Map.of("alerts", alerts, "total", alerts.size(), "basePath", storage.getBasePath()));
137+
}
138+
}
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
package org.example.controller;
2+
3+
import io.swagger.v3.oas.annotations.Operation;
4+
import io.swagger.v3.oas.annotations.Parameter;
5+
import io.swagger.v3.oas.annotations.tags.Tag;
6+
import org.springframework.http.ResponseEntity;
7+
import org.springframework.web.bind.annotation.*;
8+
9+
import java.util.ArrayList;
10+
import java.util.List;
11+
import java.util.Map;
12+
import java.util.UUID;
13+
14+
@RestController
15+
@RequestMapping("/api/audit")
16+
@Tag(name = "Audit", description = "Audit logging and history")
17+
public class AuditController {
18+
19+
@GetMapping("/logs")
20+
@Operation(summary = "Get audit log detail", description = "Returns detailed audit log entry")
21+
public ResponseEntity<Map<String, Object>> getLogDetail(
22+
@Parameter(description = "Log ID") @RequestParam(required = false) String id,
23+
@Parameter(description = "Page number") @RequestParam(defaultValue = "1") int page,
24+
@Parameter(description = "Page size") @RequestParam(defaultValue = "20") int size,
25+
@Parameter(description = "Filter by action") @RequestParam(required = false) String action) {
26+
27+
if (id != null) {
28+
return ResponseEntity.ok(Map.of(
29+
"id", id,
30+
"timestamp", System.currentTimeMillis(),
31+
"action", "update",
32+
"resource", "monitor",
33+
"resourceId", "mon-123",
34+
"user", "admin",
35+
"ip", "192.168.1.100",
36+
"userAgent", "Mozilla/5.0",
37+
"status", "success",
38+
"details", Map.of(
39+
"before", Map.of("status", "inactive"),
40+
"after", Map.of("status", "active")
41+
)
42+
));
43+
}
44+
45+
int safePage = Math.max(page, 1);
46+
int safeSize = Math.min(Math.max(size, 1), 100);
47+
48+
List<Map<String, Object>> logs = new ArrayList<>();
49+
String[] actions = {"login", "logout", "create", "update", "delete", "view"};
50+
String[] resources = {"monitor", "alert", "user", "config", "report"};
51+
52+
for (int i = 0; i < safeSize; i++) {
53+
String logAction = actions[i % actions.length];
54+
if (action == null || action.equals(logAction)) {
55+
logs.add(Map.of(
56+
"id", UUID.randomUUID().toString().substring(0, 8),
57+
"timestamp", System.currentTimeMillis() - (i * 300000L),
58+
"action", logAction,
59+
"resource", resources[i % resources.length],
60+
"user", "admin",
61+
"ip", "192.168.1." + (100 + i % 50),
62+
"status", "success"
63+
));
64+
}
65+
}
66+
67+
return ResponseEntity.ok(Map.of(
68+
"logs", logs,
69+
"page", safePage,
70+
"size", safeSize,
71+
"total", 1000,
72+
"totalPages", 50
73+
));
74+
}
75+
76+
77+
78+
@GetMapping("/summary")
79+
@Operation(summary = "Get audit summary", description = "Returns audit statistics summary")
80+
public ResponseEntity<Map<String, Object>> getSummary(
81+
@Parameter(description = "Time period") @RequestParam(defaultValue = "24h") String period) {
82+
return ResponseEntity.ok(Map.of(
83+
"period", period,
84+
"totalEvents", 1523,
85+
"byAction", Map.of(
86+
"login", 245,
87+
"logout", 230,
88+
"create", 156,
89+
"update", 489,
90+
"delete", 45,
91+
"view", 358
92+
),
93+
"byStatus", Map.of(
94+
"success", 1498,
95+
"failure", 25
96+
),
97+
"uniqueUsers", 12
98+
));
99+
}
100+
101+
@GetMapping("/users")
102+
@Operation(summary = "Get user audit history", description = "Returns audit logs for specific user")
103+
public ResponseEntity<Map<String, Object>> getUserAudit(
104+
@Parameter(description = "User ID") @RequestParam String userId,
105+
@Parameter(description = "Limit") @RequestParam(defaultValue = "20") int limit) {
106+
107+
int safeLimit = Math.min(Math.max(limit, 1), 100);
108+
List<Map<String, Object>> logs = new ArrayList<>();
109+
110+
for (int i = 0; i < safeLimit; i++) {
111+
logs.add(Map.of(
112+
"id", UUID.randomUUID().toString().substring(0, 8),
113+
"timestamp", System.currentTimeMillis() - (i * 600000L),
114+
"action", i % 2 == 0 ? "view" : "update",
115+
"resource", "monitor",
116+
"status", "success"
117+
));
118+
}
119+
120+
return ResponseEntity.ok(Map.of("userId", userId, "logs", logs, "total", logs.size()));
121+
}
122+
123+
@GetMapping("/actions")
124+
@Operation(summary = "List audit actions", description = "Returns available audit action types")
125+
public ResponseEntity<Map<String, Object>> listActions() {
126+
return ResponseEntity.ok(Map.of(
127+
"actions", List.of(
128+
Map.of("id", "login", "name", "Login", "category", "auth"),
129+
Map.of("id", "logout", "name", "Logout", "category", "auth"),
130+
Map.of("id", "create", "name", "Create", "category", "crud"),
131+
Map.of("id", "update", "name", "Update", "category", "crud"),
132+
Map.of("id", "delete", "name", "Delete", "category", "crud"),
133+
Map.of("id", "view", "name", "View", "category", "crud")
134+
),
135+
"total", 6
136+
));
137+
}
138+
139+
@PostMapping("/export")
140+
@Operation(summary = "Export audit logs", description = "Exports audit logs in specified format")
141+
public ResponseEntity<Map<String, Object>> exportLogs(
142+
@Parameter(description = "Export format") @RequestParam(defaultValue = "json") String format,
143+
@Parameter(description = "Start date") @RequestParam(required = false) String startDate,
144+
@Parameter(description = "End date") @RequestParam(required = false) String endDate) {
145+
return ResponseEntity.ok(Map.of(
146+
"exportId", UUID.randomUUID().toString().substring(0, 8),
147+
"format", format,
148+
"status", "processing",
149+
"estimatedRecords", 1523,
150+
"requestedAt", System.currentTimeMillis()
151+
));
152+
}
153+
}

0 commit comments

Comments
 (0)