feat: add SensitiveKeysMerger with case-insensitive union dedup
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user