diff --git a/cameleer-server-app/src/test/java/com/cameleer/server/core/license/LicenseGateTest.java b/cameleer-server-app/src/test/java/com/cameleer/server/core/license/LicenseGateTest.java index 95186fdd..45772531 100644 --- a/cameleer-server-app/src/test/java/com/cameleer/server/core/license/LicenseGateTest.java +++ b/cameleer-server-app/src/test/java/com/cameleer/server/core/license/LicenseGateTest.java @@ -5,37 +5,28 @@ import org.junit.jupiter.api.Test; import java.time.Instant; import java.time.temporal.ChronoUnit; import java.util.Map; -import java.util.Set; import static org.assertj.core.api.Assertions.assertThat; class LicenseGateTest { @Test - void noLicense_allFeaturesEnabled() { + void noLicense_returnsOpenTier() { LicenseGate gate = new LicenseGate(); - // No license loaded -> open mode - - assertThat(gate.isEnabled(Feature.debugger)).isTrue(); - assertThat(gate.isEnabled(Feature.replay)).isTrue(); - assertThat(gate.isEnabled(Feature.lineage)).isTrue(); assertThat(gate.getTier()).isEqualTo("open"); + assertThat(gate.getLimit("max_apps", 99)).isEqualTo(99); } @Test - void withLicense_onlyLicensedFeaturesEnabled() { + void loaded_exposesLimits() { LicenseGate gate = new LicenseGate(); - LicenseInfo license = new LicenseInfo("MID", - Set.of(Feature.topology, Feature.lineage, Feature.correlation), + LicenseInfo info = new LicenseInfo("MID", Map.of("max_agents", 10, "retention_days", 30), Instant.now(), Instant.now().plus(365, ChronoUnit.DAYS)); - gate.load(license); + gate.load(info); - assertThat(gate.isEnabled(Feature.topology)).isTrue(); - assertThat(gate.isEnabled(Feature.lineage)).isTrue(); - assertThat(gate.isEnabled(Feature.debugger)).isFalse(); - assertThat(gate.isEnabled(Feature.replay)).isFalse(); assertThat(gate.getTier()).isEqualTo("MID"); assertThat(gate.getLimit("max_agents", 0)).isEqualTo(10); + assertThat(gate.getLimit("missing", 7)).isEqualTo(7); } } diff --git a/cameleer-server-app/src/test/java/com/cameleer/server/core/license/LicenseValidatorTest.java b/cameleer-server-app/src/test/java/com/cameleer/server/core/license/LicenseValidatorTest.java index d321a2b0..00c698e7 100644 --- a/cameleer-server-app/src/test/java/com/cameleer/server/core/license/LicenseValidatorTest.java +++ b/cameleer-server-app/src/test/java/com/cameleer/server/core/license/LicenseValidatorTest.java @@ -33,7 +33,7 @@ class LicenseValidatorTest { Instant expires = Instant.now().plus(365, ChronoUnit.DAYS); String payload = """ - {"tier":"HIGH","features":["topology","lineage","debugger"],"limits":{"max_agents":50,"retention_days":90},"iat":%d,"exp":%d} + {"tier":"HIGH","limits":{"max_agents":50,"retention_days":90},"iat":%d,"exp":%d} """.formatted(Instant.now().getEpochSecond(), expires.getEpochSecond()).trim(); String signature = sign(kp.getPrivate(), payload); String token = Base64.getEncoder().encodeToString(payload.getBytes()) + "." + signature; @@ -41,8 +41,6 @@ class LicenseValidatorTest { LicenseInfo info = validator.validate(token); assertThat(info.tier()).isEqualTo("HIGH"); - assertThat(info.hasFeature(Feature.debugger)).isTrue(); - assertThat(info.hasFeature(Feature.replay)).isFalse(); assertThat(info.getLimit("max_agents", 0)).isEqualTo(50); assertThat(info.isExpired()).isFalse(); } @@ -55,7 +53,7 @@ class LicenseValidatorTest { Instant past = Instant.now().minus(1, ChronoUnit.DAYS); String payload = """ - {"tier":"LOW","features":["topology"],"limits":{},"iat":%d,"exp":%d} + {"tier":"LOW","limits":{},"iat":%d,"exp":%d} """.formatted(past.minus(30, ChronoUnit.DAYS).getEpochSecond(), past.getEpochSecond()).trim(); String signature = sign(kp.getPrivate(), payload); String token = Base64.getEncoder().encodeToString(payload.getBytes()) + "." + signature; @@ -72,7 +70,7 @@ class LicenseValidatorTest { LicenseValidator validator = new LicenseValidator(publicKeyBase64); String payload = """ - {"tier":"LOW","features":["topology"],"limits":{},"iat":0,"exp":9999999999} + {"tier":"LOW","limits":{},"iat":0,"exp":9999999999} """.trim(); String signature = sign(kp.getPrivate(), payload); diff --git a/cameleer-server-core/src/main/java/com/cameleer/server/core/license/Feature.java b/cameleer-server-core/src/main/java/com/cameleer/server/core/license/Feature.java deleted file mode 100644 index 72e74df4..00000000 --- a/cameleer-server-core/src/main/java/com/cameleer/server/core/license/Feature.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.cameleer.server.core.license; - -public enum Feature { - topology, - lineage, - correlation, - debugger, - replay -} diff --git a/cameleer-server-core/src/main/java/com/cameleer/server/core/license/LicenseGate.java b/cameleer-server-core/src/main/java/com/cameleer/server/core/license/LicenseGate.java index b6ca83fa..245c8be0 100644 --- a/cameleer-server-core/src/main/java/com/cameleer/server/core/license/LicenseGate.java +++ b/cameleer-server-core/src/main/java/com/cameleer/server/core/license/LicenseGate.java @@ -13,12 +13,8 @@ public class LicenseGate { public void load(LicenseInfo license) { current.set(license); - log.info("License loaded: tier={}, features={}, expires={}", - license.tier(), license.features(), license.expiresAt()); - } - - public boolean isEnabled(Feature feature) { - return current.get().hasFeature(feature); + log.info("License loaded: tier={}, limits={}, expires={}", + license.tier(), license.limits(), license.expiresAt()); } public String getTier() { diff --git a/cameleer-server-core/src/main/java/com/cameleer/server/core/license/LicenseInfo.java b/cameleer-server-core/src/main/java/com/cameleer/server/core/license/LicenseInfo.java index 2940e3e6..ead03616 100644 --- a/cameleer-server-core/src/main/java/com/cameleer/server/core/license/LicenseInfo.java +++ b/cameleer-server-core/src/main/java/com/cameleer/server/core/license/LicenseInfo.java @@ -2,11 +2,9 @@ package com.cameleer.server.core.license; import java.time.Instant; import java.util.Map; -import java.util.Set; public record LicenseInfo( String tier, - Set features, Map limits, Instant issuedAt, Instant expiresAt @@ -15,16 +13,11 @@ public record LicenseInfo( return expiresAt != null && Instant.now().isAfter(expiresAt); } - public boolean hasFeature(Feature feature) { - return features.contains(feature); - } - public int getLimit(String key, int defaultValue) { return limits.getOrDefault(key, defaultValue); } - /** Open license — all features enabled, no limits. Used when no license is configured. */ public static LicenseInfo open() { - return new LicenseInfo("open", Set.of(Feature.values()), Map.of(), Instant.now(), null); + return new LicenseInfo("open", Map.of(), Instant.now(), null); } } diff --git a/cameleer-server-core/src/main/java/com/cameleer/server/core/license/LicenseValidator.java b/cameleer-server-core/src/main/java/com/cameleer/server/core/license/LicenseValidator.java index 07e29043..7436f941 100644 --- a/cameleer-server-core/src/main/java/com/cameleer/server/core/license/LicenseValidator.java +++ b/cameleer-server-core/src/main/java/com/cameleer/server/core/license/LicenseValidator.java @@ -56,17 +56,6 @@ public class LicenseValidator { String tier = root.get("tier").asText(); - Set features = new HashSet<>(); - if (root.has("features")) { - for (JsonNode f : root.get("features")) { - try { - features.add(Feature.valueOf(f.asText())); - } catch (IllegalArgumentException e) { - log.warn("Unknown feature in license: {}", f.asText()); - } - } - } - Map limits = new HashMap<>(); if (root.has("limits")) { root.get("limits").fields().forEachRemaining(entry -> @@ -76,7 +65,7 @@ public class LicenseValidator { Instant issuedAt = root.has("iat") ? Instant.ofEpochSecond(root.get("iat").asLong()) : Instant.now(); Instant expiresAt = root.has("exp") ? Instant.ofEpochSecond(root.get("exp").asLong()) : null; - LicenseInfo info = new LicenseInfo(tier, features, limits, issuedAt, expiresAt); + LicenseInfo info = new LicenseInfo(tier, limits, issuedAt, expiresAt); if (info.isExpired()) { throw new IllegalArgumentException("License expired at " + expiresAt);