diff --git a/.circleci/config.continue.yml.j2 b/.circleci/config.continue.yml.j2 index 8029d84fb12..03c6b555812 100644 --- a/.circleci/config.continue.yml.j2 +++ b/.circleci/config.continue.yml.j2 @@ -862,6 +862,8 @@ jobs: APPSEC_API_SECURITY APPSEC_API_SECURITY_RC APPSEC_API_SECURITY_WITH_SAMPLING + APPSEC_AUTO_EVENTS_RC + APPSEC_AUTO_EVENTS_EXTENDED APPSEC_WAF_TELEMETRY APPSEC_STANDALONE_V2 IAST_STANDALONE_V2 diff --git a/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/Agent.java b/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/Agent.java index 6c73e8f1a4c..69156ff9393 100644 --- a/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/Agent.java +++ b/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/Agent.java @@ -16,6 +16,7 @@ import datadog.trace.api.Platform; import datadog.trace.api.StatsDClientManager; import datadog.trace.api.WithGlobalTracer; +import datadog.trace.api.appsec.AppSecEventTracker; import datadog.trace.api.config.AppSecConfig; import datadog.trace.api.config.CiVisibilityConfig; import datadog.trace.api.config.CwsConfig; @@ -816,6 +817,14 @@ private static StatsDClientManager statsDClientManager() throws Exception { } private static void maybeStartAppSec(Class scoClass, Object o) { + + try { + // event tracking SDK must be available for customers even if AppSec is fully disabled + AppSecEventTracker.install(); + } catch (final Exception e) { + log.debug("Error starting AppSec Event Tracker", e); + } + if (!(appSecEnabled || (remoteConfigEnabled && !appSecFullyDisabled))) { return; } diff --git a/dd-java-agent/appsec/src/main/java/com/datadog/appsec/AppSecSystem.java b/dd-java-agent/appsec/src/main/java/com/datadog/appsec/AppSecSystem.java index 2651f45a61b..9d86b681cc1 100644 --- a/dd-java-agent/appsec/src/main/java/com/datadog/appsec/AppSecSystem.java +++ b/dd-java-agent/appsec/src/main/java/com/datadog/appsec/AppSecSystem.java @@ -17,7 +17,6 @@ import datadog.remoteconfig.ConfigurationPoller; import datadog.trace.api.Config; import datadog.trace.api.ProductActivation; -import datadog.trace.api.appsec.AppSecEventTracker; import datadog.trace.api.gateway.SubscriptionService; import datadog.trace.api.telemetry.ProductChange; import datadog.trace.api.telemetry.ProductChangeCollector; @@ -97,8 +96,6 @@ private static void doStart(SubscriptionService gw, SharedCommunicationObjects s Blocking.setBlockingService(new BlockingServiceImpl(REPLACEABLE_EVENT_PRODUCER)); - AppSecEventTracker.setEventTracker(new AppSecEventTracker()); - STARTED.set(true); String startedAppSecModules = String.join(", ", STARTED_MODULES_INFO.values()); diff --git a/dd-java-agent/appsec/src/main/java/com/datadog/appsec/gateway/AppSecRequestContext.java b/dd-java-agent/appsec/src/main/java/com/datadog/appsec/gateway/AppSecRequestContext.java index 6dc893f2faf..34536773602 100644 --- a/dd-java-agent/appsec/src/main/java/com/datadog/appsec/gateway/AppSecRequestContext.java +++ b/dd-java-agent/appsec/src/main/java/com/datadog/appsec/gateway/AppSecRequestContext.java @@ -8,7 +8,6 @@ import com.datadog.appsec.report.AppSecEvent; import com.datadog.appsec.util.StandardizedLogging; import datadog.trace.api.Config; -import datadog.trace.api.UserIdCollectionMode; import datadog.trace.api.http.StoredBodySupplier; import datadog.trace.api.internal.TraceSegment; import datadog.trace.bootstrap.instrumentation.api.AgentSpan; @@ -136,10 +135,8 @@ public class AppSecRequestContext implements DataBundle, Closeable { // keep a reference to the last published usr.id private volatile String userId; - private volatile UserIdCollectionMode userIdSource; // keep a reference to the last published usr.login private volatile String userLogin; - private volatile UserIdCollectionMode userLoginSource; // keep a reference to the last published usr.session_id private volatile String sessionId; @@ -536,36 +533,30 @@ public void setRespDataPublished(boolean respDataPublished) { this.respDataPublished = respDataPublished; } - public String getUserId() { - return userId; - } - - public void setUserId(String userId) { + /** + * Updates the current used usr.id + * + * @return {@code false} if the user id has not been updated + */ + public boolean updateUserId(String userId) { + if (Objects.equals(this.userId, userId)) { + return false; + } this.userId = userId; + return true; } - public UserIdCollectionMode getUserIdSource() { - return userIdSource; - } - - public void setUserIdSource(UserIdCollectionMode userIdSource) { - this.userIdSource = userIdSource; - } - - public String getUserLogin() { - return userLogin; - } - - public void setUserLogin(String userLogin) { + /** + * Updates current used usr.login + * + * @return {@code false} if the user login has not been updated + */ + public boolean updateUserLogin(String userLogin) { + if (Objects.equals(this.userLogin, userLogin)) { + return false; + } this.userLogin = userLogin; - } - - public UserIdCollectionMode getUserLoginSource() { - return userLoginSource; - } - - public void setUserLoginSource(UserIdCollectionMode userLoginSource) { - this.userLoginSource = userLoginSource; + return true; } public void setSessionId(String sessionId) { diff --git a/dd-java-agent/appsec/src/main/java/com/datadog/appsec/gateway/GatewayBridge.java b/dd-java-agent/appsec/src/main/java/com/datadog/appsec/gateway/GatewayBridge.java index be8d9b487b4..7d6440932ba 100644 --- a/dd-java-agent/appsec/src/main/java/com/datadog/appsec/gateway/GatewayBridge.java +++ b/dd-java-agent/appsec/src/main/java/com/datadog/appsec/gateway/GatewayBridge.java @@ -1,16 +1,10 @@ package com.datadog.appsec.gateway; import static com.datadog.appsec.event.data.MapDataBundle.Builder.CAPACITY_0_2; -import static com.datadog.appsec.event.data.MapDataBundle.Builder.CAPACITY_3_4; import static com.datadog.appsec.event.data.MapDataBundle.Builder.CAPACITY_6_10; import static com.datadog.appsec.gateway.AppSecRequestContext.DEFAULT_REQUEST_HEADERS_ALLOW_LIST; import static com.datadog.appsec.gateway.AppSecRequestContext.REQUEST_HEADERS_ALLOW_LIST; import static com.datadog.appsec.gateway.AppSecRequestContext.RESPONSE_HEADERS_ALLOW_LIST; -import static datadog.trace.api.UserIdCollectionMode.ANONYMIZATION; -import static datadog.trace.api.UserIdCollectionMode.DISABLED; -import static datadog.trace.api.UserIdCollectionMode.SDK; -import static datadog.trace.api.telemetry.LogCollector.SEND_TELEMETRY; -import static datadog.trace.util.Strings.toHexString; import com.datadog.appsec.AppSecSystem; import com.datadog.appsec.api.security.ApiSecurityRequestSampler; @@ -28,7 +22,6 @@ import com.datadog.appsec.report.AppSecEventWrapper; import datadog.trace.api.Config; import datadog.trace.api.ProductTraceSource; -import datadog.trace.api.UserIdCollectionMode; import datadog.trace.api.gateway.Events; import datadog.trace.api.gateway.Flow; import datadog.trace.api.gateway.IGSpanInfo; @@ -48,19 +41,17 @@ import java.net.URISyntaxException; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.EnumMap; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.atomic.AtomicBoolean; import java.util.regex.Pattern; import java.util.stream.Collectors; import org.slf4j.Logger; @@ -76,21 +67,16 @@ public class GatewayBridge { private static final Pattern QUERY_PARAM_SPLITTER = Pattern.compile("&"); private static final Map> EMPTY_QUERY_PARAMS = Collections.emptyMap(); - private static final int HASH_SIZE_BYTES = 16; // 128 bits - private static final String ANON_PREFIX = "anon_"; - private static final AtomicBoolean SHA_MISSING_REPORTED = new AtomicBoolean(false); - /** User tracking tags that will force the collection of request headers */ private static final String[] USER_TRACKING_TAGS = { "appsec.events.users.login.success.track", "appsec.events.users.login.failure.track" }; - private static final Map EVENT_MAPPINGS = new HashMap<>(); + private static final Map> EVENT_MAPPINGS = new EnumMap<>(LoginEvent.class); static { - EVENT_MAPPINGS.put("users.login.success", LoginEvent.LOGIN_SUCCESS); - EVENT_MAPPINGS.put("users.login.failure", LoginEvent.LOGIN_FAILURE); - EVENT_MAPPINGS.put("users.signup", LoginEvent.SIGN_UP); + EVENT_MAPPINGS.put(LoginEvent.LOGIN_SUCCESS, KnownAddresses.LOGIN_SUCCESS); + EVENT_MAPPINGS.put(LoginEvent.LOGIN_FAILURE, KnownAddresses.LOGIN_FAILURE); } private static final String METASTRUCT_EXPLOIT = "exploit"; @@ -198,44 +184,16 @@ public void reset() { shellCmdSubInfo = null; } - private Flow onUser( - final RequestContext ctx_, final UserIdCollectionMode mode, final String originalUser) { - if (mode == DISABLED) { - return NoopFlow.INSTANCE; - } - final String user = anonymizeUser(mode, originalUser); - if (user == null) { - return NoopFlow.INSTANCE; - } + private Flow onUser(final RequestContext ctx_, final String user) { final AppSecRequestContext ctx = ctx_.getData(RequestContextSlot.APPSEC); if (ctx == null) { return NoopFlow.INSTANCE; } - final TraceSegment segment = ctx_.getTraceSegment(); - - // span with ASM data - segment.setTagTop(Tags.ASM_KEEP, true); - segment.setTagTop(Tags.PROPAGATED_TRACE_SOURCE, ProductTraceSource.ASM); - - // skip event if we have an SDK one - if (mode != SDK) { - segment.setTagTop("_dd.appsec.usr.id", user); - if (ctx.getUserIdSource() == SDK) { - return NoopFlow.INSTANCE; - } - } - - // update span tags - segment.setTagTop("usr.id", user); - segment.setTagTop("_dd.appsec.user.collection_mode", mode.fullName()); // update current context with new user id - ctx.setUserIdSource(mode); - final boolean newUserId = !user.equals(ctx.getUserId()); - if (!newUserId) { + if (!ctx.updateUserId(user)) { return NoopFlow.INSTANCE; } - ctx.setUserId(user); // call waf if we have a new user id while (true) { @@ -259,96 +217,29 @@ private Flow onUser( } private Flow onLoginEvent( - final RequestContext ctx_, - final UserIdCollectionMode mode, - final String eventName, - final Boolean exists, - final String originalUser, - final Map metadata) { - if (mode == DISABLED) { - return NoopFlow.INSTANCE; - } + final RequestContext ctx_, final LoginEvent event, final String login) { final AppSecRequestContext ctx = ctx_.getData(RequestContextSlot.APPSEC); if (ctx == null) { return NoopFlow.INSTANCE; } - final TraceSegment segment = ctx_.getTraceSegment(); - - // span with ASM data - segment.setTagTop(Tags.ASM_KEEP, true); - segment.setTagTop(Tags.PROPAGATED_TRACE_SOURCE, ProductTraceSource.ASM); - - // update span tags - segment.setTagTop("appsec.events." + eventName + ".track", true, true); - if (metadata != null && !metadata.isEmpty()) { - segment.setTagTop("appsec.events." + eventName, metadata, true); - } - if (mode == SDK) { - segment.setTagTop("_dd.appsec.events." + eventName + ".sdk", true, true); - } else { - segment.setTagTop("_dd.appsec.events." + eventName + ".auto.mode", mode.fullName(), true); - } - - if (exists != null) { - if (mode == SDK || ctx.getUserLoginSource() != SDK) { - segment.setTagTop("appsec.events." + eventName + ".usr.exists", exists, true); - } - } - - final String user = anonymizeUser(mode, originalUser); - if (user == null) { - // can happen in custom events - return NoopFlow.INSTANCE; - } - - // parse the event (might be null for custom events sent via the SDK) - final LoginEvent sourceEvent = EVENT_MAPPINGS.get(eventName); - - // skip event if we have an SDK one - if (mode != SDK) { - segment.setTagTop("_dd.appsec.usr.login", user); - if (ctx.getUserLoginSource() == SDK) { - return NoopFlow.INSTANCE; - } - } else { - if (sourceEvent == LoginEvent.LOGIN_SUCCESS) { - segment.setTagTop("usr.id", user, false); - } else { - segment.setTagTop("appsec.events." + eventName + ".usr.id", user, true); - } - segment.setTagTop("_dd.appsec.user.collection_mode", mode.fullName()); - } - - // update user span tags - segment.setTagTop("appsec.events." + eventName + ".usr.login", user, true); // update current context with new user login - ctx.setUserLoginSource(mode); - if (mode == SDK) { - ctx.setUserIdSource(mode); // we are setting the usr.id through the SDK - } - final boolean newUserLogin = !user.equals(ctx.getUserLogin()); - if (!newUserLogin) { + if (!ctx.updateUserLogin(login)) { return NoopFlow.INSTANCE; } - ctx.setUserLogin(user); // call waf if we have a new user login - final List> addresses = new ArrayList<>(3); - final MapDataBundle.Builder bundleBuilder = new MapDataBundle.Builder(CAPACITY_3_4); + final List> addresses = new ArrayList<>(2); + final MapDataBundle.Builder bundleBuilder = new MapDataBundle.Builder(CAPACITY_0_2); addresses.add(KnownAddresses.USER_LOGIN); - bundleBuilder.add(KnownAddresses.USER_LOGIN, user); - if (mode == SDK) { - addresses.add(KnownAddresses.USER_ID); - bundleBuilder.add(KnownAddresses.USER_ID, user); - } - // we don't support null values for the address so we use an invalid placeholder here - if (sourceEvent == LoginEvent.LOGIN_SUCCESS) { - addresses.add(KnownAddresses.LOGIN_SUCCESS); - bundleBuilder.add(KnownAddresses.LOGIN_SUCCESS, "invalid"); - } else if (sourceEvent == LoginEvent.LOGIN_FAILURE) { - addresses.add(KnownAddresses.LOGIN_FAILURE); - bundleBuilder.add(KnownAddresses.LOGIN_FAILURE, "invalid"); + bundleBuilder.add(KnownAddresses.USER_LOGIN, login); + + // parse the event + Address address = EVENT_MAPPINGS.get(event); + if (address != null) { + addresses.add(address); + // we don't support null values for the address so we use an invalid placeholder here + bundleBuilder.add(address, "invalid"); } final DataBundle bundle = bundleBuilder.build(); final String subInfoKey = @@ -1159,33 +1050,6 @@ private static int byteToDigit(byte b) { return -1; } - protected static String anonymizeUser(final UserIdCollectionMode mode, final String userId) { - if (mode != ANONYMIZATION || userId == null) { - return userId; - } - MessageDigest digest; - try { - // TODO avoid lookup a new instance every time - digest = MessageDigest.getInstance("SHA-256"); - } catch (NoSuchAlgorithmException e) { - if (!SHA_MISSING_REPORTED.getAndSet(true)) { - log.error( - SEND_TELEMETRY, - "Missing SHA-256 digest, user collection in 'anon' mode cannot continue", - e); - } - return null; - } - digest.update(userId.getBytes()); - byte[] hash = digest.digest(); - if (hash.length > HASH_SIZE_BYTES) { - byte[] temp = new byte[HASH_SIZE_BYTES]; - System.arraycopy(hash, 0, temp, 0, temp.length); - hash = temp; - } - return ANON_PREFIX + toHexString(hash); - } - private static class IGAppSecEventDependencies { private static final Map, Collection>> diff --git a/dd-java-agent/appsec/src/test/groovy/com/datadog/appsec/gateway/GatewayBridgeSpecification.groovy b/dd-java-agent/appsec/src/test/groovy/com/datadog/appsec/gateway/GatewayBridgeSpecification.groovy index e5285983003..c083745173c 100644 --- a/dd-java-agent/appsec/src/test/groovy/com/datadog/appsec/gateway/GatewayBridgeSpecification.groovy +++ b/dd-java-agent/appsec/src/test/groovy/com/datadog/appsec/gateway/GatewayBridgeSpecification.groovy @@ -8,9 +8,6 @@ import com.datadog.appsec.event.data.DataBundle import com.datadog.appsec.event.data.KnownAddresses import com.datadog.appsec.report.AppSecEvent import com.datadog.appsec.report.AppSecEventWrapper -import datadog.trace.api.ProductTraceSource -import datadog.trace.api.UserIdCollectionMode -import datadog.trace.api.appsec.LoginEventCallback import datadog.trace.api.function.TriConsumer import datadog.trace.api.function.TriFunction import datadog.trace.api.gateway.BlockResponseFunction @@ -21,6 +18,7 @@ import datadog.trace.api.gateway.RequestContextSlot import datadog.trace.api.gateway.SubscriptionService import datadog.trace.api.http.StoredBodySupplier import datadog.trace.api.internal.TraceSegment +import datadog.trace.api.telemetry.LoginEvent import datadog.trace.bootstrap.instrumentation.api.AgentSpan import datadog.trace.bootstrap.instrumentation.api.URIDataAdapter import datadog.trace.bootstrap.instrumentation.api.URIDataAdapterBase @@ -31,16 +29,14 @@ import java.util.function.BiFunction import java.util.function.Function import java.util.function.Supplier -import static datadog.trace.api.UserIdCollectionMode.ANONYMIZATION -import static datadog.trace.api.UserIdCollectionMode.DISABLED -import static datadog.trace.api.UserIdCollectionMode.IDENTIFICATION -import static datadog.trace.api.UserIdCollectionMode.SDK import static datadog.trace.api.gateway.Events.EVENTS +import static datadog.trace.api.telemetry.LoginEvent.LOGIN_FAILURE +import static datadog.trace.api.telemetry.LoginEvent.LOGIN_SUCCESS +import static datadog.trace.api.telemetry.LoginEvent.SIGN_UP class GatewayBridgeSpecification extends DDSpecification { private static final String USER_ID = 'user' - private static final String ANONYMIZED_USER_ID = 'anon_04f8996da763b7a969b1028ee3007569' SubscriptionService ig = Mock() EventDispatcher eventDispatcher = Mock() @@ -105,8 +101,8 @@ class GatewayBridgeSpecification extends DDSpecification { BiFunction> requestSessionCB BiFunction> execCmdCB BiFunction> shellCmdCB - TriFunction> userCB - LoginEventCallback loginEventCB + BiFunction> userCB + TriFunction> loginEventCB void setup() { callInitAndCaptureCBs() @@ -1055,285 +1051,72 @@ class GatewayBridgeSpecification extends DDSpecification { gatewayContext.isTransient == false } - void "test onUserEvent (#mode)"() { + void "test onUserEvent"() { setup: - final expectedUser = mode == ANONYMIZATION ? ANONYMIZED_USER_ID : USER_ID eventDispatcher.getDataSubscribers(_) >> nonEmptyDsInfo when: - userCB.apply(ctx, mode, USER_ID) + userCB.apply(ctx, USER_ID) then: - if (mode == DISABLED) { - 0 * _ - } else { - 1 * traceSegment.setTagTop('usr.id', expectedUser) - if (mode != SDK) { - 1 * traceSegment.setTagTop('_dd.appsec.usr.id', expectedUser) - } - 1 * traceSegment.setTagTop('_dd.appsec.user.collection_mode', mode.fullName()) - 1 * eventDispatcher.publishDataEvent(nonEmptyDsInfo, ctx.data, _ as DataBundle, _ as GatewayContext) >> { a, b, DataBundle db, GatewayContext gw -> - assert db.get(KnownAddresses.USER_ID) == expectedUser - assert !gw.isTransient - return NoopFlow.INSTANCE - } + 1 * eventDispatcher.publishDataEvent(nonEmptyDsInfo, ctx.data, _ as DataBundle, _ as GatewayContext) >> { a, b, DataBundle db, GatewayContext gw -> + assert db.get(KnownAddresses.USER_ID) == USER_ID + assert !gw.isTransient + return NoopFlow.INSTANCE } when: - userCB.apply(ctx, mode, USER_ID) + userCB.apply(ctx, USER_ID) then: 'no call to the WAF for duplicated calls' 0 * eventDispatcher.publishDataEvent - - where: - mode << UserIdCollectionMode.values() } - void "test onSignup (#mode)"() { + void "test onSignup"() { setup: - final expectedUser = mode == ANONYMIZATION ? ANONYMIZED_USER_ID : USER_ID - final metadata = ['key1': 'value1', 'key2': 'value2'] eventDispatcher.getDataSubscribers(_) >> nonEmptyDsInfo when: - loginEventCB.apply(ctx, mode, 'users.signup', null, USER_ID, metadata) + loginEventCB.apply(ctx, SIGN_UP, USER_ID) then: - if (mode == DISABLED) { - 0 * _ - } else { - 1 * traceSegment.setTagTop('appsec.events.users.signup.usr.login', expectedUser, true) - if (mode != SDK) { - 1 * traceSegment.setTagTop('_dd.appsec.usr.login', expectedUser) - 1 * traceSegment.setTagTop('_dd.appsec.events.users.signup.auto.mode', mode.fullName(), true) - } else { - 1 * traceSegment.setTagTop('appsec.events.users.signup.usr.id', expectedUser, true) - 1 * traceSegment.setTagTop('_dd.appsec.events.users.signup.sdk', true, true) - } - 1 * traceSegment.setTagTop('appsec.events.users.signup.track', true, true) - 1 * traceSegment.setTagTop('appsec.events.users.signup', ['key1': 'value1', 'key2': 'value2'], true) - 1 * traceSegment.setTagTop('asm.keep', true) - 1 * traceSegment.setTagTop('_dd.p.ts', ProductTraceSource.ASM) - 1 * eventDispatcher.publishDataEvent(nonEmptyDsInfo, ctx.data, _ as DataBundle, _ as GatewayContext) >> { a, b, DataBundle db, GatewayContext gw -> - if (mode == SDK) { - assert db.get(KnownAddresses.USER_ID) == expectedUser - } - assert db.get(KnownAddresses.USER_LOGIN) == expectedUser - assert !gw.isTransient - return NoopFlow.INSTANCE - } + 1 * eventDispatcher.publishDataEvent(nonEmptyDsInfo, ctx.data, _ as DataBundle, _ as GatewayContext) >> { a, b, DataBundle db, GatewayContext gw -> + assert db.get(KnownAddresses.USER_LOGIN) == USER_ID + assert !gw.isTransient + return NoopFlow.INSTANCE } - - where: - mode << UserIdCollectionMode.values() } - void "test onLoginSuccess (#mode)"() { + void "test onLoginSuccess"() { setup: - final expectedUser = mode == ANONYMIZATION ? ANONYMIZED_USER_ID : USER_ID - final metadata = ['key1': 'value1', 'key2': 'value2'] eventDispatcher.getDataSubscribers(_) >> nonEmptyDsInfo when: - loginEventCB.apply(ctx, mode, 'users.login.success', null, USER_ID, metadata) + loginEventCB.apply(ctx, LOGIN_SUCCESS, USER_ID) then: - if (mode == DISABLED) { - 0 * _ - } else { - 1 * traceSegment.setTagTop('appsec.events.users.login.success.usr.login', expectedUser, true) - if (mode != SDK) { - 1 * traceSegment.setTagTop('_dd.appsec.usr.login', expectedUser) - 1 * traceSegment.setTagTop('_dd.appsec.events.users.login.success.auto.mode', mode.fullName(), true) - } else { - 1 * traceSegment.setTagTop('usr.id', expectedUser, false) - 1 * traceSegment.setTagTop('_dd.appsec.events.users.login.success.sdk', true, true) - } - 1 * traceSegment.setTagTop('appsec.events.users.login.success.track', true, true) - 1 * traceSegment.setTagTop('appsec.events.users.login.success', ['key1': 'value1', 'key2': 'value2'], true) - 1 * traceSegment.setTagTop('asm.keep', true) - 1 * traceSegment.setTagTop('_dd.p.ts', ProductTraceSource.ASM) - 1 * eventDispatcher.publishDataEvent(nonEmptyDsInfo, ctx.data, _ as DataBundle, _ as GatewayContext) >> { a, b, DataBundle db, GatewayContext gw -> - if (mode == SDK) { - assert db.get(KnownAddresses.USER_ID) == expectedUser - } - assert db.get(KnownAddresses.USER_LOGIN) == expectedUser - assert db.get(KnownAddresses.LOGIN_SUCCESS) != null - assert !gw.isTransient - return NoopFlow.INSTANCE - } + 1 * eventDispatcher.publishDataEvent(nonEmptyDsInfo, ctx.data, _ as DataBundle, _ as GatewayContext) >> { a, b, DataBundle db, GatewayContext gw -> + assert db.get(KnownAddresses.USER_LOGIN) == USER_ID + assert db.get(KnownAddresses.LOGIN_SUCCESS) != null + assert !gw.isTransient + return NoopFlow.INSTANCE } - - where: - mode << UserIdCollectionMode.values() } void "test onLoginFailure (#mode)"() { setup: - final expectedUser = mode == ANONYMIZATION ? ANONYMIZED_USER_ID : USER_ID - final metadata = ['key1': 'value1', 'key2': 'value2'] eventDispatcher.getDataSubscribers(_) >> nonEmptyDsInfo when: - loginEventCB.apply(ctx, mode, 'users.login.failure', false, USER_ID, metadata) + loginEventCB.apply(ctx, LOGIN_FAILURE, USER_ID) then: - if (mode == DISABLED) { - 0 * _ - } else { - 1 * traceSegment.setTagTop('appsec.events.users.login.failure.usr.login', expectedUser, true) - if (mode != SDK) { - 1 * traceSegment.setTagTop('_dd.appsec.usr.login', expectedUser) - 1 * traceSegment.setTagTop('_dd.appsec.events.users.login.failure.auto.mode', mode.fullName(), true) - } else { - 1 * traceSegment.setTagTop('appsec.events.users.login.failure.usr.id', expectedUser, true) - 1 * traceSegment.setTagTop('_dd.appsec.events.users.login.failure.sdk', true, true) - } - 1 * traceSegment.setTagTop('appsec.events.users.login.failure.track', true, true) - 1 * traceSegment.setTagTop('appsec.events.users.login.failure.usr.exists', false, true) - 1 * traceSegment.setTagTop('appsec.events.users.login.failure', ['key1': 'value1', 'key2': 'value2'], true) - 1 * traceSegment.setTagTop('asm.keep', true) - 1 * traceSegment.setTagTop('_dd.p.ts', ProductTraceSource.ASM) - 1 * eventDispatcher.publishDataEvent(nonEmptyDsInfo, ctx.data, _ as DataBundle, _ as GatewayContext) >> { a, b, DataBundle db, GatewayContext gw -> - if (mode == SDK) { - assert db.get(KnownAddresses.USER_ID) == expectedUser - } - assert db.get(KnownAddresses.USER_LOGIN) == expectedUser - assert db.get(KnownAddresses.LOGIN_FAILURE) != null - assert !gw.isTransient - return NoopFlow.INSTANCE - } + 1 * eventDispatcher.publishDataEvent(nonEmptyDsInfo, ctx.data, _ as DataBundle, _ as GatewayContext) >> { a, b, DataBundle db, GatewayContext gw -> + assert db.get(KnownAddresses.USER_LOGIN) == USER_ID + assert db.get(KnownAddresses.LOGIN_FAILURE) != null + assert !gw.isTransient + return NoopFlow.INSTANCE } - - where: - mode << UserIdCollectionMode.values() - } - - void "test onCustomEvent (#mode)"() { - setup: - final metadata = ['key1': 'value1', 'key2': 'value2'] - eventDispatcher.getDataSubscribers(_) >> nonEmptyDsInfo - - when: - loginEventCB.apply(ctx, SDK, 'my.event', null, null, metadata) - - then: - 1 * traceSegment.setTagTop('_dd.appsec.events.my.event.sdk', true, true) - 1 * traceSegment.setTagTop('appsec.events.my.event.track', true, true) - 1 * traceSegment.setTagTop('appsec.events.my.event', ['key1': 'value1', 'key2': 'value2'], true) - 1 * traceSegment.setTagTop('asm.keep', true) - 1 * traceSegment.setTagTop('_dd.p.ts', ProductTraceSource.ASM) - 0 * eventDispatcher.publishDataEvent - } - - void "test onUserEvent (automated login events should not overwrite SDK)"() { - setup: - final firstUser = 'first-user' - final secondUser = 'second-user' - eventDispatcher.getDataSubscribers(_) >> nonEmptyDsInfo - - when: - userCB.apply(ctx, SDK, firstUser) - - then: - 1 * traceSegment.setTagTop('usr.id', firstUser) - 1 * traceSegment.setTagTop('_dd.appsec.user.collection_mode', SDK.fullName()) - 0 * traceSegment.setTagTop('_dd.appsec.usr.id', _) - 1 * eventDispatcher.publishDataEvent(nonEmptyDsInfo, ctx.data, _ as DataBundle, _ as GatewayContext) >> NoopFlow.INSTANCE - - when: - userCB.apply(ctx, IDENTIFICATION, secondUser) - - then: 'SDK data remains untouched' - 0 * traceSegment.setTagTop('usr.id', _) - 0 * traceSegment.setTagTop('_dd.appsec.user.collection_mode', _) - 1 * traceSegment.setTagTop('_dd.appsec.usr.id', secondUser) - 0 * eventDispatcher.publishDataEvent - } - - void "test onLoginSuccess (automated login events should not overwrite SDK)"() { - setup: - final firstUser = 'user1' - final secondUser = 'user2' - eventDispatcher.getDataSubscribers(_) >> nonEmptyDsInfo - - when: - loginEventCB.apply(ctx, SDK, 'users.login.success', null, firstUser, null) - - then: - 1 * traceSegment.setTagTop('appsec.events.users.login.success.usr.login', firstUser, true) - 1 * traceSegment.setTagTop('usr.id', firstUser, false) - 1 * traceSegment.setTagTop('_dd.appsec.events.users.login.success.sdk', true, true) - 1 * traceSegment.setTagTop('_dd.appsec.user.collection_mode', 'sdk') - - 0 * traceSegment.setTagTop('_dd.appsec.usr.login', _) - 0 * traceSegment.setTagTop('_dd.appsec.events.users.login.success.auto.mode', _, _) - - 1 * eventDispatcher.publishDataEvent(nonEmptyDsInfo, ctx.data, _ as DataBundle, _ as GatewayContext) >> NoopFlow.INSTANCE - - when: - loginEventCB.apply(ctx, IDENTIFICATION, 'users.login.success', null, secondUser, null) - - then: - 0 * traceSegment.setTagTop('appsec.events.users.login.success.usr.login', _, _) - 0 * traceSegment.setTagTop('usr.id', _, _) - 0 * traceSegment.setTagTop('_dd.appsec.events.users.login.success.sdk', _, _) - 0 * traceSegment.setTagTop('_dd.appsec.user.collection_mode', _) - - 1 * traceSegment.setTagTop('_dd.appsec.usr.login', secondUser) - 1 * traceSegment.setTagTop('_dd.appsec.events.users.login.success.auto.mode', IDENTIFICATION.fullName(), true) - - 0 * eventDispatcher.publishDataEvent - } - - void "test onLoginFailure (automated login events should not overwrite SDK)"() { - setup: - final firstUser = 'user1' - final secondUser = 'user2' - eventDispatcher.getDataSubscribers(_) >> nonEmptyDsInfo - - when: - loginEventCB.apply(ctx, SDK, 'users.login.failure', true, firstUser, null) - - then: - 1 * traceSegment.setTagTop('appsec.events.users.login.failure.usr.login', firstUser, true) - 1 * traceSegment.setTagTop('_dd.appsec.events.users.login.failure.sdk', true, true) - 1 * traceSegment.setTagTop('_dd.appsec.user.collection_mode', 'sdk') - 1 * traceSegment.setTagTop('appsec.events.users.login.failure.usr.exists', true, true) - - 0 * traceSegment.setTagTop('_dd.appsec.usr.login', _) - 0 * traceSegment.setTagTop('_dd.appsec.events.users.login.failure.auto.mode', _, _) - - 1 * eventDispatcher.publishDataEvent(nonEmptyDsInfo, ctx.data, _ as DataBundle, _ as GatewayContext) >> NoopFlow.INSTANCE - - when: - loginEventCB.apply(ctx, IDENTIFICATION, 'users.login.failure', false, secondUser, null) - - then: - 0 * traceSegment.setTagTop('appsec.events.users.login.failure.usr.login', _, _) - 0 * traceSegment.setTagTop('_dd.appsec.events.users.login.failure.sdk', _, _) - 0 * traceSegment.setTagTop('_dd.appsec.user.collection_mode', _) - 0 * traceSegment.setTagTop('appsec.events.users.login.failure.usr.exists', _, _) - - 1 * traceSegment.setTagTop('_dd.appsec.usr.login', secondUser) - 1 * traceSegment.setTagTop('_dd.appsec.events.users.login.failure.auto.mode', IDENTIFICATION.fullName(), true) - - 0 * eventDispatcher.publishDataEvent - } - - void 'test onUserNotFound'() { - setup: - eventDispatcher.getDataSubscribers(_) >> nonEmptyDsInfo - - when: - loginEventCB.apply(ctx, IDENTIFICATION, 'users.login.failure', exists, null, null) - - then: - 1 * traceSegment.setTagTop('appsec.events.users.login.failure.usr.exists', exists, true) - 0 * eventDispatcher.publishDataEvent - - where: - exists << [true, false] } void 'test configuration updates should reset cached subscriptions'() { diff --git a/dd-java-agent/appsec/src/test/groovy/com/datadog/appsec/user/AppSecEventTrackerSpecification.groovy b/dd-java-agent/appsec/src/test/groovy/com/datadog/appsec/user/AppSecEventTrackerSpecification.groovy index 9872914df7e..4721e6de178 100644 --- a/dd-java-agent/appsec/src/test/groovy/com/datadog/appsec/user/AppSecEventTrackerSpecification.groovy +++ b/dd-java-agent/appsec/src/test/groovy/com/datadog/appsec/user/AppSecEventTrackerSpecification.groovy @@ -5,28 +5,37 @@ import datadog.appsec.api.blocking.BlockingContentType import datadog.appsec.api.blocking.BlockingException import datadog.trace.api.EventTracker import datadog.trace.api.GlobalTracer +import datadog.trace.api.ProductTraceSource import datadog.trace.api.UserIdCollectionMode import datadog.trace.api.appsec.AppSecEventTracker -import datadog.trace.api.appsec.LoginEventCallback import datadog.trace.api.function.TriFunction import datadog.trace.api.gateway.CallbackProvider import datadog.trace.api.gateway.Flow import datadog.trace.api.gateway.RequestContext import datadog.trace.api.gateway.RequestContextSlot import datadog.trace.api.internal.TraceSegment +import datadog.trace.api.telemetry.LoginEvent import datadog.trace.bootstrap.ActiveSubsystems import datadog.trace.bootstrap.instrumentation.api.AgentSpan import datadog.trace.bootstrap.instrumentation.api.AgentTracer.TracerAPI import datadog.trace.test.util.DDSpecification import spock.lang.Shared +import java.util.function.BiFunction + +import static datadog.trace.api.UserIdCollectionMode.ANONYMIZATION import static datadog.trace.api.UserIdCollectionMode.DISABLED +import static datadog.trace.api.UserIdCollectionMode.IDENTIFICATION import static datadog.trace.api.UserIdCollectionMode.SDK import static datadog.trace.api.gateway.Events.EVENTS +import static datadog.trace.api.telemetry.LoginEvent.LOGIN_FAILURE +import static datadog.trace.api.telemetry.LoginEvent.LOGIN_SUCCESS +import static datadog.trace.api.telemetry.LoginEvent.SIGN_UP class AppSecEventTrackerSpecification extends DDSpecification { private static final String USER_ID = 'user' + private static final String ANONYMIZED_USER_ID = 'anon_04f8996da763b7a969b1028ee3007569' @Shared private static boolean appSecActiveBefore = ActiveSubsystems.APPSEC_ACTIVE @@ -38,14 +47,14 @@ class AppSecEventTrackerSpecification extends DDSpecification { private TracerAPI tracer private AgentSpan span private CallbackProvider provider - private TriFunction> user - private LoginEventCallback loginEvent + private BiFunction> user + private TriFunction> loginEvent void setup() { traceSegment = Mock(TraceSegment) span = Stub(AgentSpan) - user = Mock(TriFunction) - loginEvent = Mock(LoginEventCallback) + user = Mock(BiFunction) + loginEvent = Mock(TriFunction) provider = Stub(CallbackProvider) { getCallback(EVENTS.user()) >> user @@ -73,19 +82,36 @@ class AppSecEventTrackerSpecification extends DDSpecification { def 'test track login success event (SDK)'() { when: - GlobalTracer.getEventTracker().trackLoginSuccessEvent('user1', ['key1': 'value1', 'key2': 'value2']) + GlobalTracer.getEventTracker().trackLoginSuccessEvent(USER_ID, ['key1': 'value1', 'key2': 'value2']) then: - 1 * loginEvent.apply(_ as RequestContext, SDK, 'users.login.success', null, 'user1', ['key1': 'value1', 'key2': 'value2']) >> NoopFlow.INSTANCE + 1 * traceSegment.setTagTop('usr.id', USER_ID) + 1 * traceSegment.setTagTop('appsec.events.users.login.success.usr.login', USER_ID) + 1 * traceSegment.setTagTop('appsec.events.users.login.success', ['key1':'value1', 'key2':'value2']) + 1 * traceSegment.setTagTop('appsec.events.users.login.success.track', true) + 1 * traceSegment.setTagTop('_dd.appsec.events.users.login.success.sdk', true) + 1 * traceSegment.setTagTop('asm.keep', true) + 1 * traceSegment.setTagTop('_dd.p.ts', ProductTraceSource.ASM) + 1 * loginEvent.apply(_ as RequestContext, LOGIN_SUCCESS, USER_ID) >> NoopFlow.INSTANCE + 1 * user.apply(_ as RequestContext, USER_ID) >> NoopFlow.INSTANCE 0 * _ } def 'test track login failure event (SDK)'() { when: - GlobalTracer.getEventTracker().trackLoginFailureEvent('user1', true, ['key1': 'value1', 'key2': 'value2']) + GlobalTracer.getEventTracker().trackLoginFailureEvent(USER_ID, true, ['key1': 'value1', 'key2': 'value2']) then: - 1 * loginEvent.apply(_ as RequestContext, SDK, 'users.login.failure', true, 'user1', ['key1': 'value1', 'key2': 'value2']) >> NoopFlow.INSTANCE + 1 * traceSegment.setTagTop('appsec.events.users.login.failure.usr.id', USER_ID) + 1 * traceSegment.setTagTop('appsec.events.users.login.failure.usr.login', USER_ID) + 1 * traceSegment.setTagTop('appsec.events.users.login.failure.usr.exists', true) + 1 * traceSegment.setTagTop('appsec.events.users.login.failure', ['key1':'value1', 'key2':'value2']) + 1 * traceSegment.setTagTop('appsec.events.users.login.failure.track', true) + 1 * traceSegment.setTagTop('_dd.appsec.events.users.login.failure.sdk', true) + 1 * traceSegment.setTagTop('asm.keep', true) + 1 * traceSegment.setTagTop('_dd.p.ts', ProductTraceSource.ASM) + 1 * loginEvent.apply(_ as RequestContext, LOGIN_FAILURE, USER_ID) >> NoopFlow.INSTANCE + 1 * user.apply(_ as RequestContext, USER_ID) >> NoopFlow.INSTANCE 0 * _ } @@ -94,7 +120,11 @@ class AppSecEventTrackerSpecification extends DDSpecification { GlobalTracer.getEventTracker().trackCustomEvent('myevent', ['key1': 'value1', 'key2': 'value2']) then: - 1 * loginEvent.apply(_ as RequestContext, SDK, 'myevent', null, null, ['key1': 'value1', 'key2': 'value2']) >> NoopFlow.INSTANCE + 1 * traceSegment.setTagTop('appsec.events.myevent', ['key1':'value1', 'key2':'value2'], true) + 1 * traceSegment.setTagTop('appsec.events.myevent.track', true, true) + 1 * traceSegment.setTagTop('_dd.appsec.events.myevent.sdk', true, true) + 1 * traceSegment.setTagTop('asm.keep', true) + 1 * traceSegment.setTagTop('_dd.p.ts', ProductTraceSource.ASM) 0 * _ } @@ -137,12 +167,31 @@ class AppSecEventTrackerSpecification extends DDSpecification { } def "test onSignup (#mode)"() { + setup: + final expectedUserId = mode == ANONYMIZATION ? ANONYMIZED_USER_ID: USER_ID + when: tracker.onSignupEvent(mode, USER_ID, ['key1': 'value1', 'key2': 'value2']) then: if (mode != DISABLED) { - 1 * loginEvent.apply(_ as RequestContext, mode, 'users.signup', null, USER_ID, ['key1': 'value1', 'key2': 'value2']) >> NoopFlow.INSTANCE + if (mode == SDK) { + 1 * traceSegment.setTagTop('appsec.events.users.signup.usr.id', USER_ID) + 1 * traceSegment.setTagTop('_dd.appsec.events.users.signup.sdk', true) + } else { + 1 * traceSegment.getTagTop('_dd.appsec.events.users.signup.sdk') >> null // no SDK event before + 1 * traceSegment.setTagTop('_dd.appsec.usr.login', expectedUserId) + 1 * traceSegment.setTagTop('_dd.appsec.events.users.signup.auto.mode', mode.fullName()) + } + 1 * traceSegment.setTagTop('appsec.events.users.signup.usr.login', expectedUserId) + 1 * traceSegment.setTagTop('appsec.events.users.signup', ['key1':'value1', 'key2':'value2']) + 1 * traceSegment.setTagTop('appsec.events.users.signup.track', true) + 1 * traceSegment.setTagTop('asm.keep', true) + 1 * traceSegment.setTagTop('_dd.p.ts', ProductTraceSource.ASM) + 1 * loginEvent.apply(_ as RequestContext, SIGN_UP, expectedUserId) >> NoopFlow.INSTANCE + if (mode == SDK) { + 1 * user.apply(_ as RequestContext, USER_ID) >> NoopFlow.INSTANCE + } } 0 * _ @@ -151,12 +200,31 @@ class AppSecEventTrackerSpecification extends DDSpecification { } def "test onLoginSuccess (#mode)"() { + setup: + final expectedUserId = mode == ANONYMIZATION ? ANONYMIZED_USER_ID: USER_ID + when: tracker.onLoginSuccessEvent(mode, USER_ID, ['key1': 'value1', 'key2': 'value2']) then: if (mode != DISABLED) { - 1 * loginEvent.apply(_ as RequestContext, mode, 'users.login.success', null, USER_ID, ['key1': 'value1', 'key2': 'value2']) >> NoopFlow.INSTANCE + if (mode == SDK) { + 1 * traceSegment.setTagTop('usr.id', USER_ID) + 1 * traceSegment.setTagTop('_dd.appsec.events.users.login.success.sdk', true) + } else { + 1 * traceSegment.getTagTop('_dd.appsec.events.users.login.success.sdk') >> null // no SDK event before + 1 * traceSegment.setTagTop('_dd.appsec.usr.login', expectedUserId) + 1 * traceSegment.setTagTop('_dd.appsec.events.users.login.success.auto.mode', mode.fullName()) + } + 1 * traceSegment.setTagTop('appsec.events.users.login.success.usr.login', expectedUserId) + 1 * traceSegment.setTagTop('appsec.events.users.login.success', ['key1':'value1', 'key2':'value2']) + 1 * traceSegment.setTagTop('appsec.events.users.login.success.track', true) + 1 * traceSegment.setTagTop('asm.keep', true) + 1 * traceSegment.setTagTop('_dd.p.ts', ProductTraceSource.ASM) + 1 * loginEvent.apply(_ as RequestContext, LOGIN_SUCCESS, expectedUserId) >> NoopFlow.INSTANCE + if (mode == SDK) { + 1 * user.apply(_ as RequestContext, USER_ID) >> NoopFlow.INSTANCE + } } 0 * _ @@ -165,12 +233,32 @@ class AppSecEventTrackerSpecification extends DDSpecification { } def "test onLoginFailed (#mode)"() { + setup: + final expectedUserId = mode == ANONYMIZATION ? ANONYMIZED_USER_ID: USER_ID + when: - tracker.onLoginFailureEvent(mode, USER_ID, null, ['key1': 'value1', 'key2': 'value2']) + tracker.onLoginFailureEvent(mode, USER_ID, true, ['key1': 'value1', 'key2': 'value2']) then: if (mode != DISABLED) { - 1 * loginEvent.apply(_ as RequestContext, mode, 'users.login.failure', null, USER_ID, ['key1': 'value1', 'key2': 'value2']) >> NoopFlow.INSTANCE + if (mode == SDK) { + 1 * traceSegment.setTagTop('appsec.events.users.login.failure.usr.id', USER_ID) + 1 * traceSegment.setTagTop('_dd.appsec.events.users.login.failure.sdk', true) + } else { + 1 * traceSegment.getTagTop('_dd.appsec.events.users.login.failure.sdk') >> null // no SDK event before + 1 * traceSegment.setTagTop('_dd.appsec.usr.login', expectedUserId) + 1 * traceSegment.setTagTop('_dd.appsec.events.users.login.failure.auto.mode', mode.fullName()) + } + 1 * traceSegment.setTagTop('appsec.events.users.login.failure.usr.login', expectedUserId) + 1 * traceSegment.setTagTop('appsec.events.users.login.failure', ['key1':'value1', 'key2':'value2']) + 1 * traceSegment.setTagTop('appsec.events.users.login.failure.usr.exists', true) + 1 * traceSegment.setTagTop('appsec.events.users.login.failure.track', true) + 1 * traceSegment.setTagTop('asm.keep', true) + 1 * traceSegment.setTagTop('_dd.p.ts', ProductTraceSource.ASM) + 1 * loginEvent.apply(_ as RequestContext, LOGIN_FAILURE, expectedUserId) >> NoopFlow.INSTANCE + if (mode == SDK) { + 1 * user.apply(_ as RequestContext, USER_ID) >> NoopFlow.INSTANCE + } } 0 * _ @@ -179,12 +267,23 @@ class AppSecEventTrackerSpecification extends DDSpecification { } def "test onUserEvent (#mode)"() { + setup: + final expectedUserId = mode == ANONYMIZATION ? ANONYMIZED_USER_ID: USER_ID + when: tracker.onUserEvent(mode, USER_ID) then: if (mode != DISABLED) { - 1 * user.apply(_ as RequestContext, mode, USER_ID) >> NoopFlow.INSTANCE + if (mode != SDK) { + 1 * traceSegment.setTagTop('_dd.appsec.usr.id', expectedUserId) + 1 * traceSegment.getTagTop('_dd.appsec.user.collection_mode') >> null // no user event before + } + 1 * traceSegment.setTagTop('_dd.appsec.user.collection_mode', mode.fullName()) + 1 * traceSegment.setTagTop('usr.id', expectedUserId) + 1 * traceSegment.setTagTop('asm.keep', true) + 1 * traceSegment.setTagTop('_dd.p.ts', ProductTraceSource.ASM) + 1 * user.apply(_ as RequestContext, expectedUserId) >> NoopFlow.INSTANCE } 0 * _ @@ -198,7 +297,12 @@ class AppSecEventTrackerSpecification extends DDSpecification { then: if (mode != DISABLED) { - 1 * loginEvent.apply(_ as RequestContext, mode, 'users.login.failure', false, null, null) >> NoopFlow.INSTANCE + if (mode != SDK) { + 1 * traceSegment.getTagTop('_dd.appsec.events.users.login.failure.sdk') >> null // no SDK event before + } + 1 * traceSegment.setTagTop('appsec.events.users.login.failure.usr.exists', false) + 1 * traceSegment.setTagTop('asm.keep', true) + 1 * traceSegment.setTagTop('_dd.p.ts', ProductTraceSource.ASM) } 0 * _ @@ -259,7 +363,7 @@ class AppSecEventTrackerSpecification extends DDSpecification { void 'test blocking on a userId'() { setup: final action = new Flow.Action.RequestBlockingAction(403, BlockingContentType.AUTO) - loginEvent.apply(_ as RequestContext, SDK, 'users.login.success', null, USER_ID, ['key1': 'value1', 'key2': 'value2']) >> new ActionFlow(action: action) + loginEvent.apply(_ as RequestContext, LOGIN_SUCCESS, USER_ID) >> new ActionFlow(action: action) when: tracker.onLoginSuccessEvent(SDK, USER_ID, ['key1': 'value1', 'key2': 'value2']) @@ -270,13 +374,55 @@ class AppSecEventTrackerSpecification extends DDSpecification { void 'should not fail on null callback'() { when: - tracker.onUserEvent(UserIdCollectionMode.IDENTIFICATION, 'test-user') + tracker.onUserEvent(IDENTIFICATION, 'test-user') then: noExceptionThrown() provider.getCallback(EVENTS.user()) >> null } + void 'test onUserEvent (automated login events should not overwrite SDK)'() { + when: + tracker.onUserEvent(IDENTIFICATION, USER_ID) + + then: 'SDK data remains untouched' + 1 * traceSegment.getTagTop('_dd.appsec.user.collection_mode') >> SDK.fullName() + 1 * traceSegment.setTagTop('_dd.appsec.usr.id', USER_ID) + 0 * _ + } + + + void 'test onLoginSuccess (automated login events should not overwrite SDK)'() { + when: + tracker.onLoginSuccessEvent(IDENTIFICATION, USER_ID, [:]) + + then: + 1 * traceSegment.getTagTop('_dd.appsec.events.users.login.success.sdk') >> true + 1 * traceSegment.setTagTop('_dd.appsec.usr.login', USER_ID) + 1 * traceSegment.setTagTop('_dd.appsec.events.users.login.success.auto.mode', IDENTIFICATION.fullName()) + 0 * _ + } + + void 'test onLoginFailure (automated login events should not overwrite SDK)'() { + when: + tracker.onLoginFailureEvent(IDENTIFICATION, USER_ID, null, [:]) + + then: + 1 * traceSegment.getTagTop('_dd.appsec.events.users.login.failure.sdk') >> true + 1 * traceSegment.setTagTop('_dd.appsec.usr.login', USER_ID) + 1 * traceSegment.setTagTop('_dd.appsec.events.users.login.failure.auto.mode', IDENTIFICATION.fullName()) + 0 * _ + } + + void 'test onUserNotFound (automated login events should not overwrite SDK)'() { + when: + tracker.onUserNotFound(IDENTIFICATION) + + then: + 1 * traceSegment.getTagTop('_dd.appsec.events.users.login.failure.sdk') >> true + 0 * _ + } + private static class ActionFlow implements Flow { private Action action diff --git a/dd-java-agent/appsec/src/test/groovy/com/datadog/appsec/user/EventTrackerAppSecDisabledForkedTest.groovy b/dd-java-agent/appsec/src/test/groovy/com/datadog/appsec/user/EventTrackerAppSecDisabledForkedTest.groovy new file mode 100644 index 00000000000..3912a1785bc --- /dev/null +++ b/dd-java-agent/appsec/src/test/groovy/com/datadog/appsec/user/EventTrackerAppSecDisabledForkedTest.groovy @@ -0,0 +1,106 @@ +package com.datadog.appsec.user + + +import datadog.trace.api.GlobalTracer +import datadog.trace.api.UserIdCollectionMode +import datadog.trace.api.appsec.AppSecEventTracker +import datadog.trace.api.gateway.RequestContextSlot +import datadog.trace.api.internal.TraceSegment +import datadog.trace.bootstrap.ActiveSubsystems +import datadog.trace.bootstrap.instrumentation.api.AgentTracer +import datadog.trace.test.util.DDSpecification + +import static datadog.trace.api.UserIdCollectionMode.IDENTIFICATION + +class EventTrackerAppSecDisabledForkedTest extends DDSpecification { + + TraceSegment traceSegment + + AppSecEventTracker tracker + + void setupSpec() { + injectSysConfig('dd.appsec.enabled', 'false') + ActiveSubsystems.APPSEC_ACTIVE = false + } + + void setup() { + tracker = new AppSecEventTracker() + GlobalTracer.setEventTracker(tracker) + + traceSegment = Mock(TraceSegment) + final tracer = Stub(AgentTracer.TracerAPI) { + getTraceSegment() >> traceSegment + getCallbackProvider(RequestContextSlot.APPSEC) >> null + } + AgentTracer.forceRegister(tracer) + } + + void 'test track login success event (SDK)'() { + when: + GlobalTracer.getEventTracker().trackLoginSuccessEvent('user', ['key1': 'value1', 'key2': 'value2']) + + then: + 1 * traceSegment.setTagTop('_dd.appsec.events.users.login.success.sdk', true) + } + + void 'test track login failure event (SDK)'() { + when: + GlobalTracer.getEventTracker().trackLoginFailureEvent('user', true, ['key1': 'value1', 'key2': 'value2']) + + then: + 1 * traceSegment.setTagTop('_dd.appsec.events.users.login.failure.sdk', true) + } + + void 'test track custom event (SDK)'() { + when: + GlobalTracer.getEventTracker().trackCustomEvent('myevent', ['key1': 'value1', 'key2': 'value2']) + + then: + 1 * traceSegment.setTagTop('_dd.appsec.events.myevent.sdk', true, true) + } + + void 'test onSignup'() { + when: + tracker.onSignupEvent(IDENTIFICATION, 'user', ['key1': 'value1', 'key2': 'value2']) + + then: + 0 * _ + } + + void 'test onLoginSuccess'() { + + when: + tracker.onLoginSuccessEvent(IDENTIFICATION, 'user', ['key1': 'value1', 'key2': 'value2']) + + then: + 0 * _ + } + + void 'test onLoginFailed'() { + when: + tracker.onLoginFailureEvent(IDENTIFICATION, 'user', true, ['key1': 'value1', 'key2': 'value2']) + + then: + 0 * _ + + where: + mode << UserIdCollectionMode.values() + } + + def 'test onUserEvent'() { + when: + tracker.onUserEvent(IDENTIFICATION, 'user') + + then: + 0 * _ + } + + def 'test onUserNotFound'() { + when: + tracker.onUserNotFound(IDENTIFICATION) + + then: + 0 * _ + } +} + diff --git a/dd-java-agent/appsec/src/testFixtures/groovy/com/datadog/appsec/AppSecHttpServerTest.groovy b/dd-java-agent/appsec/src/testFixtures/groovy/com/datadog/appsec/AppSecHttpServerTest.groovy index 6182f9e0a4a..f03ed7b0a64 100644 --- a/dd-java-agent/appsec/src/testFixtures/groovy/com/datadog/appsec/AppSecHttpServerTest.groovy +++ b/dd-java-agent/appsec/src/testFixtures/groovy/com/datadog/appsec/AppSecHttpServerTest.groovy @@ -4,6 +4,8 @@ import datadog.communication.ddagent.SharedCommunicationObjects import datadog.communication.monitor.Monitoring import datadog.trace.agent.test.base.WithHttpServer import datadog.trace.api.Config +import datadog.trace.api.GlobalTracer +import datadog.trace.api.appsec.AppSecEventTracker import datadog.trace.api.gateway.RequestContextSlot import datadog.trace.api.gateway.SubscriptionService import datadog.trace.bootstrap.instrumentation.api.AgentTracer @@ -25,6 +27,8 @@ abstract class AppSecHttpServerTest extends WithHttpServer { assert sco.configurationPoller(config) == null assert sco.monitoring instanceof Monitoring.DisabledMonitoring + GlobalTracer.setEventTracker(new AppSecEventTracker()) + AppSecSystem.start(ss, sco) } diff --git a/dd-java-agent/instrumentation/spring-security-5/src/main/java/datadog/trace/instrumentation/springsecurity5/SpringSecurityUserEventDecorator.java b/dd-java-agent/instrumentation/spring-security-5/src/main/java/datadog/trace/instrumentation/springsecurity5/SpringSecurityUserEventDecorator.java index 63cd1624b96..d7285e7f976 100644 --- a/dd-java-agent/instrumentation/spring-security-5/src/main/java/datadog/trace/instrumentation/springsecurity5/SpringSecurityUserEventDecorator.java +++ b/dd-java-agent/instrumentation/spring-security-5/src/main/java/datadog/trace/instrumentation/springsecurity5/SpringSecurityUserEventDecorator.java @@ -3,6 +3,8 @@ import static datadog.trace.api.UserIdCollectionMode.IDENTIFICATION; import static datadog.trace.api.telemetry.LogCollector.SEND_TELEMETRY; +import datadog.trace.api.EventTracker; +import datadog.trace.api.GlobalTracer; import datadog.trace.api.UserIdCollectionMode; import datadog.trace.api.appsec.AppSecEventTracker; import datadog.trace.api.telemetry.LoginEvent; @@ -34,7 +36,7 @@ public class SpringSecurityUserEventDecorator { Collections.newSetFromMap(new ConcurrentHashMap<>()); public void onUserNotFound() { - final AppSecEventTracker tracker = AppSecEventTracker.getEventTracker(); + final AppSecEventTracker tracker = getEventTracker(); if (tracker == null) { return; } @@ -48,7 +50,7 @@ public void onSignup(UserDetails user, Throwable throwable) { return; } - final AppSecEventTracker tracker = AppSecEventTracker.getEventTracker(); + final AppSecEventTracker tracker = getEventTracker(); if (tracker == null) { return; } @@ -75,7 +77,7 @@ public void onLogin(Authentication authentication, Throwable throwable, Authenti return; } - final AppSecEventTracker tracker = AppSecEventTracker.getEventTracker(); + final AppSecEventTracker tracker = getEventTracker(); if (tracker == null) { return; } @@ -127,7 +129,7 @@ public void onUser(final Authentication authentication) { return; } - final AppSecEventTracker tracker = AppSecEventTracker.getEventTracker(); + final AppSecEventTracker tracker = getEventTracker(); if (tracker == null) { return; } @@ -181,4 +183,9 @@ private static boolean missingUsername(final String username, final LoginEvent e } return false; } + + private static AppSecEventTracker getEventTracker() { + final EventTracker tracker = GlobalTracer.getEventTracker(); + return tracker instanceof AppSecEventTracker ? (AppSecEventTracker) tracker : null; + } } diff --git a/internal-api/src/main/java/datadog/trace/api/appsec/AppSecEventTracker.java b/internal-api/src/main/java/datadog/trace/api/appsec/AppSecEventTracker.java index 9e2a49527d7..2fed75c22cb 100644 --- a/internal-api/src/main/java/datadog/trace/api/appsec/AppSecEventTracker.java +++ b/internal-api/src/main/java/datadog/trace/api/appsec/AppSecEventTracker.java @@ -1,12 +1,18 @@ package datadog.trace.api.appsec; +import static datadog.trace.api.UserIdCollectionMode.ANONYMIZATION; import static datadog.trace.api.UserIdCollectionMode.DISABLED; import static datadog.trace.api.UserIdCollectionMode.SDK; import static datadog.trace.api.gateway.Events.EVENTS; +import static datadog.trace.api.telemetry.LoginEvent.LOGIN_FAILURE; +import static datadog.trace.api.telemetry.LoginEvent.LOGIN_SUCCESS; +import static datadog.trace.api.telemetry.LoginEvent.SIGN_UP; +import static datadog.trace.util.Strings.toHexString; import datadog.appsec.api.blocking.BlockingException; import datadog.trace.api.EventTracker; import datadog.trace.api.GlobalTracer; +import datadog.trace.api.ProductTraceSource; import datadog.trace.api.UserIdCollectionMode; import datadog.trace.api.gateway.BlockResponseFunction; import datadog.trace.api.gateway.CallbackProvider; @@ -14,23 +20,27 @@ import datadog.trace.api.gateway.Flow; import datadog.trace.api.gateway.RequestContext; import datadog.trace.api.gateway.RequestContextSlot; +import datadog.trace.api.internal.TraceSegment; import datadog.trace.bootstrap.ActiveSubsystems; import datadog.trace.bootstrap.instrumentation.api.AgentSpan; import datadog.trace.bootstrap.instrumentation.api.AgentTracer; +import datadog.trace.bootstrap.instrumentation.api.Tags; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; import java.util.Map; import java.util.function.BiFunction; public class AppSecEventTracker extends EventTracker { - private static AppSecEventTracker INSTANCE; + private static final int HASH_SIZE_BYTES = 16; // 128 bits + private static final String ANON_PREFIX = "anon_"; - public static AppSecEventTracker getEventTracker() { - return INSTANCE; - } + private static final String LOGIN_SUCCESS_TAG = "users.login.success"; + private static final String LOGIN_FAILURE_TAG = "users.login.failure"; + private static final String SIGNUP_TAG = "users.signup"; - public static void setEventTracker(final AppSecEventTracker tracker) { - INSTANCE = tracker; - GlobalTracer.setEventTracker(tracker == null ? EventTracker.NO_EVENT_TRACKER : tracker); + public static void install() { + GlobalTracer.setEventTracker(new AppSecEventTracker()); } @Override @@ -62,16 +72,47 @@ public void onUserNotFound(final UserIdCollectionMode mode) { if (!isEnabled(mode)) { return; } - dispatch( - EVENTS.loginEvent(), - (ctx, callback) -> callback.apply(ctx, mode, "users.login.failure", false, null, null)); + final AgentTracer.TracerAPI tracer = tracer(); + if (tracer == null) { + return; + } + final TraceSegment segment = tracer.getTraceSegment(); + if (segment == null) { + return; + } + if (isNewLoginEvent(mode, segment, LOGIN_FAILURE_TAG)) { + segment.setTagTop("appsec.events.users.login.failure.usr.exists", false); + segment.setTagTop(Tags.ASM_KEEP, true); + segment.setTagTop(Tags.PROPAGATED_TRACE_SOURCE, ProductTraceSource.ASM); + } } public void onUserEvent(final UserIdCollectionMode mode, final String userId) { if (!isEnabled(mode)) { return; } - dispatch(EVENTS.user(), (ctx, cb) -> cb.apply(ctx, mode, userId)); + final AgentTracer.TracerAPI tracer = tracer(); + if (tracer == null) { + return; + } + final TraceSegment segment = tracer.getTraceSegment(); + if (segment == null) { + return; + } + final String finalUserId = anonymizeUser(mode, userId); + if (finalUserId == null) { + return; + } + if (mode != SDK) { + segment.setTagTop("_dd.appsec.usr.id", finalUserId); + } + if (isNewUser(mode, segment)) { + segment.setTagTop("usr.id", finalUserId); + segment.setTagTop("_dd.appsec.user.collection_mode", mode.fullName()); + segment.setTagTop(Tags.ASM_KEEP, true); + segment.setTagTop(Tags.PROPAGATED_TRACE_SOURCE, ProductTraceSource.ASM); + dispatch(tracer, EVENTS.user(), (ctx, cb) -> cb.apply(ctx, finalUserId)); + } } public void onSignupEvent( @@ -79,9 +120,43 @@ public void onSignupEvent( if (!isEnabled(mode)) { return; } - dispatch( - EVENTS.loginEvent(), - (ctx, callback) -> callback.apply(ctx, mode, "users.signup", null, userId, metadata)); + final AgentTracer.TracerAPI tracer = tracer(); + if (tracer == null) { + return; + } + final TraceSegment segment = tracer.getTraceSegment(); + if (segment == null) { + return; + } + final String finalUserId = anonymizeUser(mode, userId); + if (finalUserId == null) { + return; + } + + if (mode == SDK) { + // TODO update SDK separating usr.login / usr.id + segment.setTagTop("appsec.events.users.signup.usr.id", finalUserId); + segment.setTagTop("_dd.appsec.events.users.signup.sdk", true); + } else { + segment.setTagTop("_dd.appsec.usr.login", finalUserId); + segment.setTagTop("_dd.appsec.events.users.signup.auto.mode", mode.fullName()); + } + if (isNewLoginEvent(mode, segment, SIGNUP_TAG)) { + segment.setTagTop("appsec.events.users.signup.usr.login", finalUserId); + if (metadata != null && !metadata.isEmpty()) { + segment.setTagTop("appsec.events.users.signup", metadata); + } + segment.setTagTop("appsec.events.users.signup.track", true); + segment.setTagTop(Tags.ASM_KEEP, true); + segment.setTagTop(Tags.PROPAGATED_TRACE_SOURCE, ProductTraceSource.ASM); + dispatch( + tracer, + EVENTS.loginEvent(), + (ctx, callback) -> callback.apply(ctx, SIGN_UP, finalUserId)); + if (mode == SDK) { + dispatch(tracer, EVENTS.user(), (ctx, callback) -> callback.apply(ctx, finalUserId)); + } + } } public void onLoginSuccessEvent( @@ -89,9 +164,42 @@ public void onLoginSuccessEvent( if (!isEnabled(mode)) { return; } - dispatch( - EVENTS.loginEvent(), - (ctx, cb) -> cb.apply(ctx, mode, "users.login.success", null, userId, metadata)); + final AgentTracer.TracerAPI tracer = tracer(); + if (tracer == null) { + return; + } + final TraceSegment segment = tracer.getTraceSegment(); + if (segment == null) { + return; + } + final String finalUserId = anonymizeUser(mode, userId); + if (finalUserId == null) { + return; + } + if (mode == SDK) { + // TODO update SDK separating usr.login / usr.id + segment.setTagTop("usr.id", finalUserId); + segment.setTagTop("_dd.appsec.events.users.login.success.sdk", true); + } else { + segment.setTagTop("_dd.appsec.usr.login", finalUserId); + segment.setTagTop("_dd.appsec.events.users.login.success.auto.mode", mode.fullName()); + } + if (isNewLoginEvent(mode, segment, LOGIN_SUCCESS_TAG)) { + segment.setTagTop("appsec.events.users.login.success.usr.login", finalUserId); + if (metadata != null && !metadata.isEmpty()) { + segment.setTagTop("appsec.events.users.login.success", metadata); + } + segment.setTagTop("appsec.events.users.login.success.track", true); + segment.setTagTop(Tags.ASM_KEEP, true); + segment.setTagTop(Tags.PROPAGATED_TRACE_SOURCE, ProductTraceSource.ASM); + dispatch( + tracer, + EVENTS.loginEvent(), + (ctx, callback) -> callback.apply(ctx, LOGIN_SUCCESS, finalUserId)); + if (mode == SDK) { + dispatch(tracer, EVENTS.user(), (ctx, callback) -> callback.apply(ctx, finalUserId)); + } + } } public void onLoginFailureEvent( @@ -102,9 +210,46 @@ public void onLoginFailureEvent( if (!isEnabled(mode)) { return; } - dispatch( - EVENTS.loginEvent(), - (ctx, cb) -> cb.apply(ctx, mode, "users.login.failure", exists, userId, metadata)); + final AgentTracer.TracerAPI tracer = tracer(); + if (tracer == null) { + return; + } + final TraceSegment segment = tracer.getTraceSegment(); + if (segment == null) { + return; + } + final String finalUserId = anonymizeUser(mode, userId); + if (finalUserId == null) { + return; + } + + if (mode == SDK) { + // TODO update SDK separating usr.login / usr.id + segment.setTagTop("appsec.events.users.login.failure.usr.id", finalUserId); + segment.setTagTop("_dd.appsec.events.users.login.failure.sdk", true); + } else { + segment.setTagTop("_dd.appsec.usr.login", finalUserId); + segment.setTagTop("_dd.appsec.events.users.login.failure.auto.mode", mode.fullName()); + } + if (isNewLoginEvent(mode, segment, LOGIN_FAILURE_TAG)) { + segment.setTagTop("appsec.events.users.login.failure.usr.login", finalUserId); + if (metadata != null && !metadata.isEmpty()) { + segment.setTagTop("appsec.events.users.login.failure", metadata); + } + if (exists != null) { + segment.setTagTop("appsec.events.users.login.failure.usr.exists", exists); + } + segment.setTagTop("appsec.events.users.login.failure.track", true); + segment.setTagTop(Tags.ASM_KEEP, true); + segment.setTagTop(Tags.PROPAGATED_TRACE_SOURCE, ProductTraceSource.ASM); + dispatch( + tracer, + EVENTS.loginEvent(), + (ctx, callback) -> callback.apply(ctx, LOGIN_FAILURE, finalUserId)); + if (mode == SDK) { + dispatch(tracer, EVENTS.user(), (ctx, callback) -> callback.apply(ctx, finalUserId)); + } + } } public void onCustomEvent( @@ -112,13 +257,47 @@ public void onCustomEvent( if (!isEnabled(mode)) { return; } - dispatch( - EVENTS.loginEvent(), (ctx, cb) -> cb.apply(ctx, mode, eventName, null, null, metadata)); + final AgentTracer.TracerAPI tracer = tracer(); + if (tracer == null) { + return; + } + final TraceSegment segment = tracer.getTraceSegment(); + if (segment == null) { + return; + } + if (mode == SDK) { + segment.setTagTop("_dd.appsec.events." + eventName + ".sdk", true, true); + } + if (isNewLoginEvent(mode, segment, eventName)) { + if (metadata != null && !metadata.isEmpty()) { + segment.setTagTop("appsec.events." + eventName, metadata, true); + } + segment.setTagTop("appsec.events." + eventName + ".track", true, true); + segment.setTagTop(Tags.ASM_KEEP, true); + segment.setTagTop(Tags.PROPAGATED_TRACE_SOURCE, ProductTraceSource.ASM); + } + } + + private boolean isNewLoginEvent( + final UserIdCollectionMode mode, final TraceSegment segment, final String event) { + if (mode == SDK) { + return true; + } + return segment.getTagTop("_dd.appsec.events." + event + ".sdk") == null; + } + + private boolean isNewUser(final UserIdCollectionMode mode, final TraceSegment segment) { + if (mode == SDK) { + return true; + } + final Object value = segment.getTagTop("_dd.appsec.user.collection_mode"); + return value == null || !"sdk".equalsIgnoreCase(value.toString()); } private void dispatch( - final EventType event, final BiFunction> consumer) { - final AgentTracer.TracerAPI tracer = tracer(); + final AgentTracer.TracerAPI tracer, + final EventType event, + final BiFunction> consumer) { if (tracer == null) { return; } @@ -157,8 +336,29 @@ private void dispatch( } } + protected static String anonymizeUser(final UserIdCollectionMode mode, final String userId) { + if (mode != ANONYMIZATION || userId == null) { + return userId; + } + MessageDigest digest; + try { + // TODO avoid lookup a new instance every time + digest = MessageDigest.getInstance("SHA-256"); + } catch (NoSuchAlgorithmException e) { + return null; + } + digest.update(userId.getBytes()); + byte[] hash = digest.digest(); + if (hash.length > HASH_SIZE_BYTES) { + byte[] temp = new byte[HASH_SIZE_BYTES]; + System.arraycopy(hash, 0, temp, 0, temp.length); + hash = temp; + } + return ANON_PREFIX + toHexString(hash); + } + protected boolean isEnabled(final UserIdCollectionMode mode) { - return ActiveSubsystems.APPSEC_ACTIVE && mode != DISABLED; + return mode == SDK || (ActiveSubsystems.APPSEC_ACTIVE && mode != DISABLED); } // Extract this to allow for easier testing diff --git a/internal-api/src/main/java/datadog/trace/api/appsec/LoginEventCallback.java b/internal-api/src/main/java/datadog/trace/api/appsec/LoginEventCallback.java deleted file mode 100644 index 11e12103904..00000000000 --- a/internal-api/src/main/java/datadog/trace/api/appsec/LoginEventCallback.java +++ /dev/null @@ -1,17 +0,0 @@ -package datadog.trace.api.appsec; - -import datadog.trace.api.UserIdCollectionMode; -import datadog.trace.api.gateway.Flow; -import datadog.trace.api.gateway.RequestContext; -import java.util.Map; - -public interface LoginEventCallback { - - Flow apply( - RequestContext context, - UserIdCollectionMode mode, - String eventName, - Boolean exists, - String user, - Map metadata); -} diff --git a/internal-api/src/main/java/datadog/trace/api/gateway/Events.java b/internal-api/src/main/java/datadog/trace/api/gateway/Events.java index a9195ec5510..d840cad01c3 100644 --- a/internal-api/src/main/java/datadog/trace/api/gateway/Events.java +++ b/internal-api/src/main/java/datadog/trace/api/gateway/Events.java @@ -1,10 +1,9 @@ package datadog.trace.api.gateway; -import datadog.trace.api.UserIdCollectionMode; -import datadog.trace.api.appsec.LoginEventCallback; import datadog.trace.api.function.TriConsumer; import datadog.trace.api.function.TriFunction; import datadog.trace.api.http.StoredBodySupplier; +import datadog.trace.api.telemetry.LoginEvent; import datadog.trace.bootstrap.instrumentation.api.URIDataAdapter; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; @@ -279,8 +278,8 @@ public EventType>> requestSession( /** Triggered in every authenticated request to the application */ @SuppressWarnings("unchecked") - public EventType>> user() { - return (EventType>>) USER; + public EventType>> user() { + return (EventType>>) USER; } static final int LOGIN_EVENT_ID = 23; @@ -288,10 +287,9 @@ public EventType("login.event", LOGIN_EVENT_ID); - /** Triggered when the SDK sends a custom event */ @SuppressWarnings("unchecked") - public EventType loginEvent() { - return (EventType) LOGIN_EVENT; + public EventType>> loginEvent() { + return (EventType>>) LOGIN_EVENT; } static final int EXEC_CMD_ID = 24; diff --git a/internal-api/src/main/java/datadog/trace/api/gateway/InstrumentationGateway.java b/internal-api/src/main/java/datadog/trace/api/gateway/InstrumentationGateway.java index 72a690596d0..cd5219f1dfc 100644 --- a/internal-api/src/main/java/datadog/trace/api/gateway/InstrumentationGateway.java +++ b/internal-api/src/main/java/datadog/trace/api/gateway/InstrumentationGateway.java @@ -28,11 +28,10 @@ import static datadog.trace.api.gateway.Events.SHELL_CMD_ID; import static datadog.trace.api.gateway.Events.USER_ID; -import datadog.trace.api.UserIdCollectionMode; -import datadog.trace.api.appsec.LoginEventCallback; import datadog.trace.api.function.TriConsumer; import datadog.trace.api.function.TriFunction; import datadog.trace.api.http.StoredBodySupplier; +import datadog.trace.api.telemetry.LoginEvent; import datadog.trace.bootstrap.instrumentation.api.URIDataAdapter; import java.util.Map; import java.util.concurrent.atomic.AtomicReferenceArray; @@ -388,14 +387,12 @@ public void accept(RequestContext ctx, String arg) { }; case USER_ID: return (C) - new TriFunction>() { + new BiFunction>() { @Override - public Flow apply( - RequestContext ctx, UserIdCollectionMode mode, String userId) { + public Flow apply(RequestContext ctx, String userId) { try { - return ((TriFunction>) - callback) - .apply(ctx, mode, userId); + return ((BiFunction>) callback) + .apply(ctx, userId); } catch (Throwable t) { log.warn("Callback for {} threw.", eventType, t); return Flow.ResultFlow.empty(); @@ -404,19 +401,13 @@ public Flow apply( }; case LOGIN_EVENT_ID: return (C) - new LoginEventCallback() { + new TriFunction>() { @Override - public Flow apply( - RequestContext ctx, - UserIdCollectionMode mode, - String eventName, - Boolean exists, - String user, - Map metadata) { + public Flow apply(RequestContext ctx, LoginEvent event, String user) { try { - return ((LoginEventCallback) callback) - .apply(ctx, mode, eventName, exists, user, metadata); + return ((TriFunction>) callback) + .apply(ctx, event, user); } catch (Throwable t) { log.warn("Callback for {} threw.", eventType, t); return Flow.ResultFlow.empty(); diff --git a/internal-api/src/test/java/datadog/trace/api/gateway/InstrumentationGatewayTest.java b/internal-api/src/test/java/datadog/trace/api/gateway/InstrumentationGatewayTest.java index 85e88df9600..8f2840d4308 100644 --- a/internal-api/src/test/java/datadog/trace/api/gateway/InstrumentationGatewayTest.java +++ b/internal-api/src/test/java/datadog/trace/api/gateway/InstrumentationGatewayTest.java @@ -4,7 +4,6 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import datadog.appsec.api.blocking.BlockingContentType; -import datadog.trace.api.appsec.LoginEventCallback; import datadog.trace.api.function.TriConsumer; import datadog.trace.api.function.TriFunction; import datadog.trace.api.http.StoredBodySupplier; @@ -215,9 +214,9 @@ public void testNormalCalls() { ss.registerCallback(events.fileLoaded(), callback); cbp.getCallback(events.fileLoaded()).apply(null, null); ss.registerCallback(events.user(), callback); - cbp.getCallback(events.user()).apply(null, null, null); - ss.registerCallback(events.loginEvent(), callback.asLoginEventCallback()); - cbp.getCallback(events.loginEvent()).apply(null, null, null, null, null, null); + cbp.getCallback(events.user()).apply(null, null); + ss.registerCallback(events.loginEvent(), callback); + cbp.getCallback(events.loginEvent()).apply(null, null, null); ss.registerCallback(events.requestSession(), callback); cbp.getCallback(events.requestSession()).apply(null, null); ss.registerCallback(events.execCmd(), callback); @@ -285,9 +284,9 @@ public void testThrowableBlocking() { ss.registerCallback(events.fileLoaded(), throwback); cbp.getCallback(events.fileLoaded()).apply(null, null); ss.registerCallback(events.user(), throwback); - cbp.getCallback(events.user()).apply(null, null, null); - ss.registerCallback(events.loginEvent(), throwback.asLoginEventCallback()); - cbp.getCallback(events.loginEvent()).apply(null, null, null, null, null, null); + cbp.getCallback(events.user()).apply(null, null); + ss.registerCallback(events.loginEvent(), throwback); + cbp.getCallback(events.loginEvent()).apply(null, null, null); ss.registerCallback(events.requestSession(), throwback); cbp.getCallback(events.requestSession()).apply(null, null); ss.registerCallback(events.execCmd(), throwback); @@ -507,13 +506,6 @@ public Flow apply(RequestContext requestContext, T t, T t2) { count++; return flow; } - - public LoginEventCallback asLoginEventCallback() { - return (context, mode, eventName, exists, user, metadata) -> { - count++; - return flow; - }; - } } private static class Throwback @@ -583,13 +575,6 @@ public BiFunction> asRequestBodyD }; } - public LoginEventCallback asLoginEventCallback() { - return (context, mode, eventName, exists, user, metadata) -> { - count++; - throw new IllegalArgumentException(); - }; - } - public int getCount() { return count; }