refactor: merge tenant isolation into single HandlerInterceptor
Replace TenantResolutionFilter + TenantOwnershipValidator (15 manual
calls across 5 controllers) with a single TenantIsolationInterceptor
that uses Spring HandlerMapping path variables for fail-closed tenant
isolation. New endpoints with {tenantId}, {environmentId}, or {appId}
path variables are automatically isolated without manual code.
Simplify OrgResolver from dual-token fetch to single token — Logto
merges all scopes into either token type.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -3,7 +3,6 @@ package net.siegeln.cameleer.saas.app;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import net.siegeln.cameleer.saas.app.dto.AppResponse;
|
||||
import net.siegeln.cameleer.saas.app.dto.CreateAppRequest;
|
||||
import net.siegeln.cameleer.saas.config.TenantOwnershipValidator;
|
||||
import net.siegeln.cameleer.saas.environment.EnvironmentService;
|
||||
import net.siegeln.cameleer.saas.runtime.RuntimeConfig;
|
||||
import net.siegeln.cameleer.saas.tenant.TenantRepository;
|
||||
@@ -36,19 +35,16 @@ public class AppController {
|
||||
private final EnvironmentService environmentService;
|
||||
private final RuntimeConfig runtimeConfig;
|
||||
private final TenantRepository tenantRepository;
|
||||
private final TenantOwnershipValidator tenantOwnershipValidator;
|
||||
|
||||
public AppController(AppService appService, ObjectMapper objectMapper,
|
||||
EnvironmentService environmentService,
|
||||
RuntimeConfig runtimeConfig,
|
||||
TenantRepository tenantRepository,
|
||||
TenantOwnershipValidator tenantOwnershipValidator) {
|
||||
TenantRepository tenantRepository) {
|
||||
this.appService = appService;
|
||||
this.objectMapper = objectMapper;
|
||||
this.environmentService = environmentService;
|
||||
this.runtimeConfig = runtimeConfig;
|
||||
this.tenantRepository = tenantRepository;
|
||||
this.tenantOwnershipValidator = tenantOwnershipValidator;
|
||||
}
|
||||
|
||||
@PostMapping(consumes = "multipart/form-data")
|
||||
@@ -58,7 +54,7 @@ public class AppController {
|
||||
@RequestPart("metadata") String metadataJson,
|
||||
@RequestPart("file") MultipartFile file,
|
||||
Authentication authentication) {
|
||||
tenantOwnershipValidator.validateEnvironmentAccess(environmentId);
|
||||
|
||||
try {
|
||||
var request = objectMapper.readValue(metadataJson, CreateAppRequest.class);
|
||||
UUID actorId = resolveActorId(authentication);
|
||||
@@ -79,7 +75,7 @@ public class AppController {
|
||||
|
||||
@GetMapping
|
||||
public ResponseEntity<List<AppResponse>> list(@PathVariable UUID environmentId) {
|
||||
tenantOwnershipValidator.validateEnvironmentAccess(environmentId);
|
||||
|
||||
var apps = appService.listByEnvironmentId(environmentId)
|
||||
.stream()
|
||||
.map(this::toResponse)
|
||||
@@ -91,7 +87,7 @@ public class AppController {
|
||||
public ResponseEntity<AppResponse> getById(
|
||||
@PathVariable UUID environmentId,
|
||||
@PathVariable UUID appId) {
|
||||
tenantOwnershipValidator.validateEnvironmentAccess(environmentId);
|
||||
|
||||
return appService.getById(appId)
|
||||
.map(entity -> ResponseEntity.ok(toResponse(entity)))
|
||||
.orElse(ResponseEntity.notFound().build());
|
||||
@@ -104,7 +100,7 @@ public class AppController {
|
||||
@PathVariable UUID appId,
|
||||
@RequestPart("file") MultipartFile file,
|
||||
Authentication authentication) {
|
||||
tenantOwnershipValidator.validateEnvironmentAccess(environmentId);
|
||||
|
||||
try {
|
||||
UUID actorId = resolveActorId(authentication);
|
||||
var entity = appService.reuploadJar(appId, file, actorId);
|
||||
@@ -120,7 +116,7 @@ public class AppController {
|
||||
@PathVariable UUID environmentId,
|
||||
@PathVariable UUID appId,
|
||||
Authentication authentication) {
|
||||
tenantOwnershipValidator.validateEnvironmentAccess(environmentId);
|
||||
|
||||
try {
|
||||
UUID actorId = resolveActorId(authentication);
|
||||
appService.delete(appId, actorId);
|
||||
@@ -137,7 +133,7 @@ public class AppController {
|
||||
@PathVariable UUID appId,
|
||||
@RequestBody net.siegeln.cameleer.saas.observability.dto.UpdateRoutingRequest request,
|
||||
Authentication authentication) {
|
||||
tenantOwnershipValidator.validateEnvironmentAccess(environmentId);
|
||||
|
||||
try {
|
||||
var actorId = resolveActorId(authentication);
|
||||
var app = appService.updateRouting(appId, request.exposedPort(), actorId);
|
||||
|
||||
@@ -19,7 +19,6 @@ import org.springframework.security.oauth2.jwt.JwtDecoder;
|
||||
import org.springframework.security.oauth2.jwt.JwtValidators;
|
||||
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
|
||||
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
|
||||
import org.springframework.security.oauth2.server.resource.web.authentication.BearerTokenAuthenticationFilter;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
|
||||
import java.net.URL;
|
||||
@@ -31,12 +30,6 @@ import java.util.List;
|
||||
@EnableMethodSecurity
|
||||
public class SecurityConfig {
|
||||
|
||||
private final TenantResolutionFilter tenantResolutionFilter;
|
||||
|
||||
public SecurityConfig(TenantResolutionFilter tenantResolutionFilter) {
|
||||
this.tenantResolutionFilter = tenantResolutionFilter;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||
http
|
||||
@@ -51,8 +44,7 @@ public class SecurityConfig {
|
||||
.anyRequest().authenticated()
|
||||
)
|
||||
.oauth2ResourceServer(oauth2 -> oauth2.jwt(jwt ->
|
||||
jwt.jwtAuthenticationConverter(jwtAuthenticationConverter())))
|
||||
.addFilterAfter(tenantResolutionFilter, BearerTokenAuthenticationFilter.class);
|
||||
jwt.jwtAuthenticationConverter(jwtAuthenticationConverter())));
|
||||
|
||||
return http.build();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
package net.siegeln.cameleer.saas.config;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import net.siegeln.cameleer.saas.app.AppRepository;
|
||||
import net.siegeln.cameleer.saas.environment.EnvironmentRepository;
|
||||
import net.siegeln.cameleer.saas.tenant.TenantService;
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.oauth2.jwt.Jwt;
|
||||
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.servlet.HandlerInterceptor;
|
||||
import org.springframework.web.servlet.HandlerMapping;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Single interceptor handling both tenant resolution (JWT org_id → TenantContext)
|
||||
* and tenant isolation (path variable validation). Fail-closed: any endpoint with
|
||||
* {tenantId}, {environmentId}, or {appId} in its path is automatically isolated.
|
||||
* Platform admins (SCOPE_platform:admin) bypass all isolation checks.
|
||||
*/
|
||||
@Component
|
||||
public class TenantIsolationInterceptor implements HandlerInterceptor {
|
||||
|
||||
private final TenantService tenantService;
|
||||
private final EnvironmentRepository environmentRepository;
|
||||
private final AppRepository appRepository;
|
||||
|
||||
public TenantIsolationInterceptor(TenantService tenantService,
|
||||
EnvironmentRepository environmentRepository,
|
||||
AppRepository appRepository) {
|
||||
this.tenantService = tenantService;
|
||||
this.environmentRepository = environmentRepository;
|
||||
this.appRepository = appRepository;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
|
||||
Object handler) throws Exception {
|
||||
var authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||
if (!(authentication instanceof JwtAuthenticationToken jwtAuth)) return true;
|
||||
|
||||
// 1. Resolve: JWT organization_id → TenantContext
|
||||
Jwt jwt = jwtAuth.getToken();
|
||||
String orgId = jwt.getClaimAsString("organization_id");
|
||||
if (orgId != null) {
|
||||
tenantService.getByLogtoOrgId(orgId)
|
||||
.ifPresent(tenant -> TenantContext.setTenantId(tenant.getId()));
|
||||
}
|
||||
|
||||
// 2. Validate: read path variables from Spring's HandlerMapping
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, String> pathVars = (Map<String, String>) request.getAttribute(
|
||||
HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
|
||||
if (pathVars == null || pathVars.isEmpty()) return true;
|
||||
|
||||
UUID resolvedTenantId = TenantContext.getTenantId();
|
||||
boolean isPlatformAdmin = jwtAuth.getAuthorities().stream()
|
||||
.anyMatch(a -> a.getAuthority().equals("SCOPE_platform:admin"));
|
||||
|
||||
if (isPlatformAdmin) return true;
|
||||
|
||||
// Check tenantId in path (e.g., /api/tenants/{tenantId}/environments)
|
||||
if (pathVars.containsKey("tenantId")) {
|
||||
if (resolvedTenantId == null) {
|
||||
response.sendError(HttpServletResponse.SC_FORBIDDEN, "No organization context");
|
||||
return false;
|
||||
}
|
||||
UUID pathTenantId = UUID.fromString(pathVars.get("tenantId"));
|
||||
if (!pathTenantId.equals(resolvedTenantId)) {
|
||||
response.sendError(HttpServletResponse.SC_FORBIDDEN, "Tenant mismatch");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Check environmentId in path (e.g., /api/environments/{environmentId}/apps)
|
||||
if (pathVars.containsKey("environmentId") && resolvedTenantId != null) {
|
||||
UUID envId = UUID.fromString(pathVars.get("environmentId"));
|
||||
environmentRepository.findById(envId).ifPresent(env -> {
|
||||
if (!env.getTenantId().equals(resolvedTenantId)) {
|
||||
throw new AccessDeniedException("Environment does not belong to tenant");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Check appId in path (e.g., /api/apps/{appId}/deploy)
|
||||
if (pathVars.containsKey("appId") && resolvedTenantId != null) {
|
||||
UUID appId = UUID.fromString(pathVars.get("appId"));
|
||||
appRepository.findById(appId).ifPresent(app ->
|
||||
environmentRepository.findById(app.getEnvironmentId()).ifPresent(env -> {
|
||||
if (!env.getTenantId().equals(resolvedTenantId)) {
|
||||
throw new AccessDeniedException("App does not belong to tenant");
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
|
||||
Object handler, Exception ex) {
|
||||
TenantContext.clear();
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
package net.siegeln.cameleer.saas.config;
|
||||
|
||||
import net.siegeln.cameleer.saas.app.AppRepository;
|
||||
import net.siegeln.cameleer.saas.environment.EnvironmentRepository;
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@Component
|
||||
public class TenantOwnershipValidator {
|
||||
|
||||
private final EnvironmentRepository environmentRepository;
|
||||
private final AppRepository appRepository;
|
||||
|
||||
public TenantOwnershipValidator(EnvironmentRepository environmentRepository, AppRepository appRepository) {
|
||||
this.environmentRepository = environmentRepository;
|
||||
this.appRepository = appRepository;
|
||||
}
|
||||
|
||||
public void validateEnvironmentAccess(UUID environmentId) {
|
||||
UUID currentTenantId = TenantContext.getTenantId();
|
||||
if (currentTenantId == null) return; // platform admin or no org context
|
||||
environmentRepository.findById(environmentId).ifPresent(env -> {
|
||||
if (!env.getTenantId().equals(currentTenantId)) {
|
||||
throw new AccessDeniedException("Environment does not belong to current tenant");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void validateAppAccess(UUID appId) {
|
||||
UUID currentTenantId = TenantContext.getTenantId();
|
||||
if (currentTenantId == null) return;
|
||||
appRepository.findById(appId).ifPresent(app -> {
|
||||
environmentRepository.findById(app.getEnvironmentId()).ifPresent(env -> {
|
||||
if (!env.getTenantId().equals(currentTenantId)) {
|
||||
throw new AccessDeniedException("App does not belong to current tenant");
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
package net.siegeln.cameleer.saas.config;
|
||||
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import net.siegeln.cameleer.saas.tenant.TenantService;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.oauth2.jwt.Jwt;
|
||||
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.UUID;
|
||||
|
||||
@Component
|
||||
public class TenantResolutionFilter extends OncePerRequestFilter {
|
||||
|
||||
private final TenantService tenantService;
|
||||
|
||||
public TenantResolutionFilter(TenantService tenantService) {
|
||||
this.tenantService = tenantService;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(HttpServletRequest request,
|
||||
HttpServletResponse response,
|
||||
FilterChain filterChain) throws ServletException, IOException {
|
||||
try {
|
||||
var authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||
|
||||
if (authentication instanceof JwtAuthenticationToken jwtAuth) {
|
||||
Jwt jwt = jwtAuth.getToken();
|
||||
String orgId = jwt.getClaimAsString("organization_id");
|
||||
|
||||
if (orgId != null) {
|
||||
tenantService.getByLogtoOrgId(orgId)
|
||||
.ifPresent(tenant -> TenantContext.setTenantId(tenant.getId()));
|
||||
}
|
||||
|
||||
// Path-based tenant validation for /api/tenants/{uuid}/** endpoints
|
||||
String path = request.getRequestURI();
|
||||
if (path.startsWith("/api/tenants/")) {
|
||||
UUID resolvedTenantId = TenantContext.getTenantId();
|
||||
String[] segments = path.split("/");
|
||||
if (segments.length >= 4) {
|
||||
try {
|
||||
UUID pathTenantId = UUID.fromString(segments[3]);
|
||||
boolean isPlatformAdmin = jwtAuth.getAuthorities().stream()
|
||||
.anyMatch(a -> a.getAuthority().equals("SCOPE_platform:admin"));
|
||||
if (resolvedTenantId == null && !isPlatformAdmin) {
|
||||
response.sendError(HttpServletResponse.SC_FORBIDDEN, "No organization context");
|
||||
return;
|
||||
}
|
||||
if (resolvedTenantId != null && !pathTenantId.equals(resolvedTenantId) && !isPlatformAdmin) {
|
||||
response.sendError(HttpServletResponse.SC_FORBIDDEN, "Tenant mismatch");
|
||||
return;
|
||||
}
|
||||
} catch (IllegalArgumentException ignored) {
|
||||
// Non-UUID segment like "by-slug" — allow through
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
filterChain.doFilter(request, response);
|
||||
} finally {
|
||||
TenantContext.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package net.siegeln.cameleer.saas.config;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
@Configuration
|
||||
public class WebConfig implements WebMvcConfigurer {
|
||||
|
||||
private final TenantIsolationInterceptor tenantIsolationInterceptor;
|
||||
|
||||
public WebConfig(TenantIsolationInterceptor tenantIsolationInterceptor) {
|
||||
this.tenantIsolationInterceptor = tenantIsolationInterceptor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addInterceptors(InterceptorRegistry registry) {
|
||||
registry.addInterceptor(tenantIsolationInterceptor).addPathPatterns("/api/**");
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
package net.siegeln.cameleer.saas.deployment;
|
||||
|
||||
import net.siegeln.cameleer.saas.config.TenantOwnershipValidator;
|
||||
import net.siegeln.cameleer.saas.deployment.dto.DeploymentResponse;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
@@ -20,12 +19,9 @@ import java.util.UUID;
|
||||
public class DeploymentController {
|
||||
|
||||
private final DeploymentService deploymentService;
|
||||
private final TenantOwnershipValidator tenantOwnershipValidator;
|
||||
|
||||
public DeploymentController(DeploymentService deploymentService,
|
||||
TenantOwnershipValidator tenantOwnershipValidator) {
|
||||
public DeploymentController(DeploymentService deploymentService) {
|
||||
this.deploymentService = deploymentService;
|
||||
this.tenantOwnershipValidator = tenantOwnershipValidator;
|
||||
}
|
||||
|
||||
@PostMapping("/deploy")
|
||||
@@ -33,7 +29,7 @@ public class DeploymentController {
|
||||
public ResponseEntity<DeploymentResponse> deploy(
|
||||
@PathVariable UUID appId,
|
||||
Authentication authentication) {
|
||||
tenantOwnershipValidator.validateAppAccess(appId);
|
||||
|
||||
try {
|
||||
UUID actorId = resolveActorId(authentication);
|
||||
var entity = deploymentService.deploy(appId, actorId);
|
||||
@@ -47,7 +43,7 @@ public class DeploymentController {
|
||||
|
||||
@GetMapping("/deployments")
|
||||
public ResponseEntity<List<DeploymentResponse>> listDeployments(@PathVariable UUID appId) {
|
||||
tenantOwnershipValidator.validateAppAccess(appId);
|
||||
|
||||
var deployments = deploymentService.listByAppId(appId)
|
||||
.stream()
|
||||
.map(this::toResponse)
|
||||
@@ -59,7 +55,7 @@ public class DeploymentController {
|
||||
public ResponseEntity<DeploymentResponse> getDeployment(
|
||||
@PathVariable UUID appId,
|
||||
@PathVariable UUID deploymentId) {
|
||||
tenantOwnershipValidator.validateAppAccess(appId);
|
||||
|
||||
return deploymentService.getById(deploymentId)
|
||||
.map(entity -> ResponseEntity.ok(toResponse(entity)))
|
||||
.orElse(ResponseEntity.notFound().build());
|
||||
@@ -70,7 +66,7 @@ public class DeploymentController {
|
||||
public ResponseEntity<DeploymentResponse> stop(
|
||||
@PathVariable UUID appId,
|
||||
Authentication authentication) {
|
||||
tenantOwnershipValidator.validateAppAccess(appId);
|
||||
|
||||
try {
|
||||
UUID actorId = resolveActorId(authentication);
|
||||
var entity = deploymentService.stop(appId, actorId);
|
||||
@@ -87,7 +83,7 @@ public class DeploymentController {
|
||||
public ResponseEntity<DeploymentResponse> restart(
|
||||
@PathVariable UUID appId,
|
||||
Authentication authentication) {
|
||||
tenantOwnershipValidator.validateAppAccess(appId);
|
||||
|
||||
try {
|
||||
UUID actorId = resolveActorId(authentication);
|
||||
var entity = deploymentService.restart(appId, actorId);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package net.siegeln.cameleer.saas.environment;
|
||||
|
||||
import jakarta.validation.Valid;
|
||||
import net.siegeln.cameleer.saas.config.TenantOwnershipValidator;
|
||||
import net.siegeln.cameleer.saas.environment.dto.CreateEnvironmentRequest;
|
||||
import net.siegeln.cameleer.saas.environment.dto.EnvironmentResponse;
|
||||
import net.siegeln.cameleer.saas.environment.dto.UpdateEnvironmentRequest;
|
||||
@@ -26,12 +25,9 @@ import java.util.UUID;
|
||||
public class EnvironmentController {
|
||||
|
||||
private final EnvironmentService environmentService;
|
||||
private final TenantOwnershipValidator tenantOwnershipValidator;
|
||||
|
||||
public EnvironmentController(EnvironmentService environmentService,
|
||||
TenantOwnershipValidator tenantOwnershipValidator) {
|
||||
public EnvironmentController(EnvironmentService environmentService) {
|
||||
this.environmentService = environmentService;
|
||||
this.tenantOwnershipValidator = tenantOwnershipValidator;
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
@@ -64,7 +60,7 @@ public class EnvironmentController {
|
||||
public ResponseEntity<EnvironmentResponse> getById(
|
||||
@PathVariable UUID tenantId,
|
||||
@PathVariable UUID environmentId) {
|
||||
tenantOwnershipValidator.validateEnvironmentAccess(environmentId);
|
||||
|
||||
return environmentService.getById(environmentId)
|
||||
.map(entity -> ResponseEntity.ok(toResponse(entity)))
|
||||
.orElse(ResponseEntity.notFound().build());
|
||||
@@ -77,7 +73,7 @@ public class EnvironmentController {
|
||||
@PathVariable UUID environmentId,
|
||||
@Valid @RequestBody UpdateEnvironmentRequest request,
|
||||
Authentication authentication) {
|
||||
tenantOwnershipValidator.validateEnvironmentAccess(environmentId);
|
||||
|
||||
try {
|
||||
UUID actorId = resolveActorId(authentication);
|
||||
var entity = environmentService.updateDisplayName(environmentId, request.displayName(), actorId);
|
||||
@@ -93,7 +89,7 @@ public class EnvironmentController {
|
||||
@PathVariable UUID tenantId,
|
||||
@PathVariable UUID environmentId,
|
||||
Authentication authentication) {
|
||||
tenantOwnershipValidator.validateEnvironmentAccess(environmentId);
|
||||
|
||||
try {
|
||||
UUID actorId = resolveActorId(authentication);
|
||||
environmentService.delete(environmentId, actorId);
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package net.siegeln.cameleer.saas.log;
|
||||
|
||||
import net.siegeln.cameleer.saas.config.TenantOwnershipValidator;
|
||||
import net.siegeln.cameleer.saas.log.dto.LogEntry;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
@@ -20,12 +19,9 @@ import java.util.UUID;
|
||||
public class LogController {
|
||||
|
||||
private final ContainerLogService containerLogService;
|
||||
private final TenantOwnershipValidator tenantOwnershipValidator;
|
||||
|
||||
public LogController(ContainerLogService containerLogService,
|
||||
TenantOwnershipValidator tenantOwnershipValidator) {
|
||||
public LogController(ContainerLogService containerLogService) {
|
||||
this.containerLogService = containerLogService;
|
||||
this.tenantOwnershipValidator = tenantOwnershipValidator;
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
@@ -36,7 +32,6 @@ public class LogController {
|
||||
@RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) Instant until,
|
||||
@RequestParam(defaultValue = "500") int limit,
|
||||
@RequestParam(defaultValue = "both") String stream) {
|
||||
tenantOwnershipValidator.validateAppAccess(appId);
|
||||
List<LogEntry> entries = containerLogService.query(appId, since, until, limit, stream);
|
||||
return ResponseEntity.ok(entries);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package net.siegeln.cameleer.saas.observability;
|
||||
|
||||
import net.siegeln.cameleer.saas.config.TenantOwnershipValidator;
|
||||
import net.siegeln.cameleer.saas.observability.dto.AgentStatusResponse;
|
||||
import net.siegeln.cameleer.saas.observability.dto.ObservabilityStatusResponse;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
@@ -14,18 +13,15 @@ import java.util.UUID;
|
||||
public class AgentStatusController {
|
||||
|
||||
private final AgentStatusService agentStatusService;
|
||||
private final TenantOwnershipValidator tenantOwnershipValidator;
|
||||
|
||||
public AgentStatusController(AgentStatusService agentStatusService,
|
||||
TenantOwnershipValidator tenantOwnershipValidator) {
|
||||
public AgentStatusController(AgentStatusService agentStatusService) {
|
||||
this.agentStatusService = agentStatusService;
|
||||
this.tenantOwnershipValidator = tenantOwnershipValidator;
|
||||
}
|
||||
|
||||
@GetMapping("/agent-status")
|
||||
@PreAuthorize("hasAuthority('SCOPE_observe:read')")
|
||||
public ResponseEntity<AgentStatusResponse> getAgentStatus(@PathVariable UUID appId) {
|
||||
tenantOwnershipValidator.validateAppAccess(appId);
|
||||
|
||||
try {
|
||||
return ResponseEntity.ok(agentStatusService.getAgentStatus(appId));
|
||||
} catch (IllegalArgumentException e) {
|
||||
@@ -36,7 +32,7 @@ public class AgentStatusController {
|
||||
@GetMapping("/observability-status")
|
||||
@PreAuthorize("hasAuthority('SCOPE_observe:read')")
|
||||
public ResponseEntity<ObservabilityStatusResponse> getObservabilityStatus(@PathVariable UUID appId) {
|
||||
tenantOwnershipValidator.validateAppAccess(appId);
|
||||
|
||||
try {
|
||||
return ResponseEntity.ok(agentStatusService.getObservabilityStatus(appId));
|
||||
} catch (IllegalArgumentException e) {
|
||||
|
||||
Reference in New Issue
Block a user