refactor(license): LicenseAdminController delegates to LicenseService
GET returns {state, invalidReason, envelope, lastValidatedAt}. POST
delegates to licenseService.install(token, userId, "api") so install
goes through audit + persistence + event publish. Removes the inline
LicenseValidator construction from the controller.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,54 +1,71 @@
|
|||||||
package com.cameleer.server.app.controller;
|
package com.cameleer.server.app.controller;
|
||||||
|
|
||||||
|
import com.cameleer.server.app.license.LicenseRepository;
|
||||||
|
import com.cameleer.server.app.license.LicenseService;
|
||||||
import com.cameleer.server.core.license.LicenseGate;
|
import com.cameleer.server.core.license.LicenseGate;
|
||||||
import com.cameleer.server.core.license.LicenseInfo;
|
import com.cameleer.server.core.license.LicenseInfo;
|
||||||
import com.cameleer.server.core.license.LicenseValidator;
|
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.security.access.prepost.PreAuthorize;
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* License management for ADMIN users. All mutation goes through {@link LicenseService} so that
|
||||||
|
* install / replace flows are uniformly audited, persisted, and published to listeners (retention
|
||||||
|
* policy, license metrics, etc.).
|
||||||
|
*
|
||||||
|
* <p>GET returns {@code {state, invalidReason, envelope, lastValidatedAt?}}. The raw JWT-style
|
||||||
|
* token is deliberately omitted from the response — only the parsed {@link LicenseInfo} is
|
||||||
|
* exposed.</p>
|
||||||
|
*/
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/v1/admin/license")
|
@RequestMapping("/api/v1/admin/license")
|
||||||
@PreAuthorize("hasRole('ADMIN')")
|
@PreAuthorize("hasRole('ADMIN')")
|
||||||
@Tag(name = "License Admin", description = "License management")
|
@Tag(name = "License Admin", description = "License management")
|
||||||
public class LicenseAdminController {
|
public class LicenseAdminController {
|
||||||
|
|
||||||
private final LicenseGate licenseGate;
|
private final LicenseService licenseService;
|
||||||
private final String licensePublicKey;
|
private final LicenseGate gate;
|
||||||
private final String tenantId;
|
private final LicenseRepository repo;
|
||||||
|
|
||||||
public LicenseAdminController(LicenseGate licenseGate,
|
public LicenseAdminController(LicenseService svc, LicenseGate gate, LicenseRepository repo) {
|
||||||
@Value("${cameleer.server.license.publickey:}") String licensePublicKey,
|
this.licenseService = svc;
|
||||||
@Value("${cameleer.server.tenant.id:default}") String tenantId) {
|
this.gate = gate;
|
||||||
this.licenseGate = licenseGate;
|
this.repo = repo;
|
||||||
this.licensePublicKey = licensePublicKey;
|
|
||||||
this.tenantId = tenantId;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping
|
@GetMapping
|
||||||
@Operation(summary = "Get current license info")
|
@Operation(summary = "Get current license state, invalid reason, and parsed envelope")
|
||||||
public ResponseEntity<LicenseInfo> getCurrent() {
|
public ResponseEntity<Map<String, Object>> getCurrent() {
|
||||||
return ResponseEntity.ok(licenseGate.getCurrent());
|
Map<String, Object> body = new LinkedHashMap<>();
|
||||||
|
body.put("state", gate.getState().name());
|
||||||
|
body.put("invalidReason", gate.getInvalidReason());
|
||||||
|
body.put("envelope", gate.getCurrent()); // null when ABSENT/INVALID; raw token deliberately omitted
|
||||||
|
repo.findByTenantId(licenseService.getTenantId()).ifPresent(rec ->
|
||||||
|
body.put("lastValidatedAt", rec.lastValidatedAt().toString()));
|
||||||
|
return ResponseEntity.ok(body);
|
||||||
}
|
}
|
||||||
|
|
||||||
record UpdateLicenseRequest(String token) {}
|
public record UpdateLicenseRequest(String token) {}
|
||||||
|
|
||||||
@PostMapping
|
@PostMapping
|
||||||
@Operation(summary = "Update license token at runtime")
|
@Operation(summary = "Install or replace the license token at runtime")
|
||||||
public ResponseEntity<?> update(@RequestBody UpdateLicenseRequest request) {
|
public ResponseEntity<?> update(@RequestBody UpdateLicenseRequest request, Authentication auth) {
|
||||||
if (licensePublicKey == null || licensePublicKey.isBlank()) {
|
String userId = auth == null ? "system" : auth.getName().replaceFirst("^user:", "");
|
||||||
return ResponseEntity.badRequest().body(Map.of("error", "No license public key configured"));
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
LicenseValidator validator = new LicenseValidator(licensePublicKey, tenantId);
|
LicenseInfo info = licenseService.install(request.token(), userId, "api");
|
||||||
LicenseInfo info = validator.validate(request.token());
|
return ResponseEntity.ok(Map.of(
|
||||||
licenseGate.load(info);
|
"state", gate.getState().name(),
|
||||||
return ResponseEntity.ok(info);
|
"envelope", info));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
return ResponseEntity.badRequest().body(Map.of("error", e.getMessage()));
|
return ResponseEntity.badRequest().body(Map.of("error", e.getMessage()));
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user