package com.xfestudio.xfeservermanager.core.policy;
import com.xfestudio.xfeservermanager.api.policy.PolicyRule;
import com.xfestudio.xfeservermanager.api.policy.RuleTier;
import com.xfestudio.xfeservermanager.api.policy.DecisionEffect;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.HashMap;
import java.util.List;
import java.util.Set;
public final class PolicyValidator {
public PolicyValidationResult validate(List<PolicyRule> rules) {
List<PolicyValidationIssue> issues = new ArrayList<>();
Set<String> ids = new HashSet<>();
var groups = new HashMap<String, GroupDefinition>();
for (PolicyRule rule : rules) {
if (!ids.add(rule.id())) {
issues.add(issue(rule, "duplicate_rule_id", "Rule ids must be unique"));
}
validateTier(rule, issues);
validateConstraints(rule, issues);
validateGroup(rule, groups, issues);
}
return new PolicyValidationResult(issues);
}
private static void validateGroup(
PolicyRule rule,
java.util.Map<String, GroupDefinition> groups,
List<PolicyValidationIssue> issues) {
if (!rule.groupId().matches("[A-Za-z0-9._-]{1,64}")) {
issues.add(issue(rule, "invalid_group_id",
"Policy group ids must use 1-64 letters, digits, dots, underscores, or hyphens"));
}
if (rule.groupName().length() > 128) {
issues.add(issue(rule, "group_name_too_long", "Policy group names cannot exceed 128 characters"));
}
GroupDefinition definition = new GroupDefinition(rule.groupName(), rule.groupEnabled());
GroupDefinition existing = groups.putIfAbsent(rule.groupId(), definition);
if (existing != null && !existing.equals(definition)) {
issues.add(issue(rule, "inconsistent_group_metadata",
"Rules in the same policy group must share its name and enabled state"));
}
}
private static void validateTier(PolicyRule rule, List<PolicyValidationIssue> issues) {
if (rule.tier() == RuleTier.UUID && rule.subject().actorIds().isEmpty()) {
issues.add(issue(rule, "uuid_tier_without_uuid", "UUID rules must name at least one actor UUID"));
}
if (rule.tier() == RuleTier.ROLE && rule.subject().roles().isEmpty()) {
issues.add(issue(rule, "role_tier_without_role", "Role rules must name at least one role"));
}
if (rule.tier() == RuleTier.OP_LEVEL
&& rule.subject().minimumOpLevel() == null && rule.subject().maximumOpLevel() == null) {
issues.add(issue(rule, "op_tier_without_level", "OP rules must declare an OP level range"));
}
if (rule.tier() == RuleTier.DEFAULT
&& (!rule.subject().actorIds().isEmpty() || !rule.subject().roles().isEmpty()
|| rule.subject().minimumOpLevel() != null || rule.subject().maximumOpLevel() != null)) {
issues.add(issue(rule, "default_tier_with_identity", "Default rules cannot match UUID, role, or OP level"));
}
if (rule.tier() == RuleTier.HARD_SAFETY && rule.effect() == DecisionEffect.GRANT) {
issues.add(issue(rule, "hard_safety_grant", "Hard safety rules cannot grant permission"));
}
}
private static void validateConstraints(PolicyRule rule, List<PolicyValidationIssue> issues) {
Set<String> overlap = new HashSet<>(rule.constraints().selector().allowedEntityTypes());
overlap.retainAll(rule.constraints().selector().deniedEntityTypes());
if (!overlap.isEmpty()) {
issues.add(issue(rule, "entity_type_overlap", "Entity types cannot be both allowed and denied: " + overlap));
}
overlap = new HashSet<>(rule.constraints().item().allowedItemIds());
overlap.retainAll(rule.constraints().item().deniedItemIds());
if (!overlap.isEmpty()) {
issues.add(issue(rule, "item_id_overlap", "Items cannot be both allowed and denied: " + overlap));
}
overlap = new HashSet<>(rule.constraints().item().allowedItemTags());
overlap.retainAll(rule.constraints().item().deniedItemTags());
if (!overlap.isEmpty()) {
issues.add(issue(rule, "item_tag_overlap", "Item tags cannot be both allowed and denied: " + overlap));
}
overlap = new HashSet<>(rule.constraints().item().allowedDataKeys());
overlap.retainAll(rule.constraints().item().deniedDataKeys());
if (!overlap.isEmpty()) {
issues.add(issue(rule, "data_key_overlap", "Data keys cannot be both allowed and denied: " + overlap));
}
}
private static PolicyValidationIssue issue(PolicyRule rule, String code, String message) {
return new PolicyValidationIssue(rule.id(), code, message);
}
private record GroupDefinition(String name, Boolean enabled) { }
}
package com.xfestudio.xfeservermanager.core.policy;
import com.xfestudio.xfeservermanager.api.policy.PolicyRule;
import com.xfestudio.xfeservermanager.api.policy.RuleTier;
import com.xfestudio.xfeservermanager.api.policy.DecisionEffect;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.HashMap;
import java.util.List;
import java.util.Set;
public final class PolicyValidator {
public PolicyValidationResult validate(List<PolicyRule> rules) {
List<PolicyValidationIssue> issues = new ArrayList<>();
Set<String> ids = new HashSet<>();
var groups = new HashMap<String, GroupDefinition>();
for (PolicyRule rule : rules) {
if (!ids.add(rule.id())) {
issues.add(issue(rule, "duplicate_rule_id", "Rule ids must be unique"));
}
validateTier(rule, issues);
validateConstraints(rule, issues);
validateGroup(rule, groups, issues);
}
return new PolicyValidationResult(issues);
}
private static void validateGroup(
PolicyRule rule,
java.util.Map<String, GroupDefinition> groups,
List<PolicyValidationIssue> issues) {
if (!rule.groupId().matches("[A-Za-z0-9._-]{1,64}")) {
issues.add(issue(rule, "invalid_group_id",
"Policy group ids must use 1-64 letters, digits, dots, underscores, or hyphens"));
}
if (rule.groupName().length() > 128) {
issues.add(issue(rule, "group_name_too_long", "Policy group names cannot exceed 128 characters"));
}
GroupDefinition definition = new GroupDefinition(rule.groupName(), rule.groupEnabled());
GroupDefinition existing = groups.putIfAbsent(rule.groupId(), definition);
if (existing != null && !existing.equals(definition)) {
issues.add(issue(rule, "inconsistent_group_metadata",
"Rules in the same policy group must share its name and enabled state"));
}
}
private static void validateTier(PolicyRule rule, List<PolicyValidationIssue> issues) {
if (rule.tier() == RuleTier.UUID && rule.subject().actorIds().isEmpty()) {
issues.add(issue(rule, "uuid_tier_without_uuid", "UUID rules must name at least one actor UUID"));
}
if (rule.tier() == RuleTier.ROLE && rule.subject().roles().isEmpty()) {
issues.add(issue(rule, "role_tier_without_role", "Role rules must name at least one role"));
}
if (rule.tier() == RuleTier.OP_LEVEL
&& rule.subject().minimumOpLevel() == null && rule.subject().maximumOpLevel() == null) {
issues.add(issue(rule, "op_tier_without_level", "OP rules must declare an OP level range"));
}
if (rule.tier() == RuleTier.DEFAULT
&& (!rule.subject().actorIds().isEmpty() || !rule.subject().roles().isEmpty()
|| rule.subject().minimumOpLevel() != null || rule.subject().maximumOpLevel() != null)) {
issues.add(issue(rule, "default_tier_with_identity", "Default rules cannot match UUID, role, or OP level"));
}
if (rule.tier() == RuleTier.HARD_SAFETY && rule.effect() == DecisionEffect.GRANT) {
issues.add(issue(rule, "hard_safety_grant", "Hard safety rules cannot grant permission"));
}
}
private static void validateConstraints(PolicyRule rule, List<PolicyValidationIssue> issues) {
Set<String> overlap = new HashSet<>(rule.constraints().selector().allowedEntityTypes());
overlap.retainAll(rule.constraints().selector().deniedEntityTypes());
if (!overlap.isEmpty()) {
issues.add(issue(rule, "entity_type_overlap", "Entity types cannot be both allowed and denied: " + overlap));
}
overlap = new HashSet<>(rule.constraints().item().allowedItemIds());
overlap.retainAll(rule.constraints().item().deniedItemIds());
if (!overlap.isEmpty()) {
issues.add(issue(rule, "item_id_overlap", "Items cannot be both allowed and denied: " + overlap));
}
overlap = new HashSet<>(rule.constraints().item().allowedItemTags());
overlap.retainAll(rule.constraints().item().deniedItemTags());
if (!overlap.isEmpty()) {
issues.add(issue(rule, "item_tag_overlap", "Item tags cannot be both allowed and denied: " + overlap));
}
overlap = new HashSet<>(rule.constraints().item().allowedDataKeys());
overlap.retainAll(rule.constraints().item().deniedDataKeys());
if (!overlap.isEmpty()) {
issues.add(issue(rule, "data_key_overlap", "Data keys cannot be both allowed and denied: " + overlap));
}
}
private static PolicyValidationIssue issue(PolicyRule rule, String code, String message) {
return new PolicyValidationIssue(rule.id(), code, message);
}
private record GroupDefinition(String name, Boolean enabled) { }
}