feat: implement ClaimMappingService with equals/contains/regex matching

- Evaluates JWT claims against mapping rules
- Supports equals, contains (list + space-separated), regex match types
- Results sorted by priority
- 7 unit tests covering all match types and edge cases

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-04-07 23:09:50 +02:00
parent b5cf35ef9a
commit e1cb17707b
2 changed files with 181 additions and 0 deletions

View File

@@ -0,0 +1,66 @@
package com.cameleer3.server.core.rbac;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;
import java.util.regex.Pattern;
public class ClaimMappingService {
private static final Logger log = LoggerFactory.getLogger(ClaimMappingService.class);
public record MappingResult(ClaimMappingRule rule) {}
public List<MappingResult> evaluate(List<ClaimMappingRule> rules, Map<String, Object> claims) {
return rules.stream()
.sorted(Comparator.comparingInt(ClaimMappingRule::priority))
.filter(rule -> matches(rule, claims))
.map(MappingResult::new)
.toList();
}
private boolean matches(ClaimMappingRule rule, Map<String, Object> claims) {
Object claimValue = claims.get(rule.claim());
if (claimValue == null) return false;
return switch (rule.matchType()) {
case "equals" -> equalsMatch(claimValue, rule.matchValue());
case "contains" -> containsMatch(claimValue, rule.matchValue());
case "regex" -> regexMatch(claimValue, rule.matchValue());
default -> {
log.warn("Unknown match type: {}", rule.matchType());
yield false;
}
};
}
private boolean equalsMatch(Object claimValue, String matchValue) {
if (claimValue instanceof String s) {
return s.equalsIgnoreCase(matchValue);
}
return String.valueOf(claimValue).equalsIgnoreCase(matchValue);
}
private boolean containsMatch(Object claimValue, String matchValue) {
if (claimValue instanceof List<?> list) {
return list.stream().anyMatch(item -> String.valueOf(item).equalsIgnoreCase(matchValue));
}
if (claimValue instanceof String s) {
// Space-separated string (e.g., OAuth2 scope claim)
return Arrays.stream(s.split("\\s+"))
.anyMatch(part -> part.equalsIgnoreCase(matchValue));
}
return false;
}
private boolean regexMatch(Object claimValue, String matchValue) {
String s = String.valueOf(claimValue);
try {
return Pattern.matches(matchValue, s);
} catch (Exception e) {
log.warn("Invalid regex in claim mapping rule: {}", matchValue, e);
return false;
}
}
}