diff --git a/multiapps-controller-web/src/main/java/org/cloudfoundry/multiapps/controller/web/Constants.java b/multiapps-controller-web/src/main/java/org/cloudfoundry/multiapps/controller/web/Constants.java index 620de4f9f0..b1cb48209b 100644 --- a/multiapps-controller-web/src/main/java/org/cloudfoundry/multiapps/controller/web/Constants.java +++ b/multiapps-controller-web/src/main/java/org/cloudfoundry/multiapps/controller/web/Constants.java @@ -71,6 +71,27 @@ private Endpoints() { public static final String PURGE = "/purge"; } + public static class ApiEndpointsNames { + + private ApiEndpointsNames() { + } + + // Files API + public static final String GET_FILES = "getFiles"; + public static final String UPLOAD_FILE = "uploadFile"; + public static final String START_UPLOAD_FROM_URL = "startUploadFromUrl"; + public static final String GET_UPLOAD_FROM_URL_JOB = "getUploadFromUrlJob"; + + // Operations API + public static final String GET_OPERATIONS = "getOperations"; + public static final String START_OPERATION = "startOperation"; + public static final String GET_OPERATION = "getOperation"; + public static final String GET_OPERATION_ACTIONS = "getOperationActions"; + public static final String GET_OPERATION_LOGS = "getOperationLogs"; + public static final String GET_OPERATION_LOG_CONTENT = "getOperationLogContent"; + public static final String EXECUTE_OPERATION_ACTION = "executeOperationAction"; + } + public static final Set NAMES_OF_SERVICE_PARAMETERS = Set.of( VARIABLE_NAME_SERVICE_ID, Variables.USER.getName(), Variables.USER_GUID.getName(), Variables.SPACE_NAME.getName(), Variables.SPACE_GUID.getName(), diff --git a/multiapps-controller-web/src/main/java/org/cloudfoundry/multiapps/controller/web/Messages.java b/multiapps-controller-web/src/main/java/org/cloudfoundry/multiapps/controller/web/Messages.java index 4bbccac51f..23a46ccb03 100644 --- a/multiapps-controller-web/src/main/java/org/cloudfoundry/multiapps/controller/web/Messages.java +++ b/multiapps-controller-web/src/main/java/org/cloudfoundry/multiapps/controller/web/Messages.java @@ -25,7 +25,6 @@ public final class Messages { public static final String MISSING_PROPERTIES_FOR_CREATING_THE_SPECIFIC_PROVIDER = "Missing properties for creating the specific provider!"; public static final String DEPLOY_FROM_URL_WRONG_CREDENTIALS_FOR_JOB_WITH_ID = "Credentials to {0} are wrong. Make sure that they are correct. Job id: {1}"; public static final String JOB_NOT_UPDATED_FOR_0_SECONDS = "Job not updated for {0} seconds"; - public static final String FAILED_TO_CREATE_BLOB_STORE_CONTEXT = "Failed to create BlobStoreContext"; // Audit log messages diff --git a/multiapps-controller-web/src/main/java/org/cloudfoundry/multiapps/controller/web/api/impl/FilesApiServiceImpl.java b/multiapps-controller-web/src/main/java/org/cloudfoundry/multiapps/controller/web/api/impl/FilesApiServiceImpl.java index 501a3cd49e..c1ef367a6b 100644 --- a/multiapps-controller-web/src/main/java/org/cloudfoundry/multiapps/controller/web/api/impl/FilesApiServiceImpl.java +++ b/multiapps-controller-web/src/main/java/org/cloudfoundry/multiapps/controller/web/api/impl/FilesApiServiceImpl.java @@ -1,18 +1,8 @@ package org.cloudfoundry.multiapps.controller.web.api.impl; -import java.io.BufferedInputStream; -import java.io.InputStream; -import java.math.BigInteger; -import java.text.MessageFormat; -import java.time.LocalDateTime; -import java.time.temporal.ChronoUnit; -import java.util.Base64; -import java.util.List; -import java.util.concurrent.ExecutorService; -import java.util.stream.Collectors; - import jakarta.inject.Inject; import jakarta.inject.Named; +import jakarta.servlet.http.HttpServletRequest; import org.cloudfoundry.multiapps.common.SLException; import org.cloudfoundry.multiapps.controller.api.FilesApiService; import org.cloudfoundry.multiapps.controller.api.model.AsyncUploadResult; @@ -36,6 +26,7 @@ import org.cloudfoundry.multiapps.controller.process.util.PriorityFuture; import org.cloudfoundry.multiapps.controller.web.Constants; import org.cloudfoundry.multiapps.controller.web.Messages; +import org.cloudfoundry.multiapps.controller.web.monitoring.ApiUsageLogger; import org.cloudfoundry.multiapps.controller.web.upload.AsyncUploadJobOrchestrator; import org.cloudfoundry.multiapps.controller.web.upload.exception.RejectedAsyncUploadJobException; import org.cloudfoundry.multiapps.controller.web.util.SecurityContextUtil; @@ -48,6 +39,17 @@ import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartHttpServletRequest; +import java.io.BufferedInputStream; +import java.io.InputStream; +import java.math.BigInteger; +import java.text.MessageFormat; +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; +import java.util.Base64; +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.stream.Collectors; + @Named public class FilesApiServiceImpl implements FilesApiService { @@ -74,10 +76,15 @@ public class FilesApiServiceImpl implements FilesApiService { @Inject private AsyncUploadJobOrchestrator asyncUploadJobOrchestrator; @Inject + private ApiUsageLogger apiUsageLogger; + @Inject private ExecutorService fileStorageThreadPool; + @Inject + private HttpServletRequest httpServletRequest; @Override public ResponseEntity> getFiles(String spaceGuid, String namespace) { + apiUsageLogger.logFilesReadCall(spaceGuid, namespace, Constants.ApiEndpointsNames.GET_FILES, httpServletRequest); try { filesApiServiceAuditLog.logGetFiles(SecurityContextUtil.getUsername(), spaceGuid, namespace); List entries = fileService.listFiles(spaceGuid, namespace); @@ -93,6 +100,7 @@ public ResponseEntity> getFiles(String spaceGuid, String name @Override public ResponseEntity uploadFile(MultipartHttpServletRequest request, String spaceGuid, String namespace) { + apiUsageLogger.logFilesMutatingCall(spaceGuid, namespace, Constants.ApiEndpointsNames.UPLOAD_FILE, request); LOGGER.trace(Messages.RECEIVED_UPLOAD_REQUEST, ServletUtil.decodeUri(request)); var multipartFile = getFileFromRequest(request); try (InputStream in = new BufferedInputStream(multipartFile.getInputStream(), INPUT_STREAM_BUFFER_SIZE)) { @@ -113,11 +121,13 @@ public ResponseEntity uploadFile(MultipartHttpServletRequest reque @Override public ResponseEntity startUploadFromUrl(String spaceGuid, String namespace, FileUrl fileUrl) { + apiUsageLogger.logFilesMutatingCall(spaceGuid, namespace, Constants.ApiEndpointsNames.START_UPLOAD_FROM_URL, httpServletRequest); String decodedUrl = new String(Base64.getUrlDecoder() .decode(fileUrl.getFileUrl())); String urlWithoutUserInfo = UriUtil.stripUserInfo(decodedUrl); LOGGER.trace(Messages.RECEIVED_UPLOAD_FROM_URL_REQUEST, urlWithoutUserInfo); filesApiServiceAuditLog.logStartUploadFromUrl(SecurityContextUtil.getUsername(), spaceGuid, decodedUrl); + var existingJob = getExistingJob(spaceGuid, namespace, urlWithoutUserInfo); if (existingJob == null) { return triggerUploadFromUrl(spaceGuid, namespace, urlWithoutUserInfo, decodedUrl, fileUrl.getUserCredentials()); @@ -144,6 +154,7 @@ private String getLocationHeader(String spaceGuid, String jobId) { @Override public ResponseEntity getUploadFromUrlJob(String spaceGuid, String namespace, String jobId) { + apiUsageLogger.logFilesReadCall(spaceGuid, namespace, Constants.ApiEndpointsNames.GET_UPLOAD_FROM_URL_JOB, httpServletRequest); filesApiServiceAuditLog.logGetUploadFromUrlJob(SecurityContextUtil.getUsername(), spaceGuid, namespace, jobId); AsyncUploadJobEntry job = getJob(jobId, spaceGuid, namespace); if (job == null) { diff --git a/multiapps-controller-web/src/main/java/org/cloudfoundry/multiapps/controller/web/api/impl/OperationsApiServiceImpl.java b/multiapps-controller-web/src/main/java/org/cloudfoundry/multiapps/controller/web/api/impl/OperationsApiServiceImpl.java index 0cc3ef0e5a..f84bee4731 100644 --- a/multiapps-controller-web/src/main/java/org/cloudfoundry/multiapps/controller/web/api/impl/OperationsApiServiceImpl.java +++ b/multiapps-controller-web/src/main/java/org/cloudfoundry/multiapps/controller/web/api/impl/OperationsApiServiceImpl.java @@ -1,19 +1,5 @@ package org.cloudfoundry.multiapps.controller.web.api.impl; -import java.text.MessageFormat; -import java.time.ZoneId; -import java.time.ZonedDateTime; -import java.time.format.DateTimeFormatter; -import java.util.Arrays; -import java.util.Collections; -import java.util.Date; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.UUID; -import java.util.stream.Collectors; - import jakarta.inject.Inject; import jakarta.inject.Named; import jakarta.persistence.NoResultException; @@ -38,7 +24,6 @@ import org.cloudfoundry.multiapps.controller.core.cf.CloudControllerClientFactory; import org.cloudfoundry.multiapps.controller.core.security.token.TokenService; import org.cloudfoundry.multiapps.controller.core.util.UserInfo; -import org.cloudfoundry.multiapps.controller.persistence.Constants; import org.cloudfoundry.multiapps.controller.persistence.OrderDirection; import org.cloudfoundry.multiapps.controller.persistence.model.ProgressMessage; import org.cloudfoundry.multiapps.controller.persistence.model.ProgressMessage.ProgressMessageType; @@ -54,7 +39,9 @@ import org.cloudfoundry.multiapps.controller.process.metadata.ProcessTypeToOperationMetadataMapper; import org.cloudfoundry.multiapps.controller.process.util.OperationsHelper; import org.cloudfoundry.multiapps.controller.process.variables.Variables; +import org.cloudfoundry.multiapps.controller.web.Constants; import org.cloudfoundry.multiapps.controller.web.Messages; +import org.cloudfoundry.multiapps.controller.web.monitoring.ApiUsageLogger; import org.cloudfoundry.multiapps.controller.web.util.SecurityContextUtil; import org.flowable.engine.runtime.ProcessInstance; import org.slf4j.Logger; @@ -63,6 +50,21 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.server.ResponseStatusException; +import java.text.MessageFormat; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Arrays; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; + +import static org.cloudfoundry.multiapps.controller.persistence.Constants.VARIABLE_NAME_SERVICE_ID; import static org.cloudfoundry.multiapps.controller.web.Constants.NAMES_OF_SERVICE_PARAMETERS; @Named @@ -89,9 +91,14 @@ public class OperationsApiServiceImpl implements OperationsApiService { private ProcessActionRegistry processActionRegistry; @Inject private OperationsApiServiceAuditLog operationsApiServiceAuditLog; + @Inject + private ApiUsageLogger apiUsageLogger; + @Inject + private HttpServletRequest httpServletRequest; @Override public ResponseEntity> getOperations(String spaceGuid, String mtaId, List stateStrings, Integer last) { + apiUsageLogger.logOperationsReadCall(spaceGuid, Constants.ApiEndpointsNames.GET_OPERATIONS, null, httpServletRequest); operationsApiServiceAuditLog.logGetOperations(SecurityContextUtil.getUsername(), spaceGuid, mtaId); List states = getStates(stateStrings); List operations = filterByQueryParameters(last, states, spaceGuid, mtaId); @@ -101,6 +108,8 @@ public ResponseEntity> getOperations(String spaceGuid, String mt @Override public ResponseEntity executeOperationAction(String spaceGuid, String operationId, String actionId) { + apiUsageLogger.logOperationsMutatingCall(spaceGuid, Constants.ApiEndpointsNames.EXECUTE_OPERATION_ACTION, operationId, + httpServletRequest); operationsApiServiceAuditLog.logExecuteOperationAction(SecurityContextUtil.getUsername(), spaceGuid, operationId, actionId); Operation operation = getOperationByOperationGuidAndSpaceGuid(operationId, spaceGuid); List availableOperations = getAvailableActions(operation); @@ -119,6 +128,8 @@ public ResponseEntity executeOperationAction(String spaceGuid, String oper @Override public ResponseEntity> getOperationLogs(String spaceGuid, String operationId) { try { + apiUsageLogger.logOperationsReadCall(spaceGuid, Constants.ApiEndpointsNames.GET_OPERATION_LOGS, operationId, + httpServletRequest); operationsApiServiceAuditLog.logGetOperationLogs(SecurityContextUtil.getUsername(), spaceGuid, operationId); getOperationByOperationGuidAndSpaceGuid(operationId, spaceGuid); List logIds = logsService.getLogNames(spaceGuid, operationId); @@ -138,6 +149,8 @@ public ResponseEntity> getOperationLogs(String spaceGuid, String opera @Override public ResponseEntity getOperationLogContent(String spaceGuid, String operationId, String logId) { try { + apiUsageLogger.logOperationsReadCall(spaceGuid, Constants.ApiEndpointsNames.GET_OPERATION_LOG_CONTENT, operationId, + httpServletRequest); operationsApiServiceAuditLog.logGetOperationLogContent(SecurityContextUtil.getUsername(), spaceGuid, operationId, logId); String content = logsService.getOperationLog(spaceGuid, operationId, logId); @@ -150,6 +163,7 @@ public ResponseEntity getOperationLogContent(String spaceGuid, String op @Override public ResponseEntity startOperation(String spaceGuid, Operation operation, HttpServletRequest httpServletRequest) { + apiUsageLogger.logOperationsMutatingCall(spaceGuid, Constants.ApiEndpointsNames.START_OPERATION, null, httpServletRequest); operationsApiServiceAuditLog.logStartOperation(SecurityContextUtil.getUsername(), spaceGuid, operation); UserInfo authenticatedUser = getAuthenticatedUser(); String processDefinitionKey = operationsHelper.getProcessDefinitionKey(operation); @@ -167,6 +181,7 @@ public ResponseEntity startOperation(String spaceGuid, Operation oper @Override public ResponseEntity getOperation(String spaceGuid, String operationId, String embed) { + apiUsageLogger.logOperationsReadCall(spaceGuid, Constants.ApiEndpointsNames.GET_OPERATION, operationId, httpServletRequest); operationsApiServiceAuditLog.logGetOperation(SecurityContextUtil.getUsername(), spaceGuid, operationId, embed); Operation operation = getOperationByOperationGuidAndSpaceGuid(operationId, spaceGuid); if (!operation.getSpaceId() @@ -213,6 +228,7 @@ private List filterByQueryParameters(Integer lastRequestedOperationsC @Override public ResponseEntity> getOperationActions(String spaceGuid, String operationId) { + apiUsageLogger.logOperationsReadCall(spaceGuid, Constants.ApiEndpointsNames.GET_OPERATION_ACTIONS, operationId, httpServletRequest); operationsApiServiceAuditLog.logGetOperationActions(spaceGuid, SecurityContextUtil.getUsername(), operationId); Operation operation = getOperationByOperationGuidAndSpaceGuid(operationId, spaceGuid); return ResponseEntity.ok() @@ -257,7 +273,7 @@ private Operation addServiceParameters(Operation operation, String spaceGuid, St String processDefinitionKey = operationsHelper.getProcessDefinitionKey(operation); - parameters.put(Constants.VARIABLE_NAME_SERVICE_ID, processDefinitionKey); + parameters.put(VARIABLE_NAME_SERVICE_ID, processDefinitionKey); parameters.put(Variables.USER.getName(), user); parameters.put(Variables.USER_GUID.getName(), userGuid); parameters.put(Variables.SPACE_NAME.getName(), space.getName()); diff --git a/multiapps-controller-web/src/main/java/org/cloudfoundry/multiapps/controller/web/monitoring/ApiUsageLogger.java b/multiapps-controller-web/src/main/java/org/cloudfoundry/multiapps/controller/web/monitoring/ApiUsageLogger.java new file mode 100644 index 0000000000..0f9450a01f --- /dev/null +++ b/multiapps-controller-web/src/main/java/org/cloudfoundry/multiapps/controller/web/monitoring/ApiUsageLogger.java @@ -0,0 +1,14 @@ +package org.cloudfoundry.multiapps.controller.web.monitoring; + +import jakarta.servlet.http.HttpServletRequest; + +public interface ApiUsageLogger { + + void logFilesMutatingCall(String spaceGuid, String namespace, String endpoint, HttpServletRequest request); + + void logFilesReadCall(String spaceGuid, String namespace, String endpoint, HttpServletRequest request); + + void logOperationsMutatingCall(String spaceGuid, String endpoint, String operationId, HttpServletRequest request); + + void logOperationsReadCall(String spaceGuid, String endpoint, String operationId, HttpServletRequest request); +} diff --git a/multiapps-controller-web/src/main/java/org/cloudfoundry/multiapps/controller/web/monitoring/NoopApiUsageLogger.java b/multiapps-controller-web/src/main/java/org/cloudfoundry/multiapps/controller/web/monitoring/NoopApiUsageLogger.java new file mode 100644 index 0000000000..160de5c41e --- /dev/null +++ b/multiapps-controller-web/src/main/java/org/cloudfoundry/multiapps/controller/web/monitoring/NoopApiUsageLogger.java @@ -0,0 +1,28 @@ +package org.cloudfoundry.multiapps.controller.web.monitoring; + +import jakarta.inject.Named; +import jakarta.servlet.http.HttpServletRequest; + +@Named +public class NoopApiUsageLogger implements ApiUsageLogger { + + @Override + public void logFilesMutatingCall(String spaceGuid, String namespace, String endpoint, HttpServletRequest request) { + // no-op + } + + @Override + public void logFilesReadCall(String spaceGuid, String namespace, String endpoint, HttpServletRequest request) { + // no-op + } + + @Override + public void logOperationsMutatingCall(String spaceGuid, String endpoint, String operationId, HttpServletRequest request) { + // no-op + } + + @Override + public void logOperationsReadCall(String spaceGuid, String endpoint, String operationId, HttpServletRequest request) { + // no-op + } +} diff --git a/multiapps-controller-web/src/test/java/org/cloudfoundry/multiapps/controller/web/api/impl/FilesApiServiceImplTest.java b/multiapps-controller-web/src/test/java/org/cloudfoundry/multiapps/controller/web/api/impl/FilesApiServiceImplTest.java index 77cad5d5ee..220c8865fb 100644 --- a/multiapps-controller-web/src/test/java/org/cloudfoundry/multiapps/controller/web/api/impl/FilesApiServiceImplTest.java +++ b/multiapps-controller-web/src/test/java/org/cloudfoundry/multiapps/controller/web/api/impl/FilesApiServiceImplTest.java @@ -30,6 +30,7 @@ import org.cloudfoundry.multiapps.controller.persistence.services.AsyncUploadJobService; import org.cloudfoundry.multiapps.controller.persistence.services.FileService; import org.cloudfoundry.multiapps.controller.persistence.services.FileStorageException; +import org.cloudfoundry.multiapps.controller.web.monitoring.ApiUsageLogger; import org.cloudfoundry.multiapps.controller.web.upload.AsyncUploadJobOrchestrator; import org.cloudfoundry.multiapps.controller.web.upload.exception.RejectedAsyncUploadJobException; import org.junit.jupiter.api.AfterEach; @@ -76,6 +77,8 @@ class FilesApiServiceImplTest { private final FilesApiServiceImpl testedClass = new FilesApiServiceImpl(); @Mock private FilesApiServiceAuditLog filesApiServiceAuditLog; + @Mock + private ApiUsageLogger apiUsageLogger; @Mock(name = "fileStorageThreadPool") private ExecutorService fileStorageThreadPool; @Mock diff --git a/multiapps-controller-web/src/test/java/org/cloudfoundry/multiapps/controller/web/api/impl/OperationsApiServiceImplTest.java b/multiapps-controller-web/src/test/java/org/cloudfoundry/multiapps/controller/web/api/impl/OperationsApiServiceImplTest.java index 724d388fb8..ca723aea16 100644 --- a/multiapps-controller-web/src/test/java/org/cloudfoundry/multiapps/controller/web/api/impl/OperationsApiServiceImplTest.java +++ b/multiapps-controller-web/src/test/java/org/cloudfoundry/multiapps/controller/web/api/impl/OperationsApiServiceImplTest.java @@ -37,6 +37,7 @@ import org.cloudfoundry.multiapps.controller.process.metadata.ProcessTypeToOperationMetadataMapper; import org.cloudfoundry.multiapps.controller.process.util.OperationsHelper; import org.cloudfoundry.multiapps.controller.process.variables.Variables; +import org.cloudfoundry.multiapps.controller.web.monitoring.ApiUsageLogger; import org.flowable.engine.runtime.ProcessInstance; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; @@ -85,6 +86,8 @@ class OperationsApiServiceImplTest { private ProcessAction processAction; @Mock private OperationsApiServiceAuditLog operationsApiServiceAuditLog; + @Mock + private ApiUsageLogger apiUsageLogger; @InjectMocks private OperationsApiServiceImpl operationsApiService = new OperationsApiServiceImpl();