Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,14 @@
*/
package com.sap.cds.feature.auditlog.ng;

import java.io.IOException;
import java.time.Duration;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.sap.cds.services.utils.CdsErrorStatuses;
import com.sap.cds.services.utils.ErrorStatusException;
import com.sap.cloud.environment.servicebinding.api.ServiceBinding;
import com.sap.cloud.sdk.cloudplatform.resilience.ResilienceConfiguration;
import com.sap.cloud.sdk.cloudplatform.resilience.ResilienceDecorator;
import com.sap.cloud.sdk.cloudplatform.resilience.ResilienceIsolationMode;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.methods.HttpPost;
Expand All @@ -16,14 +21,8 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.sap.cds.services.utils.CdsErrorStatuses;
import com.sap.cds.services.utils.ErrorStatusException;
import com.sap.cloud.environment.servicebinding.api.ServiceBinding;
import com.sap.cloud.sdk.cloudplatform.resilience.ResilienceConfiguration;
import com.sap.cloud.sdk.cloudplatform.resilience.ResilienceDecorator;
import com.sap.cloud.sdk.cloudplatform.resilience.ResilienceIsolationMode;
import java.io.IOException;
import java.time.Duration;

public class AuditLogNGCommunicator {

Expand All @@ -33,7 +32,8 @@ public class AuditLogNGCommunicator {
private static final int NUMBER_RETRIES = 3;
private static final Duration TIMEOUT_DURATION = Duration.ofMillis(30000);
private static final String RESILIENCE_CONFIG_NAME = "auditlog";
private static final String AUDITLOG_EVENTS_ENDPOINT = "/ingestion/v1/events";
private static final String AUDITLOG_V1_INGESTION_ENDPOINT = "/ingestion/v1/events";
private static final String AUDITLOG_V2_INGESTION_ENDPOINT = "/ingestion/v2/events";

private final ResilienceConfiguration resilienceConfig;
private final String serviceUrl;
Expand Down Expand Up @@ -68,11 +68,10 @@ public AuditLogNGCommunicator(ServiceBinding binding) {
}
}

String sendBulkRequest(Object auditLogEvents) throws JsonProcessingException {
String sendBulkRequest(Object auditLogEvents, final boolean isLegacyEvent) throws JsonProcessingException {
logger.debug("Sending bulk request to audit log service");
String bulkRequestJson = serializeBulkRequest(auditLogEvents);
HttpPost request = new HttpPost(serviceUrl + AUDITLOG_EVENTS_ENDPOINT);
request.setEntity(new StringEntity(bulkRequestJson, ContentType.APPLICATION_JSON));
HttpPost request = prepareRequest(isLegacyEvent, bulkRequestJson);
try {
return ResilienceDecorator.executeCallable(() -> executeBulkRequest(request), resilienceConfig);
} catch (ErrorStatusException ese) {
Expand All @@ -87,6 +86,23 @@ String sendBulkRequest(Object auditLogEvents) throws JsonProcessingException {
}
}

/**
* Prepares the HTTP POST request for the given audit log events payload.
* Legacy events are sent to the v1 ingestion endpoint with {@code application/json} content type.
* Non-legacy events are sent to the v2 ingestion endpoint with {@code application/cloudevents-batch+json} content type.
*
* @param isLegacyEvent whether the event uses the legacy v1 format
* @param bulkRequestJson the serialized JSON payload to send
* @return a configured {@link HttpPost} request ready to execute
*/
private HttpPost prepareRequest(boolean isLegacyEvent, String bulkRequestJson) {
String endpoint = isLegacyEvent ? AUDITLOG_V1_INGESTION_ENDPOINT : AUDITLOG_V2_INGESTION_ENDPOINT;
ContentType contentType = isLegacyEvent ? ContentType.APPLICATION_JSON : ContentType.create("application/cloudevents-batch+json", "UTF-8");
HttpPost request = new HttpPost(serviceUrl + endpoint);
request.setEntity(new StringEntity(bulkRequestJson, contentType));
return request;
}

/**
* Serializes the audit log events object to JSON.
*/
Expand Down

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,79 +1,139 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"oneOf": [
{
"$ref": "#/definitions/EventEnvelope"
"$ref": "#/$defs/EventEnvelope"
},
{
"$ref": "#/definitions/ConfigurationChange"
"$ref": "#/$defs/ConfigurationChange"
},
{
"$ref": "#/$defs/_Common"
}
],
"definitions": {
"$defs": {
"ConfigurationChange": {
"required": [
"newValue",
"oldValue",
"common",
"propertyName",
"objectType"
"type"
],
"properties": {
"newValue": {
"oneOf": [
{
"type": "array"
},
{
"type": "boolean"
},
{
"type": "number"
},
{
"type": "object"
},
{
"type": "string"
}
],
"title": "Value",
"description": "`Value` represents a dynamically typed value which can be either null, a number, a string, a boolean, a recursive struct value, or a list of values. A producer of value is expected to set one of these variants. Absence of any variant indicates an error. The JSON representation for `Value` is JSON value."
},
"oldValue": {
"oneOf": [
{
"type": "array"
},
{
"type": "boolean"
},
{
"type": "number"
},
{
"type": "object"
},
{
"type": "string"
}
],
"title": "Value",
"description": "`Value` represents a dynamically typed value which can be either null, a number, a string, a boolean, a recursive struct value, or a list of values. A producer of value is expected to set one of these variants. Absence of any variant indicates an error. The JSON representation for `Value` is JSON value."
"common": {
"$ref": "#/$defs/_Common"
},
"propertyName": {
"newValue": {},
"objectId": {
"type": "string"
},
"objectType": {
"maxLength": 20,
"type": "string"
},
"objectId": {
"oldValue": {},
"propertyName": {
"minLength": 1,
"type": "string"
},
"type": {
"const": "ConfigurationChange"
}
},
"additionalProperties": false,
"type": "object",
"title": "Configuration Change",
"description": "ConfigurationChange states that Configuration has been modified."
},
"_UserContext": {
"additionalProperties": false,
"properties": {
"attributes": {
"additionalProperties": {
"type": "string"
},
"description": "Additional custom attributes",
"propertyNames": {
"type": "string"
},
"type": "object"
},
"type": {
"description": "UserType",
"enum": [
"USER_TYPE_BUSINESS_USER",
"USER_TYPE_TECHNICAL_USER",
"USER_TYPE_SAP_SUPPORT_USER"
],
"title": "User Type",
"type": "string"
}
},
"title": "User Context",
"type": "object"
},
"_Common": {
"additionalProperties": false,
"description": "\n\n| Annotation | Value |\n| --- | --- |\n",
"properties": {
"appContext": {
"additionalProperties": {
"type": "string"
},
"propertyNames": {
"type": "string"
},
"type": "object"
},
"appId": {
"type": "string"
},
"sourceIp": {
"description": "IP Address of the source in IPv4 or IPv6",
"items": {
"type": "string"
},
"type": "array"
},
"tenantId": {
"description": "tenant ID of the source",
"type": "string"
},
"timestamp": {
"type": "string",
"format": "date-time"
},
"userGlobalId": {
"description": "contains the global user ID, who caused the audit log event",
"type": "string"
},
"userImpersonatedContext": {
"$ref": "#/$defs/_UserContext",
"description": "Extended user context for Impersonated User"
},
"userImpersonatedGlobalId": {
"description": "contains the global user ID of the impersonated user",
"type": "string"
},
"userImpersonatedId": {
"description": "contains the local user ID, which is used in case of impersonation",
"type": "string"
},
"userInitiatorContext": {
"$ref": "#/$defs/_UserContext",
"description": "Extended user context for User Initiator"
},
"userInitiatorId": {
"description": "contains the local user ID, who caused the audit log event",
"type": "string"
},
"userSessionContextId": {
"type": "string"
}
},
"required": [],
"title": "Common",
"type": "object"
},
"EventEnvelope": {
"type": "object",
"properties": {
Expand All @@ -92,29 +152,11 @@
"time": {
"type": "string"
},
"dataschema": {
"type": "string"
},
"data": {
"type": "object",
"properties": {
"metadata": {
"type": "object"
},
"data": {
"type": "object",
"properties": {
"configurationChange": {
"$ref": "#/definitions/ConfigurationChange"
}
},
"required": [
"configurationChange"
],
"additionalProperties": true
}
},
"required": [
"data"
],
"additionalProperties": true
"$ref": "#/$defs/ConfigurationChange"
}
},
"required": [
Expand All @@ -125,4 +167,4 @@
"additionalProperties": true
}
}
}
}
Loading
Loading