GET /documents listet alle Dokumente und machte damit jede UUID auffindbar — das Zugriffsmodell "unerratbarer Link" waere hinfaellig. Der Endpunkt verlangt jetzt HTTP Basic; alles andere bleibt bewusst offen. Ohne konfigurierten Hash ist die Liste ausdruecklich gesperrt (denyAll), nicht offen: Ein vergessener Umgebungswert gaebe sonst jede UUID preis, und niemandem fiele es auf, weil alles funktioniert. Bewusst als Regel und nicht bloss als zufaelliges Passwort, das niemand kennt — der Unterschied ist pruefbar: Mit dem Zufallspasswort blieb die Gegenprobe stumm, mit denyAll faellt genau die danach benannte Zusicherung. Die Sperre nach Fehlversuchen ist global statt je Adresse: Es gibt genau ein Passwort, und hinter dem Reverse Proxy der Zielumgebung saehe der Server ohnehin fuer alle dieselbe Adresse. Preis benannt (D76-Nachtrag 6). 135 Tests. Gegenproben: Schutz entfernt, Sperre entfernt, Voreinstellung geoeffnet -> es faellt jeweils genau die danach benannte. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
140 lines
4.7 KiB
Kotlin
140 lines
4.7 KiB
Kotlin
plugins {
|
|
kotlin("jvm") version "2.2.20"
|
|
kotlin("plugin.spring") version "2.2.20"
|
|
kotlin("plugin.jpa") version "2.2.20"
|
|
id("org.springframework.boot") version "4.0.2"
|
|
id("io.spring.dependency-management") version "1.1.7"
|
|
id("org.openapi.generator") version "7.25.0"
|
|
jacoco
|
|
}
|
|
|
|
group = "de.werkbaum"
|
|
version = "0.1.0-SNAPSHOT"
|
|
|
|
java {
|
|
toolchain {
|
|
languageVersion = JavaLanguageVersion.of(21)
|
|
}
|
|
}
|
|
|
|
repositories {
|
|
mavenCentral()
|
|
}
|
|
|
|
val cucumberVersion = "7.23.0"
|
|
val mockkVersion = "1.13.16"
|
|
val kotestVersion = "5.9.1"
|
|
|
|
dependencies {
|
|
implementation("org.springframework.boot:spring-boot-starter-web")
|
|
implementation("org.springframework.boot:spring-boot-starter-validation")
|
|
// Nur fuer das Master-Passwort der Dokumentenliste (D76); alles andere
|
|
// bleibt ueber die unerratbare UUID erreichbar.
|
|
implementation("org.springframework.boot:spring-boot-starter-security")
|
|
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
|
|
// Boot 4: Liquibase-Autokonfiguration liegt im eigenen Starter (zieht liquibase-core mit)
|
|
implementation("org.springframework.boot:spring-boot-starter-liquibase")
|
|
runtimeOnly("com.h2database:h2")
|
|
implementation("org.jetbrains.kotlin:kotlin-reflect")
|
|
// Jackson 3 (Standard in Spring Boot 4) + Kotlin-Modul
|
|
implementation("tools.jackson.module:jackson-module-kotlin")
|
|
|
|
// --- Tests ---
|
|
testImplementation("org.springframework.boot:spring-boot-starter-test")
|
|
// RestTestClient-Autokonfiguration (Boot 4). TestRestTemplate gilt dort als
|
|
// Auslaufmodell; die BDD-Tests nutzen den Nachfolger aus spring-test.
|
|
testImplementation("org.springframework.boot:spring-boot-resttestclient")
|
|
testImplementation("io.mockk:mockk:$mockkVersion")
|
|
// Nur die Assertions - Testrunner bleibt JUnit 5 (backend/CLAUDE.md)
|
|
testImplementation("io.kotest:kotest-assertions-core:$kotestVersion")
|
|
|
|
// Behavior-Tests (BDD) mit Cucumber
|
|
testImplementation("io.cucumber:cucumber-java:$cucumberVersion")
|
|
testImplementation("io.cucumber:cucumber-spring:$cucumberVersion")
|
|
testImplementation("io.cucumber:cucumber-junit-platform-engine:$cucumberVersion")
|
|
testImplementation("org.junit.platform:junit-platform-suite")
|
|
testImplementation("org.springframework.security:spring-security-test")
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// API First: Code-Generierung aus der OpenAPI-Spezifikation
|
|
// ---------------------------------------------------------------------------
|
|
openApiGenerate {
|
|
generatorName.set("kotlin-spring")
|
|
inputSpec.set("$projectDir/src/main/resources/openapi/api.yaml")
|
|
outputDir.set(layout.buildDirectory.dir("generated/openapi").get().asFile.path)
|
|
apiPackage.set("de.werkbaum.generated.api")
|
|
modelPackage.set("de.werkbaum.generated.model")
|
|
configOptions.set(
|
|
mapOf(
|
|
"useSpringBoot4" to "true",
|
|
"interfaceOnly" to "true", // nur Interfaces + Modelle, Implementierung liegt bei uns
|
|
"skipDefaultInterface" to "true", // Controller MUSS alle Operationen implementieren
|
|
"useTags" to "true", // Interface-Name aus Tag: DocumentsApi
|
|
"useBeanValidation" to "true",
|
|
"documentationProvider" to "none",
|
|
"enumPropertyNaming" to "UPPERCASE",
|
|
"gradleBuildFile" to "false",
|
|
)
|
|
)
|
|
}
|
|
|
|
sourceSets {
|
|
main {
|
|
kotlin {
|
|
srcDir(layout.buildDirectory.dir("generated/openapi/src/main/kotlin"))
|
|
}
|
|
}
|
|
}
|
|
|
|
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().configureEach {
|
|
dependsOn(tasks.openApiGenerate)
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Tests + Code Coverage
|
|
// ---------------------------------------------------------------------------
|
|
tasks.test {
|
|
useJUnitPlatform()
|
|
finalizedBy(tasks.jacocoTestReport)
|
|
}
|
|
|
|
jacoco {
|
|
toolVersion = "0.8.13"
|
|
}
|
|
|
|
tasks.jacocoTestReport {
|
|
dependsOn(tasks.test)
|
|
reports {
|
|
xml.required = true
|
|
html.required = true
|
|
}
|
|
// Generierter Code zaehlt nicht zur Coverage
|
|
classDirectories.setFrom(
|
|
classDirectories.files.map {
|
|
fileTree(it) { exclude("de/werkbaum/generated/**") }
|
|
}
|
|
)
|
|
}
|
|
|
|
tasks.jacocoTestCoverageVerification {
|
|
dependsOn(tasks.test)
|
|
classDirectories.setFrom(
|
|
classDirectories.files.map {
|
|
fileTree(it) { exclude("de/werkbaum/generated/**") }
|
|
}
|
|
)
|
|
violationRules {
|
|
rule {
|
|
limit {
|
|
counter = "LINE"
|
|
minimum = "0.80".toBigDecimal()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
tasks.check {
|
|
dependsOn(tasks.jacocoTestCoverageVerification)
|
|
}
|