feat: add tenant ownership check to license file parser
Require @AuthenticationPrincipal tenantId in parseLicenseFile endpoint and verify the decrypted appKey belongs to the current tenant before returning license contents. Returns 403 "权限不足无法展示" for cross-tenant license files. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
这个提交包含在:
父节点
94fda7ad6e
当前提交
8f2f29170e
@ -180,13 +180,24 @@ public class AppController {
|
|||||||
* Used by the security center to verify license file information.
|
* Used by the security center to verify license file information.
|
||||||
*/
|
*/
|
||||||
@PostMapping("/license/parse")
|
@PostMapping("/license/parse")
|
||||||
public ResponseEntity<ApiResponse<Map<String, Object>>> parseLicenseFile(@RequestBody Map<String, String> body) {
|
public ResponseEntity<ApiResponse<Map<String, Object>>> parseLicenseFile(
|
||||||
|
@RequestBody Map<String, String> body,
|
||||||
|
@AuthenticationPrincipal String tenantId) {
|
||||||
String content = body.get("content");
|
String content = body.get("content");
|
||||||
if (content == null || content.isBlank()) {
|
if (content == null || content.isBlank()) {
|
||||||
throw new BusinessException("License file content is required");
|
throw new BusinessException("License file content is required");
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
LicenseFileCrypto.LicensePayload payload = LicenseFileCrypto.decrypt(content.trim());
|
LicenseFileCrypto.LicensePayload payload = LicenseFileCrypto.decrypt(content.trim());
|
||||||
|
// Verify the license file belongs to the current tenant
|
||||||
|
try {
|
||||||
|
appService.getByAppKey(payload.appKey(), tenantId);
|
||||||
|
} catch (BusinessException e) {
|
||||||
|
if (e.getCode() == 403 || e.getCode() == 404) {
|
||||||
|
throw new BusinessException(403, "权限不足无法展示");
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
Map<String, Object> data = new java.util.LinkedHashMap<>();
|
Map<String, Object> data = new java.util.LinkedHashMap<>();
|
||||||
data.put("appKey", payload.appKey());
|
data.put("appKey", payload.appKey());
|
||||||
data.put("appName", payload.appName());
|
data.put("appName", payload.appName());
|
||||||
@ -196,6 +207,8 @@ public class AppController {
|
|||||||
data.put("baseUrl", payload.baseUrl());
|
data.put("baseUrl", payload.baseUrl());
|
||||||
data.put("serverUrl", payload.serverUrl());
|
data.put("serverUrl", payload.serverUrl());
|
||||||
return ResponseEntity.ok(ApiResponse.success(data));
|
return ResponseEntity.ok(ApiResponse.success(data));
|
||||||
|
} catch (BusinessException e) {
|
||||||
|
throw e;
|
||||||
} catch (IllegalArgumentException e) {
|
} catch (IllegalArgumentException e) {
|
||||||
throw new BusinessException("Invalid license file: " + e.getMessage());
|
throw new BusinessException("Invalid license file: " + e.getMessage());
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
|||||||
正在加载...
在新工单中引用
屏蔽一个用户