feat: add SensitiveKeysMerger with case-insensitive union dedup

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-04-14 18:07:53 +02:00
parent 86b6c85aa7
commit d72a6511da
2 changed files with 111 additions and 0 deletions

View File

@@ -0,0 +1,45 @@
package com.cameleer3.server.core.admin;
import java.util.ArrayList;
import java.util.List;
import java.util.TreeSet;
/**
* Merges global (enforced) sensitive keys with per-app additions.
* Union-only: per-app can add keys, never remove global keys.
* Case-insensitive deduplication, preserves first-seen casing.
*/
public final class SensitiveKeysMerger {
private SensitiveKeysMerger() {}
/**
* @param global enforced global keys (null = not configured)
* @param perApp per-app additional keys (null = none)
* @return merged list, or null if both inputs are null
*/
public static List<String> merge(List<String> global, List<String> perApp) {
if (global == null && perApp == null) {
return null;
}
TreeSet<String> seen = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
List<String> result = new ArrayList<>();
if (global != null) {
for (String key : global) {
if (seen.add(key)) {
result.add(key);
}
}
}
if (perApp != null) {
for (String key : perApp) {
if (seen.add(key)) {
result.add(key);
}
}
}
return result;
}
}

View File

@@ -0,0 +1,66 @@
package com.cameleer3.server.core.admin;
import org.junit.jupiter.api.Test;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
class SensitiveKeysMergerTest {
@Test
void bothNull_returnsNull() {
assertNull(SensitiveKeysMerger.merge(null, null));
}
@Test
void globalOnly_returnsGlobal() {
List<String> result = SensitiveKeysMerger.merge(
List.of("Authorization", "Cookie"), null);
assertEquals(List.of("Authorization", "Cookie"), result);
}
@Test
void perAppOnly_returnsPerApp() {
List<String> result = SensitiveKeysMerger.merge(
null, List.of("X-Internal-*"));
assertEquals(List.of("X-Internal-*"), result);
}
@Test
void union_mergesWithoutDuplicates() {
List<String> result = SensitiveKeysMerger.merge(
List.of("Authorization", "Cookie"),
List.of("Cookie", "X-Custom"));
assertEquals(List.of("Authorization", "Cookie", "X-Custom"), result);
}
@Test
void caseInsensitiveDedup_preservesFirstCasing() {
List<String> result = SensitiveKeysMerger.merge(
List.of("Authorization"),
List.of("authorization", "AUTHORIZATION"));
assertEquals(List.of("Authorization"), result);
}
@Test
void emptyGlobal_returnsEmptyList() {
List<String> result = SensitiveKeysMerger.merge(List.of(), null);
assertEquals(List.of(), result);
}
@Test
void emptyGlobalWithPerApp_returnsPerApp() {
List<String> result = SensitiveKeysMerger.merge(
List.of(), List.of("X-Custom"));
assertEquals(List.of("X-Custom"), result);
}
@Test
void globPatterns_preserved() {
List<String> result = SensitiveKeysMerger.merge(
List.of("*password*", "*secret*"),
List.of("X-Internal-*"));
assertEquals(List.of("*password*", "*secret*", "X-Internal-*"), result);
}
}