Splits the pure license contract types (LicenseInfo, LicenseValidator, LicenseState, LicenseStateMachine, LicenseLimits, DefaultTierLimits) into a new cameleer-license-api module under package com.cameleer.license. Why: cameleer-license-minter previously depended on cameleer-server-core for these types, dragging cameleer-server-core + cameleer-common onto the classpath of every minter consumer (notably cameleer-saas). The SaaS management plane has no business carrying server-runtime types — it only needs the license contract to mint and verify tokens. After: cameleer-license-minter -> cameleer-license-api (no server internals) cameleer-server-core -> cameleer-license-api cameleer-saas -> cameleer-license-minter -> cameleer-license-api Verified: mvn -pl cameleer-license-minter dependency:tree shows the minter no longer pulls cameleer-server-core or cameleer-common. Full reactor verify (-DskipITs) green: 371 tests pass. LicenseGate stays in server-core (server-runtime state holder, not contract). Closes cameleer/cameleer-server#156 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
140 lines
5.0 KiB
Java
140 lines
5.0 KiB
Java
package com.cameleer.server.app;
|
|
|
|
import com.cameleer.server.core.agent.AgentRegistryService;
|
|
import com.cameleer.server.core.license.LicenseGate;
|
|
import com.cameleer.license.LicenseInfo;
|
|
import com.cameleer.server.core.security.JwtService;
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
|
import org.springframework.http.HttpHeaders;
|
|
import org.springframework.http.MediaType;
|
|
import org.springframework.stereotype.Component;
|
|
|
|
import java.time.Instant;
|
|
import java.time.temporal.ChronoUnit;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.UUID;
|
|
|
|
/**
|
|
* Test utility for creating JWT-authenticated requests in integration tests.
|
|
* <p>
|
|
* Registers a test agent and issues a JWT access token that can be used
|
|
* to authenticate against protected endpoints.
|
|
*/
|
|
@Component
|
|
public class TestSecurityHelper {
|
|
|
|
private final JwtService jwtService;
|
|
private final AgentRegistryService agentRegistryService;
|
|
private final LicenseGate licenseGate;
|
|
|
|
@Autowired
|
|
public TestSecurityHelper(JwtService jwtService,
|
|
AgentRegistryService agentRegistryService,
|
|
LicenseGate licenseGate) {
|
|
this.jwtService = jwtService;
|
|
this.agentRegistryService = agentRegistryService;
|
|
this.licenseGate = licenseGate;
|
|
}
|
|
|
|
/**
|
|
* Loads a synthetic, signature-bypassing license into {@link LicenseGate} so the test can
|
|
* exercise paths that would otherwise be rejected by default-tier caps. The license is
|
|
* always-ACTIVE (1 day from now, no grace) and limits are merged over defaults — only
|
|
* supply the keys you want to lift. Use this from {@code @BeforeEach} in ITs that need to
|
|
* create more than the default-tier allowance of envs/apps/users/etc.
|
|
*/
|
|
public void installSyntheticUnsignedLicense(Map<String, Integer> caps) {
|
|
LicenseInfo info = new LicenseInfo(
|
|
UUID.randomUUID(),
|
|
"default",
|
|
"test-license",
|
|
Map.copyOf(caps),
|
|
Instant.now(),
|
|
Instant.now().plus(1, ChronoUnit.DAYS),
|
|
0);
|
|
licenseGate.load(info);
|
|
}
|
|
|
|
/** Clears any test license previously installed via {@link #installSyntheticUnsignedLicense}. */
|
|
public void clearTestLicense() {
|
|
licenseGate.clear();
|
|
}
|
|
|
|
/**
|
|
* Registers a test agent and returns a valid JWT access token with AGENT role.
|
|
*/
|
|
public String registerTestAgent(String instanceId) {
|
|
agentRegistryService.register(instanceId, instanceId, "test-group", "default", "1.0", List.of(), Map.of());
|
|
return jwtService.createAccessToken(instanceId, "test-group", List.of("AGENT"));
|
|
}
|
|
|
|
/**
|
|
* Returns a valid JWT access token with the given roles (no agent registration).
|
|
*/
|
|
public String createToken(String subject, String application, List<String> roles) {
|
|
return jwtService.createAccessToken(subject, application, roles);
|
|
}
|
|
|
|
/**
|
|
* Returns a valid JWT access token with OPERATOR role.
|
|
*/
|
|
public String operatorToken() {
|
|
// Subject must start with "user:" for JwtAuthenticationFilter to treat it as a UI user token
|
|
return jwtService.createAccessToken("user:test-operator", "user", List.of("OPERATOR"));
|
|
}
|
|
|
|
/**
|
|
* Returns a valid JWT access token with ADMIN role.
|
|
*/
|
|
public String adminToken() {
|
|
return jwtService.createAccessToken("user:test-admin", "user", List.of("ADMIN"));
|
|
}
|
|
|
|
/**
|
|
* Returns a valid JWT access token with VIEWER role.
|
|
*/
|
|
public String viewerToken() {
|
|
return jwtService.createAccessToken("user:test-viewer", "user", List.of("VIEWER"));
|
|
}
|
|
|
|
/**
|
|
* Returns HttpHeaders with JWT Bearer authorization, protocol version, and JSON content type.
|
|
*/
|
|
public HttpHeaders authHeaders(String jwt) {
|
|
HttpHeaders headers = new HttpHeaders();
|
|
headers.set("Authorization", "Bearer " + jwt);
|
|
headers.set("X-Cameleer-Protocol-Version", "1");
|
|
headers.setContentType(MediaType.APPLICATION_JSON);
|
|
return headers;
|
|
}
|
|
|
|
/**
|
|
* Returns HttpHeaders with JWT Bearer authorization and protocol version (no content type).
|
|
*/
|
|
public HttpHeaders authHeadersNoBody(String jwt) {
|
|
HttpHeaders headers = new HttpHeaders();
|
|
headers.set("Authorization", "Bearer " + jwt);
|
|
headers.set("X-Cameleer-Protocol-Version", "1");
|
|
return headers;
|
|
}
|
|
|
|
/**
|
|
* Returns HttpHeaders with ADMIN JWT Bearer authorization, protocol version, and JSON content type.
|
|
*/
|
|
public HttpHeaders adminHeaders() {
|
|
return authHeaders(adminToken());
|
|
}
|
|
|
|
/**
|
|
* Returns HttpHeaders with bootstrap token authorization, protocol version, and JSON content type.
|
|
*/
|
|
public HttpHeaders bootstrapHeaders() {
|
|
HttpHeaders headers = new HttpHeaders();
|
|
headers.set("Authorization", "Bearer test-bootstrap-token");
|
|
headers.set("X-Cameleer-Protocol-Version", "1");
|
|
headers.setContentType(MediaType.APPLICATION_JSON);
|
|
return headers;
|
|
}
|
|
}
|